Commit 468a74c6 authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

Refine shibboleth signup mechanism

Refs: #3041
parent c64ab302
......@@ -64,10 +64,12 @@ class EmailBackend(ModelBackend):
def authenticate(self, username=None, password=None):
#If username is an email address, then try to pull it up
if email_re.search(username):
try:
user = AstakosUser.objects.get(email=username, is_active=True)
except AstakosUser.DoesNotExist:
users = AstakosUser.objects.filter(email=username)
if not users:
return None
for user in users:
if user.check_password(password):
return user
else:
#We have a non-email address username we
#should try username
......
......@@ -48,10 +48,11 @@ from django.contrib import messages
from django.utils.encoding import smart_str
from astakos.im.models import AstakosUser, Invitation, get_latest_terms, EmailChange
from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, \
BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL, \
RECAPTCHA_ENABLED, LOGGING_LEVEL, PASSWORD_RESET_EMAIL_SUBJECT, \
NEWPASSWD_INVALIDATE_TOKEN
from astakos.im.settings import (INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL,
BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL,
RECAPTCHA_ENABLED, LOGGING_LEVEL, PASSWORD_RESET_EMAIL_SUBJECT,
NEWPASSWD_INVALIDATE_TOKEN, THIRDPARTY_ACC_ADDITIONAL_FIELDS
)
from astakos.im.widgets import DummyWidget, RecaptchaWidget
from astakos.im.functions import send_change_email
......@@ -182,6 +183,10 @@ class InvitedLocalUserCreationForm(LocalUserCreationForm):
return user
class ThirdPartyUserCreationForm(forms.ModelForm):
third_party_identifier = forms.CharField(
widget=forms.HiddenInput(),
label=''
)
class Meta:
model = AstakosUser
fields = ("email", "first_name", "last_name", "third_party_identifier", "has_signed_terms")
......@@ -193,15 +198,16 @@ class ThirdPartyUserCreationForm(forms.ModelForm):
self.request = kwargs.get('request', None)
if self.request:
kwargs.pop('request')
latest_terms = get_latest_terms()
if latest_terms:
self._meta.fields.append('has_signed_terms')
super(ThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['email', 'first_name', 'last_name', 'third_party_identifier']
if get_latest_terms():
if latest_terms:
self.fields.keyOrder.append('has_signed_terms')
#set readonly form fields
ro = ["third_party_identifier"]
for f in ro:
self.fields[f].widget.attrs['readonly'] = True
if 'has_signed_terms' in self.fields:
# Overriding field label since we need to apply a link
# to the terms within the label
......@@ -262,20 +268,23 @@ class ShibbolethUserCreationForm(ThirdPartyUserCreationForm):
def __init__(self, *args, **kwargs):
super(ShibbolethUserCreationForm, self).__init__(*args, **kwargs)
self.fields.keyOrder.append('additional_email')
# copy email value to additional_mail in case user will change it
name = 'email'
field = self.fields[name]
self.initial['additional_email'] = self.initial.get(name, field.initial)
self.initial['email'] = None
def clean_email(self):
email = self.cleaned_data['email']
for user in AstakosUser.objects.filter(email = email):
if user.provider == 'shibboleth':
raise forms.ValidationError(_("This email is already associated with another shibboleth account."))
elif not user.is_active:
raise forms.ValidationError(_("This email is already associated with an inactive account. \
You need to wait to be activated before being able to switch to a shibboleth account."))
raise forms.ValidationError(_(
"This email is already associated with another shibboleth \
account."
)
)
else:
raise forms.ValidationError(_("This email is already used"))
super(ShibbolethUserCreationForm, self).clean_email()
return email
......@@ -321,7 +330,7 @@ class LoginForm(AuthenticationForm):
check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
if not check.is_valid:
raise forms.ValidationError(_('You have not entered the correct words'))
def clean(self):
super(LoginForm, self).clean()
if self.user_cache and self.user_cache.provider not in ('local', ''):
......
......@@ -249,23 +249,6 @@ def activate(user, email_template_name='im/welcome_email.txt',
send_helpdesk_notification(user, helpdesk_email_template_name)
send_greeting(user, email_template_name)
def switch_account_to_shibboleth(user, local_user, greeting_template_name='im/welcome_email.txt'):
if not user or not isinstance(user, AstakosUser):
return
if not local_user or not isinstance(user, AstakosUser):
return
if not user.provider == 'shibboleth':
return
user.delete()
local_user.provider = 'shibboleth'
local_user.third_party_identifier = user.third_party_identifier
local_user.save()
send_greeting(local_user, greeting_template_name)
return local_user
def invite(invitation, inviter, email_template_name='im/welcome_email.txt'):
"""
Send an invitation email and upon success reduces inviter's invitation by one.
......
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'PendingThirdPartyUser'
db.create_table('im_pendingthirdpartyuser', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('third_party_identifier', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
('provider', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
('email', self.gf('django.db.models.fields.EmailField')(max_length=75, blank=True)),
('first_name', self.gf('django.db.models.fields.CharField')(max_length=30, blank=True)),
('last_name', self.gf('django.db.models.fields.CharField')(max_length=30, blank=True)),
('affiliation', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
('username', self.gf('django.db.models.fields.CharField')(unique=True, max_length=30)),
))
db.send_create_signal('im', ['PendingThirdPartyUser'])
def backwards(self, orm):
# Deleting model 'PendingThirdPartyUser'
db.delete_table('im_pendingthirdpartyuser')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'im.additionalmail': {
'Meta': {'object_name': 'AdditionalMail'},
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
},
'im.approvalterms': {
'Meta': {'object_name': 'ApprovalTerms'},
'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 11, 15, 13, 7, 38, 626139)', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'location': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'im.astakosuser': {
'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'invitations': ('django.db.models.fields.IntegerField', [], {'default': '100'}),
'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {}),
'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'})
},
'im.emailchange': {
'Meta': {'object_name': 'EmailChange'},
'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
'requested_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 11, 15, 13, 7, 38, 627944)'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchange_user'", 'unique': 'True', 'to': "orm['im.AstakosUser']"})
},
'im.invitation': {
'Meta': {'object_name': 'Invitation'},
'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
},
'im.pendingthirdpartyuser': {
'Meta': {'object_name': 'PendingThirdPartyUser'},
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'im.service': {
'Meta': {'object_name': 'Service'},
'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'icon': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
'url': ('django.db.models.fields.FilePathField', [], {'max_length': '100'})
}
}
complete_apps = ['im']
......@@ -39,7 +39,7 @@ import json
from time import asctime
from datetime import datetime, timedelta
from base64 import b64encode
from urlparse import urlparse, urlunparse
from urlparse import urlparse
from random import randint
from django.db import models, IntegrityError
......@@ -374,6 +374,42 @@ class AdditionalMail(models.Model):
owner = models.ForeignKey(AstakosUser)
email = models.EmailField()
class PendingThirdPartyUser(models.Model):
"""
Model for registring successful third party user authentications
"""
third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
provider = models.CharField('Provider', max_length=255, blank=True)
email = models.EmailField(_('e-mail address'), blank=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
affiliation = models.CharField('Affiliation', max_length=255, blank=True)
username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
@property
def realname(self):
return '%s %s' %(self.first_name, self.last_name)
@realname.setter
def realname(self, value):
parts = value.split(' ')
if len(parts) == 2:
self.first_name = parts[0]
self.last_name = parts[1]
else:
self.last_name = parts[0]
def save(self, **kwargs):
if not self.id:
# set username
while not self.username:
username = uuid.uuid4().hex[:30]
try:
AstakosUser.objects.get(username = username)
except AstakosUser.DoesNotExist, e:
self.username = username
super(PendingThirdPartyUser, self).save(**kwargs)
def create_astakos_user(u):
try:
AstakosUser.objects.get(user_ptr=u.pk)
......
......@@ -119,4 +119,14 @@ PASSWORD_RESET_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_PASSWORD_RESET_EMAIL_S
'Password reset on %s alpha2 testing' % SITENAME)
# Enforce token renewal on password change/reset
NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)
\ No newline at end of file
NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)
# Permit local account migration
ENABLE_LOCAL_ACCOUNT_MIGRATION = getattr(settings, 'ASTAKOS_ENABLE_LOCAL_ACCOUNT_MIGRATION', True)
# A dictionary describing the user fields appearing during the second step of third party account creation
from django import forms
THIRDPARTY_ACC_ADDITIONAL_FIELDS = getattr(settings, 'ASTAKOS_THIRDPARTY_ACC_ADDITIONAL_FIELDS', {
'first_name':None,
'last_name':None,
})
\ No newline at end of file
......@@ -39,12 +39,14 @@ from django.contrib import messages
from django.utils.translation import ugettext as _
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.core.urlresolvers import reverse
from astakos.im.util import prepare_response, get_query
from astakos.im.views import requires_anonymous
from astakos.im.models import AstakosUser
from astakos.im.models import AstakosUser, PendingThirdPartyUser
from astakos.im.forms import LoginForm
from astakos.im.settings import RATELIMIT_RETRIES_ALLOWED
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION
from ratelimit.decorators import ratelimit
......@@ -62,11 +64,16 @@ def login(request, on_failure='im/login.html'):
was_limited = getattr(request, 'limited', False)
form = LoginForm(data=request.POST, was_limited=was_limited, request=request)
next = get_query(request).get('next', '')
username = get_query(request).get('key')
if not form.is_valid():
return render_to_response(on_failure,
{'login_form':form,
'next':next},
context_instance=RequestContext(request))
return render_to_response(
on_failure,
{'login_form':form,
'next':next,
'key':username},
context_instance=RequestContext(request)
)
# get the user from the cash
user = form.user_cache
......@@ -74,11 +81,39 @@ def login(request, on_failure='im/login.html'):
if not user:
message = _('Cannot authenticate account')
elif not user.is_active:
message = _('Inactive account')
if user.sent_activation:
message = _('Your request is pending activation')
else:
message = _('You have not followed the activation link')
if message:
messages.add_message(request, messages.ERROR, message)
return render_to_response(on_failure,
{'form':form},
context_instance=RequestContext(request))
return prepare_response(request, user, next)
# hook for switching account to use third party authentication
if ENABLE_LOCAL_ACCOUNT_MIGRATION and username:
try:
new = PendingThirdPartyUser.objects.get(
username=username)
except:
messages.error(
request,
_('Account failed to switch to %(provider)s' % locals())
)
return render_to_response(
on_failure,
{'login_form':form,
'next':next},
context_instance=RequestContext(request)
)
else:
user.provider = new.provider
user.third_party_identifier = new.third_party_identifier
user.save()
new.delete()
messages.success(
request,
_('Account successfully switched to %(provider)s' % user.__dict__)
)
return prepare_response(request, user, next)
\ No newline at end of file
......@@ -36,14 +36,25 @@ from django.utils.translation import ugettext as _
from django.contrib import messages
from django.template import RequestContext
from django.views.decorators.http import require_http_methods
from django.db.models import Q
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from urlparse import urlunsplit, urlsplit
from django.utils.http import urlencode
from astakos.im.util import prepare_response, get_context, get_invitation
from astakos.im.views import requires_anonymous, render_response
from astakos.im.settings import DEFAULT_USER_LEVEL
from astakos.im.models import AstakosUser, Invitation, AdditionalMail
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
from astakos.im.models import AstakosUser, PendingThirdPartyUser
from astakos.im.forms import LoginForm
from astakos.im.activation_backends import get_backend, SimpleBackend
import logging
logger = logging.getLogger(__name__)
class Tokens:
# these are mapped by the Shibboleth SP software
SHIB_EPPN = "HTTP_EPPN" # eduPersonPrincipalName
......@@ -57,7 +68,13 @@ class Tokens:
@require_http_methods(["GET", "POST"])
@requires_anonymous
def login(request, backend=None, on_login_template='im/login.html', on_creation_template='im/third_party_registration.html', extra_context={}):
def login(
request,
on_login_template='im/login.html',
on_signup_template='im/third_party_check_local.html',
extra_context=None):
extra_context = extra_context or {}
tokens = request.META
try:
......@@ -75,10 +92,13 @@ def login(request, backend=None, on_login_template='im/login.html', on_creation
return HttpResponseBadRequest("Missing user name in request")
affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
email = tokens.get(Tokens.SHIB_MAIL, None)
email = tokens.get(Tokens.SHIB_MAIL, '')
try:
user = AstakosUser.objects.get(provider='shibboleth', third_party_identifier=eppn)
user = AstakosUser.objects.get(
provider='shibboleth',
third_party_identifier=eppn
)
if user.is_active:
return prepare_response(request,
user,
......@@ -91,17 +111,74 @@ def login(request, backend=None, on_login_template='im/login.html', on_creation
login_form = LoginForm(request=request),
context_instance=RequestContext(request))
except AstakosUser.DoesNotExist, e:
user = AstakosUser(third_party_identifier=eppn, realname=realname,
affiliation=affiliation, provider='shibboleth',
email=email)
# First time
try:
user, created = PendingThirdPartyUser.objects.get_or_create(
third_party_identifier=eppn,
provider='shibboleth',
defaults=dict(
realname=realname,
affiliation=affiliation,
email=email
)
)
user.save()
except BaseException, e:
logger.exception(e)
template = on_login_template
extra_context['login_form'] = LoginForm(request=request)
messages.error(request, _('Something went wrong.'))
else:
if not ENABLE_LOCAL_ACCOUNT_MIGRATION:
url = reverse(
'astakos.im.target.shibboleth.signup'
)
parts = list(urlsplit(url))
parts[3] = urlencode({'key': user.username})
url = urlunsplit(parts)
return HttpResponseRedirect(url)
else:
template = on_signup_template
extra_context['key'] = user.username
extra_context['provider']='shibboleth'
return render_response(
template,
context_instance=get_context(request, extra_context)
)
@require_http_methods(["GET"])
@requires_anonymous
def signup(request,
backend=None,
on_creation_template='im/third_party_registration.html',
extra_context=None
):
extra_context = extra_context or {}
username = request.GET.get('key')
if not username:
return HttpResponseBadRequest(_('Missing key parameter.'))
try:
pending = PendingThirdPartyUser.objects.get(username=username)
except BaseException, e:
logger.exception(e)
return HttpResponseBadRequest(_('Invalid key.'))
else:
d = pending.__dict__
d.pop('_state', None)
d.pop('id', None)
user = AstakosUser(**d)
try:
if not backend:
backend = get_backend(request)
form = backend.get_signup_form(provider='shibboleth', instance=user)
except Exception, e:
form = SimpleBackend(request).get_signup_form(provider='shibboleth', instance=user)
messages.add_message(request, messages.ERROR, e)
return render_response(on_creation_template,
signup_form = form,
provider = 'shibboleth',
context_instance=get_context(request, extra_context))
backend = backend or get_backend(request)
except ImproperlyConfigured, e:
messages.error(request, e)
else:
extra_context['form'] = backend.get_signup_form(
provider='shibboleth',
instance=user
)
extra_context['provider']='shibboleth'
return render_response(
on_creation_template,
context_instance=get_context(request, extra_context)
)
\ No newline at end of file
......@@ -16,9 +16,17 @@
{% include "im/form_render.html" %}
{% endwith %}
<input type="hidden" name="next" value="{{ next }}">
{% if key %}
<input type="hidden" name="key" value="{{key}}">
{% else %}