146 lines
4.7 KiB
Python
146 lines
4.7 KiB
Python
from collections import namedtuple
|
|
|
|
import ldap
|
|
import ldap.filter
|
|
from django.conf import settings
|
|
|
|
ClipperAccount = namedtuple("ClipperAccount", ["uid", "name", "email", "year", "dept"])
|
|
|
|
|
|
class LDAP:
|
|
"""Classe pour se connecter à un LDAP ENS (CRI ou SPI).
|
|
Inspirée de `merle_scripts`.
|
|
"""
|
|
|
|
# À renseigner
|
|
LDAP_SERVER = {
|
|
"PROTOCOL": "ldaps",
|
|
"URL": "ldap.example.com",
|
|
"PORT": 636,
|
|
}
|
|
|
|
search_base = ""
|
|
attr_list = []
|
|
|
|
def __init__(self):
|
|
""" Initialize the LDAP object """
|
|
|
|
self.ldap_obj = ldap.initialize(
|
|
"{PROTOCOL}://{URL}:{PORT}".format(**self.LDAP_SERVER)
|
|
)
|
|
|
|
def search(self, filters="(objectClass=*)"):
|
|
"""Do a ldap.search_s with the given filters, as specified for
|
|
ldap.search_s"""
|
|
|
|
return self.ldap_obj.search_s(
|
|
self.search_base, ldap.SCOPE_SUBTREE, filters, self.attr_list
|
|
)
|
|
|
|
@staticmethod
|
|
def extract_ldap_info(entry, field):
|
|
""" Extract the given field from an LDAP entry as an UTF-8 string """
|
|
return entry[1].get(field, [b""])[0].decode("utf-8")
|
|
|
|
|
|
class ClipperLDAP(LDAP):
|
|
|
|
LDAP_SERVER = settings.LDAP["SPI"]
|
|
|
|
search_base = "dc=spi,dc=ens,dc=fr"
|
|
attr_list = ["cn", "uid", "mail", "homeDirectory"]
|
|
|
|
verbose_depts = {
|
|
"bio": "Biologie",
|
|
"chimie": "Chimie",
|
|
"dec": "Études Cognitives",
|
|
"geol": "Géosciences",
|
|
"guests": "Invité·e",
|
|
"info": "Informatique",
|
|
"litt": "Lettres",
|
|
"maths": "Mathématiques",
|
|
"pei": "PEI",
|
|
"phy": "Physique",
|
|
}
|
|
|
|
def parse_dept(self, home_dir):
|
|
"""Extrait le département d'entrée d'un·e élève à partir de son dossier.
|
|
Le dossier a le format `/users/<promo>/<dept>/<login>`.
|
|
"""
|
|
users, promo, dept, login = home_dir.split("/")[1:]
|
|
|
|
if users != "users":
|
|
raise ValueError("Invalid home directory")
|
|
|
|
# Ça casse en 2100, mais le système de naming de sas aussi...
|
|
promo = 2000 + int(promo)
|
|
|
|
return promo, self.verbose_depts.get(dept, None)
|
|
|
|
def get_clipper_list(self, promo_filter=None, verbosity=1, stdout=None):
|
|
"""Extrait la liste des comptes clipper présents dans le LDAP.
|
|
Renvoie une liste de `namedTuple` contenant leur nom, prénom, adresse mail et
|
|
département/année d'entrée.
|
|
Si `promo_filter != None`, ne renvoie que les clippers d'une promotion donnée.
|
|
"""
|
|
search_res = self.search()
|
|
clipper_list = []
|
|
|
|
for entry in search_res:
|
|
uid = self.extract_ldap_info(entry, "uid")
|
|
# Il y a des comptes "bizarre" (e.g. `root`) avec uid vide
|
|
if len(uid) > 0:
|
|
try:
|
|
name = self.extract_ldap_info(entry, "cn")
|
|
email = self.extract_ldap_info(entry, "mail")
|
|
promo, dept = self.parse_dept(
|
|
self.extract_ldap_info(entry, "homeDirectory")
|
|
)
|
|
|
|
if promo_filter is None or promo == promo_filter:
|
|
clipper_list.append(
|
|
ClipperAccount(uid, name, email, promo, dept)
|
|
)
|
|
if verbosity == 3:
|
|
stdout.write("Compte clipper trouvé : {}".format(uid))
|
|
except ValueError:
|
|
if verbosity >= 2:
|
|
stdout.write("Entrée malformée trouvée : {}".format(entry))
|
|
pass
|
|
|
|
return clipper_list
|
|
|
|
|
|
class AnnuaireLDAP(LDAP):
|
|
|
|
LDAP_SERVER = settings.LDAP["CRI"]
|
|
|
|
search_base = "ou=people,dc=ens,dc=fr"
|
|
attr_list = ["uid", "sn", "givenName", "ou"]
|
|
|
|
def try_match(self, profile, verbosity=1, stderr=None):
|
|
"""Essaie de trouver une entrée correspondant au profile donné dans
|
|
l'annuaire de l'ENS. L'heuristique est la suivante : il est très probable
|
|
que le prénom de la personne commence par le premier mot de `profile.full_name`,
|
|
et que son nom de famille finisse par le dernier mot.
|
|
"""
|
|
given_name = profile.full_name.split(" ")[0]
|
|
last_name = profile.full_name.split(" ")[-1]
|
|
|
|
search_name = self.search(
|
|
"(&(givenName={}*)(sn=*{}))".format(given_name, last_name)
|
|
)
|
|
|
|
if len(search_name) > 0:
|
|
if len(search_name) > 2:
|
|
if verbosity >= 2:
|
|
stderr.write(
|
|
"Erreur : deux logins CRI trouvés pour {} ({})".format(
|
|
profile.user.username, profile.promotion
|
|
)
|
|
)
|
|
return None
|
|
|
|
return self.extract_ldap_info(search_name[0], "uid")
|
|
|
|
return None
|