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,9 +178,9 @@ Configuration
|
||||||
Auto-signup
|
Auto-signup
|
||||||
Populated data
|
Populated data
|
||||||
- username: ``<clipper>@<entrance year>``
|
- username: ``<clipper>@<entrance year>``
|
||||||
- email: from LDAP's `mailRoutingAddress` field, or ``<clipper>@clipper.ens.fr``
|
- email: from LDAP's *mailRoutingAddress* field, or ``<clipper>@clipper.ens.fr``
|
||||||
- first_name, last_name from LDAP's `cn` field
|
- 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)
|
- 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
|
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.
|
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.
|
||||||
|
@ -188,6 +188,9 @@ Account deprecation
|
||||||
Customize
|
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.
|
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
|
Demo Site
|
||||||
*********
|
*********
|
||||||
|
|
|
@ -3,9 +3,12 @@ import ldap
|
||||||
|
|
||||||
from allauth.account.utils import user_email, user_field, user_username
|
from allauth.account.utils import user_email, user_field, user_username
|
||||||
from allauth.account.models import EmailAddress
|
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 allauth.socialaccount.models import SocialAccount
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
@ -36,6 +39,30 @@ def _init_ldap():
|
||||||
l.set_option(ldap.OPT_TIMEOUT, 10)
|
l.set_option(ldap.OPT_TIMEOUT, 10)
|
||||||
return l
|
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):
|
def get_ldap_infos(clipper):
|
||||||
assert clipper.isalnum()
|
assert clipper.isalnum()
|
||||||
data = {'email':'{}@clipper.ens.fr'.format(clipper.strip().lower())}
|
data = {'email':'{}@clipper.ens.fr'.format(clipper.strip().lower())}
|
||||||
|
@ -50,29 +77,7 @@ def get_ldap_infos(clipper):
|
||||||
str("homeDirectory") ])
|
str("homeDirectory") ])
|
||||||
|
|
||||||
if len(info) > 0:
|
if len(info) > 0:
|
||||||
infos = info[0][1]
|
data = _extract_infos_from_ldap(info[0][1], 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]
|
|
||||||
|
|
||||||
except ldap.LDAPError:
|
except ldap.LDAPError:
|
||||||
pass
|
pass
|
||||||
|
@ -87,6 +92,9 @@ class LongTermClipperAccountAdapter(DefaultSocialAccountAdapter):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def pre_social_login(self, request, sociallogin):
|
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
|
clipper = sociallogin.account.uid
|
||||||
try:
|
try:
|
||||||
a = SocialAccount.objects.get(provider='clipper_inactive',
|
a = SocialAccount.objects.get(provider='clipper_inactive',
|
||||||
|
@ -137,6 +145,8 @@ class LongTermClipperAccountAdapter(DefaultSocialAccountAdapter):
|
||||||
return "{}@{}".format(clipper, data.get('annee', '00'))
|
return "{}@{}".format(clipper, data.get('annee', '00'))
|
||||||
|
|
||||||
def save_user(self, request, sociallogin, form=None):
|
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 = sociallogin.user
|
||||||
user.set_unusable_password()
|
user.set_unusable_password()
|
||||||
|
|
||||||
|
@ -164,6 +174,11 @@ class LongTermClipperAccountAdapter(DefaultSocialAccountAdapter):
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def deprecate_clippers():
|
def deprecate_clippers():
|
||||||
|
"""
|
||||||
|
Marks all the SocialAccount with clipper as deprecated, by setting their
|
||||||
|
provider to 'clipper_inactive'
|
||||||
|
"""
|
||||||
|
|
||||||
clippers = SocialAccount.objects.filter(provider='clipper')
|
clippers = SocialAccount.objects.filter(provider='clipper')
|
||||||
c_uids = clippers.values_list('uid', flat=True)
|
c_uids = clippers.values_list('uid', flat=True)
|
||||||
|
|
||||||
|
@ -172,3 +187,49 @@ def deprecate_clippers():
|
||||||
|
|
||||||
# Deprecate accounts
|
# Deprecate accounts
|
||||||
clippers.update(provider='clipper_inactive')
|
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 fakeldap import MockLDAP
|
||||||
|
|
||||||
from allauth_cas.test.testcases import CASTestCase, CASViewTestCase
|
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
|
from allauth.socialaccount.models import SocialAccount
|
||||||
|
|
||||||
_mock_ldap = MockLDAP()
|
_mock_ldap = MockLDAP()
|
||||||
|
@ -250,10 +250,11 @@ class LongTermClipperTests(CASTestCase):
|
||||||
|
|
||||||
def test_multiple_deprecation(self):
|
def test_multiple_deprecation(self):
|
||||||
self._setup_ldap(12)
|
self._setup_ldap(12)
|
||||||
self._setup_ldap(15, "truc")
|
|
||||||
r = self.client_cas_login(self.client, provider_id="clipper",
|
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||||
username="test")
|
username="test")
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
|
||||||
|
self._setup_ldap(15, "truc")
|
||||||
r = self.client_cas_login(self.client, provider_id="clipper",
|
r = self.client_cas_login(self.client, provider_id="clipper",
|
||||||
username="truc")
|
username="truc")
|
||||||
self.client.logout()
|
self.client.logout()
|
||||||
|
@ -275,3 +276,44 @@ class LongTermClipperTests(CASTestCase):
|
||||||
# while "truc" remains
|
# while "truc" remains
|
||||||
self.assertEqual(sa0, sa2)
|
self.assertEqual(sa0, sa2)
|
||||||
self.assertEqual(sa1, sa0+1)
|
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