forked from DGNum/gestioCOF
Mass cleaning of kfet' authentication machinery
AccountBackend - Should now work if used in AUTHENTICATION_BACKENDS settings. - It does not retieve itself the password, as it should not be used this way. GenericBackend - Delete useless 'username' arg of its 'authenticate()' method. - Now delete the token in DB. TemporaryAuthMiddleware - New name of the middleware is more meaningful. - Is now responsible to retrieve the password from the request, instead of the AccountBackend. GenericTeamToken model - Add a manager' method to create token, avoiding possible error due to unicity constraint. GenericLoginView (authentication with the kfet generic user) - Replace obscure system with a 100% HTTP handling. - See comments for more information. Misc - More docstrings! - More tests! - Add some i18n. - Add kfet/confirm_form.html template: Ask user to confirm sth via a form (which will send a POST request). Context variables: * title: the page title * confirm_url: action attribute for <form> * text: displayed confirmation text - kfet.js : Add functions allowing to emit POST request from <a> tag. - Non-link nav items from kfet navbar also get a 'title'. - A utility has been found for the 'sunglasses' glyphicon!
This commit is contained in:
parent
3fa7754ff4
commit
b42452080f
18 changed files with 559 additions and 119 deletions
|
@ -100,7 +100,7 @@ MIDDLEWARE_CLASSES = [
|
|||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'kfet.auth.middleware.KFetAuthenticationMiddleware',
|
||||
'kfet.auth.middleware.TemporaryAuthMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
|
@ -128,7 +128,7 @@ TEMPLATES = [
|
|||
'wagtailmenus.context_processors.wagtailmenus',
|
||||
'djconfig.context_processors.config',
|
||||
'gestioncof.shared.context_processor',
|
||||
'kfet.auth.context_processors.auth',
|
||||
'kfet.auth.context_processors.temporary_auth',
|
||||
'kfet.context_processors.config',
|
||||
],
|
||||
},
|
||||
|
@ -191,7 +191,7 @@ CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
|||
AUTHENTICATION_BACKENDS = (
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'gestioncof.shared.COFCASBackend',
|
||||
'kfet.auth.backends.GenericTeamBackend',
|
||||
'kfet.auth.backends.GenericBackend',
|
||||
)
|
||||
|
||||
RECAPTCHA_USE_SSL = True
|
||||
|
|
|
@ -11,7 +11,6 @@ class KFetConfig(AppConfig):
|
|||
verbose_name = "Application K-Fêt"
|
||||
|
||||
def ready(self):
|
||||
import kfet.signals
|
||||
self.register_config()
|
||||
|
||||
def register_config(self):
|
||||
|
|
|
@ -9,5 +9,6 @@ class KFetAuthConfig(AppConfig):
|
|||
verbose_name = _("K-Fêt - Authentification et Autorisation")
|
||||
|
||||
def ready(self):
|
||||
from . import signals # noqa
|
||||
from .utils import setup_kfet_generic_user
|
||||
post_migrate.connect(setup_kfet_generic_user, sender=self)
|
||||
|
|
|
@ -22,32 +22,22 @@ class BaseKFetBackend:
|
|||
return None
|
||||
|
||||
|
||||
class KFetBackend(BaseKFetBackend):
|
||||
def authenticate(self, request):
|
||||
password = request.POST.get('KFETPASSWORD', '')
|
||||
password = request.META.get('HTTP_KFETPASSWORD', password)
|
||||
if not password:
|
||||
return None
|
||||
|
||||
class AccountBackend(BaseKFetBackend):
|
||||
def authenticate(self, request, kfet_password=None):
|
||||
try:
|
||||
return Account.objects.get_by_password(password).user
|
||||
return Account.objects.get_by_password(kfet_password).user
|
||||
except Account.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
class GenericTeamBackend(BaseKFetBackend):
|
||||
def authenticate(self, username=None, token=None):
|
||||
valid_token = GenericTeamToken.objects.get(token=token)
|
||||
if username == 'kfet_genericteam' and valid_token:
|
||||
return get_kfet_generic_user()
|
||||
return None
|
||||
|
||||
def get_user(self, user_id):
|
||||
class GenericBackend(BaseKFetBackend):
|
||||
def authenticate(self, request, kfet_token=None):
|
||||
try:
|
||||
return (
|
||||
User.objects
|
||||
.select_related('profile__account_kfet')
|
||||
.get(pk=user_id)
|
||||
)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
team_token = GenericTeamToken.objects.get(token=kfet_token)
|
||||
except GenericTeamToken.DoesNotExist:
|
||||
return
|
||||
|
||||
# No need to keep the token.
|
||||
team_token.delete()
|
||||
|
||||
return get_kfet_generic_user()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.contrib.auth.context_processors import PermWrapper
|
||||
|
||||
|
||||
def auth(request):
|
||||
def temporary_auth(request):
|
||||
if hasattr(request, 'real_user'):
|
||||
return {
|
||||
'user': request.real_user,
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from .backends import AccountBackend
|
||||
|
||||
from .backends import KFetBackend
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class KFetAuthenticationMiddleware(object):
|
||||
"""Authenticate another user for this request if KFetBackend succeeds.
|
||||
class TemporaryAuthMiddleware:
|
||||
"""Authenticate another user for this request if AccountBackend succeeds.
|
||||
|
||||
By the way, if a user is authenticated, we refresh its from db to add
|
||||
values from CofProfile and Account of this user.
|
||||
|
@ -15,15 +16,23 @@ class KFetAuthenticationMiddleware(object):
|
|||
def process_request(self, request):
|
||||
if request.user.is_authenticated():
|
||||
# avoid multiple db accesses in views and templates
|
||||
user_pk = request.user.pk
|
||||
request.user = (
|
||||
User.objects
|
||||
.select_related('profile__account_kfet')
|
||||
.get(pk=user_pk)
|
||||
.get(pk=request.user.pk)
|
||||
)
|
||||
|
||||
kfet_backend = KFetBackend()
|
||||
temp_request_user = kfet_backend.authenticate(request)
|
||||
temp_request_user = AccountBackend().authenticate(
|
||||
request,
|
||||
kfet_password=self.get_kfet_password(request),
|
||||
)
|
||||
|
||||
if temp_request_user:
|
||||
request.real_user = request.user
|
||||
request.user = temp_request_user
|
||||
|
||||
def get_kfet_password(self, request):
|
||||
return (
|
||||
request.META.get('HTTP_KFETPASSWORD') or
|
||||
request.POST.get('KFETPASSWORD')
|
||||
)
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
from django.db import models
|
||||
from django.utils.crypto import get_random_string
|
||||
|
||||
|
||||
class GenericTeamTokenManager(models.Manager):
|
||||
|
||||
def create_token(self):
|
||||
token = get_random_string(50)
|
||||
while self.filter(token=token).exists():
|
||||
token = get_random_string(50)
|
||||
return self.create(token=token)
|
||||
|
||||
|
||||
class GenericTeamToken(models.Model):
|
||||
token = models.CharField(max_length=50, unique=True)
|
||||
|
||||
objects = GenericTeamTokenManager()
|
||||
|
|
40
kfet/auth/signals.py
Normal file
40
kfet/auth/signals.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from django.contrib import messages
|
||||
from django.contrib.auth.signals import user_logged_in
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.dispatch import receiver
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from .utils import get_kfet_generic_user
|
||||
|
||||
|
||||
@receiver(user_logged_in)
|
||||
def suggest_auth_generic(sender, request, user, **kwargs):
|
||||
"""
|
||||
Suggest logged in user to continue as the kfet generic user.
|
||||
|
||||
Message is only added if the following conditions are met:
|
||||
- the next page (where user is going to be redirected due to successful
|
||||
authentication) is related to kfet, i.e. 'k-fet' is in its url.
|
||||
- logged in user is a kfet staff member (except the generic user).
|
||||
"""
|
||||
# Filter against the next page.
|
||||
if not(hasattr(request, 'GET') and 'next' in request.GET):
|
||||
return
|
||||
|
||||
next_page = request.GET['next']
|
||||
generic_url = reverse('kfet.login.generic')
|
||||
|
||||
if not('k-fet' in next_page and not next_page.startswith(generic_url)):
|
||||
return
|
||||
|
||||
# Filter against the logged in user.
|
||||
if not(user.has_perm('kfet.is_team') and user != get_kfet_generic_user()):
|
||||
return
|
||||
|
||||
# Seems legit to add message.
|
||||
text = _("K-Fêt — Ouvrir une session partagée ?")
|
||||
messages.info(request, mark_safe(
|
||||
'<a href="#" data-url="{}" onclick="submit_url(this)">{}</a>'
|
||||
.format(generic_url, text)
|
||||
))
|
|
@ -1,7 +0,0 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script type="text/javascript">
|
||||
close();
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,15 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.core import signing
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import AnonymousUser, Group, Permission, User
|
||||
from django.test import RequestFactory, TestCase
|
||||
|
||||
from kfet.forms import UserGroupForm
|
||||
from kfet.models import Account
|
||||
|
||||
from . import KFET_GENERIC_TRIGRAMME, KFET_GENERIC_USERNAME
|
||||
from .backends import AccountBackend, GenericBackend
|
||||
from .middleware import TemporaryAuthMiddleware
|
||||
from .models import GenericTeamToken
|
||||
from .utils import get_kfet_generic_user
|
||||
from .views import GenericLoginView
|
||||
|
||||
|
||||
##
|
||||
# Forms
|
||||
##
|
||||
|
||||
class UserGroupFormTests(TestCase):
|
||||
"""Test suite for UserGroupForm."""
|
||||
|
||||
|
@ -70,3 +81,287 @@ class KFetGenericUserTests(TestCase):
|
|||
self.assertEqual(generic.trigramme, KFET_GENERIC_TRIGRAMME)
|
||||
self.assertEqual(generic.user.username, KFET_GENERIC_USERNAME)
|
||||
self.assertEqual(get_kfet_generic_user(), generic.user)
|
||||
|
||||
|
||||
##
|
||||
# Backends
|
||||
##
|
||||
|
||||
class AccountBackendTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.request = RequestFactory().get('/')
|
||||
|
||||
def test_valid(self):
|
||||
acc = Account(trigramme='000')
|
||||
acc.change_pwd('valid')
|
||||
acc.save({'username': 'user'})
|
||||
|
||||
auth = AccountBackend().authenticate(
|
||||
self.request, kfet_password='valid')
|
||||
|
||||
self.assertEqual(auth, acc.user)
|
||||
|
||||
def test_invalid(self):
|
||||
auth = AccountBackend().authenticate(
|
||||
self.request, kfet_password='invalid')
|
||||
self.assertIsNone(auth)
|
||||
|
||||
|
||||
class GenericBackendTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.request = RequestFactory().get('/')
|
||||
|
||||
def test_valid(self):
|
||||
token = GenericTeamToken.objects.create_token()
|
||||
|
||||
auth = GenericBackend().authenticate(
|
||||
self.request, kfet_token=token.token)
|
||||
|
||||
self.assertEqual(auth, get_kfet_generic_user())
|
||||
self.assertEqual(GenericTeamToken.objects.all().count(), 0)
|
||||
|
||||
def test_invalid(self):
|
||||
auth = GenericBackend().authenticate(
|
||||
self.request, kfet_token='invalid')
|
||||
self.assertIsNone(auth)
|
||||
|
||||
|
||||
##
|
||||
# Views
|
||||
##
|
||||
|
||||
class GenericLoginViewTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
patcher_messages = mock.patch('gestioncof.signals.messages')
|
||||
patcher_messages.start()
|
||||
self.addCleanup(patcher_messages.stop)
|
||||
|
||||
user_acc = Account(trigramme='000')
|
||||
user_acc.save({'username': 'user'})
|
||||
self.user = user_acc.user
|
||||
self.user.set_password('user')
|
||||
self.user.save()
|
||||
|
||||
team_acc = Account(trigramme='100')
|
||||
team_acc.save({'username': 'team'})
|
||||
self.team = team_acc.user
|
||||
self.team.set_password('team')
|
||||
self.team.save()
|
||||
self.team.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type__app_label='kfet', codename='is_team'),
|
||||
)
|
||||
|
||||
self.url = reverse('kfet.login.generic')
|
||||
self.generic_user = get_kfet_generic_user()
|
||||
|
||||
def test_url(self):
|
||||
self.assertEqual(self.url, '/k-fet/login/generic')
|
||||
|
||||
def test_notoken_get(self):
|
||||
"""
|
||||
Send confirmation for user to emit POST request, instead of GET.
|
||||
"""
|
||||
self.client.login(username='team', password='team')
|
||||
|
||||
r = self.client.get(self.url)
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTemplateUsed(r, 'kfet/confirm_form.html')
|
||||
|
||||
def test_notoken_post(self):
|
||||
"""
|
||||
POST request without token in COOKIES sets a token and redirects to
|
||||
logout url.
|
||||
"""
|
||||
self.client.login(username='team', password='team')
|
||||
|
||||
r = self.client.post(self.url)
|
||||
|
||||
self.assertRedirects(
|
||||
r, '/logout?next={}'.format(self.url),
|
||||
fetch_redirect_response=False,
|
||||
)
|
||||
|
||||
def test_notoken_not_team(self):
|
||||
"""
|
||||
Logged in user must be a team user to initiate login as generic user.
|
||||
"""
|
||||
self.client.login(username='user', password='user')
|
||||
|
||||
# With GET.
|
||||
r = self.client.get(self.url)
|
||||
self.assertRedirects(
|
||||
r, '/login?next={}'.format(self.url),
|
||||
fetch_redirect_response=False,
|
||||
)
|
||||
|
||||
# Also with POST.
|
||||
r = self.client.post(self.url)
|
||||
self.assertRedirects(
|
||||
r, '/login?next={}'.format(self.url),
|
||||
fetch_redirect_response=False,
|
||||
)
|
||||
|
||||
def _set_signed_cookie(self, client, key, value):
|
||||
signed_value = signing.get_cookie_signer(salt=key).sign(value)
|
||||
client.cookies.load({key: signed_value})
|
||||
|
||||
def _is_cookie_deleted(self, client, key):
|
||||
try:
|
||||
self.assertNotIn(key, client.cookies)
|
||||
except AssertionError:
|
||||
try:
|
||||
cookie = client.cookies[key]
|
||||
# It also can be emptied.
|
||||
self.assertEqual(cookie.value, '')
|
||||
self.assertEqual(
|
||||
cookie['expires'], 'Thu, 01-Jan-1970 00:00:00 GMT')
|
||||
self.assertEqual(cookie['max-age'], 0)
|
||||
except AssertionError:
|
||||
raise AssertionError("The cookie '%s' still exists." % key)
|
||||
|
||||
def test_withtoken_valid(self):
|
||||
"""
|
||||
The kfet generic user is logged in.
|
||||
"""
|
||||
token = GenericTeamToken.objects.create(token='valid')
|
||||
self._set_signed_cookie(
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME, 'valid')
|
||||
|
||||
r = self.client.get(self.url)
|
||||
|
||||
self.assertRedirects(r, reverse('kfet.kpsul'))
|
||||
self.assertEqual(r.wsgi_request.user, self.generic_user)
|
||||
self._is_cookie_deleted(
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
||||
with self.assertRaises(GenericTeamToken.DoesNotExist):
|
||||
token.refresh_from_db()
|
||||
|
||||
def test_withtoken_invalid(self):
|
||||
"""
|
||||
If token is invalid, delete it and try again.
|
||||
"""
|
||||
self._set_signed_cookie(
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME, 'invalid')
|
||||
|
||||
r = self.client.get(self.url)
|
||||
|
||||
self.assertRedirects(r, self.url, fetch_redirect_response=False)
|
||||
self.assertEqual(r.wsgi_request.user, AnonymousUser())
|
||||
self._is_cookie_deleted(
|
||||
self.client, GenericLoginView.TOKEN_COOKIE_NAME)
|
||||
|
||||
def test_flow_ok(self):
|
||||
"""
|
||||
A team user is logged in as the kfet generic user.
|
||||
"""
|
||||
self.client.login(username='team', password='team')
|
||||
next_url = '/k-fet/'
|
||||
|
||||
r = self.client.post(
|
||||
'{}?next={}'.format(self.url, next_url), follow=True)
|
||||
|
||||
self.assertEqual(r.wsgi_request.user, self.generic_user)
|
||||
self.assertEqual(r.wsgi_request.path, '/k-fet/')
|
||||
|
||||
|
||||
##
|
||||
# Temporary authentication
|
||||
#
|
||||
# Includes:
|
||||
# - TemporaryAuthMiddleware
|
||||
# - temporary_auth context processor
|
||||
##
|
||||
|
||||
class TemporaryAuthTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
patcher_messages = mock.patch('gestioncof.signals.messages')
|
||||
patcher_messages.start()
|
||||
self.addCleanup(patcher_messages.stop)
|
||||
|
||||
self.factory = RequestFactory()
|
||||
|
||||
user1_acc = Account(trigramme='000')
|
||||
user1_acc.change_pwd('kfet_user1')
|
||||
user1_acc.save({'username': 'user1'})
|
||||
self.user1 = user1_acc.user
|
||||
self.user1.set_password('user1')
|
||||
self.user1.save()
|
||||
|
||||
user2_acc = Account(trigramme='100')
|
||||
user2_acc.change_pwd('kfet_user2')
|
||||
user2_acc.save({'username': 'user2'})
|
||||
self.user2 = user2_acc.user
|
||||
self.user2.set_password('user2')
|
||||
self.user2.save()
|
||||
|
||||
self.perm = Permission.objects.get(
|
||||
content_type__app_label='kfet', codename='is_team')
|
||||
self.user2.user_permissions.add(self.perm)
|
||||
|
||||
def test_middleware_header(self):
|
||||
"""
|
||||
A user can be authenticated if ``HTTP_KFETPASSWORD`` header of a
|
||||
request contains a valid kfet password.
|
||||
"""
|
||||
request = self.factory.get('/', HTTP_KFETPASSWORD='kfet_user2')
|
||||
request.user = self.user1
|
||||
|
||||
TemporaryAuthMiddleware().process_request(request)
|
||||
|
||||
self.assertEqual(request.user, self.user2)
|
||||
self.assertEqual(request.real_user, self.user1)
|
||||
|
||||
def test_middleware_post(self):
|
||||
"""
|
||||
A user can be authenticated if ``KFETPASSWORD`` of POST data contains
|
||||
a valid kfet password.
|
||||
"""
|
||||
request = self.factory.post('/', {'KFETPASSWORD': 'kfet_user2'})
|
||||
request.user = self.user1
|
||||
|
||||
TemporaryAuthMiddleware().process_request(request)
|
||||
|
||||
self.assertEqual(request.user, self.user2)
|
||||
self.assertEqual(request.real_user, self.user1)
|
||||
|
||||
def test_middleware_invalid(self):
|
||||
"""
|
||||
The given password must be a password of an Account.
|
||||
"""
|
||||
request = self.factory.post('/', {'KFETPASSWORD': 'invalid'})
|
||||
request.user = self.user1
|
||||
|
||||
TemporaryAuthMiddleware().process_request(request)
|
||||
|
||||
self.assertEqual(request.user, self.user1)
|
||||
self.assertFalse(hasattr(request, 'real_user'))
|
||||
|
||||
def test_context_processor(self):
|
||||
"""
|
||||
Context variables give the real authenticated user and his permissions.
|
||||
"""
|
||||
self.client.login(username='user1', password='user1')
|
||||
|
||||
r = self.client.get('/k-fet/accounts/', HTTP_KFETPASSWORD='kfet_user2')
|
||||
|
||||
self.assertEqual(r.context['user'], self.user1)
|
||||
self.assertNotIn('kfet.is_team', r.context['perms'])
|
||||
|
||||
def test_auth_not_persistent(self):
|
||||
"""
|
||||
The authentication is temporary, i.e. for one request.
|
||||
"""
|
||||
self.client.login(username='user1', password='user1')
|
||||
|
||||
r1 = self.client.get(
|
||||
'/k-fet/accounts/', HTTP_KFETPASSWORD='kfet_user2')
|
||||
self.assertEqual(r1.wsgi_request.user, self.user2)
|
||||
|
||||
r2 = self.client.get('/k-fet/accounts/')
|
||||
self.assertEqual(r2.wsgi_request.user, self.user1)
|
||||
|
|
|
@ -3,38 +3,105 @@ from django.contrib.messages.views import SuccessMessageMixin
|
|||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.db.models import Prefetch
|
||||
from django.shortcuts import render
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.http import QueryDict
|
||||
from django.shortcuts import redirect, render
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import View
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.views.generic.edit import CreateView, UpdateView
|
||||
|
||||
from django_cas_ng.views import logout as cas_logout_view
|
||||
|
||||
from kfet.decorators import teamkfet_required
|
||||
|
||||
from .forms import GroupForm
|
||||
from .models import GenericTeamToken
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def login_genericteam(request):
|
||||
# Check si besoin de déconnecter l'utilisateur de CAS
|
||||
cas_logout = None
|
||||
if request.user.profile.login_clipper:
|
||||
# Récupèration de la vue de déconnexion de CAS
|
||||
# Ici, car request sera modifié après
|
||||
next_page = request.META.get('HTTP_REFERER', None)
|
||||
cas_logout = cas_logout_view(request, next_page=next_page)
|
||||
class GenericLoginView(View):
|
||||
"""
|
||||
View to authenticate as kfet generic user.
|
||||
|
||||
# Authentification du compte générique
|
||||
token = GenericTeamToken.objects.create(token=get_random_string(50))
|
||||
user = authenticate(username="kfet_genericteam", token=token.token)
|
||||
login(request, user)
|
||||
It is a 2-step view. First, issue a token if user is a team member and send
|
||||
him to the logout view (for proper disconnect) with callback url to here.
|
||||
Then authenticate the token to log in as the kfet generic user.
|
||||
|
||||
messages.success(request, "Connecté en utilisateur partagé")
|
||||
Token is stored in COOKIES to avoid share it with the authentication
|
||||
provider, which can be external. Session is unusable as it will be cleared
|
||||
on logout.
|
||||
"""
|
||||
TOKEN_COOKIE_NAME = 'kfettoken'
|
||||
|
||||
return cas_logout or render(request, "kfet/login_genericteam.html")
|
||||
@method_decorator(require_http_methods(['GET', 'POST']))
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
token = request.get_signed_cookie(self.TOKEN_COOKIE_NAME, None)
|
||||
if not token:
|
||||
if not request.user.has_perm('kfet.is_team'):
|
||||
return redirect_to_login(request.get_full_path())
|
||||
|
||||
if request.method == 'POST':
|
||||
# Step 1: set token and logout user.
|
||||
return self.prepare_auth()
|
||||
else:
|
||||
# GET request should not change server/client states. Send a
|
||||
# confirmation template to emit a POST request.
|
||||
return render(request, 'kfet/confirm_form.html', {
|
||||
'title': _("Ouvrir une session partagée"),
|
||||
'text': _(
|
||||
"Êtes-vous sûr·e de vouloir ouvrir une session "
|
||||
"partagée ?"
|
||||
),
|
||||
})
|
||||
else:
|
||||
# Step 2: validate token.
|
||||
return self.validate_auth(token)
|
||||
|
||||
def prepare_auth(self):
|
||||
# Issue token.
|
||||
token = GenericTeamToken.objects.create_token()
|
||||
|
||||
# Prepare callback of logout.
|
||||
here_url = reverse(login_generic)
|
||||
if 'next' in self.request.GET:
|
||||
# Keep given next page.
|
||||
here_qd = QueryDict(mutable=True)
|
||||
here_qd['next'] = self.request.GET['next']
|
||||
here_url += '?{}'.format(here_qd.urlencode())
|
||||
|
||||
logout_url = reverse('cof-logout')
|
||||
logout_qd = QueryDict(mutable=True)
|
||||
logout_qd['next'] = here_url
|
||||
logout_url += '?{}'.format(logout_qd.urlencode(safe='/'))
|
||||
|
||||
resp = redirect(logout_url)
|
||||
resp.set_signed_cookie(
|
||||
self.TOKEN_COOKIE_NAME, token.token, httponly=True)
|
||||
return resp
|
||||
|
||||
def validate_auth(self, token):
|
||||
# Authenticate with GenericBackend.
|
||||
user = authenticate(request=self.request, kfet_token=token)
|
||||
|
||||
if user:
|
||||
# Log in generic user.
|
||||
login(self.request, user)
|
||||
messages.success(self.request, _(
|
||||
"K-Fêt — Ouverture d'une session partagée."
|
||||
))
|
||||
resp = redirect(self.get_next_url())
|
||||
else:
|
||||
# Try again.
|
||||
resp = redirect(self.request.get_full_path())
|
||||
|
||||
# Prevents blocking due to an invalid COOKIE.
|
||||
resp.delete_cookie(self.TOKEN_COOKIE_NAME)
|
||||
return resp
|
||||
|
||||
def get_next_url(self):
|
||||
return self.request.GET.get('next', reverse('kfet.kpsul'))
|
||||
|
||||
|
||||
login_generic = GenericLoginView.as_view()
|
||||
|
||||
|
||||
@permission_required('kfet.manage_perms')
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.signals import user_logged_in
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.dispatch import receiver
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
@receiver(user_logged_in)
|
||||
def messages_on_login(sender, request, user, **kwargs):
|
||||
if (not user.username == 'kfet_genericteam' and
|
||||
user.has_perm('kfet.is_team') and
|
||||
hasattr(request, 'GET') and
|
||||
'k-fet' in request.GET.get('next', '')):
|
||||
messages.info(request, mark_safe(
|
||||
'<a href="{}" class="genericteam" target="_blank">'
|
||||
' Connexion en utilisateur partagé ?'
|
||||
'</a>'
|
||||
.format(reverse('kfet.login.genericteam'))
|
||||
))
|
|
@ -1,22 +1,33 @@
|
|||
$(document).ready(function() {
|
||||
if (typeof Cookies !== 'undefined') {
|
||||
// Retrieving csrf token
|
||||
csrftoken = Cookies.get('csrftoken');
|
||||
// Appending csrf token to ajax post requests
|
||||
function csrfSafeMethod(method) {
|
||||
// these HTTP methods do not require CSRF protection
|
||||
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
|
||||
/**
|
||||
* CSRF Token
|
||||
*/
|
||||
|
||||
var csrftoken = '';
|
||||
if (typeof Cookies !== 'undefined')
|
||||
csrftoken = Cookies.get('csrftoken');
|
||||
|
||||
// Add CSRF token in header of AJAX requests.
|
||||
|
||||
function csrfSafeMethod(method) {
|
||||
// these HTTP methods do not require CSRF protection
|
||||
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
|
||||
}
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(xhr, settings) {
|
||||
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
||||
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||
}
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(xhr, settings) {
|
||||
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
||||
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function add_csrf_form($form) {
|
||||
$form.append(
|
||||
$('<input>', {'name': 'csrfmiddlewaretoken', 'value': csrftoken})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generic Websocket class and k-psul ws instanciation
|
||||
*/
|
||||
|
@ -199,3 +210,28 @@ jconfirm.defaults = {
|
|||
confirmButton: '<span class="glyphicon glyphicon-ok"></span>',
|
||||
cancelButton: '<span class="glyphicon glyphicon-remove"></span>'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create form node, given an url used as 'action', with csrftoken set.
|
||||
*/
|
||||
function create_form(url) {
|
||||
let $form = $('<form>', {
|
||||
'action': url,
|
||||
'method': 'post',
|
||||
});
|
||||
add_csrf_form($form);
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Emit a POST request from <a> tag.
|
||||
*
|
||||
* Usage:
|
||||
* <a href="#" data-url="{target url}" onclick="submit_url(this)">{…}</a>
|
||||
*/
|
||||
function submit_url(el) {
|
||||
let url = $(el).data('url');
|
||||
create_form(url).appendTo($('body')).submit();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% load static %}
|
||||
{% load i18n static %}
|
||||
{% load wagtailcore_tags %}
|
||||
|
||||
<nav class="navbar navbar-fixed-top">
|
||||
|
@ -62,7 +62,8 @@
|
|||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right nav-app">
|
||||
{% if user.username == 'kfet_genericteam' %}
|
||||
{% include "kfet/nav_item.html" with text="Équipe standard" %}
|
||||
{% trans "Session partagée" as shared_str %}
|
||||
{% include "kfet/nav_item.html" with text=shared_str glyphicon="sunglasses" %}
|
||||
{% elif user.is_authenticated and not user.profile.account_kfet %}
|
||||
{% include "kfet/nav_item.html" with class="disabled" href="#" glyphicon="user" text="Mon compte" %}
|
||||
{% elif user.profile.account_kfet.readable %}
|
||||
|
@ -87,7 +88,11 @@
|
|||
<li><a href="{% url 'kfet.order' %}">Commandes</a></li>
|
||||
{% if user.username != 'kfet_genericteam' %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'kfet.login.genericteam' %}" target="_blank" class="genericteam">Connexion standard</a></li>
|
||||
<li>
|
||||
<a href="#" data-url="{% url "kfet.login.generic" %}" onclick="submit_url(this)">
|
||||
{% trans "Ouvrir une session partagée" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.kfet.change_settings %}
|
||||
<li><a href="{% url 'kfet.settings' %}">Paramètres</a></li>
|
||||
|
@ -118,13 +123,3 @@
|
|||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.genericteam').on('click', function () {
|
||||
setTimeout(function () { location.reload() }, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
20
kfet/templates/kfet/confirm_form.html
Normal file
20
kfet/templates/kfet/confirm_form.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends "kfet/base_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block header %}{% endblock %}
|
||||
|
||||
{% block main-class %}main-bg main-padding text-center{% endblock %}
|
||||
{% block main-size %}col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<form action="{{ confirm_url }}" method="post">
|
||||
<p>
|
||||
{{ text }}
|
||||
</p>
|
||||
<button type="submit" class="btn btn-primary">{% trans "Confirmer" %}</button>
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -4,6 +4,8 @@
|
|||
<li class="{% if not href %}navbar-text{% endif %} {% if request.path == href %}active{% endif %} {{ class }}">
|
||||
{% if href %}
|
||||
<a href="{{ href }}" title="{{ text }}">
|
||||
{% else %}
|
||||
<span title="{{ text }}">
|
||||
{% endif %}<!--
|
||||
{% if glyphicon %}
|
||||
--><span class="glyphicon glyphicon-{{ glyphicon }}"></span><!--
|
||||
|
@ -14,5 +16,7 @@
|
|||
-->
|
||||
{% if href %}
|
||||
</a>
|
||||
{% else %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
|
|
@ -8,8 +8,8 @@ from kfet.decorators import teamkfet_required
|
|||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^login/genericteam$', views.login_genericteam,
|
||||
name='kfet.login.genericteam'),
|
||||
url(r'^login/generic$', views.login_generic,
|
||||
name='kfet.login.generic'),
|
||||
url(r'^history$', views.history,
|
||||
name='kfet.history'),
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ import statistics
|
|||
from kfet.statistic import ScaleMixin, last_stats_manifest, tot_ventes, WeekScale
|
||||
|
||||
from .auth.views import ( # noqa
|
||||
account_group, login_genericteam, AccountGroupCreate, AccountGroupUpdate,
|
||||
account_group, login_generic, AccountGroupCreate, AccountGroupUpdate,
|
||||
)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue