kfet -- LoginGenericView directly disconnects users.
Since allauth is installed, users are not automatically logged out of CAS when logging out GestioCOF. This change simplifies the view and avoid being stuck because of the redirect to the logout page, which happened via a GET request and so prompting to confirm.
This commit is contained in:
parent
05eeb6a25c
commit
e56200a569
2 changed files with 49 additions and 140 deletions
|
@ -1,7 +1,6 @@
|
|||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser, Group, Permission, User
|
||||
from django.core import signing
|
||||
from django.contrib.auth.models import Group, Permission, User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import RequestFactory, TestCase
|
||||
|
||||
|
@ -13,7 +12,6 @@ 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
|
||||
|
@ -144,7 +142,7 @@ class GenericLoginViewTests(TestCase):
|
|||
def test_url(self):
|
||||
self.assertEqual(self.url, "/k-fet/login/generic")
|
||||
|
||||
def test_notoken_get(self):
|
||||
def test_get(self):
|
||||
"""
|
||||
Send confirmation for user to emit POST request, instead of GET.
|
||||
"""
|
||||
|
@ -155,20 +153,20 @@ class GenericLoginViewTests(TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTemplateUsed(r, "kfet/confirm_form.html")
|
||||
|
||||
def test_notoken_post(self):
|
||||
def test_post(self):
|
||||
"""
|
||||
POST request without token in COOKIES sets a token and redirects to
|
||||
logout url.
|
||||
The kfet generic user is logged in.
|
||||
"""
|
||||
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
|
||||
)
|
||||
self.assertRedirects(r, reverse("kfet.kpsul"))
|
||||
self.assertEqual(r.wsgi_request.user, self.generic_user)
|
||||
with self.assertRaises(GenericTeamToken.DoesNotExist):
|
||||
GenericTeamToken.objects.get()
|
||||
|
||||
def test_notoken_not_team(self):
|
||||
def test_not_team(self):
|
||||
"""
|
||||
Logged in user must be a team user to initiate login as generic user.
|
||||
"""
|
||||
|
@ -177,74 +175,28 @@ class GenericLoginViewTests(TestCase):
|
|||
# With GET.
|
||||
r = self.client.get(self.url)
|
||||
self.assertRedirects(
|
||||
r, "/login?next={}".format(self.url), fetch_redirect_response=False
|
||||
r, "/profil/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
|
||||
r, "/profil/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):
|
||||
def test_post_redirect(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)
|
||||
next_url = "/any-url/"
|
||||
url = self.url + "?next=" + next_url
|
||||
|
||||
r = self.client.post(url)
|
||||
|
||||
self.assertRedirects(r, next_url, fetch_redirect_response=False)
|
||||
self.assertEqual(r.wsgi_request.user, self.generic_user)
|
||||
self.assertEqual(r.wsgi_request.path, "/k-fet/")
|
||||
|
||||
|
||||
##
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
from django.contrib import messages
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.db.models import Prefetch
|
||||
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.decorators.http import require_http_methods
|
||||
from django.views.generic import View
|
||||
from django.views.generic.edit import CreateView, UpdateView
|
||||
|
||||
from kfet.decorators import teamkfet_required
|
||||
|
||||
from .forms import GroupForm
|
||||
from .models import GenericTeamToken
|
||||
|
||||
|
@ -21,91 +19,50 @@ from .models import GenericTeamToken
|
|||
class GenericLoginView(View):
|
||||
"""
|
||||
View to authenticate as kfet generic 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.
|
||||
|
||||
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"
|
||||
|
||||
@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())
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
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.
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
GET requests should not change server/client states. Prompt user for
|
||||
confirmation.
|
||||
"""
|
||||
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 ?"
|
||||
"Ê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.
|
||||
def post(self, request, *args, **kwargs):
|
||||
# Issue token, used by GenericBackend.
|
||||
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(self.request)
|
||||
|
||||
logout_url = reverse("account_logout")
|
||||
logout_qd = QueryDict(mutable=True)
|
||||
logout_qd["next"] = here_url
|
||||
logout_url += "?{}".format(logout_qd.urlencode(safe="/"))
|
||||
# Authenticate with GenericBackend. Should always return the kfet
|
||||
# generic user.
|
||||
user = authenticate(request=self.request, kfet_token=token.token)
|
||||
|
||||
resp = redirect(logout_url)
|
||||
resp.set_signed_cookie(self.TOKEN_COOKIE_NAME, token.token, httponly=True)
|
||||
return resp
|
||||
if not user:
|
||||
return redirect(self.request.get_full_path())
|
||||
|
||||
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
|
||||
messages.success(self.request, _("K-Fêt — Ouverture d'une session partagée."))
|
||||
return redirect(self.get_next_url())
|
||||
|
||||
def get_next_url(self):
|
||||
return self.request.GET.get("next", reverse("kfet.kpsul"))
|
||||
|
||||
|
||||
login_generic = GenericLoginView.as_view()
|
||||
login_generic = teamkfet_required(GenericLoginView.as_view())
|
||||
|
||||
|
||||
@permission_required("kfet.manage_perms")
|
||||
|
|
Loading…
Reference in a new issue