Merge branch 'Aufinal/old_cas_view' into 'master'
Vues pour l'authentification vieilleux See merge request klub-dev-ens/authens!12
This commit is contained in:
commit
ab49a6479f
12 changed files with 181 additions and 12 deletions
|
@ -142,7 +142,7 @@ class ENSCASBackend:
|
|||
class OldCASBackend:
|
||||
"""Authentication backend for old CAS accounts.
|
||||
|
||||
Given a CAS login, an entrance year and a password, first finds the matching
|
||||
Given a CAS login, an entrance year and a password, first finds the matching
|
||||
OldCASAccount instance (if it exists), then checks the given password with
|
||||
the user associated to this account.
|
||||
"""
|
||||
|
@ -168,3 +168,10 @@ class OldCASBackend:
|
|||
# Taken from Django's ModelBackend
|
||||
is_active = getattr(user, "is_active", None)
|
||||
return is_active or is_active is None
|
||||
|
||||
# Django boilerplate.
|
||||
def get_user(self, user_id):
|
||||
try:
|
||||
return UserModel.objects.get(pk=user_id)
|
||||
except UserModel.DoesNotExist:
|
||||
return None
|
||||
|
|
61
authens/forms.py
Normal file
61
authens/forms.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from django import forms
|
||||
from django.contrib.auth import forms as auth_forms, authenticate
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def promo_choices():
|
||||
return [(r, r) for r in range(2000, timezone.now().year + 1)]
|
||||
|
||||
|
||||
class OldCASAuthForm(forms.Form):
|
||||
""" Adapts Django's AuthenticationForm to allow for OldCAS login.
|
||||
"""
|
||||
|
||||
cas_login = auth_forms.UsernameField(
|
||||
label=_("Ancien login clipper"), max_length=1023
|
||||
)
|
||||
password = forms.CharField(
|
||||
label=_("Mot de passe"),
|
||||
strip=False,
|
||||
widget=forms.PasswordInput(attrs={"autocomplete": "current-password"}),
|
||||
)
|
||||
entrance_year = forms.TypedChoiceField(
|
||||
label=_("Promotion"), choices=promo_choices, coerce=int
|
||||
)
|
||||
|
||||
def __init__(self, request=None, *args, **kwargs):
|
||||
self.request = request
|
||||
self.user_cache = None
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
cas_login = self.cleaned_data.get("cas_login")
|
||||
password = self.cleaned_data.get("password")
|
||||
entrance_year = self.cleaned_data.get("entrance_year")
|
||||
|
||||
if cas_login is not None and password:
|
||||
self.user_cache = authenticate(
|
||||
self.request,
|
||||
cas_login=cas_login,
|
||||
password=password,
|
||||
entrance_year=entrance_year,
|
||||
)
|
||||
if self.user_cache is None:
|
||||
raise self.get_invalid_login_error()
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
def get_user(self):
|
||||
# Necessary API for LoginView
|
||||
return self.user_cache
|
||||
|
||||
def get_invalid_login_error(self):
|
||||
return forms.ValidationError(
|
||||
_(
|
||||
"Aucun utilisateur n'existe avec ce clipper, cette promo et/ou ce mot "
|
||||
"de passe. Veuillez vérifier votre saisie. Attention, tous les champs "
|
||||
"sont sensibles à la casse !"
|
||||
),
|
||||
code="invalid_login",
|
||||
)
|
|
@ -32,7 +32,9 @@ class CASAccount(models.Model):
|
|||
verbose_name_plural = _("Comptes CAS")
|
||||
|
||||
def __str__(self):
|
||||
return _("compte CAS %(cas_login) (promo %(entrance_year)s) lié à %(user)s") % {
|
||||
return _(
|
||||
"compte CAS %(cas_login)s (promo %(entrance_year)s) lié à %(user)s"
|
||||
) % {
|
||||
"cas_login": self.cas_login,
|
||||
"entrance_year": self.entrance_year,
|
||||
"user": self.user.username,
|
||||
|
@ -74,7 +76,7 @@ class OldCASAccount(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return _(
|
||||
"Ancien compte CAS %(cas_login) (promo %(entrance_year)s) lié à %(user)s"
|
||||
"Ancien compte CAS %(cas_login)s (promo %(entrance_year)s) lié à %(user)s"
|
||||
) % {
|
||||
"cas_login": self.cas_login,
|
||||
"entrance_year": self.entrance_year,
|
||||
|
|
|
@ -59,7 +59,7 @@ form {
|
|||
}
|
||||
|
||||
form table {
|
||||
margin: auto;
|
||||
margin: 20px auto;
|
||||
border-spacing: 0.3em;
|
||||
}
|
||||
|
||||
|
@ -90,6 +90,15 @@ input[type="submit"]:hover {
|
|||
border-color: white;
|
||||
}
|
||||
|
||||
select {
|
||||
border: 0;
|
||||
font-size: 1em;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
padding:5px;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
a {
|
||||
flex: 1;
|
||||
height: 200px;
|
||||
|
|
|
@ -13,11 +13,9 @@
|
|||
{% trans "Mot de passe" %}
|
||||
</div>
|
||||
</a>
|
||||
{% comment %} TODO: https://git.eleves.ens.fr/klub-dev-ens/authens/issues/9
|
||||
<a href="#TODO">
|
||||
<a href="{% url "authens:login.oldcas" %}?next={{ next| urlencode }}">
|
||||
<div class="big-button oldcas">
|
||||
{% trans "Vieilleux" %}
|
||||
</div>
|
||||
</a>
|
||||
{% endcomment %}
|
||||
{% endblock %}
|
||||
|
|
27
authens/templates/authens/old_cas_login.html
Normal file
27
authens/templates/authens/old_cas_login.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
{% extends "authens/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% if request.site.name %}{{ request.site.name }}{% else %}AuthENS{% endif %} - {% trans "Connexion vieilleux" %}</h2>
|
||||
|
||||
{% if form.errors %}
|
||||
<p class="error">{% trans "Login CAS, promotion et/ou mot de passe incorrect" %}</p>
|
||||
{% endif %}
|
||||
|
||||
<form class="oldcas" method="post" action="">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
<tbody>
|
||||
{% for field in form %}
|
||||
<tr>
|
||||
<th>{{ field.label_tag }}</th>
|
||||
<td>{{ field }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<input type="submit" value="{% trans "Se connecter" %}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
@ -115,10 +115,11 @@ class TestCASBackend(TestCase):
|
|||
class TestOldCASBackend(TestCase):
|
||||
def test_simple_auth(self):
|
||||
user = UserModel.objects.create_user(username="johndoe31", password="password")
|
||||
wrong_user = UserModel.objects.create_user("johndoe", "password")
|
||||
OldCASAccount.objects.create(user=user, cas_login="johndoe", entrance_year=2019)
|
||||
# Decoy user that may be authenticated by mistake
|
||||
UserModel.objects.create_user(username="johndoe", password="password")
|
||||
OldCASAccount.objects.create(user=user, cas_login="johndoe", entrance_year=2014)
|
||||
|
||||
auth_user = authenticate(
|
||||
None, cas_login="johndoe", entrance_year=2019, password="password"
|
||||
None, cas_login="johndoe", entrance_year=2014, password="password"
|
||||
)
|
||||
self.assertEqual(auth_user, user)
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.contrib.sessions.models import Session
|
|||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from authens.models import CASAccount
|
||||
from authens.models import CASAccount, OldCASAccount
|
||||
from authens.tests.cas_utils import FakeCASClient
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
@ -44,6 +44,39 @@ class TestLoginViews(TestCase):
|
|||
response = Client().get(reverse("authens:login"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_oldcas_login(self):
|
||||
url = reverse("authens:login.oldcas")
|
||||
client = Client()
|
||||
|
||||
user = UserModel.objects.create_user(username="johndoe31", password="password")
|
||||
# Decoy user that may be authenticated by mistake
|
||||
UserModel.objects.create_user(username="johndoe", password="password")
|
||||
OldCASAccount.objects.create(user=user, cas_login="johndoe", entrance_year=2014)
|
||||
|
||||
post_data = dict(cas_login="johndoe", password="password", entrance_year=2014)
|
||||
response = client.post(url, post_data)
|
||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
def test_oldcas_login_error(self):
|
||||
url = reverse("authens:login.oldcas")
|
||||
client = Client()
|
||||
|
||||
user = UserModel.objects.create_user(username="johndoe31", password="password")
|
||||
OldCASAccount.objects.create(user=user, cas_login="johndoe", entrance_year=2014)
|
||||
|
||||
wrong_year = dict(cas_login="johndoe", password="password", entrance_year=2015)
|
||||
wrong_login = dict(
|
||||
cas_login="johndoe31", password="password", entrance_year=2014
|
||||
)
|
||||
|
||||
response = client.post(url, wrong_year, follow=True)
|
||||
non_field_errors = response.context["form"].non_field_errors().as_data()
|
||||
self.assertEqual(non_field_errors[0].code, "invalid_login")
|
||||
|
||||
response = client.post(url, wrong_login, follow=True)
|
||||
non_field_errors = response.context["form"].non_field_errors().as_data()
|
||||
self.assertEqual(non_field_errors[0].code, "invalid_login")
|
||||
|
||||
|
||||
class TestLogoutView(TestCase):
|
||||
def test_regular_logout(self):
|
||||
|
|
|
@ -7,5 +7,6 @@ urlpatterns = [
|
|||
path("login/choose", views.LoginSwitchView.as_view(), name="login"),
|
||||
path("login/cas", views.CASLoginView.as_view(), name="login.cas"),
|
||||
path("login/pwd", views.PasswordLoginView.as_view(), name="login.pwd"),
|
||||
path("login/oldcas", views.OldCASLoginView.as_view(), name="login.oldcas"),
|
||||
path("logout", views.LogoutView.as_view(), name="logout"),
|
||||
]
|
||||
|
|
|
@ -9,6 +9,7 @@ from django.shortcuts import redirect
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from authens.utils import get_cas_client
|
||||
from authens.forms import OldCASAuthForm
|
||||
|
||||
|
||||
class NextPageMixin:
|
||||
|
@ -79,6 +80,11 @@ class PasswordLoginView(auth_views.LoginView):
|
|||
template_name = "authens/pwd_login.html"
|
||||
|
||||
|
||||
class OldCASLoginView(auth_views.LoginView):
|
||||
template_name = "authens/old_cas_login.html"
|
||||
authentication_form = OldCASAuthForm
|
||||
|
||||
|
||||
class LogoutView(auth_views.LogoutView):
|
||||
"""Logout view of AuthENS.
|
||||
|
||||
|
|
|
@ -118,13 +118,14 @@ STATIC_URL = "/static/"
|
|||
# The only modifications to the default settings are here #
|
||||
# ------------------------------------------------------- #
|
||||
|
||||
from django.urls import reverse_lazy # noqa
|
||||
from django.urls import reverse_lazy # noqa
|
||||
|
||||
# This is mandatory
|
||||
INSTALLED_APPS += ["example", "authens"]
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
"authens.backends.ENSCASBackend",
|
||||
"authens.backends.OldCASBackend",
|
||||
]
|
||||
LOGIN_URL = reverse_lazy("authens:login")
|
||||
|
||||
|
|
23
setup.cfg
Normal file
23
setup.cfg
Normal file
|
@ -0,0 +1,23 @@
|
|||
[flake8]
|
||||
exclude = migrations
|
||||
max-line-length = 88
|
||||
ignore =
|
||||
# whitespace before ':' (not PEP8-compliant for slicing)
|
||||
E203,
|
||||
# lambda expression
|
||||
E731,
|
||||
# line break before binary operator (not PEP8-compliant)
|
||||
W503
|
||||
|
||||
[isort]
|
||||
# For black compat: https://github.com/ambv/black#how-black-wraps-lines
|
||||
combine_as_imports = true
|
||||
default_section = THIRDPARTY
|
||||
force_grid_wrap = 0
|
||||
include_trailing_comma = true
|
||||
known_django = django
|
||||
known_first_party = authens,tests
|
||||
line_length = 88
|
||||
multi_line_output = 3
|
||||
not_skip = __init__.py
|
||||
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
|
Loading…
Add table
Reference in a new issue