diff --git a/allauth_ens/adapter.py b/allauth_ens/adapter.py new file mode 100644 index 0000000..e99287d --- /dev/null +++ b/allauth_ens/adapter.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +import ldap + +from allauth.account.utils import user_email, user_field, user_username +from allauth.socialaccount.adapter import DefaultSocialAccountAdapter +from allauth.socialaccount.models import SocialAccount + +DEPARTMENTS_LIST = ( + ('phy', u'Physique'), + ('maths', u'Maths'), + ('bio', u'Biologie'), + ('chimie', u'Chimie'), + ('geol', u'Géosciences'), + ('dec', u'DEC'), + ('info', u'Informatique'), + ('litt', u'Littéraire'), + ('guests', u'Pensionnaires étrangers'), + ('pei', u'PEI'), +) + +def get_ldap_infos(clipper): + assert clipper.isalnum() + data = {'email':'{}@clipper.ens.fr'.format(clipper.strip().lower())} + try: + ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, + ldap.OPT_X_TLS_NEVER) + l = ldap.initialize("ldaps://ldap.spi.ens.fr:636") + l.set_option(ldap.OPT_REFERRALS, 0) + l.set_option(ldap.OPT_PROTOCOL_VERSION, 3) + l.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND) + l.set_option(ldap.OPT_X_TLS_DEMAND, True) + l.set_option(ldap.OPT_DEBUG_LEVEL, 255) + l.set_option(ldap.OPT_NETWORK_TIMEOUT, 10) + l.set_option(ldap.OPT_TIMEOUT, 10) + + info = l.search_s('dc=spi,dc=ens,dc=fr', + ldap.SCOPE_SUBTREE, + ('(uid=%s)' % (clipper,)), + [str("cn"), + str("mailRoutingAddress"), + str("homeDirectory") ]) + + if len(info) > 0: + infos = info[0][1] + + # Name + data['name'] = infos.get('cn', [''])[0].decode("utf-8") + + # Parsing homeDirectory to get entrance year and departments + annee = '00' + promotion = 'Inconnue' + + if 'homeDirectory' in infos: + dirs = infos['homeDirectory'][0].split('/') + if dirs[1] == 'users': + annee = dirs[2] + dep = dirs[3] + dep = dict(DEPARTMENTS_LIST).get(dep.lower(), '') + promotion = u'%s %s' % (dep, annee) + data['annee'] = annee + data['promotion'] = promotion + + # Mail + pmail = infos.get('mailRoutingAddress', []) + if len(pmail) > 0 : + data['email'] = pmail[0] + + except ldap.LDAPError: + pass + + return data + + +class LongTermClipperAccountAdapter(DefaultSocialAccountAdapter): + """ + A class to manage the fact that people loose their account at the end of + their scolarity and that their clipper login might be reused later + """ + + def pre_social_login(self, request, sociallogin): + clipper = sociallogin.account.uid + try: + a = SocialAccount.objects.get(provider='old_clipper', + uid=clipper) + # An account with that uid was registered, but potentially + # deprecated at the beginning of the year + # We need to check that the user is still the same as before + ldap_data = get_ldap_infos(clipper) + self._ldap_data = ldap_data + if a.extra_data.get('annee', '-1') != ldap_data.get('annee', '00'): + # The admission year is different + # We need a new SocialAccount + return + + # The admission year is the same, we can update the model + a.provider = 'clipper' + a.save() + sociallogin.lookup() + except SocialAccount.DoesNotExist: + return + + def create_username(self, clipper, data): + return "{}:{}".format(clipper, data.get('annee', '00')) + + def populate_user(self, request, sociallogin, data): + clipper = sociallogin.account.uid + ldap_data = self._ldap_data if hasattr(self, '_ldap_data') else get_ldap_infos(clipper) + # Save extra data (only once) + sociallogin.account.extra_data = sociallogin.extra_data = ldap_data + username = self.create_username(clipper, data) + first_name = data.get('first_name') + last_name = data.get('last_name') + email = ldap_data.get('email') + name = ldap_data.get('name') + user = sociallogin.user + user_username(user, username or '') + user_email(user, email or '') + name_parts = (name or '').split(' ') + user_field(user, 'first_name', first_name or name_parts[0]) + user_field(user, 'last_name', last_name or ' '.join(name_parts[1:])) + print(user.username, user) + return user + +def deprecate_clippers(): + clippers = SocialAccount.objects.filter(provider='clipper') + c_uids = clippers.values_list('uid', flat=True) + + # Clear old clipper accounts that wer replaced by new ones (o avoid conflicts) + SocialAccount.objects.filter(provider='old_clipper', uid__in=c_uids).delete() + + # Deprecate accounts + clippers.update(provider='old_clipper') diff --git a/allauth_ens/management/__init__.py b/allauth_ens/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/allauth_ens/management/commands/__init__.py b/allauth_ens/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/allauth_ens/management/commands/deprecate_clippers.py b/allauth_ens/management/commands/deprecate_clippers.py new file mode 100644 index 0000000..a824fd8 --- /dev/null +++ b/allauth_ens/management/commands/deprecate_clippers.py @@ -0,0 +1,14 @@ +#coding: utf-8 +from django.core.management.base import BaseCommand, CommandError + +from allauth_ens.adapter import deprecate_clippers + +class Command(BaseCommand): + help = 'Deprecates clipper SocialAccounts so as to avoid conflicts' + + def add_arguments(self, parser): + pass + + def handle(self, *args, **options): + deprecate_clippers() + self.stdout.write(self.style.SUCCESS(u'Clippers deprecation successful')) diff --git a/allauth_ens/providers/clipper/provider.py b/allauth_ens/providers/clipper/provider.py index d14bee2..fa057b2 100644 --- a/allauth_ens/providers/clipper/provider.py +++ b/allauth_ens/providers/clipper/provider.py @@ -21,41 +21,14 @@ class ClipperProvider(CASProvider): uid, extra = data return '{}@clipper.ens.fr'.format(uid.strip().lower()) + def extract_uid(self, data): + uid, _ = data + uid = uid.lower().strip() + return uid + def extract_common_fields(self, data): - def get_names(clipper): - assert clipper.isalnum() - try: - ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, - ldap.OPT_X_TLS_NEVER) - l = ldap.initialize("ldaps://ldap.spi.ens.fr:636") - l.set_option(ldap.OPT_REFERRALS, 0) - l.set_option(ldap.OPT_PROTOCOL_VERSION, 3) - l.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND) - l.set_option(ldap.OPT_X_TLS_DEMAND, True) - l.set_option(ldap.OPT_DEBUG_LEVEL, 255) - l.set_option(ldap.OPT_NETWORK_TIMEOUT, 10) - l.set_option(ldap.OPT_TIMEOUT, 10) - - info = l.search_s('dc=spi,dc=ens,dc=fr', - ldap.SCOPE_SUBTREE, - ('(uid=%s)' % (clipper,)), - [str("cn"), ]) - - if len(info) > 0: - fullname = info[0][1].get('cn', [''])[0].decode("utf-8") - first_name, last_name = fullname.split(' ', 1) - return first_name, last_name - - except ldap.LDAPError: - pass - - return '', '' - common = super(ClipperProvider, self).extract_common_fields(data) - fn, ln = get_names(common['username']) common['email'] = self.extract_email(data) - common['name'] = fn - common['last_name'] = ln return common def extract_email_addresses(self, data): diff --git a/example/adapter.py b/example/adapter.py index 3e288e5..f1079f5 100644 --- a/example/adapter.py +++ b/example/adapter.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- from allauth.account.adapter import DefaultAccountAdapter +from allauth_ens.adapter import LongTermClipperAccountAdapter from allauth.socialaccount.adapter import DefaultSocialAccountAdapter - class AccountAdapter(DefaultAccountAdapter): pass -class SocialAccountAdapter(DefaultSocialAccountAdapter): +class SocialAccountAdapter(LongTermClipperAccountAdapter): pass