diff --git a/avisstage/allauth_adapter.py b/avisstage/allauth_adapter.py
deleted file mode 100644
index 1935e47..0000000
--- a/avisstage/allauth_adapter.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from allauth.account.adapter import DefaultAccountAdapter
-from allauth.socialaccount.models import SocialAccount
-from allauth_ens.adapter import LongTermClipperAccountAdapter, get_ldap_infos
-
-class AccountAdapter(DefaultAccountAdapter):
- def is_open_for_signup(self, request):
- return False
-
-
-class SocialAccountAdapter(LongTermClipperAccountAdapter):
- def is_open_for_signup(self, request, sociallogin):
- # sociallogin.account is a SocialAccount instance.
- # See https://github.com/pennersr/django-allauth/blob/master/allauth/socialaccount/models.py
-
- if sociallogin.account.provider == 'clipper':
- return True
-
- # It returns AccountAdapter.is_open_for_signup().
- # See https://github.com/pennersr/django-allauth/blob/master/allauth/socialaccount/adapter.py
- return super().is_open_for_signup(request, sociallogin)
-
-
- # TODO : HOTFIX pour un bug d'allauth_ens
- # On remplace la déduplication des comptes faites avec "entrance_year"
- # par une déduplication sur le nom d'utilisateur
- # (Copié de allauth_ens)
- def pre_social_login(self, request, sociallogin):
- if sociallogin.account.provider != "clipper":
- return super(LongTermClipperAccountAdapter,
- self).pre_social_login(request, sociallogin)
-
- clipper_uid = sociallogin.account.uid
- try:
- old_conn = SocialAccount.objects.get(provider='clipper_inactive',
- uid=clipper_uid)
- except SocialAccount.DoesNotExist:
- return
-
- ldap_data = get_ldap_infos(clipper_uid)
- sociallogin._ldap_data = ldap_data
-
- if ldap_data is None or 'entrance_year' not in ldap_data:
- raise ValueError("No entrance year in LDAP data")
-
- old_conn_username = old_conn.user.username
-
- # HOTFIX ICI
- if self.get_username(clipper_uid, ldap_data) != old_conn_username:
- email = ldap_data.get('email', get_clipper_email(clipper_uid))
- remove_email(old_conn.user, email)
-
- return
-
- old_conn.provider = 'clipper'
- old_conn.save()
-
- sociallogin.lookup()
diff --git a/avisstage/apps.py b/avisstage/apps.py
index afc1822..4fa2dd9 100644
--- a/avisstage/apps.py
+++ b/avisstage/apps.py
@@ -1,7 +1,4 @@
-from __future__ import unicode_literals
-
from django.apps import AppConfig
-
class AvisstageConfig(AppConfig):
name = 'avisstage'
diff --git a/avisstage/management/commands/termine_scolarite.py b/avisstage/management/commands/termine_scolarite.py
new file mode 100644
index 0000000..b2b2637
--- /dev/null
+++ b/avisstage/management/commands/termine_scolarite.py
@@ -0,0 +1,12 @@
+from django.core.management.base import BaseCommand, CommandError
+from avisstage.models import Normalien
+
+class Command(BaseCommand):
+ help = 'Réinitialise les statuts "en scolarité" de tout le monde'
+
+ def add_arguments(self, parser):
+ return
+
+ def handle(self, *args, **options):
+ Normalien.objects.all().update(en_scolarite=False)
+ self.stdout.write(self.style.SUCCESS(u'Terminé'))
diff --git a/avisstage/migrations/0005_normalien_en_scolarite.py b/avisstage/migrations/0005_normalien_en_scolarite.py
new file mode 100644
index 0000000..da85767
--- /dev/null
+++ b/avisstage/migrations/0005_normalien_en_scolarite.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.17 on 2021-01-17 20:46
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('avisstage', '0004_allauth_to_authens'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='normalien',
+ name='en_scolarite',
+ field=models.BooleanField(blank=True, default=False),
+ ),
+ ]
diff --git a/avisstage/models.py b/avisstage/models.py
index 595cbf4..79f96f0 100644
--- a/avisstage/models.py
+++ b/avisstage/models.py
@@ -1,10 +1,3 @@
-# coding: utf-8
-
-from __future__ import unicode_literals
-
-from allauth.account.models import EmailAddress
-from allauth.socialaccount.models import SocialAccount
-
from django.db import models
from django.db.models.signals import post_save
from django.contrib.auth.models import User
@@ -20,10 +13,14 @@ from django.utils.html import strip_tags
from taggit_autosuggest.managers import TaggableManager
from tinymce.models import HTMLField as RichTextField
-from .utils import choices_length
-from .statics import DEPARTEMENTS_DEFAUT, PAYS_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS, TYPE_LIEU_DICT, TYPE_STAGE_DICT, NIVEAU_SCOL_OPTIONS, NIVEAU_SCOL_DICT
+from authens.signals import post_cas_connect
+from authens.models import CASAccount
-import ldap
+from .utils import choices_length, is_email_ens
+from .statics import (
+ DEPARTEMENTS_DEFAUT, PAYS_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS, TYPE_LIEU_DICT,
+ TYPE_STAGE_DICT, NIVEAU_SCOL_OPTIONS, NIVEAU_SCOL_DICT
+)
#
# Profil Normalien (extension du modèle User)
@@ -40,7 +37,8 @@ class Normalien(models.Model):
max_length=200, blank=True)
contactez_moi = models.BooleanField(u"Inviter les visiteurs à me contacter",
default=True)
- bio = models.TextField(u"À propos de moi", blank=True, default="");
+ bio = models.TextField(u"À propos de moi", blank=True, default="")
+ en_scolarite = models.BooleanField(default=False, blank=True)
class Meta:
verbose_name = u"Profil élève"
@@ -53,53 +51,62 @@ class Normalien(models.Model):
def stages_publics(self):
return self.stages.filter(public=True).order_by('-date_debut')
- @cached_property
- def en_scolarite(self):
- return SocialAccount.objects.filter(user_id=self.user_id,
- provider="clipper").exists()
-
def has_nonENS_email(self):
- a = EmailAddress.objects.filter(user_id=self.user_id,
- verified=True) \
- .exclude(email__endswith="ens.fr")
- return a.exists()
+ return not (
+ is_email_ens(self.mail, True)
+ and is_email_ens(self.user.email, True)
+ )
+
+ def nom_complet(self):
+ if self.nom.strip():
+ return self.nom
+ return self.user.username
@property
def preferred_email(self):
- a = EmailAddress.objects.filter(user_id=self.user_id,
- verified=True) \
- .exclude(email__endswith="ens.fr")\
- .order_by('-primary')
- if len(a) == 0:
- a = EmailAddress.objects.filter(user_id=self.user_id,
- verified=True) \
- .order_by('-primary')
- if len(a) == 0:
- return ""
- else:
- return a[0].email
+ return self.user.email
-# Hook à la création d'un nouvel utilisateur : récupération de ses infos par LDAP
-def create_user_profile(sender, instance, created, **kwargs):
+# Hook à la création d'un nouvel utilisateur : information de base
+def create_basic_user_profile(sender, instance, created, **kwargs):
if created:
profil, created = Normalien.objects.get_or_create(user=instance)
- try:
- saccount = SocialAccount.objects.get(user=instance,
- provider="clipper")
- except SocialAccount.DoesNotExist:
- profil.save()
- return
- edata = saccount.extra_data.get("ldap", {})
- dep = ""
- if "department_code" in edata:
- dep = dict(DEPARTEMENTS_DEFAUT).get(
- edata["department_code"].lower(), '')
- profil.promotion = "%s %s" % (dep, edata["entrance_year"])
- profil.nom = edata.get("name", "")
- profil.save()
+ if not created and profil.promotion != "":
+ return
+
+ if "@" in instance.username:
+ profil.promotion = instance.username.split("@")[1]
+ profil.save()
-post_save.connect(create_user_profile, sender=User)
+post_save.connect(create_basic_user_profile, sender=User)
+
+# Hook d'authENS : information du CAS
+def handle_cas_connection(sender, instance, created, cas_login, attributes, **kwargs):
+ profil, created = Normalien.objects.get_or_create(user=instance)
+
+ if not created:
+ if not profil.en_scolarite:
+ profil.en_scolarite = True
+ profil.save()
+ return
+
+ dirs = attributes.get("homeDirectory", "").split("/")
+ if len(dirs) < 4:
+ print("HomeDirectory invalide", dirs)
+ return
+
+ year = dirs[2]
+ departement = dirs[3]
+ print(departement, dirs)
+
+ dep = dict(DEPARTEMENTS_DEFAUT).get(departement.lower(), "")
+
+ profil.promotion = "%s %s" % (dep, year)
+ profil.nom = attributes.get("name", "")
+ profil.en_scolarite = True
+ profil.save()
+
+post_cas_connect.connect(handle_cas_connection, sender=User)
#
# Lieu de stage
diff --git a/avisstage/templates/avisstage/detail/profil.html b/avisstage/templates/avisstage/detail/profil.html
index 68281d9..75ca174 100644
--- a/avisstage/templates/avisstage/detail/profil.html
+++ b/avisstage/templates/avisstage/detail/profil.html
@@ -5,10 +5,10 @@
{% endblock %}
-{% block title %}Profil de {{ object.nom }} - ExperiENS{% endblock %}
+{% block title %}Profil de {{ object.nom_complet }} - ExperiENS{% endblock %}
{% block content %}
-
Profil de {{ object.nom }}
+ Profil de {{ object.nom_complet }}
{% if object.user == user %}
Modifier mes infos
{% endif %}
diff --git a/avisstage/templates/avisstage/detail/stage.html b/avisstage/templates/avisstage/detail/stage.html
index 9ec7b37..cb0d587 100644
--- a/avisstage/templates/avisstage/detail/stage.html
+++ b/avisstage/templates/avisstage/detail/stage.html
@@ -72,7 +72,7 @@
{{ object.sujet }}
{{ object.date_debut|date:"Y" }}{{ object.date_debut|date:"d/m" }}{{ object.date_fin|date:"d/m" }}
- {{ object.auteur.nom }}
+
{{ object.auteur.nom_complet }}
a fait {{ object.type_stage_fem|yesno:"cette,ce" }} {{ object.type_stage_fancy }}
{% if object.niveau_scol %}{{ object.niveau_scol_fancy }},{% endif %}
{% if object.structure %}au sein de {{ object.structure }}{% endif %}{% if object.encadrants %}, supervisé par {{ object.encadrants }}{% endif %}.
diff --git a/avisstage/templates/avisstage/perso.html b/avisstage/templates/avisstage/perso.html
index a21d66c..adf992a 100644
--- a/avisstage/templates/avisstage/perso.html
+++ b/avisstage/templates/avisstage/perso.html
@@ -4,7 +4,7 @@
{% block title %}Espace personnel - ExperiENS{% endblock %}
{% block content %}
- Bonjour {{ user.profil.nom }} !
+ Bonjour {{ user.profil.nom_complet }} !
Mon compte
@@ -17,20 +17,20 @@
{% else %}
Statut : Archicube
Vous ne pouvez plus accéder qu'à vos propres expériences pour les modifier, et tenir à jour votre profil.
- Si vous êtes encore en scolarité, merci de vous reconnecter en passant par le serveur d'authentification de l'ENS pour mettre à jour votre statut.
+ Si vous êtes encore en scolarité, merci de vous reconnecter en passant par le serveur d'authentification de l'ENS pour mettre à jour votre statut.
{% endif %}
Le statut est mis à jour automatiquement chaque année selon le mode de connexion que vous utilisez.
Mode de connexion
- {% if user.profil.en_scolarite %}En scolarité, utilisez le serveur central d'authentification pour vous connecter. Quand vous n'aurez plus de compte clipper, vous devrez vous connecter directement via l'accès archicubes, avec l'identifiant {{ user.username }}
{% endif %}
- {% if not user.password %}Vous n'avez pas créé de mot de passe interne à ExperiENS. Pensez-y pour garder l'accès au site quand vous n'aurez plus de compte clipper !
{% endif %}
- Créer / changer mon mot de passe ExperiENS
+ {% if user.profil.en_scolarite %}En scolarité, utilisez le serveur central d'authentification pour vous connecter. Quand vous n'aurez plus de compte clipper, vous devrez vous connecter directement via l'accès archicubes, avec votre login {{ user.cas_account.cas_login }} et le mot de passe spécifique à ExperiENS que vous aurez défini.
{% endif %}
+ {% if not user.password or not user.has_usable_password %}Vous n'avez pas créé de mot de passe interne à ExperiENS. Pensez-y pour garder l'accès au site quand vous n'aurez plus de compte clipper !
{% endif %}
+ Créer / changer mon mot de passe ExperiENS
diff --git a/avisstage/utils.py b/avisstage/utils.py
index 9aad26d..57e8410 100644
--- a/avisstage/utils.py
+++ b/avisstage/utils.py
@@ -1,6 +1,3 @@
-# coding: utf-8
-
-from allauth.socialaccount.models import SocialAccount
from functools import reduce
from math import cos, radians, sqrt
@@ -18,3 +15,8 @@ def approximate_distance(a, b):
dlat = (lat_a - lat_b)
distance = 6371000 * sqrt(dlon*dlon + dlat*dlat)
return distance
+
+def is_email_ens(mail, none=False):
+ if mail is None:
+ return none
+ return mail.endswith(".ens.fr") or mail.endswith(".ens.psl.eu")
diff --git a/experiENS/auth.py b/experiENS/auth.py
index e310a39..b8f8d41 100644
--- a/experiENS/auth.py
+++ b/experiENS/auth.py
@@ -1,5 +1,12 @@
-from django_cas_ng.backends import CASBackend
+from authens.backends import ENSCASBackend as AuthENSBackend
+from authens.utils import parse_entrance_year
-class ENSCASBackend(CASBackend):
- def clean_username(self, username):
- return username.lower().strip()
+class ENSCASBackend(AuthENSBackend):
+ # Override AuthENS backend user creation to implement the @ logic
+
+ def get_free_username(self, cas_login, attributes):
+ entrance_year = parse_entrance_year(attributes.get("homeDirectory"))
+ if entrance_year is None:
+ return super().get_free_username(cas_login, attributes)
+ entrance_year %= 100
+ return "%s@%02d" % (cas_login, entrance_year)
diff --git a/experiENS/settings_base.py b/experiENS/settings_base.py
index 0962539..7208020 100644
--- a/experiENS/settings_base.py
+++ b/experiENS/settings_base.py
@@ -36,14 +36,6 @@ INSTALLED_APPS = [
'django_elasticsearch_dsl',
'widget_tweaks',
- 'allauth_ens',
-
- 'allauth',
- 'allauth.account',
- 'allauth.socialaccount',
- 'allauth_cas',
-
- 'allauth_ens.providers.clipper',
'authens',
'tastypie',
@@ -118,24 +110,17 @@ STATIC_URL = '/static/'
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
- 'authens.backends.ENSCASBackend',
+ 'experiENS.auth.ENSCASBackend',
)
CAS_SERVER_URL = "https://cas.eleves.ens.fr/" #SPI CAS
-CAS_VERIFY_URL = "https://cas.eleves.ens.fr/"
-CAS_IGNORE_REFERER = True
-CAS_REDIRECT_URL = reverse_lazy('avisstage:perso')
-CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
-CAS_FORCE_CHANGE_USERNAME_CASE = "lower"
-CAS_VERSION = 'CAS_2_SAML_1_0'
-ACCOUNT_ADAPTER = 'avisstage.allauth_adapter.AccountAdapter'
-SOCIALACCOUNT_ADAPTER = 'avisstage.allauth_adapter.SocialAccountAdapter'
+AUTHENS_USE_OLDCAS = False
LOGIN_URL = reverse_lazy('authens:login')
LOGOUT_URL = reverse_lazy('authens:logout')
-LOGIN_REDIRECT_URL = reverse_lazy('avisstage:perso')
-ACCOUNT_HOME_URL = reverse_lazy('avisstage:index')
+LOGIN_REDIRECT_URL = "/perso/"#reverse_lazy('avisstage:perso')
+LOGOUT_REDIRECT_URL = "/"
LOGGING = {
'version': 1,
diff --git a/experiENS/urls.py b/experiENS/urls.py
index 4a8e6f7..4bb2bfb 100644
--- a/experiENS/urls.py
+++ b/experiENS/urls.py
@@ -7,7 +7,6 @@ urlpatterns = [
path("authens/", include("authens.urls")),
- path('account/', include('allauth_ens.urls')),
path('tinymce/', include('tinymce.urls')),
path('taggit_autosuggest/', include('taggit_autosuggest.urls')),