Util management command to install longtermadapter + fixes
This commit is contained in:
parent
b6f5acaa46
commit
f0a73f6ef6
4 changed files with 163 additions and 30 deletions
|
@ -178,15 +178,18 @@ Configuration
|
|||
Auto-signup
|
||||
Populated data
|
||||
- username: ``<clipper>@<entrance year>``
|
||||
- email: from LDAP's `mailRoutingAddress` field, or ``<clipper>@clipper.ens.fr``
|
||||
- first_name, last_name from LDAP's `cn` field
|
||||
- extra_data in SociallAccount instance, containing these field, plus `anne` and `promotion` from LDAP's `homeDirectory` field (available only on first connection)
|
||||
- email: from LDAP's *mailRoutingAddress* field, or ``<clipper>@clipper.ens.fr``
|
||||
- first_name, last_name from LDAP's *cn* field
|
||||
- extra_data in SociallAccount instance, containing these field, plus *annee* and *promotion* parsed from LDAP's *homeDirectory* field (available only on first connection)
|
||||
|
||||
Account deprecation
|
||||
At the beginning of each year (i.e. early November), to prevent clipper username conflicts, you should run ``$ python manage.py deprecate_clippers``. Every association clipper username <-> user will then be set on hold, and at the first subsequent connection, a verification of the account will be made (using LDAP), so that a known user keeps his account, but a newcomer won't inherit an archicube's.
|
||||
|
||||
Customize
|
||||
You can customize the SocialAccountAdapter by inheriting ``allauth_ens.adapter.LongTermClipperAccountAdapter``. You might want to modify ``get_username(clipper, data)`` to change the default username format. This function is used to disambiguate in the account deprecation process.
|
||||
|
||||
Initial migration
|
||||
If you used allauth without LongTermClipperAccountAdapter, or another CAS interface to log in, you need to update the Users to the new username policy, and (in the second case) to create the SocialAccount instances to link CAS and Users. This can be done easily with ``$ python manage.py install_longterm``.
|
||||
|
||||
*********
|
||||
Demo Site
|
||||
|
|
|
@ -3,9 +3,12 @@ import ldap
|
|||
|
||||
from allauth.account.utils import user_email, user_field, user_username
|
||||
from allauth.account.models import EmailAddress
|
||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter, get_account_adapter
|
||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter, get_account_adapter, get_adapter
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
import six
|
||||
|
||||
|
@ -36,6 +39,30 @@ def _init_ldap():
|
|||
l.set_option(ldap.OPT_TIMEOUT, 10)
|
||||
return l
|
||||
|
||||
def _extract_infos_from_ldap(infos, data={}):
|
||||
# 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]
|
||||
return data
|
||||
|
||||
def get_ldap_infos(clipper):
|
||||
assert clipper.isalnum()
|
||||
data = {'email':'{}@clipper.ens.fr'.format(clipper.strip().lower())}
|
||||
|
@ -50,30 +77,8 @@ def get_ldap_infos(clipper):
|
|||
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]
|
||||
|
||||
data = _extract_infos_from_ldap(info[0][1], data)
|
||||
|
||||
except ldap.LDAPError:
|
||||
pass
|
||||
|
||||
|
@ -87,6 +92,9 @@ class LongTermClipperAccountAdapter(DefaultSocialAccountAdapter):
|
|||
"""
|
||||
|
||||
def pre_social_login(self, request, sociallogin):
|
||||
if sociallogin.account.provider != "clipper":
|
||||
return Super(LongTermClipperAccountAdapter, self).pre_social_login(request, sociallogin)
|
||||
|
||||
clipper = sociallogin.account.uid
|
||||
try:
|
||||
a = SocialAccount.objects.get(provider='clipper_inactive',
|
||||
|
@ -137,6 +145,8 @@ class LongTermClipperAccountAdapter(DefaultSocialAccountAdapter):
|
|||
return "{}@{}".format(clipper, data.get('annee', '00'))
|
||||
|
||||
def save_user(self, request, sociallogin, form=None):
|
||||
if sociallogin.account.provider != "clipper":
|
||||
return Super(LongTermClipperAccountAdapter, self).save_user(request, sociallogin, form)
|
||||
user = sociallogin.user
|
||||
user.set_unusable_password()
|
||||
|
||||
|
@ -164,6 +174,11 @@ class LongTermClipperAccountAdapter(DefaultSocialAccountAdapter):
|
|||
return user
|
||||
|
||||
def deprecate_clippers():
|
||||
"""
|
||||
Marks all the SocialAccount with clipper as deprecated, by setting their
|
||||
provider to 'clipper_inactive'
|
||||
"""
|
||||
|
||||
clippers = SocialAccount.objects.filter(provider='clipper')
|
||||
c_uids = clippers.values_list('uid', flat=True)
|
||||
|
||||
|
@ -172,3 +187,49 @@ def deprecate_clippers():
|
|||
|
||||
# Deprecate accounts
|
||||
clippers.update(provider='clipper_inactive')
|
||||
|
||||
def install_longterm_adapter(fake=False):
|
||||
"""
|
||||
Manages the transition from an older django_cas or an allauth_ens installation
|
||||
without LongTermClipperAccountAdapter
|
||||
"""
|
||||
|
||||
accounts = {u.username: u for u in User.objects.all() if u.username.isalnum()}
|
||||
l = _init_ldap()
|
||||
ltc_adapter = get_adapter()
|
||||
|
||||
info = l.search_s('dc=spi,dc=ens,dc=fr',
|
||||
ldap.SCOPE_SUBTREE,
|
||||
("(|{})".format(''.join(("(uid=%s)" % (un,))
|
||||
for un in accounts.keys()))),
|
||||
[str("uid"),
|
||||
str("cn"),
|
||||
str("mailRoutingAddress"),
|
||||
str("homeDirectory") ])
|
||||
|
||||
logs = {"created": [], "updated": []}
|
||||
cases = []
|
||||
|
||||
for userinfo in info:
|
||||
infos = userinfo[1]
|
||||
data = _extract_infos_from_ldap(infos)
|
||||
clipper = infos["uid"][0]
|
||||
user = accounts.get(clipper, None)
|
||||
if user is None:
|
||||
continue
|
||||
user.username = ltc_adapter.get_username(clipper, data)
|
||||
if fake:
|
||||
cases.append(clipper)
|
||||
else:
|
||||
user.save()
|
||||
cases.append(user.username)
|
||||
if SocialAccount.objects.filter(provider='clipper', uid=clipper).exists():
|
||||
logs["updated"].append((clipper, user.username))
|
||||
continue
|
||||
sa = SocialAccount(user=user, provider='clipper', uid=clipper, extra_data=data)
|
||||
if not fake:
|
||||
sa.save()
|
||||
logs["created"].append((clipper, user.username))
|
||||
|
||||
logs["unmodified"] = User.objects.exclude(username__in=cases).values_list("username", flat=True)
|
||||
return logs
|
||||
|
|
27
allauth_ens/management/commands/install_longterm.py
Normal file
27
allauth_ens/management/commands/install_longterm.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
#coding: utf-8
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from allauth_ens.adapter import install_longterm_adapter
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Manages the transition from an older django_cas or an allauth_ens installation without LongTermClipperAccountAdapter'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--fake',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Does not save the models created/updated, only shows the list',
|
||||
)
|
||||
pass
|
||||
|
||||
def handle(self, *args, **options):
|
||||
logs = install_longterm_adapter(options.get("fake", False))
|
||||
self.stdout.write("Social accounts created : %d" % len(logs["created"]))
|
||||
self.stdout.write(" ".join(("%s -> %s" % s) for s in logs["created"]))
|
||||
self.stdout.write("Social accounts displaced : %d" % len(logs["updated"]))
|
||||
self.stdout.write(" ".join(("%s -> %s" % s) for s in logs["updated"]))
|
||||
self.stdout.write("User accounts unmodified : %d" % len(logs["unmodified"]))
|
||||
self.stdout.write(" ".join(logs["unmodified"]))
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(u'LongTermClipper migration successful'))
|
|
@ -10,7 +10,7 @@ from mock import patch
|
|||
from fakeldap import MockLDAP
|
||||
|
||||
from allauth_cas.test.testcases import CASTestCase, CASViewTestCase
|
||||
from .adapter import deprecate_clippers
|
||||
from .adapter import deprecate_clippers, install_longterm_adapter
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
|
||||
_mock_ldap = MockLDAP()
|
||||
|
@ -250,10 +250,11 @@ class LongTermClipperTests(CASTestCase):
|
|||
|
||||
def test_multiple_deprecation(self):
|
||||
self._setup_ldap(12)
|
||||
self._setup_ldap(15, "truc")
|
||||
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||
username="test")
|
||||
self.client.logout()
|
||||
|
||||
self._setup_ldap(15, "truc")
|
||||
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||
username="truc")
|
||||
self.client.logout()
|
||||
|
@ -275,3 +276,44 @@ class LongTermClipperTests(CASTestCase):
|
|||
# while "truc" remains
|
||||
self.assertEqual(sa0, sa2)
|
||||
self.assertEqual(sa1, sa0+1)
|
||||
|
||||
def test_longterm_installer_from_allauth(self):
|
||||
self._setup_ldap(12)
|
||||
with self.settings(SOCIALACCOUNT_ADAPTER=\
|
||||
'allauth.socialaccount.adapter.DefaultSocialAccountAdapter'):
|
||||
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||
username='test')
|
||||
user0 = r.context["user"]
|
||||
nsa0 = SocialAccount.objects.count()
|
||||
self.assertEqual(user0.username, "test")
|
||||
self.client.logout()
|
||||
|
||||
l = install_longterm_adapter()
|
||||
|
||||
self.assertEqual(l["updated"], [("test", "test@12")])
|
||||
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||
username='test')
|
||||
user1 = r.context["user"]
|
||||
nsa1 = SocialAccount.objects.count()
|
||||
self.assertEqual(user1.id, user0.id)
|
||||
self.assertEqual(nsa1, nsa0)
|
||||
self.assertEqual(user1.username, "test@12")
|
||||
|
||||
def test_longterm_installer_from_djangocas(self):
|
||||
with self.settings(SOCIALACCOUNT_ADAPTER=\
|
||||
'allauth.socialaccount.adapter.DefaultSocialAccountAdapter'):
|
||||
user0 = User.objects.create_user('test', 'test@clipper.ens.fr', 'test')
|
||||
nsa0 = SocialAccount.objects.count()
|
||||
|
||||
self._setup_ldap(12)
|
||||
|
||||
l = install_longterm_adapter()
|
||||
|
||||
self.assertEqual(l["created"], [("test", "test@12")])
|
||||
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||
username='test')
|
||||
user1 = r.context["user"]
|
||||
nsa1 = SocialAccount.objects.count()
|
||||
self.assertEqual(user1.id, user0.id)
|
||||
self.assertEqual(nsa1, nsa0+1)
|
||||
self.assertEqual(user1.username, "test@12")
|
||||
|
|
Loading…
Reference in a new issue