From b615e300d6dd49fb8bc4b4ef992b4a6776ca6bb7 Mon Sep 17 00:00:00 2001 From: Benjamin Renard Date: Fri, 24 Feb 2017 14:39:41 +0100 Subject: [PATCH] Add API method to generate auto-login link --- src/authentic2/api_urls.py | 4 ++ src/authentic2/api_views.py | 114 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/src/authentic2/api_urls.py b/src/authentic2/api_urls.py index aa061f5..d4b0c44 100644 --- a/src/authentic2/api_urls.py +++ b/src/authentic2/api_urls.py @@ -11,5 +11,9 @@ urlpatterns = patterns('', name='a2-api-user'), url(r'^roles/(?P[\w+]*)/members/(?P[\w+]*)/$', api_views.roles, name='a2-api-role-member'), + url(r'^user/autologin$', api_views.userautologin, + name='a2-api-user-autologin'), + url(r'^user/autologin/(?P[\w: -]+)$', api_views.userautologinview, + name='autologin'), ) urlpatterns += api_views.router.urls diff --git a/src/authentic2/api_views.py b/src/authentic2/api_views.py index db001c0..a9d68e4 100644 --- a/src/authentic2/api_views.py +++ b/src/authentic2/api_views.py @@ -1,14 +1,21 @@ '''Views for Authentic2 API''' import logging import smtplib +import datetime +from django import http from django.db import models -from django.contrib.auth import get_user_model +from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model +from django.core import signing from django.core.exceptions import MultipleObjectsReturned from django.utils.translation import ugettext as _ +from django.utils.timezone import now +from django.utils.dateparse import parse_datetime from django.views.decorators.vary import vary_on_headers from django.views.decorators.cache import cache_control from django.shortcuts import get_object_or_404 +from django.views.generic.base import RedirectView +from django.core.urlresolvers import reverse from django_rbac.utils import get_ou_model, get_role_model @@ -27,6 +34,8 @@ from . import utils, decorators from .models import Attribute, PasswordReset from .a2_rbac.utils import get_default_ou +from utils import login + class HasUserAddPermission(permissions.BasePermission): def has_permission(self, request, view): @@ -34,6 +43,13 @@ class HasUserAddPermission(permissions.BasePermission): return False return True +class IsSuperUser(permissions.BasePermission): + """ + Allows access only to super users. + """ + + def has_permission(self, request, view): + return request.user and request.user.is_superuser class RegistrationSerializer(serializers.Serializer): '''Register RPC payload''' @@ -242,6 +258,102 @@ class PasswordChange(BaseRpcView): password_change = PasswordChange.as_view() +class UserAutoLoginSerializer(serializers.Serializer): + '''UserAutoLogin RPC payload''' + email = serializers.EmailField() + ou = serializers.SlugRelatedField( + queryset=get_ou_model().objects.all(), + slug_field='slug', + required=False, allow_null=True) + expire_duration = serializers.IntegerField( + min_value=1, required=False, allow_null=False) + redirect_url = serializers.CharField( + required=False, allow_null=True) + + def validate(self, data): + User = get_user_model() + qs = User.objects.filter(email=data['email']) + if data.get('ou'): + qs = qs.filter(ou=data.get('ou')) + try: + self.user = qs.get() + except User.DoesNotExist: + raise serializers.ValidationError('no user found') + except MultipleObjectsReturned: + raise serializers.ValidationError('more than one user have this email') + return data + + +class UserAutoLogin(BaseRpcView): + permission_classes = (permissions.IsAuthenticated, + IsSuperUser) + + serializer_class = UserAutoLoginSerializer + + def rpc(self, request, serializer): + data = {} + data['email'] = serializer.validated_data.get('email') + data['expire'] = (now()+datetime.timedelta(seconds=serializer.validated_data.get('expire_duration',300))).isoformat() + if serializer.validated_data.get('ou'): + data['ou'] = serializer.validated_data.get('ou') + data[REDIRECT_FIELD_NAME] = serializer.validated_data.get('redirect_url') + token = signing.dumps(data) + autologinurl = request.build_absolute_uri( + reverse('autologin', kwargs={'token': token})) + return {'autologinurl': autologinurl}, status.HTTP_200_OK + +userautologin = UserAutoLogin.as_view() + +class UserAutoLoginView(RedirectView): + + def valid_token(self, token): + try: + result = signing.loads(token.replace(' ', '')) + expire = result.get('expire') + if expire: + if now()>=parse_datetime(expire): + logging.getLogger(__name__).error(u'Specified token is expired') + else: + return result + else: + logging.getLogger(__name__).error(u'No expire in token') + except signing.BadSignature: + logging.getLogger(__name__).error(u'Specified token is invalid') + return None + + def get_redirect_url(self, *args, **kwargs): + if kwargs.get('token') and kwargs.get('token').get(REDIRECT_FIELD_NAME): + return kwargs.get('token').get(REDIRECT_FIELD_NAME) + return reverse('auth_homepage') + + def get(self, request, *args, **kwargs): + token = self.valid_token(kwargs.get('token')) + + url = None + if token: + logging.getLogger(__name__).info(u"Valid token : %s" % token) + User = get_user_model() + qs = User.objects.filter(email=token['email']) + if token.get('ou'): + qs = qs.filter(ou=token['ou']) + try: + user = qs.get() + logging.getLogger(__name__).info(u"User : %s" % user) + user.backend = 'authentic2.backends.models_backend.ModelBackend' + login(request, user, 'autologin') + url = self.get_redirect_url(token = token) + logging.getLogger(__name__).info(u"Redirect to : %s" % url) + except User.DoesNotExist: + logging.getLogger(__name__).error(u'User %s from token does not exists' % token['email']) + except MultipleObjectsReturned: + logging.getLogger(__name__).error(u"More than one user return from token's email (%s)" % token['email']) + + if not url: + url = reverse('auth_login') + + return http.HttpResponseRedirect(url) + +userautologinview = UserAutoLoginView.as_view() @vary_on_headers('Cookie', 'Origin', 'Referer') @cache_control(private=True, max_age=60) -- 2.1.4