Commit d572aa02 authored by Kostas Papadimitriou's avatar Kostas Papadimitriou
Browse files

Auth providers improvements

- Enrich login/logout messages.
- Clear unverified accounts when user adds the same third party account
  to an existing account.
- Other minor improvements.
parent 477d51b3
......@@ -79,6 +79,10 @@ class AuthProvider(object):
one_per_user = False
login_prompt = _('Login using ')
primary_login_prompt = _('Login using ')
login_message = None
logout_message = 'You may still be logged in at "%(provider)s". Consider logging out.'
remote_authenticate = True
remote_logout_url = None
def get_message(self, msg, **kwargs):
params = kwargs
......@@ -105,6 +109,13 @@ class AuthProvider(object):
if override != None:
setattr(self, key, override)
self.login_message = self.login_message or self.get_title_display
if self.logout_message and "%" in self.logout_message:
self.logout_message = self.logout_message % {'provider':
self.get_login_message_display}
else:
self.logout_message = self.logout_message or ''
def __getattr__(self, key):
if not key.startswith('get_'):
return super(AuthProvider, self).__getattribute__(key)
......@@ -118,6 +129,12 @@ class AuthProvider(object):
else:
return super(AuthProvider, self).__getattr__(key)
def get_logout_message(self):
content = ''
if self.remote_logout_url:
content = '<a href="%s" title="Logout from %%s"></a>' % self.remote_logou_url
return content % (self.get_logout_message_display % self.get_title_display)
def get_setting(self, name, default=None):
attr = 'ASTAKOS_AUTH_PROVIDER_%s_%s' % (self.module.upper(), name.upper())
attr_sec = 'ASTAKOS_%s_%s' % (self.module.upper(), name.upper())
......@@ -158,6 +175,8 @@ class LocalAuthProvider(AuthProvider):
signup_prompt = _('New to ~okeanos ?')
signup_link_prompt = _('create an account now')
login_view = 'password_change'
remote_authenticate = False
logout_message = ''
one_per_user = True
......
......@@ -274,7 +274,12 @@ class ThirdPartyUserCreationForm(forms.ModelForm, StoreUserMixin):
if not email:
raise forms.ValidationError(_(astakos_messages.REQUIRED_FIELD))
if reserved_verified_email(email):
raise forms.ValidationError(_(astakos_messages.EMAIL_USED))
provider = auth_providers.get_provider(self.request.REQUEST.get('provider', 'local'))
extra_message = _(astakos_messages.EXISTING_EMAIL_THIRD_PARTY_NOTIFICATION) % \
(provider.get_title_display, reverse('edit_profile'))
raise forms.ValidationError(_(astakos_messages.EMAIL_USED) + ' ' + \
extra_message)
return email
def clean_has_signed_terms(self):
......
......@@ -67,8 +67,8 @@ USER_LEFT_GROUP = 'User %(realname)s left the project.
USER_MEMBERSHIP_REJECTED = 'User\'s %(realname)s request to join the project has been rejected.'
MEMBER_REMOVED = 'User %(realname)s has been successfully removed from the project.'
BILLING_ERROR = 'Service response status: %(status)d'
LOGOUT_SUCCESS = 'You are now logged out.'
LOGIN_SUCCESS = 'You are now logged in.'
LOGOUT_SUCCESS = 'You are now logged out from your ~okeanos account.'
LOGIN_SUCCESS = 'You have logged in to your ~okeanos account using "%s".'
GENERIC_ERROR = 'Hmm... It seems something bad has happened, and we don\'t know the details right now. \
Please contact the administrators by email for more details.'
......@@ -197,6 +197,9 @@ AUTH_PROVIDER_INVALID_LOGIN = "No account exists."
AUTH_PROVIDER_REQUIRED = "%(provider)s login method is required. Add one from your profile page."
AUTH_PROVIDER_CANNOT_CHANGE_PASSWORD = "Changing password is not supported."
EXISTING_EMAIL_THIRD_PARTY_NOTIFICATION = "You can add '%s' login method to your existing account from your " \
" <a href='%s'>profile page</a>"
messages = locals().keys()
for msg in messages:
if msg == msg.upper():
......
......@@ -533,6 +533,7 @@ class AstakosUser(User):
if 'identifier' in kwargs:
try:
# provider with specified params already exist
kwargs['user__email_verified'] = True
existing_user = AstakosUser.objects.get_auth_provider_user(provider,
**kwargs)
except AstakosUser.DoesNotExist:
......@@ -577,6 +578,8 @@ class AstakosUser(User):
info_data = json.dumps(info_data)
if self.can_add_auth_provider(provider, **kwargs):
AstakosUserAuthProvider.objects.remove_unverified_providers(provider,
**kwargs)
self.auth_providers.create(module=provider, active=True,
info_data=info_data,
**kwargs)
......@@ -692,6 +695,15 @@ class AstakosUserAuthProviderManager(models.Manager):
def active(self, **filters):
return self.filter(active=True, **filters)
def remove_unverified_providers(self, provider, **filters):
try:
existing = self.filter(module=provider, user__email_verified=False, **filters)
for p in existing:
p.user.delete()
except:
pass
class AstakosUserAuthProvider(models.Model):
"""
......@@ -960,9 +972,12 @@ class PendingThirdPartyUser(models.Model):
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, null=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)
first_name = models.CharField(_('first name'), max_length=30, blank=True,
null=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True,
null=True)
affiliation = models.CharField('Affiliation', max_length=255, blank=True,
null=True)
username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
token = models.CharField(_('Token'), max_length=255, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
......
......@@ -182,7 +182,10 @@ def authenticated(
user,
request.GET.get('next'),
'renew' in request.GET)
messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
provider = auth_providers.get_provider('google')
messages.success(request, _(astakos_messages.LOGIN_SUCCESS) %
_(provider.get_login_message_display))
add_pending_auth_provider(request, third_party_key)
response.set_cookie('astakos_last_login_method', 'google')
return response
......
......@@ -175,7 +175,9 @@ def authenticated(
user,
request.GET.get('next'),
'renew' in request.GET)
messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
provider = auth_providers.get_provider('linkedin')
messages.success(request, _(astakos_messages.LOGIN_SUCCESS) %
_(provider.get_login_message_display))
add_pending_auth_provider(request, third_party_key)
response.set_cookie('astakos_last_login_method', 'linkedin')
return response
......
......@@ -51,6 +51,7 @@ from astakos.im.settings import (RATELIMIT_RETRIES_ALLOWED,
import astakos.im.messages as astakos_messages
from astakos.im.views import requires_auth_provider
from astakos.im import settings
from astakos.im import auth_providers
from ratelimit.decorators import ratelimit
......@@ -118,7 +119,9 @@ def login(request, on_failure='im/login.html'):
except PendingThirdPartyUser.DoesNotExist:
messages.error(request, _(astakos_messages.AUTH_PROVIDER_ADD_FAILED))
messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
provider = auth_providers.get_provider('local')
messages.success(request, _(astakos_messages.LOGIN_SUCCESS) %
_(provider.get_login_message_display))
response.set_cookie('astakos_last_login_method', 'local')
return response
......
......@@ -87,33 +87,33 @@ def login(
tokens = request.META
third_party_key = get_pending_key(request)
# try:
# eppn = tokens.get(Tokens.SHIB_EPPN)
# if not eppn:
# raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_EPPN) % {
# 'domain': settings.BASEURL,
# 'contact_email': settings.DEFAULT_CONTACT_EMAIL
# })
# if Tokens.SHIB_DISPLAYNAME in tokens:
# realname = tokens[Tokens.SHIB_DISPLAYNAME]
# elif Tokens.SHIB_CN in tokens:
# realname = tokens[Tokens.SHIB_CN]
# elif Tokens.SHIB_NAME in tokens and Tokens.SHIB_SURNAME in tokens:
# realname = tokens[Tokens.SHIB_NAME] + ' ' + tokens[Tokens.SHIB_SURNAME]
# else:
# if settings.SHIBBOLETH_REQUIRE_NAME_INFO:
# raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_NAME))
# else:
# realname = ''
#
# except KeyError, e:
# # invalid shibboleth headers, redirect to login, display message
# messages.error(request, e.message)
# return HttpResponseRedirect(login_url(request))
# affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
# email = tokens.get(Tokens.SHIB_MAIL, '')
eppn, email, realname, affiliation = 'spapagian@grnet-hq.admin.grnet.gr', 'spapagian@.grnet.gr', 'sff', None
try:
eppn = tokens.get(Tokens.SHIB_EPPN)
if not eppn:
raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_EPPN) % {
'domain': settings.BASEURL,
'contact_email': settings.DEFAULT_CONTACT_EMAIL
})
if Tokens.SHIB_DISPLAYNAME in tokens:
realname = tokens[Tokens.SHIB_DISPLAYNAME]
elif Tokens.SHIB_CN in tokens:
realname = tokens[Tokens.SHIB_CN]
elif Tokens.SHIB_NAME in tokens and Tokens.SHIB_SURNAME in tokens:
realname = tokens[Tokens.SHIB_NAME] + ' ' + tokens[Tokens.SHIB_SURNAME]
else:
if settings.SHIBBOLETH_REQUIRE_NAME_INFO:
raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_NAME))
else:
realname = ''
except KeyError, e:
# invalid shibboleth headers, redirect to login, display message
messages.error(request, e.message)
return HttpResponseRedirect(login_url(request))
affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, 'Shibboleth')
email = tokens.get(Tokens.SHIB_MAIL, '')
#eppn, email, realname, affiliation = 'test@grnet-hq.admin.grnet.gr', 'test@grnet.gr', 'sff', None
provider_info = {'eppn': eppn, 'email': email, 'name': realname}
userid = eppn
......@@ -151,7 +151,9 @@ def login(
user,
request.GET.get('next'),
'renew' in request.GET)
messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
provider = auth_providers.get_provider('shibboleth')
messages.success(request, _(astakos_messages.LOGIN_SUCCESS) %
_(provider.get_login_message_display))
add_pending_auth_provider(request, third_party_key)
response.set_cookie('astakos_last_login_method', 'local')
return response
......
......@@ -168,7 +168,9 @@ def authenticated(
user,
request.GET.get('next'),
'renew' in request.GET)
messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
provider = auth_providers.get_provider('twitter')
messages.success(request, _(astakos_messages.LOGIN_SUCCESS) %
_(provider.get_login_message_display))
add_pending_auth_provider(request, third_party_key)
response.set_cookie('astakos_last_login_method', 'twitter')
return response
......
......@@ -14,7 +14,7 @@
<div class="form-stacked">
<form action="{% url signup %}" method="post"
class="innerlabels signup">{% csrf_token %}
<h2><span>SIGN UP</span></h2>
<h2><span>SIGN UP</span></h2>
<input type="hidden" name="next" value="{{ next }}">
<input type="hidden" name="code" value="{{ code }}">
<input type="hidden" name="provider" value={{ provider|default:"local" }}>
......
......@@ -516,8 +516,16 @@ class LocalUserTests(TestCase):
data = {'email':'KPAP@grnet.gr', 'password1':'password',
'password2':'password', 'first_name': 'Kostas',
'last_name': 'Mitroglou', 'provider': 'local'}
r = self.client.post("/im/signup", data)
self.assertContains(r, messages.EMAIL_USED)
r = self.client.post("/im/signup", data, follow=True)
self.assertRedirects(r, reverse('index'))
self.assertContains(r, messages.NOTIFICATION_SENT)
# previous user replaced
user = AstakosUser.objects.get(pk=user.pk)
self.assertTrue(user.activation_sent)
self.assertFalse(user.email_verified)
self.assertFalse(user.is_active)
self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 2)
# hmmm, email exists; lets request a password change
r = self.client.get('/im/local/password_reset')
......
......@@ -56,6 +56,7 @@ from django.http import (
from django.shortcuts import redirect
from django.template import RequestContext, loader as template_loader
from django.utils.http import urlencode
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _
from django.views.generic.create_update import (
apply_extra_context, lookup_object, delete_object, get_model_and_form_class)
......@@ -607,7 +608,14 @@ def logout(request, template='registration/logged_out.html', extra_context=None)
response['Location'] = LOGOUT_NEXT
response.status_code = 301
else:
messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
message = _(astakos_messages.LOGOUT_SUCCESS)
last_provider = request.COOKIES.get('astakos_last_login_method', None)
if last_provider:
provider = auth_providers.get_provider(last_provider)
extra_message = provider.get_logout_message_display
if extra_message:
message += '<br />' + extra_message
messages.add_message(request, messages.SUCCESS, mark_safe(message))
response['Location'] = reverse('index')
response.status_code = 301
return response
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment