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
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue