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
|
@ -168,3 +168,10 @@ class OldCASBackend:
|
||||||
# Taken from Django's ModelBackend
|
# Taken from Django's ModelBackend
|
||||||
is_active = getattr(user, "is_active", None)
|
is_active = getattr(user, "is_active", None)
|
||||||
return is_active or is_active is 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")
|
verbose_name_plural = _("Comptes CAS")
|
||||||
|
|
||||||
def __str__(self):
|
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,
|
"cas_login": self.cas_login,
|
||||||
"entrance_year": self.entrance_year,
|
"entrance_year": self.entrance_year,
|
||||||
"user": self.user.username,
|
"user": self.user.username,
|
||||||
|
@ -74,7 +76,7 @@ class OldCASAccount(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _(
|
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,
|
"cas_login": self.cas_login,
|
||||||
"entrance_year": self.entrance_year,
|
"entrance_year": self.entrance_year,
|
||||||
|
|
|
@ -59,7 +59,7 @@ form {
|
||||||
}
|
}
|
||||||
|
|
||||||
form table {
|
form table {
|
||||||
margin: auto;
|
margin: 20px auto;
|
||||||
border-spacing: 0.3em;
|
border-spacing: 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +90,15 @@ input[type="submit"]:hover {
|
||||||
border-color: white;
|
border-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
border: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
background-color: white;
|
||||||
|
width: 100%;
|
||||||
|
padding:5px;
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
|
|
|
@ -13,11 +13,9 @@
|
||||||
{% trans "Mot de passe" %}
|
{% trans "Mot de passe" %}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{% comment %} TODO: https://git.eleves.ens.fr/klub-dev-ens/authens/issues/9
|
<a href="{% url "authens:login.oldcas" %}?next={{ next| urlencode }}">
|
||||||
<a href="#TODO">
|
|
||||||
<div class="big-button oldcas">
|
<div class="big-button oldcas">
|
||||||
{% trans "Vieilleux" %}
|
{% trans "Vieilleux" %}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{% endcomment %}
|
|
||||||
{% endblock %}
|
{% 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):
|
class TestOldCASBackend(TestCase):
|
||||||
def test_simple_auth(self):
|
def test_simple_auth(self):
|
||||||
user = UserModel.objects.create_user(username="johndoe31", password="password")
|
user = UserModel.objects.create_user(username="johndoe31", password="password")
|
||||||
wrong_user = UserModel.objects.create_user("johndoe", "password")
|
# Decoy user that may be authenticated by mistake
|
||||||
OldCASAccount.objects.create(user=user, cas_login="johndoe", entrance_year=2019)
|
UserModel.objects.create_user(username="johndoe", password="password")
|
||||||
|
OldCASAccount.objects.create(user=user, cas_login="johndoe", entrance_year=2014)
|
||||||
|
|
||||||
auth_user = authenticate(
|
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)
|
self.assertEqual(auth_user, user)
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.contrib.sessions.models import Session
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from authens.models import CASAccount
|
from authens.models import CASAccount, OldCASAccount
|
||||||
from authens.tests.cas_utils import FakeCASClient
|
from authens.tests.cas_utils import FakeCASClient
|
||||||
|
|
||||||
UserModel = get_user_model()
|
UserModel = get_user_model()
|
||||||
|
@ -44,6 +44,39 @@ class TestLoginViews(TestCase):
|
||||||
response = Client().get(reverse("authens:login"))
|
response = Client().get(reverse("authens:login"))
|
||||||
self.assertEqual(response.status_code, 200)
|
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):
|
class TestLogoutView(TestCase):
|
||||||
def test_regular_logout(self):
|
def test_regular_logout(self):
|
||||||
|
|
|
@ -7,5 +7,6 @@ urlpatterns = [
|
||||||
path("login/choose", views.LoginSwitchView.as_view(), name="login"),
|
path("login/choose", views.LoginSwitchView.as_view(), name="login"),
|
||||||
path("login/cas", views.CASLoginView.as_view(), name="login.cas"),
|
path("login/cas", views.CASLoginView.as_view(), name="login.cas"),
|
||||||
path("login/pwd", views.PasswordLoginView.as_view(), name="login.pwd"),
|
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"),
|
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 django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from authens.utils import get_cas_client
|
from authens.utils import get_cas_client
|
||||||
|
from authens.forms import OldCASAuthForm
|
||||||
|
|
||||||
|
|
||||||
class NextPageMixin:
|
class NextPageMixin:
|
||||||
|
@ -79,6 +80,11 @@ class PasswordLoginView(auth_views.LoginView):
|
||||||
template_name = "authens/pwd_login.html"
|
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):
|
class LogoutView(auth_views.LogoutView):
|
||||||
"""Logout view of AuthENS.
|
"""Logout view of AuthENS.
|
||||||
|
|
||||||
|
|
|
@ -125,6 +125,7 @@ INSTALLED_APPS += ["example", "authens"]
|
||||||
AUTHENTICATION_BACKENDS = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
"django.contrib.auth.backends.ModelBackend",
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
"authens.backends.ENSCASBackend",
|
"authens.backends.ENSCASBackend",
|
||||||
|
"authens.backends.OldCASBackend",
|
||||||
]
|
]
|
||||||
LOGIN_URL = reverse_lazy("authens:login")
|
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