Revision ef20ea07
b/snf-astakos-app/astakos/im/auth_backends.py | ||
---|---|---|
64 | 64 |
def authenticate(self, username=None, password=None): |
65 | 65 |
#If username is an email address, then try to pull it up |
66 | 66 |
if email_re.search(username): |
67 |
try: |
|
68 |
user = AstakosUser.objects.get(email=username, is_active=True) |
|
69 |
except AstakosUser.DoesNotExist: |
|
67 |
users = AstakosUser.objects.filter(email=username) |
|
68 |
if not users: |
|
70 | 69 |
return None |
70 |
for user in users: |
|
71 |
if user.check_password(password): |
|
72 |
return user |
|
71 | 73 |
else: |
72 | 74 |
#We have a non-email address username we |
73 | 75 |
#should try username |
b/snf-astakos-app/astakos/im/forms.py | ||
---|---|---|
48 | 48 |
from django.utils.encoding import smart_str |
49 | 49 |
|
50 | 50 |
from astakos.im.models import AstakosUser, Invitation, get_latest_terms, EmailChange |
51 |
from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, \ |
|
52 |
BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL, \ |
|
53 |
RECAPTCHA_ENABLED, LOGGING_LEVEL, PASSWORD_RESET_EMAIL_SUBJECT, \ |
|
54 |
NEWPASSWD_INVALIDATE_TOKEN |
|
51 |
from astakos.im.settings import (INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, |
|
52 |
BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL, |
|
53 |
RECAPTCHA_ENABLED, LOGGING_LEVEL, PASSWORD_RESET_EMAIL_SUBJECT, |
|
54 |
NEWPASSWD_INVALIDATE_TOKEN, THIRDPARTY_ACC_ADDITIONAL_FIELDS |
|
55 |
) |
|
55 | 56 |
from astakos.im.widgets import DummyWidget, RecaptchaWidget |
56 | 57 |
from astakos.im.functions import send_change_email |
57 | 58 |
|
... | ... | |
182 | 183 |
return user |
183 | 184 |
|
184 | 185 |
class ThirdPartyUserCreationForm(forms.ModelForm): |
186 |
third_party_identifier = forms.CharField( |
|
187 |
widget=forms.HiddenInput(), |
|
188 |
label='' |
|
189 |
) |
|
185 | 190 |
class Meta: |
186 | 191 |
model = AstakosUser |
187 | 192 |
fields = ("email", "first_name", "last_name", "third_party_identifier", "has_signed_terms") |
... | ... | |
193 | 198 |
self.request = kwargs.get('request', None) |
194 | 199 |
if self.request: |
195 | 200 |
kwargs.pop('request') |
201 |
|
|
202 |
latest_terms = get_latest_terms() |
|
203 |
if latest_terms: |
|
204 |
self._meta.fields.append('has_signed_terms') |
|
205 |
|
|
196 | 206 |
super(ThirdPartyUserCreationForm, self).__init__(*args, **kwargs) |
197 |
self.fields.keyOrder = ['email', 'first_name', 'last_name', 'third_party_identifier'] |
|
198 |
if get_latest_terms():
|
|
207 |
|
|
208 |
if latest_terms:
|
|
199 | 209 |
self.fields.keyOrder.append('has_signed_terms') |
200 |
#set readonly form fields |
|
201 |
ro = ["third_party_identifier"] |
|
202 |
for f in ro: |
|
203 |
self.fields[f].widget.attrs['readonly'] = True |
|
204 |
|
|
210 |
|
|
205 | 211 |
if 'has_signed_terms' in self.fields: |
206 | 212 |
# Overriding field label since we need to apply a link |
207 | 213 |
# to the terms within the label |
... | ... | |
262 | 268 |
|
263 | 269 |
def __init__(self, *args, **kwargs): |
264 | 270 |
super(ShibbolethUserCreationForm, self).__init__(*args, **kwargs) |
265 |
self.fields.keyOrder.append('additional_email') |
|
266 | 271 |
# copy email value to additional_mail in case user will change it |
267 | 272 |
name = 'email' |
268 | 273 |
field = self.fields[name] |
269 | 274 |
self.initial['additional_email'] = self.initial.get(name, field.initial) |
270 |
|
|
275 |
self.initial['email'] = None |
|
276 |
|
|
271 | 277 |
def clean_email(self): |
272 | 278 |
email = self.cleaned_data['email'] |
273 | 279 |
for user in AstakosUser.objects.filter(email = email): |
274 | 280 |
if user.provider == 'shibboleth': |
275 |
raise forms.ValidationError(_("This email is already associated with another shibboleth account.")) |
|
276 |
elif not user.is_active: |
|
277 |
raise forms.ValidationError(_("This email is already associated with an inactive account. \ |
|
278 |
You need to wait to be activated before being able to switch to a shibboleth account.")) |
|
281 |
raise forms.ValidationError(_( |
|
282 |
"This email is already associated with another shibboleth \ |
|
283 |
account." |
|
284 |
) |
|
285 |
) |
|
286 |
else: |
|
287 |
raise forms.ValidationError(_("This email is already used")) |
|
279 | 288 |
super(ShibbolethUserCreationForm, self).clean_email() |
280 | 289 |
return email |
281 | 290 |
|
... | ... | |
321 | 330 |
check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip) |
322 | 331 |
if not check.is_valid: |
323 | 332 |
raise forms.ValidationError(_('You have not entered the correct words')) |
324 |
|
|
333 |
|
|
325 | 334 |
def clean(self): |
326 | 335 |
super(LoginForm, self).clean() |
327 | 336 |
if self.user_cache and self.user_cache.provider not in ('local', ''): |
b/snf-astakos-app/astakos/im/functions.py | ||
---|---|---|
249 | 249 |
send_helpdesk_notification(user, helpdesk_email_template_name) |
250 | 250 |
send_greeting(user, email_template_name) |
251 | 251 |
|
252 |
def switch_account_to_shibboleth(user, local_user, greeting_template_name='im/welcome_email.txt'): |
|
253 |
if not user or not isinstance(user, AstakosUser): |
|
254 |
return |
|
255 |
|
|
256 |
if not local_user or not isinstance(user, AstakosUser): |
|
257 |
return |
|
258 |
|
|
259 |
if not user.provider == 'shibboleth': |
|
260 |
return |
|
261 |
|
|
262 |
user.delete() |
|
263 |
local_user.provider = 'shibboleth' |
|
264 |
local_user.third_party_identifier = user.third_party_identifier |
|
265 |
local_user.save() |
|
266 |
send_greeting(local_user, greeting_template_name) |
|
267 |
return local_user |
|
268 |
|
|
269 | 252 |
def invite(invitation, inviter, email_template_name='im/welcome_email.txt'): |
270 | 253 |
""" |
271 | 254 |
Send an invitation email and upon success reduces inviter's invitation by one. |
b/snf-astakos-app/astakos/im/migrations/0015_auto__add_pendingthirdpartyuser.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 'PendingThirdPartyUser' |
|
12 |
db.create_table('im_pendingthirdpartyuser', ( |
|
13 |
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
|
14 |
('third_party_identifier', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), |
|
15 |
('provider', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), |
|
16 |
('email', self.gf('django.db.models.fields.EmailField')(max_length=75, blank=True)), |
|
17 |
('first_name', self.gf('django.db.models.fields.CharField')(max_length=30, blank=True)), |
|
18 |
('last_name', self.gf('django.db.models.fields.CharField')(max_length=30, blank=True)), |
|
19 |
('affiliation', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), |
|
20 |
('username', self.gf('django.db.models.fields.CharField')(unique=True, max_length=30)), |
|
21 |
)) |
|
22 |
db.send_create_signal('im', ['PendingThirdPartyUser']) |
|
23 |
|
|
24 |
|
|
25 |
def backwards(self, orm): |
|
26 |
|
|
27 |
# Deleting model 'PendingThirdPartyUser' |
|
28 |
db.delete_table('im_pendingthirdpartyuser') |
|
29 |
|
|
30 |
|
|
31 |
models = { |
|
32 |
'auth.group': { |
|
33 |
'Meta': {'object_name': 'Group'}, |
|
34 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
35 |
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
|
36 |
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
|
37 |
}, |
|
38 |
'auth.permission': { |
|
39 |
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
|
40 |
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
|
41 |
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
|
42 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
43 |
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
|
44 |
}, |
|
45 |
'auth.user': { |
|
46 |
'Meta': {'object_name': 'User'}, |
|
47 |
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
|
48 |
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
|
49 |
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
|
50 |
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
|
51 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
52 |
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
|
53 |
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
|
54 |
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
|
55 |
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
|
56 |
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
|
57 |
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
|
58 |
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
|
59 |
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
|
60 |
}, |
|
61 |
'contenttypes.contenttype': { |
|
62 |
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
|
63 |
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
|
64 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
65 |
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
|
66 |
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
|
67 |
}, |
|
68 |
'im.additionalmail': { |
|
69 |
'Meta': {'object_name': 'AdditionalMail'}, |
|
70 |
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), |
|
71 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
72 |
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}) |
|
73 |
}, |
|
74 |
'im.approvalterms': { |
|
75 |
'Meta': {'object_name': 'ApprovalTerms'}, |
|
76 |
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 11, 15, 13, 7, 38, 626139)', 'db_index': 'True'}), |
|
77 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
78 |
'location': ('django.db.models.fields.CharField', [], {'max_length': '255'}) |
|
79 |
}, |
|
80 |
'im.astakosuser': { |
|
81 |
'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'AstakosUser', '_ormbases': ['auth.User']}, |
|
82 |
'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), |
|
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', 'blank': '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': '100'}), |
|
92 |
'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
|
93 |
'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
|
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.emailchange': { |
|
100 |
'Meta': {'object_name': 'EmailChange'}, |
|
101 |
'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}), |
|
102 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
103 |
'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), |
|
104 |
'requested_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 11, 15, 13, 7, 38, 627944)'}), |
|
105 |
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchange_user'", 'unique': 'True', 'to': "orm['im.AstakosUser']"}) |
|
106 |
}, |
|
107 |
'im.invitation': { |
|
108 |
'Meta': {'object_name': 'Invitation'}, |
|
109 |
'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}), |
|
110 |
'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), |
|
111 |
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
|
112 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
113 |
'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}), |
|
114 |
'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
|
115 |
'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
|
116 |
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) |
|
117 |
}, |
|
118 |
'im.pendingthirdpartyuser': { |
|
119 |
'Meta': {'object_name': 'PendingThirdPartyUser'}, |
|
120 |
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
|
121 |
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
|
122 |
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
|
123 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
124 |
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
|
125 |
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
|
126 |
'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
|
127 |
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
|
128 |
}, |
|
129 |
'im.service': { |
|
130 |
'Meta': {'object_name': 'Service'}, |
|
131 |
'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), |
|
132 |
'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), |
|
133 |
'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), |
|
134 |
'icon': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}), |
|
135 |
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
|
136 |
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), |
|
137 |
'url': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}) |
|
138 |
} |
|
139 |
} |
|
140 |
|
|
141 |
complete_apps = ['im'] |
b/snf-astakos-app/astakos/im/models.py | ||
---|---|---|
39 | 39 |
from time import asctime |
40 | 40 |
from datetime import datetime, timedelta |
41 | 41 |
from base64 import b64encode |
42 |
from urlparse import urlparse, urlunparse
|
|
42 |
from urlparse import urlparse |
|
43 | 43 |
from random import randint |
44 | 44 |
|
45 | 45 |
from django.db import models, IntegrityError |
... | ... | |
374 | 374 |
owner = models.ForeignKey(AstakosUser) |
375 | 375 |
email = models.EmailField() |
376 | 376 |
|
377 |
class PendingThirdPartyUser(models.Model): |
|
378 |
""" |
|
379 |
Model for registring successful third party user authentications |
|
380 |
""" |
|
381 |
third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True) |
|
382 |
provider = models.CharField('Provider', max_length=255, blank=True) |
|
383 |
email = models.EmailField(_('e-mail address'), blank=True) |
|
384 |
first_name = models.CharField(_('first name'), max_length=30, blank=True) |
|
385 |
last_name = models.CharField(_('last name'), max_length=30, blank=True) |
|
386 |
affiliation = models.CharField('Affiliation', max_length=255, blank=True) |
|
387 |
username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters")) |
|
388 |
|
|
389 |
@property |
|
390 |
def realname(self): |
|
391 |
return '%s %s' %(self.first_name, self.last_name) |
|
392 |
|
|
393 |
@realname.setter |
|
394 |
def realname(self, value): |
|
395 |
parts = value.split(' ') |
|
396 |
if len(parts) == 2: |
|
397 |
self.first_name = parts[0] |
|
398 |
self.last_name = parts[1] |
|
399 |
else: |
|
400 |
self.last_name = parts[0] |
|
401 |
|
|
402 |
def save(self, **kwargs): |
|
403 |
if not self.id: |
|
404 |
# set username |
|
405 |
while not self.username: |
|
406 |
username = uuid.uuid4().hex[:30] |
|
407 |
try: |
|
408 |
AstakosUser.objects.get(username = username) |
|
409 |
except AstakosUser.DoesNotExist, e: |
|
410 |
self.username = username |
|
411 |
super(PendingThirdPartyUser, self).save(**kwargs) |
|
412 |
|
|
377 | 413 |
def create_astakos_user(u): |
378 | 414 |
try: |
379 | 415 |
AstakosUser.objects.get(user_ptr=u.pk) |
b/snf-astakos-app/astakos/im/settings.py | ||
---|---|---|
119 | 119 |
'Password reset on %s alpha2 testing' % SITENAME) |
120 | 120 |
|
121 | 121 |
# Enforce token renewal on password change/reset |
122 |
NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True) |
|
122 |
NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True) |
|
123 |
|
|
124 |
# Permit local account migration |
|
125 |
ENABLE_LOCAL_ACCOUNT_MIGRATION = getattr(settings, 'ASTAKOS_ENABLE_LOCAL_ACCOUNT_MIGRATION', True) |
|
126 |
|
|
127 |
# A dictionary describing the user fields appearing during the second step of third party account creation |
|
128 |
from django import forms |
|
129 |
THIRDPARTY_ACC_ADDITIONAL_FIELDS = getattr(settings, 'ASTAKOS_THIRDPARTY_ACC_ADDITIONAL_FIELDS', { |
|
130 |
'first_name':None, |
|
131 |
'last_name':None, |
|
132 |
}) |
b/snf-astakos-app/astakos/im/target/local.py | ||
---|---|---|
39 | 39 |
from django.utils.translation import ugettext as _ |
40 | 40 |
from django.views.decorators.csrf import csrf_exempt |
41 | 41 |
from django.views.decorators.http import require_http_methods |
42 |
from django.core.urlresolvers import reverse |
|
42 | 43 |
|
43 | 44 |
from astakos.im.util import prepare_response, get_query |
44 | 45 |
from astakos.im.views import requires_anonymous |
45 |
from astakos.im.models import AstakosUser |
|
46 |
from astakos.im.models import AstakosUser, PendingThirdPartyUser
|
|
46 | 47 |
from astakos.im.forms import LoginForm |
47 | 48 |
from astakos.im.settings import RATELIMIT_RETRIES_ALLOWED |
49 |
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION |
|
48 | 50 |
|
49 | 51 |
from ratelimit.decorators import ratelimit |
50 | 52 |
|
... | ... | |
62 | 64 |
was_limited = getattr(request, 'limited', False) |
63 | 65 |
form = LoginForm(data=request.POST, was_limited=was_limited, request=request) |
64 | 66 |
next = get_query(request).get('next', '') |
67 |
username = get_query(request).get('key') |
|
68 |
|
|
65 | 69 |
if not form.is_valid(): |
66 |
return render_to_response(on_failure, |
|
67 |
{'login_form':form, |
|
68 |
'next':next}, |
|
69 |
context_instance=RequestContext(request)) |
|
70 |
return render_to_response( |
|
71 |
on_failure, |
|
72 |
{'login_form':form, |
|
73 |
'next':next, |
|
74 |
'key':username}, |
|
75 |
context_instance=RequestContext(request) |
|
76 |
) |
|
70 | 77 |
# get the user from the cash |
71 | 78 |
user = form.user_cache |
72 | 79 |
|
... | ... | |
74 | 81 |
if not user: |
75 | 82 |
message = _('Cannot authenticate account') |
76 | 83 |
elif not user.is_active: |
77 |
message = _('Inactive account') |
|
84 |
if user.sent_activation: |
|
85 |
message = _('Your request is pending activation') |
|
86 |
else: |
|
87 |
message = _('You have not followed the activation link') |
|
78 | 88 |
if message: |
79 | 89 |
messages.add_message(request, messages.ERROR, message) |
80 | 90 |
return render_to_response(on_failure, |
81 | 91 |
{'form':form}, |
82 | 92 |
context_instance=RequestContext(request)) |
83 | 93 |
|
84 |
return prepare_response(request, user, next) |
|
94 |
# hook for switching account to use third party authentication |
|
95 |
if ENABLE_LOCAL_ACCOUNT_MIGRATION and username: |
|
96 |
try: |
|
97 |
new = PendingThirdPartyUser.objects.get( |
|
98 |
username=username) |
|
99 |
except: |
|
100 |
messages.error( |
|
101 |
request, |
|
102 |
_('Account failed to switch to %(provider)s' % locals()) |
|
103 |
) |
|
104 |
return render_to_response( |
|
105 |
on_failure, |
|
106 |
{'login_form':form, |
|
107 |
'next':next}, |
|
108 |
context_instance=RequestContext(request) |
|
109 |
) |
|
110 |
else: |
|
111 |
user.provider = new.provider |
|
112 |
user.third_party_identifier = new.third_party_identifier |
|
113 |
user.save() |
|
114 |
new.delete() |
|
115 |
messages.success( |
|
116 |
request, |
|
117 |
_('Account successfully switched to %(provider)s' % user.__dict__) |
|
118 |
) |
|
119 |
return prepare_response(request, user, next) |
b/snf-astakos-app/astakos/im/target/shibboleth.py | ||
---|---|---|
36 | 36 |
from django.contrib import messages |
37 | 37 |
from django.template import RequestContext |
38 | 38 |
from django.views.decorators.http import require_http_methods |
39 |
from django.db.models import Q |
|
40 |
from django.core.exceptions import ValidationError |
|
41 |
from django.http import HttpResponseRedirect |
|
42 |
from django.core.urlresolvers import reverse |
|
43 |
from urlparse import urlunsplit, urlsplit |
|
44 |
from django.utils.http import urlencode |
|
39 | 45 |
|
40 | 46 |
from astakos.im.util import prepare_response, get_context, get_invitation |
41 | 47 |
from astakos.im.views import requires_anonymous, render_response |
42 |
from astakos.im.settings import DEFAULT_USER_LEVEL |
|
43 |
from astakos.im.models import AstakosUser, Invitation, AdditionalMail |
|
48 |
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL |
|
49 |
|
|
50 |
from astakos.im.models import AstakosUser, PendingThirdPartyUser |
|
44 | 51 |
from astakos.im.forms import LoginForm |
45 | 52 |
from astakos.im.activation_backends import get_backend, SimpleBackend |
46 | 53 |
|
54 |
import logging |
|
55 |
|
|
56 |
logger = logging.getLogger(__name__) |
|
57 |
|
|
47 | 58 |
class Tokens: |
48 | 59 |
# these are mapped by the Shibboleth SP software |
49 | 60 |
SHIB_EPPN = "HTTP_EPPN" # eduPersonPrincipalName |
... | ... | |
57 | 68 |
|
58 | 69 |
@require_http_methods(["GET", "POST"]) |
59 | 70 |
@requires_anonymous |
60 |
def login(request, backend=None, on_login_template='im/login.html', on_creation_template='im/third_party_registration.html', extra_context={}): |
|
71 |
def login( |
|
72 |
request, |
|
73 |
on_login_template='im/login.html', |
|
74 |
on_signup_template='im/third_party_check_local.html', |
|
75 |
extra_context=None): |
|
76 |
extra_context = extra_context or {} |
|
77 |
|
|
61 | 78 |
tokens = request.META |
62 | 79 |
|
63 | 80 |
try: |
... | ... | |
75 | 92 |
return HttpResponseBadRequest("Missing user name in request") |
76 | 93 |
|
77 | 94 |
affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '') |
78 |
email = tokens.get(Tokens.SHIB_MAIL, None)
|
|
79 |
|
|
95 |
email = tokens.get(Tokens.SHIB_MAIL, '')
|
|
96 |
|
|
80 | 97 |
try: |
81 |
user = AstakosUser.objects.get(provider='shibboleth', third_party_identifier=eppn) |
|
98 |
user = AstakosUser.objects.get( |
|
99 |
provider='shibboleth', |
|
100 |
third_party_identifier=eppn |
|
101 |
) |
|
82 | 102 |
if user.is_active: |
83 | 103 |
return prepare_response(request, |
84 | 104 |
user, |
... | ... | |
91 | 111 |
login_form = LoginForm(request=request), |
92 | 112 |
context_instance=RequestContext(request)) |
93 | 113 |
except AstakosUser.DoesNotExist, e: |
94 |
user = AstakosUser(third_party_identifier=eppn, realname=realname, |
|
95 |
affiliation=affiliation, provider='shibboleth', |
|
96 |
email=email) |
|
114 |
# First time |
|
115 |
try: |
|
116 |
user, created = PendingThirdPartyUser.objects.get_or_create( |
|
117 |
third_party_identifier=eppn, |
|
118 |
provider='shibboleth', |
|
119 |
defaults=dict( |
|
120 |
realname=realname, |
|
121 |
affiliation=affiliation, |
|
122 |
email=email |
|
123 |
) |
|
124 |
) |
|
125 |
user.save() |
|
126 |
except BaseException, e: |
|
127 |
logger.exception(e) |
|
128 |
template = on_login_template |
|
129 |
extra_context['login_form'] = LoginForm(request=request) |
|
130 |
messages.error(request, _('Something went wrong.')) |
|
131 |
else: |
|
132 |
if not ENABLE_LOCAL_ACCOUNT_MIGRATION: |
|
133 |
url = reverse( |
|
134 |
'astakos.im.target.shibboleth.signup' |
|
135 |
) |
|
136 |
parts = list(urlsplit(url)) |
|
137 |
parts[3] = urlencode({'key': user.username}) |
|
138 |
url = urlunsplit(parts) |
|
139 |
return HttpResponseRedirect(url) |
|
140 |
else: |
|
141 |
template = on_signup_template |
|
142 |
extra_context['key'] = user.username |
|
143 |
|
|
144 |
extra_context['provider']='shibboleth' |
|
145 |
return render_response( |
|
146 |
template, |
|
147 |
context_instance=get_context(request, extra_context) |
|
148 |
) |
|
149 |
|
|
150 |
@require_http_methods(["GET"]) |
|
151 |
@requires_anonymous |
|
152 |
def signup(request, |
|
153 |
backend=None, |
|
154 |
on_creation_template='im/third_party_registration.html', |
|
155 |
extra_context=None |
|
156 |
): |
|
157 |
extra_context = extra_context or {} |
|
158 |
username = request.GET.get('key') |
|
159 |
if not username: |
|
160 |
return HttpResponseBadRequest(_('Missing key parameter.')) |
|
161 |
try: |
|
162 |
pending = PendingThirdPartyUser.objects.get(username=username) |
|
163 |
except BaseException, e: |
|
164 |
logger.exception(e) |
|
165 |
return HttpResponseBadRequest(_('Invalid key.')) |
|
166 |
else: |
|
167 |
d = pending.__dict__ |
|
168 |
d.pop('_state', None) |
|
169 |
d.pop('id', None) |
|
170 |
user = AstakosUser(**d) |
|
97 | 171 |
try: |
98 |
if not backend: |
|
99 |
backend = get_backend(request) |
|
100 |
form = backend.get_signup_form(provider='shibboleth', instance=user) |
|
101 |
except Exception, e: |
|
102 |
form = SimpleBackend(request).get_signup_form(provider='shibboleth', instance=user) |
|
103 |
messages.add_message(request, messages.ERROR, e) |
|
104 |
return render_response(on_creation_template, |
|
105 |
signup_form = form, |
|
106 |
provider = 'shibboleth', |
|
107 |
context_instance=get_context(request, extra_context)) |
|
172 |
backend = backend or get_backend(request) |
|
173 |
except ImproperlyConfigured, e: |
|
174 |
messages.error(request, e) |
|
175 |
else: |
|
176 |
extra_context['form'] = backend.get_signup_form( |
|
177 |
provider='shibboleth', |
|
178 |
instance=user |
|
179 |
) |
|
180 |
extra_context['provider']='shibboleth' |
|
181 |
return render_response( |
|
182 |
on_creation_template, |
|
183 |
context_instance=get_context(request, extra_context) |
|
184 |
) |
b/snf-astakos-app/astakos/im/templates/im/login_base.html | ||
---|---|---|
16 | 16 |
{% include "im/form_render.html" %} |
17 | 17 |
{% endwith %} |
18 | 18 |
<input type="hidden" name="next" value="{{ next }}"> |
19 |
|
|
20 |
{% if key %} |
|
21 |
<input type="hidden" name="key" value="{{key}}"> |
|
22 |
{% else %} |
|
23 |
{% if request.GET.key %} |
|
24 |
<input type="hidden" name="key" value="{{request.GET.key}}"> |
|
25 |
{% endif %} |
|
26 |
{% endif %} |
|
27 |
|
|
19 | 28 |
<div class="form-row submit clearfix"> |
20 |
|
|
21 |
<input type="submit" class="submit altcol" value="SUBMIT" /> |
|
29 |
<input type="submit" class="submit altcol" value="SUBMIT" /> |
|
22 | 30 |
<a class="extra-link" href="{% url django.contrib.auth.views.password_reset %}">Forgot your password?</a> |
23 | 31 |
</div> |
24 | 32 |
</form> |
b/snf-astakos-app/astakos/im/templates/im/third_party_check_local.html | ||
---|---|---|
1 |
{% extends 'im/base_two_cols.html' %} |
|
2 |
|
|
3 |
{% block page.title %} |
|
4 |
Signup |
|
5 |
{% endblock %} |
|
6 |
|
|
7 |
{% block body.left %} |
|
8 |
<img class="pic" src="{{ IM_STATIC_URL }}images/pictures/accounts_3.png" /> |
|
9 |
{% comment %}{% include "im/services_description.html" %}{% endcomment %} |
|
10 |
{% endblock body.left %} |
|
11 |
|
|
12 |
{% block body.right %} |
|
13 |
{% if "local" in im_modules %} |
|
14 |
<div class="form-stacked"> |
|
15 |
<h2><span>Already have an account?</span></h2> |
|
16 |
<a href="{% url astakos.im.views.index %}?key={{key}}">YES</a> |
|
17 |
<a href="{% url astakos.im.target.shibboleth.signup %}?key={{key}}">NO</a> |
|
18 |
</div> |
|
19 |
{% endif %} |
|
20 |
{% endblock %} |
b/snf-astakos-app/astakos/im/templates/im/third_party_registration.html | ||
---|---|---|
17 | 17 |
<div class="form-stacked"> |
18 | 18 |
<form action="{% url astakos.im.views.signup %}" method="post" |
19 | 19 |
class="innerlabels signup">{% csrf_token %} |
20 |
<h2><span>Provide an email address to complete the registration:</span></h2>
|
|
20 |
<h2><span>SIGN UP</span></h2>
|
|
21 | 21 |
<input type="hidden" name="next" value="{{ next }}"> |
22 | 22 |
<input type="hidden" name="code" value="{{ code }}"> |
23 | 23 |
<input type="hidden" name="provider" value={{ provider|default:"local" }}> |
24 |
{% with signup_form as form %} |
|
25 | 24 |
{% include "im/form_render.html" %} |
26 |
{% endwith %} |
|
27 | 25 |
<div class="form-row submit"> |
28 | 26 |
<input type="submit" class="submit altcol" value="SUBMIT" /> |
29 | 27 |
</div> |
b/snf-astakos-app/astakos/im/urls.py | ||
---|---|---|
86 | 86 |
|
87 | 87 |
if 'shibboleth' in IM_MODULES: |
88 | 88 |
urlpatterns += patterns('astakos.im.target', |
89 |
url(r'^login/shibboleth/?$', 'shibboleth.login') |
|
89 |
url(r'^login/shibboleth/?$', 'shibboleth.login'), |
|
90 |
url(r'^login/shibboleth/signup/?$', 'shibboleth.signup') |
|
90 | 91 |
) |
91 | 92 |
|
92 | 93 |
if 'twitter' in IM_MODULES: |
b/snf-astakos-app/astakos/im/views.py | ||
---|---|---|
52 | 52 |
from django.db.utils import IntegrityError |
53 | 53 |
from django.contrib.auth.views import password_change |
54 | 54 |
from django.core.exceptions import ValidationError |
55 |
from django.db.models import Q |
|
56 | 55 |
from django.views.decorators.http import require_http_methods |
57 | 56 |
|
58 | 57 |
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms |
59 | 58 |
from astakos.im.activation_backends import get_backend, SimpleBackend |
60 | 59 |
from astakos.im.util import get_context, prepare_response, set_cookie, get_query |
61 | 60 |
from astakos.im.forms import * |
62 |
from astakos.im.functions import send_greeting, send_feedback, SendMailError, \ |
|
63 |
invite as invite_func, logout as auth_logout, activate as activate_func, switch_account_to_shibboleth |
|
64 |
from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, LOGOUT_NEXT, LOGGING_LEVEL |
|
61 |
from astakos.im.functions import (send_greeting, send_feedback, SendMailError, |
|
62 |
invite as invite_func, logout as auth_logout, activate as activate_func |
|
63 |
) |
|
64 |
from astakos.im.settings import (DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, |
|
65 |
COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, LOGOUT_NEXT, LOGGING_LEVEL |
|
66 |
) |
|
65 | 67 |
|
66 | 68 |
logger = logging.getLogger(__name__) |
67 | 69 |
|
... | ... | |
137 | 139 |
template_name = login_template_name |
138 | 140 |
if request.user.is_authenticated(): |
139 | 141 |
return HttpResponseRedirect(reverse('astakos.im.views.edit_profile')) |
140 |
return render_response(template_name, |
|
141 |
login_form = LoginForm(request=request), |
|
142 |
context_instance = get_context(request, extra_context)) |
|
142 |
|
|
143 |
return render_response( |
|
144 |
template_name, |
|
145 |
login_form = LoginForm(request=request), |
|
146 |
context_instance = get_context(request, extra_context) |
|
147 |
) |
|
143 | 148 |
|
144 | 149 |
@require_http_methods(["GET", "POST"]) |
145 | 150 |
@login_required |
... | ... | |
462 | 467 |
return index(request) |
463 | 468 |
|
464 | 469 |
try: |
465 |
local_user = AstakosUser.objects.get(~Q(id = user.id), email=user.email, is_active=True) |
|
466 |
except AstakosUser.DoesNotExist: |
|
467 |
try: |
|
468 |
activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True) |
|
469 |
response = prepare_response(request, user, next, renew=True) |
|
470 |
transaction.commit() |
|
471 |
return response |
|
472 |
except SendMailError, e: |
|
473 |
message = e.message |
|
474 |
messages.add_message(request, messages.ERROR, message) |
|
475 |
transaction.rollback() |
|
476 |
return index(request) |
|
477 |
except BaseException, e: |
|
478 |
status = messages.ERROR |
|
479 |
message = _('Something went wrong.') |
|
480 |
messages.add_message(request, messages.ERROR, message) |
|
481 |
logger.exception(e) |
|
482 |
transaction.rollback() |
|
483 |
return index(request) |
|
484 |
else: |
|
485 |
try: |
|
486 |
user = switch_account_to_shibboleth(user, local_user, greeting_email_template_name) |
|
487 |
response = prepare_response(request, user, next, renew=True) |
|
488 |
transaction.commit() |
|
489 |
return response |
|
490 |
except SendMailError, e: |
|
491 |
message = e.message |
|
492 |
messages.add_message(request, messages.ERROR, message) |
|
493 |
transaction.rollback() |
|
494 |
return index(request) |
|
495 |
except BaseException, e: |
|
496 |
status = messages.ERROR |
|
497 |
message = _('Something went wrong.') |
|
498 |
messages.add_message(request, messages.ERROR, message) |
|
499 |
logger.exception(e) |
|
500 |
transaction.rollback() |
|
501 |
return index(request) |
|
470 |
activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True) |
|
471 |
response = prepare_response(request, user, next, renew=True) |
|
472 |
transaction.commit() |
|
473 |
return response |
|
474 |
except SendMailError, e: |
|
475 |
message = e.message |
|
476 |
messages.add_message(request, messages.ERROR, message) |
|
477 |
transaction.rollback() |
|
478 |
return index(request) |
|
479 |
except BaseException, e: |
|
480 |
status = messages.ERROR |
|
481 |
message = _('Something went wrong.') |
|
482 |
messages.add_message(request, messages.ERROR, message) |
|
483 |
logger.exception(e) |
|
484 |
transaction.rollback() |
|
485 |
return index(request) |
|
502 | 486 |
|
503 | 487 |
@require_http_methods(["GET", "POST"]) |
504 | 488 |
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context={}): |
Also available in: Unified diff