Revision 270dd48d
b/docs/source/devguide.rst | ||
---|---|---|
158 | 158 |
auth_token Authentication token |
159 | 159 |
auth_token_expires Token expiration date |
160 | 160 |
auth_token_created Token creation date |
161 |
has_credits Whether user has credits |
|
162 |
has_signed_terms Whether user has aggred on terms |
|
161 | 163 |
=========================== ============================ |
162 | 164 |
|
163 | 165 |
Example reply: |
... | ... | |
168 | 170 |
"uniq": "papagian@example.com" |
169 | 171 |
"auth_token": "0000", |
170 | 172 |
"auth_token_expires": "Tue, 11-Sep-2012 09:17:14 ", |
171 |
"auth_token_created": "Sun, 11-Sep-2011 09:17:14 "} |
|
173 |
"auth_token_created": "Sun, 11-Sep-2011 09:17:14 ", |
|
174 |
"has_credits": false, |
|
175 |
"has_signed_terms": true} |
|
172 | 176 |
|
173 | 177 |
| |
174 | 178 |
|
... | ... | |
177 | 181 |
=========================== ===================== |
178 | 182 |
204 (No Content) The request succeeded |
179 | 183 |
400 (Bad Request) The request is invalid |
180 |
401 (Unauthorized) Missing token or inactive user |
|
184 |
401 (Unauthorized) Missing token or inactive user or penging approval terms
|
|
181 | 185 |
500 (Internal Server Error) The request cannot be completed because of an internal error |
182 | 186 |
=========================== ===================== |
183 | 187 |
|
b/snf-astakos-app/README | ||
---|---|---|
92 | 92 |
modifyuser Modify a user's attributes |
93 | 93 |
showinvitation Show invitation info |
94 | 94 |
showuser Show user info |
95 |
addterms Add new approval terms |
|
95 | 96 |
=============== =========================== |
b/snf-astakos-app/astakos/im/api.py | ||
---|---|---|
46 | 46 |
from astakos.im.faults import BadRequest, Unauthorized, InternalServerError |
47 | 47 |
from astakos.im.models import AstakosUser |
48 | 48 |
from astakos.im.settings import CLOUD_SERVICES, INVITATIONS_ENABLED |
49 |
from astakos.im.util import has_signed_terms |
|
49 | 50 |
|
50 | 51 |
logger = logging.getLogger(__name__) |
51 | 52 |
|
... | ... | |
85 | 86 |
# Check if the token has expired. |
86 | 87 |
if (time() - mktime(user.auth_token_expires.timetuple())) > 0: |
87 | 88 |
return render_fault(request, Unauthorized('Authentication expired')) |
88 |
|
|
89 |
|
|
90 |
if not has_signed_terms(user): |
|
91 |
return render_fault(request, Unauthorized('Pending approval terms')) |
|
92 |
|
|
89 | 93 |
response = HttpResponse() |
90 | 94 |
response.status=204 |
91 | 95 |
user_info = {'username':user.username, |
... | ... | |
93 | 97 |
'auth_token':user.auth_token, |
94 | 98 |
'auth_token_created':user.auth_token_created.isoformat(), |
95 | 99 |
'auth_token_expires':user.auth_token_expires.isoformat(), |
96 |
'has_credits':user.has_credits} |
|
100 |
'has_credits':user.has_credits, |
|
101 |
'has_signed_terms':has_signed_terms(user)} |
|
97 | 102 |
response.content = json.dumps(user_info) |
98 | 103 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
99 | 104 |
response['Content-Length'] = len(response.content) |
b/snf-astakos-app/astakos/im/context_processors.py | ||
---|---|---|
43 | 43 |
return {'im_modules': IM_MODULES} |
44 | 44 |
|
45 | 45 |
def next(request): |
46 |
return {'next' : request.GET.get('next', '')} |
|
46 |
query_dict = request.__getattribute__(request.method) |
|
47 |
return {'next' : query_dict.get('next', '')} |
|
47 | 48 |
|
48 | 49 |
def code(request): |
49 | 50 |
return {'code' : request.GET.get('code', '')} |
b/snf-astakos-app/astakos/im/forms.py | ||
---|---|---|
31 | 31 |
# interpreted as representing official policies, either expressed |
32 | 32 |
# or implied, of GRNET S.A. |
33 | 33 |
from urlparse import urljoin |
34 |
from datetime import datetime |
|
34 | 35 |
|
35 | 36 |
from django import forms |
36 | 37 |
from django.utils.translation import ugettext as _ |
... | ... | |
40 | 41 |
from django.template import Context, loader |
41 | 42 |
from django.utils.http import int_to_base36 |
42 | 43 |
from django.core.urlresolvers import reverse |
44 |
from django.utils.functional import lazy |
|
43 | 45 |
|
44 | 46 |
from astakos.im.models import AstakosUser |
45 | 47 |
from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL |
46 |
from astakos.im.widgets import DummyWidget, RecaptchaWidget |
|
48 |
from astakos.im.widgets import DummyWidget, RecaptchaWidget, ApprovalTermsWidget |
|
49 |
|
|
50 |
# since Django 1.4 use django.core.urlresolvers.reverse_lazy instead |
|
51 |
from astakos.im.util import reverse_lazy |
|
47 | 52 |
|
48 | 53 |
import logging |
49 | 54 |
import recaptcha.client.captcha as captcha |
... | ... | |
63 | 68 |
|
64 | 69 |
class Meta: |
65 | 70 |
model = AstakosUser |
66 |
fields = ("email", "first_name", "last_name") |
|
71 |
fields = ("email", "first_name", "last_name", "has_signed_terms") |
|
72 |
widgets = {"has_signed_terms":ApprovalTermsWidget(terms_uri=reverse_lazy('latest_terms'))} |
|
67 | 73 |
|
68 | 74 |
def __init__(self, *args, **kwargs): |
69 | 75 |
""" |
... | ... | |
75 | 81 |
super(LocalUserCreationForm, self).__init__(*args, **kwargs) |
76 | 82 |
self.fields.keyOrder = ['email', 'first_name', 'last_name', |
77 | 83 |
'password1', 'password2', |
84 |
'has_signed_terms', |
|
78 | 85 |
'recaptcha_challenge_field', |
79 |
'recaptcha_response_field'] |
|
86 |
'recaptcha_response_field',]
|
|
80 | 87 |
|
81 | 88 |
def clean_email(self): |
82 | 89 |
email = self.cleaned_data['email'] |
... | ... | |
88 | 95 |
except AstakosUser.DoesNotExist: |
89 | 96 |
return email |
90 | 97 |
|
98 |
def clean_has_signed_terms(self): |
|
99 |
has_signed_terms = self.cleaned_data['has_signed_terms'] |
|
100 |
if not has_signed_terms: |
|
101 |
raise forms.ValidationError(_('You have to agree with the terms')) |
|
102 |
return has_signed_terms |
|
103 |
|
|
91 | 104 |
def clean_recaptcha_response_field(self): |
92 | 105 |
if 'recaptcha_challenge_field' in self.cleaned_data: |
93 | 106 |
self.validate_captcha() |
... | ... | |
112 | 125 |
""" |
113 | 126 |
user = super(LocalUserCreationForm, self).save(commit=False) |
114 | 127 |
user.renew_token() |
128 |
user.date_signed_terms = datetime.now() |
|
115 | 129 |
if commit: |
116 | 130 |
user.save() |
117 | 131 |
logger.info('Created user %s', user) |
... | ... | |
126 | 140 |
|
127 | 141 |
class Meta: |
128 | 142 |
model = AstakosUser |
129 |
fields = ("email", "first_name", "last_name") |
|
143 |
fields = ("email", "first_name", "last_name", "has_signed_terms") |
|
144 |
widgets = {"has_signed_terms":ApprovalTermsWidget(terms_uri=reverse_lazy('latest_terms'))} |
|
130 | 145 |
|
131 | 146 |
def __init__(self, *args, **kwargs): |
132 | 147 |
""" |
... | ... | |
135 | 150 |
super(InvitedLocalUserCreationForm, self).__init__(*args, **kwargs) |
136 | 151 |
self.fields.keyOrder = ['email', 'inviter', 'first_name', |
137 | 152 |
'last_name', 'password1', 'password2', |
153 |
'has_signed_terms', |
|
138 | 154 |
'recaptcha_challenge_field', |
139 | 155 |
'recaptcha_response_field'] |
140 | 156 |
#set readonly form fields |
... | ... | |
270 | 286 |
from_email = DEFAULT_FROM_EMAIL |
271 | 287 |
send_mail(_("Password reset on %s alpha2 testing") % SITENAME, |
272 | 288 |
t.render(Context(c)), from_email, [user.email]) |
289 |
|
|
290 |
class SignApprovalTermsForm(forms.ModelForm): |
|
291 |
class Meta: |
|
292 |
model = AstakosUser |
|
293 |
fields = ("has_signed_terms",) |
|
294 |
|
|
295 |
def __init__(self, *args, **kwargs): |
|
296 |
super(SignApprovalTermsForm, self).__init__(*args, **kwargs) |
|
297 |
|
|
298 |
def clean_has_signed_terms(self): |
|
299 |
has_signed_terms = self.cleaned_data['has_signed_terms'] |
|
300 |
if not has_signed_terms: |
|
301 |
raise forms.ValidationError(_('You have to agree with the terms')) |
|
302 |
return has_signed_terms |
|
303 |
|
|
304 |
def save(self, commit=True): |
|
305 |
""" |
|
306 |
Saves the , after the normal |
|
307 |
save behavior is complete. |
|
308 |
""" |
|
309 |
user = super(SignApprovalTermsForm, self).save(commit=False) |
|
310 |
user.date_signed_terms = datetime.now() |
|
311 |
if commit: |
|
312 |
user.save() |
|
313 |
return user |
b/snf-astakos-app/astakos/im/management/commands/addterms.py | ||
---|---|---|
1 |
# Copyright 2012 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
from optparse import make_option |
|
35 |
from random import choice |
|
36 |
from string import digits, lowercase, uppercase |
|
37 |
from uuid import uuid4 |
|
38 |
from time import time |
|
39 |
|
|
40 |
from django.core.management.base import BaseCommand, CommandError |
|
41 |
|
|
42 |
from astakos.im.models import ApprovalTerms |
|
43 |
|
|
44 |
class Command(BaseCommand): |
|
45 |
args = "<location>" |
|
46 |
help = "Insert approval terms" |
|
47 |
|
|
48 |
def handle(self, *args, **options): |
|
49 |
if len(args) != 1: |
|
50 |
raise CommandError("Invalid number of arguments") |
|
51 |
|
|
52 |
location = args[0].decode('utf8') |
|
53 |
try: |
|
54 |
f = open(location, 'r') |
|
55 |
except IOError: |
|
56 |
raise CommandError("Invalid location") |
|
57 |
|
|
58 |
terms = ApprovalTerms(location=location) |
|
59 |
terms.save() |
|
60 |
|
|
61 |
msg = "Created term id %d" % (terms.id,) |
|
62 |
self.stdout.write(msg + '\n') |
b/snf-astakos-app/astakos/im/management/commands/showuser.py | ||
---|---|---|
73 | 73 |
'invitation level': user.level, |
74 | 74 |
'provider': user.provider, |
75 | 75 |
'verified': format_bool(user.is_verified), |
76 |
'has_credits': format_bool(user.has_credits) |
|
76 |
'has_credits': format_bool(user.has_credits), |
|
77 |
'has_signed_terms': format_bool(user.has_signed_terms), |
|
78 |
'date_signed_terms': format_date(user.date_signed_terms) |
|
77 | 79 |
} |
78 | 80 |
|
79 | 81 |
for key, val in sorted(kv.items()): |
80 |
line = '%s: %s\n' % (key.rjust(16), val)
|
|
82 |
line = '%s: %s\n' % (key.rjust(17), val)
|
|
81 | 83 |
self.stdout.write(line.encode('utf8')) |
b/snf-astakos-app/astakos/im/migrations/0006_auto__add_approvalterms__add_field_astakosuser_has_signed_terms__add_f.py | ||
---|---|---|
1 |
# encoding: utf-8 |
|
2 |
import datetime |
|
3 |
from south.db import db |
|
4 |
from south.v2 import SchemaMigration |
|
5 |
from django.db import models |
|
6 |
|
|
7 |
class Migration(SchemaMigration): |
|
8 |
|
|
9 |
def forwards(self, orm): |
|
10 |
|
|
11 |
# Adding model 'ApprovalTerms' |
|
12 |
db.create_table('im_approvalterms', ( |
|
13 |
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
|
14 |
('date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2012, 3, 20, 14, 24, 30, 616341), db_index=True)), |
|
15 |
('location', self.gf('django.db.models.fields.CharField')(max_length=255)), |
|
16 |
)) |
|
17 |
db.send_create_signal('im', ['ApprovalTerms']) |
|
18 |
|
|
19 |
# Adding field 'AstakosUser.has_signed_terms' |
|
20 |
db.add_column('im_astakosuser', 'has_signed_terms', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False) |
|
21 |
|
|
22 |
# Adding field 'AstakosUser.date_signed_terms' |
|
23 |
db.add_column('im_astakosuser', 'date_signed_terms', self.gf('django.db.models.fields.DateTimeField')(null=True), keep_default=False) |
|
24 |
|
|
25 |
|
|
26 |
def backwards(self, orm): |
|
27 |
|
|
28 |
# Deleting model 'ApprovalTerms' |
|
29 |
db.delete_table('im_approvalterms') |
|
30 |
|
|
31 |
# Deleting field 'AstakosUser.has_signed_terms' |
|
32 |
db.delete_column('im_astakosuser', 'has_signed_terms') |
|
33 |
|
|
34 |
# Deleting field 'AstakosUser.date_signed_terms' |
|
35 |
db.delete_column('im_astakosuser', 'date_signed_terms') |
|
36 |
|
|
37 |
|
|
38 |
models = { |
|
39 |
'auth.group': { |
|
40 |
'Meta': {'object_name': 'Group'}, |
|
41 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
42 |
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
|
43 |
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
|
44 |
}, |
|
45 |
'auth.permission': { |
|
46 |
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
|
47 |
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
|
48 |
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
|
49 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
50 |
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
|
51 |
}, |
|
52 |
'auth.user': { |
|
53 |
'Meta': {'object_name': 'User'}, |
|
54 |
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
|
55 |
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
|
56 |
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
|
57 |
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
|
58 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
59 |
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
|
60 |
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
|
61 |
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
|
62 |
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
|
63 |
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
|
64 |
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
|
65 |
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
|
66 |
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
|
67 |
}, |
|
68 |
'contenttypes.contenttype': { |
|
69 |
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
|
70 |
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
|
71 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
72 |
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
|
73 |
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
|
74 |
}, |
|
75 |
'im.approvalterms': { |
|
76 |
'Meta': {'object_name': 'ApprovalTerms'}, |
|
77 |
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 3, 20, 14, 24, 30, 616341)', 'db_index': 'True'}), |
|
78 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
79 |
'location': ('django.db.models.fields.CharField', [], {'max_length': '255'}) |
|
80 |
}, |
|
81 |
'im.astakosuser': { |
|
82 |
'Meta': {'object_name': 'AstakosUser', '_ormbases': ['auth.User']}, |
|
83 |
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
|
84 |
'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), |
|
85 |
'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), |
|
86 |
'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), |
|
87 |
'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), |
|
88 |
'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
|
89 |
'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
|
90 |
'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
|
91 |
'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
|
92 |
'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
|
93 |
'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}), |
|
94 |
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
|
95 |
'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
|
96 |
'updated': ('django.db.models.fields.DateTimeField', [], {}), |
|
97 |
'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}) |
|
98 |
}, |
|
99 |
'im.invitation': { |
|
100 |
'Meta': {'object_name': 'Invitation'}, |
|
101 |
'accepted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), |
|
102 |
'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}), |
|
103 |
'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), |
|
104 |
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
|
105 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
106 |
'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}), |
|
107 |
'is_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
|
108 |
'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
|
109 |
'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
|
110 |
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) |
|
111 |
} |
|
112 |
} |
|
113 |
|
|
114 |
complete_apps = ['im'] |
b/snf-astakos-app/astakos/im/models.py | ||
---|---|---|
76 | 76 |
email_verified = models.BooleanField('Email verified?', default=False) |
77 | 77 |
|
78 | 78 |
has_credits = models.BooleanField('Has credits?', default=False) |
79 |
has_signed_terms = models.BooleanField('Agree with the terms?', default=False) |
|
80 |
date_signed_terms = models.DateTimeField('Signed terms date', null=True) |
|
79 | 81 |
|
80 | 82 |
@property |
81 | 83 |
def realname(self): |
... | ... | |
130 | 132 |
def __unicode__(self): |
131 | 133 |
return self.username |
132 | 134 |
|
135 |
class ApprovalTerms(models.Model): |
|
136 |
""" |
|
137 |
Model for approval terms |
|
138 |
""" |
|
139 |
|
|
140 |
date = models.DateTimeField('Issue date', db_index=True, default=datetime.now()) |
|
141 |
location = models.CharField('Terms location', max_length=255) |
|
142 |
|
|
133 | 143 |
class Invitation(models.Model): |
134 | 144 |
""" |
135 | 145 |
Model for registring invitations |
... | ... | |
171 | 181 |
eventType = 'create' if not user.id else 'modify' |
172 | 182 |
body = UserEvent(QUEUE_CLIENT_ID, user, eventType, {}).format() |
173 | 183 |
conn = exchange_connect(QUEUE_CONNECTION) |
174 |
routing_key = '%s.user' % QUEUE_CONNECTION |
|
184 |
parts = urlparse(exchange) |
|
185 |
exchange = parts.path[1:] |
|
186 |
routing_key = '%s.user' % exchange |
|
175 | 187 |
exchange_send(conn, routing_key, body) |
176 | 188 |
exchange_close(conn) |
b/snf-astakos-app/astakos/im/templates/im/approval_terms.html | ||
---|---|---|
1 |
{% block body %} |
|
2 |
{{ terms }} |
|
3 |
|
|
4 |
{% if form %} |
|
5 |
<div class="section"> |
|
6 |
<form action="{% url latest_terms %}" method="post" class="login innerlabels">{% csrf_token %} |
|
7 |
{% include "im/form_render.html" %} |
|
8 |
<input type="hidden" name="next" value="{{ next }}"> |
|
9 |
<div class="form-row submit"> |
|
10 |
<input type="submit" class="submit altcol" value="SUBMIT" /> |
|
11 |
</div> |
|
12 |
</form> |
|
13 |
{% endif %} |
|
14 |
{% endblock body %} |
b/snf-astakos-app/astakos/im/urls.py | ||
---|---|---|
32 | 32 |
# or implied, of GRNET S.A. |
33 | 33 |
|
34 | 34 |
from django.conf.urls.defaults import patterns, include, url |
35 |
from django.contrib.auth.views import password_change |
|
35 | 36 |
|
36 | 37 |
from astakos.im.forms import ExtendedPasswordResetForm, LoginForm |
37 | 38 |
from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED |
39 |
from astakos.im.views import signed_terms_required |
|
38 | 40 |
|
39 | 41 |
urlpatterns = patterns('astakos.im.views', |
40 | 42 |
url(r'^$', 'index', {}, name='index'), |
... | ... | |
43 | 45 |
url(r'^feedback/?$', 'send_feedback'), |
44 | 46 |
url(r'^signup/?$', 'signup', {'on_success':'im/login.html', 'extra_context':{'form':LoginForm()}}), |
45 | 47 |
url(r'^logout/?$', 'logout', {'template':'im/login.html', 'extra_context':{'form':LoginForm()}}), |
46 |
url(r'^activate/?$', 'activate') |
|
48 |
url(r'^activate/?$', 'activate'), |
|
49 |
url(r'^approval_terms/?$', 'approval_terms', {}, name='latest_terms'), |
|
50 |
url(r'^approval_terms/(?P<term_id>\d+)?$', 'approval_terms'), |
|
51 |
url(r'^password/?$', 'change_password', {}, name='password_change') |
|
47 | 52 |
) |
48 | 53 |
|
49 | 54 |
urlpatterns += patterns('astakos.im.target', |
... | ... | |
62 | 67 |
url(r'^local/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', |
63 | 68 |
'password_reset_confirm'), |
64 | 69 |
url(r'^local/password/reset/complete/$', 'password_reset_complete'), |
65 |
url(r'^password/?$', 'password_change', {'post_change_redirect':'profile'}, name='password_change')
|
|
70 |
url(r'^password_change/?$', 'password_change', {'post_change_redirect':'profile'})
|
|
66 | 71 |
) |
67 | 72 |
|
68 | 73 |
if INVITATIONS_ENABLED: |
b/snf-astakos-app/astakos/im/util.py | ||
---|---|---|
46 | 46 |
from django.contrib.auth import login, authenticate |
47 | 47 |
from django.core.urlresolvers import reverse |
48 | 48 |
|
49 |
from astakos.im.models import AstakosUser, Invitation |
|
49 |
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
|
|
50 | 50 |
from astakos.im.settings import INVITATIONS_PER_LEVEL, COOKIE_NAME, COOKIE_DOMAIN, COOKIE_SECURE, FORCE_PROFILE_UPDATE |
51 | 51 |
|
52 | 52 |
logger = logging.getLogger(__name__) |
... | ... | |
128 | 128 |
expired, if the 'renew' parameter is present |
129 | 129 |
or user has not a valid token. |
130 | 130 |
""" |
131 |
|
|
132 | 131 |
renew = renew or (not user.auth_token) |
133 | 132 |
renew = renew or (user.auth_token_expires and user.auth_token_expires < datetime.datetime.now()) |
134 | 133 |
if renew: |
... | ... | |
161 | 160 |
response.set_cookie(COOKIE_NAME, value=cookie_value, |
162 | 161 |
expires=expire_fmt, path='/', |
163 | 162 |
domain=COOKIE_DOMAIN, secure=COOKIE_SECURE) |
163 |
|
|
164 |
class lazy_string(object): |
|
165 |
def __init__(self, function, *args, **kwargs): |
|
166 |
self.function=function |
|
167 |
self.args=args |
|
168 |
self.kwargs=kwargs |
|
169 |
|
|
170 |
def __str__(self): |
|
171 |
if not hasattr(self, 'str'): |
|
172 |
self.str=self.function(*self.args, **self.kwargs) |
|
173 |
return self.str |
|
174 |
|
|
175 |
def reverse_lazy(*args, **kwargs): |
|
176 |
return lazy_string(reverse, *args, **kwargs) |
|
177 |
|
|
178 |
def has_signed_terms(user): |
|
179 |
if not user.has_signed_terms: |
|
180 |
return False |
|
181 |
try: |
|
182 |
term = ApprovalTerms.objects.order_by('-id')[0] |
|
183 |
if user.date_signed_terms < term.date: |
|
184 |
return False |
|
185 |
except IndexError: |
|
186 |
pass |
|
187 |
return True |
b/snf-astakos-app/astakos/im/views.py | ||
---|---|---|
51 | 51 |
from django.utils.http import urlencode |
52 | 52 |
from django.http import HttpResponseRedirect, HttpResponseBadRequest |
53 | 53 |
from django.db.utils import IntegrityError |
54 |
from django.contrib.auth.views import password_change |
|
54 | 55 |
|
55 |
from astakos.im.models import AstakosUser, Invitation |
|
56 |
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
|
|
56 | 57 |
from astakos.im.backends import get_backend |
57 |
from astakos.im.util import get_context, prepare_response, set_cookie |
|
58 |
from astakos.im.util import get_context, prepare_response, set_cookie, has_signed_terms
|
|
58 | 59 |
from astakos.im.forms import * |
59 | 60 |
from astakos.im.functions import send_greeting |
60 | 61 |
from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, BASEURL, LOGOUT_NEXT |
... | ... | |
80 | 81 |
|
81 | 82 |
def requires_anonymous(func): |
82 | 83 |
""" |
83 |
Decorator checkes whether the request.user is Anonymous and in that case |
|
84 |
Decorator checkes whether the request.user is not Anonymous and in that case
|
|
84 | 85 |
redirects to `logout`. |
85 | 86 |
""" |
86 | 87 |
@wraps(func) |
87 | 88 |
def wrapper(request, *args): |
88 | 89 |
if not request.user.is_anonymous(): |
89 | 90 |
next = urlencode({'next': request.build_absolute_uri()}) |
90 |
login_uri = reverse(logout) + '?' + next
|
|
91 |
return HttpResponseRedirect(login_uri)
|
|
91 |
logout_uri = reverse(logout) + '?' + next
|
|
92 |
return HttpResponseRedirect(logout_uri)
|
|
92 | 93 |
return func(request, *args) |
93 | 94 |
return wrapper |
94 | 95 |
|
96 |
def signed_terms_required(func): |
|
97 |
""" |
|
98 |
Decorator checkes whether the request.user is Anonymous and in that case |
|
99 |
redirects to `logout`. |
|
100 |
""" |
|
101 |
@wraps(func) |
|
102 |
def wrapper(request, *args, **kwargs): |
|
103 |
if request.user.is_authenticated() and not has_signed_terms(request.user): |
|
104 |
params = urlencode({'next': request.build_absolute_uri(), |
|
105 |
'show_form':''}) |
|
106 |
terms_uri = reverse('latest_terms') + '?' + params |
|
107 |
return HttpResponseRedirect(terms_uri) |
|
108 |
return func(request, *args, **kwargs) |
|
109 |
return wrapper |
|
110 |
|
|
111 |
@signed_terms_required |
|
95 | 112 |
def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context={}): |
96 | 113 |
""" |
97 | 114 |
If there is logged on user renders the profile page otherwise renders login page. |
... | ... | |
124 | 141 |
context_instance = get_context(request, extra_context)) |
125 | 142 |
|
126 | 143 |
@login_required |
144 |
@signed_terms_required |
|
127 | 145 |
@transaction.commit_manually |
128 | 146 |
def invite(request, template_name='im/invitations.html', extra_context={}): |
129 | 147 |
""" |
... | ... | |
197 | 215 |
context_instance = context) |
198 | 216 |
|
199 | 217 |
@login_required |
218 |
@signed_terms_required |
|
200 | 219 |
def edit_profile(request, template_name='im/profile.html', extra_context={}): |
201 | 220 |
""" |
202 | 221 |
Allows a user to edit his/her profile. |
... | ... | |
321 | 340 |
context_instance=get_context(request, extra_context)) |
322 | 341 |
|
323 | 342 |
@login_required |
343 |
@signed_terms_required |
|
324 | 344 |
def send_feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context={}): |
325 | 345 |
""" |
326 | 346 |
Allows a user to send feedback. |
... | ... | |
426 | 446 |
messages.add_message(request, messages.ERROR, message) |
427 | 447 |
transaction.rollback() |
428 | 448 |
return signup(request, on_failure='im/signup.html') |
449 |
|
|
450 |
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context={}): |
|
451 |
term = None |
|
452 |
terms = None |
|
453 |
if not term_id: |
|
454 |
try: |
|
455 |
term = ApprovalTerms.objects.order_by('-id')[0] |
|
456 |
except IndexError: |
|
457 |
pass |
|
458 |
else: |
|
459 |
try: |
|
460 |
term = ApprovalTerms.objects.get(id=term_id) |
|
461 |
except ApprovalTermDoesNotExist, e: |
|
462 |
pass |
|
463 |
|
|
464 |
if not term: |
|
465 |
return HttpResponseBadRequest(_('No approval terms found.')) |
|
466 |
f = open(term.location, 'r') |
|
467 |
terms = f.read() |
|
468 |
|
|
469 |
if request.method == 'POST': |
|
470 |
next = request.POST.get('next') |
|
471 |
if not next: |
|
472 |
return HttpResponseBadRequest(_('No next param.')) |
|
473 |
form = SignApprovalTermsForm(request.POST, instance=request.user) |
|
474 |
if not form.is_valid(): |
|
475 |
return render_response(template_name, |
|
476 |
terms = terms, |
|
477 |
form = form, |
|
478 |
context_instance = get_context(request, extra_context)) |
|
479 |
user = form.save() |
|
480 |
return HttpResponseRedirect(next) |
|
481 |
else: |
|
482 |
form = SignApprovalTermsForm(instance=request.user) if request.user.is_authenticated() else None |
|
483 |
return render_response(template_name, |
|
484 |
terms = terms, |
|
485 |
form = form, |
|
486 |
context_instance = get_context(request, extra_context)) |
|
487 |
|
|
488 |
@signed_terms_required |
|
489 |
def change_password(request): |
|
490 |
return password_change(request, post_change_redirect=reverse('astakos.im.views.edit_profile')) |
b/snf-astakos-app/astakos/im/widgets.py | ||
---|---|---|
36 | 36 |
from django import forms |
37 | 37 |
from django.utils.safestring import mark_safe |
38 | 38 |
from django.utils import simplejson as json |
39 |
from django.utils.translation import ugettext as _ |
|
39 | 40 |
|
40 | 41 |
from astakos.im.settings import RECAPTCHA_PUBLIC_KEY, RECAPTCHA_OPTIONS, \ |
41 | 42 |
RECAPTCHA_USE_SSL |
... | ... | |
61 | 62 |
is_hidden=True |
62 | 63 |
def render(self, *args, **kwargs): |
63 | 64 |
return '' |
65 |
|
|
66 |
class ApprovalTermsWidget(forms.CheckboxInput): |
|
67 |
""" |
|
68 |
A CheckboxInput class with a link to the approval terms. |
|
69 |
""" |
|
70 |
def __init__(self, attrs=None, check_test=bool, terms_uri='', terms_label=_('Read the terms')): |
|
71 |
super(ApprovalTermsWidget, self).__init__(attrs, check_test) |
|
72 |
self.uri = terms_uri |
|
73 |
self.label = terms_label |
|
74 |
|
|
75 |
def render(self, name, value, attrs=None): |
|
76 |
html = super(ApprovalTermsWidget, self).render(name, value, attrs) |
|
77 |
return html + mark_safe('<a href=%s target="_blank">%s</a>' % (self.uri, self.label)) |
Also available in: Unified diff