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