From 2c57fb7a4dc6143230c0f60d01f76ff58e15bd23 Mon Sep 17 00:00:00 2001 From: Evarin Date: Sun, 21 Oct 2018 22:27:15 +0200 Subject: [PATCH 1/5] New parameters for install_longterm --- allauth_ens/adapter.py | 12 +++- .../management/commands/install_longterm.py | 38 ++++++++++- allauth_ens/tests.py | 65 +++++++++++++++++++ 3 files changed, 111 insertions(+), 4 deletions(-) diff --git a/allauth_ens/adapter.py b/allauth_ens/adapter.py index 662c98a..c68a5bf 100644 --- a/allauth_ens/adapter.py +++ b/allauth_ens/adapter.py @@ -147,14 +147,20 @@ def deprecate_clippers(): clippers.update(provider='clipper_inactive') -def install_longterm_adapter(fake=False): +def install_longterm_adapter(fake=False, accounts=None): """ Manages the transition from an older django_cas or an allauth_ens installation without LongTermClipperAccountAdapter + + accounts is an optional dictionary containing the association between + clipper usernames and django's User accounts. If not provided, the + function will assumer Users' usernames are their clipper uid. """ - accounts = {u.username: u for u in User.objects.all() - if u.username.isalnum()} + if accounts is None: + accounts = {u.username: u for u in User.objects.all() + if u.username.isalnum()} + ldap_connection = init_ldap() ltc_adapter = get_adapter() diff --git a/allauth_ens/management/commands/install_longterm.py b/allauth_ens/management/commands/install_longterm.py index 40ccde3..40cc988 100644 --- a/allauth_ens/management/commands/install_longterm.py +++ b/allauth_ens/management/commands/install_longterm.py @@ -1,6 +1,9 @@ # coding: utf-8 +from django.contrib.auth import get_user_model from django.core.management.base import BaseCommand +from allauth.socialaccount.models import SocialAccount + from allauth_ens.adapter import install_longterm_adapter @@ -17,10 +20,43 @@ class Command(BaseCommand): help=('Does not save the models created/updated,' 'only shows the list'), ) + parser.add_argument( + '--use-socialaccounts', + action='store_true', + default=False, + help=('Use the existing SocialAccounts rather than all the Users'), + ) + parser.add_argument( + '--clipper-field', + default=None, + type=str + ) pass def handle(self, *args, **options): - logs = install_longterm_adapter(options.get("fake", False)) + fake = options.get("fake", False) + + if options.get('use_socialaccounts', False): + accounts = {account.uid: account.user for account in + (SocialAccount.objects.filter(provider="clipper") + .prefetch_related("user"))} + elif options.get('clipper_field', None): + fields = options['clipper_field'].split('.') + User = get_user_model() + + def get_subattr(obj, fields): + # Allows to follow OneToOne relationships + if len(fields) == 1: + return getattr(obj, fields[0]) + return get_subattr(getattr(obj, fields[0]), fields[1:]) + + accounts = {get_subattr(account, fields): account for account in + User.objects.all()} + else: + accounts = None + + logs = install_longterm_adapter(fake, accounts) + self.stdout.write("Social accounts created : %d" % len(logs["created"])) self.stdout.write(" ".join(("%s -> %s" % s) for s in logs["created"])) diff --git a/allauth_ens/tests.py b/allauth_ens/tests.py index a35ca3b..a7afe33 100644 --- a/allauth_ens/tests.py +++ b/allauth_ens/tests.py @@ -7,6 +7,7 @@ from django.contrib.auth import HASH_SESSION_KEY, get_user_model from django.contrib.sites.models import Site from django.core import mail from django.test import TestCase, override_settings +from django.test.utils import captured_stdout from allauth.socialaccount.models import SocialAccount @@ -18,6 +19,7 @@ from mock import patch from allauth_ens.utils import get_ldap_infos from .adapter import deprecate_clippers, install_longterm_adapter +from .management.commands.install_longterm import Command as InstallLongterm _mock_ldap = MockLDAP() ldap_patcher = patch('allauth_ens.utils.ldap.initialize', @@ -331,6 +333,69 @@ class LongTermClipperTests(CASTestCase): self.assertEqual(user1.username, "test@12") self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12') + def test_longterm_installer_from_allauth_command_username(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() + + with captured_stdout() as stdout: + command = InstallLongterm() + command.handle(clipper_field='username') + + output = stdout.getvalue() + self.assertIn('test -> test@12', output) + + r = self.client_cas_login(self.client, provider_id="clipper", + username='test') + user1 = r.context["user"] + nsa1 = SocialAccount.objects.count() + conn = user1.socialaccount_set.get(provider='clipper') + self.assertEqual(user1.id, user0.id) + self.assertEqual(nsa1, nsa0) + self.assertEqual(user1.username, "test@12") + self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12') + + def test_longterm_installer_from_allauth_command_socialaccounts(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"] + self.assertEqual(user0.username, "test") + self.client.logout() + + user1 = User.objects.create_user('bidule', 'bidule@clipper.ens.fr', + 'bidule') + nsa0 = SocialAccount.objects.count() + + with captured_stdout() as stdout: + command = InstallLongterm() + command.handle(use_socialaccounts=True) + + output = stdout.getvalue() + self.assertIn('test -> test@12', output) + self.assertNotIn('bidule ->', output) + + r = self.client_cas_login(self.client, provider_id="clipper", + username='test') + user1 = r.context["user"] + nsa1 = SocialAccount.objects.count() + conn = user1.socialaccount_set.get(provider='clipper') + self.assertEqual(user1.id, user0.id) + self.assertEqual(nsa1, nsa0) + self.assertEqual(nsa1, nsa0) + self.assertEqual(user1.username, "test@12") + self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12') + def test_longterm_installer_from_djangocas(self): with self.settings( SOCIALACCOUNT_ADAPTER='allauth.socialaccount.' From 126f367e7665781eb15544331739b06a48740e76 Mon Sep 17 00:00:00 2001 From: Evarin Date: Sun, 21 Oct 2018 22:35:44 +0200 Subject: [PATCH 2/5] flake8 --- allauth_ens/management/commands/install_longterm.py | 6 +++--- allauth_ens/tests.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/allauth_ens/management/commands/install_longterm.py b/allauth_ens/management/commands/install_longterm.py index 40cc988..fdff5f1 100644 --- a/allauth_ens/management/commands/install_longterm.py +++ b/allauth_ens/management/commands/install_longterm.py @@ -43,18 +43,18 @@ class Command(BaseCommand): elif options.get('clipper_field', None): fields = options['clipper_field'].split('.') User = get_user_model() - + def get_subattr(obj, fields): # Allows to follow OneToOne relationships if len(fields) == 1: return getattr(obj, fields[0]) return get_subattr(getattr(obj, fields[0]), fields[1:]) - + accounts = {get_subattr(account, fields): account for account in User.objects.all()} else: accounts = None - + logs = install_longterm_adapter(fake, accounts) self.stdout.write("Social accounts created : %d" diff --git a/allauth_ens/tests.py b/allauth_ens/tests.py index a7afe33..1824b60 100644 --- a/allauth_ens/tests.py +++ b/allauth_ens/tests.py @@ -361,7 +361,7 @@ class LongTermClipperTests(CASTestCase): self.assertEqual(nsa1, nsa0) self.assertEqual(user1.username, "test@12") self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12') - + def test_longterm_installer_from_allauth_command_socialaccounts(self): self._setup_ldap(12) with self.settings( @@ -384,7 +384,7 @@ class LongTermClipperTests(CASTestCase): output = stdout.getvalue() self.assertIn('test -> test@12', output) self.assertNotIn('bidule ->', output) - + r = self.client_cas_login(self.client, provider_id="clipper", username='test') user1 = r.context["user"] @@ -395,7 +395,7 @@ class LongTermClipperTests(CASTestCase): self.assertEqual(nsa1, nsa0) self.assertEqual(user1.username, "test@12") self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12') - + def test_longterm_installer_from_djangocas(self): with self.settings( SOCIALACCOUNT_ADAPTER='allauth.socialaccount.' From 1a91ca80904c4d896d97b656389051ceafdbc9d3 Mon Sep 17 00:00:00 2001 From: Evarin Date: Sun, 21 Oct 2018 23:21:47 +0200 Subject: [PATCH 3/5] Flag to keep usernames in install_longterm --- allauth_ens/adapter.py | 7 +++-- .../management/commands/install_longterm.py | 11 ++++++- allauth_ens/tests.py | 31 ++++++++++++++++++- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/allauth_ens/adapter.py b/allauth_ens/adapter.py index c68a5bf..3c02f2a 100644 --- a/allauth_ens/adapter.py +++ b/allauth_ens/adapter.py @@ -147,7 +147,7 @@ def deprecate_clippers(): clippers.update(provider='clipper_inactive') -def install_longterm_adapter(fake=False, accounts=None): +def install_longterm_adapter(fake=False, accounts=None, keep_usernames=False): """ Manages the transition from an older django_cas or an allauth_ens installation without LongTermClipperAccountAdapter @@ -184,7 +184,10 @@ def install_longterm_adapter(fake=False, accounts=None): user = accounts.get(clipper_uid, None) if user is None: continue - user.username = ltc_adapter.get_username(clipper_uid, data) + + if not keep_usernames: + user.username = ltc_adapter.get_username(clipper_uid, data) + if fake: cases.append(clipper_uid) else: diff --git a/allauth_ens/management/commands/install_longterm.py b/allauth_ens/management/commands/install_longterm.py index fdff5f1..a839555 100644 --- a/allauth_ens/management/commands/install_longterm.py +++ b/allauth_ens/management/commands/install_longterm.py @@ -26,6 +26,14 @@ class Command(BaseCommand): default=False, help=('Use the existing SocialAccounts rather than all the Users'), ) + parser.add_argument( + '--keep-usernames', + action='store_true', + default=False, + help=('Do not apply the username template (e.g. clipper@promo) to' + 'the existing account, only populate the SocialAccounts with' + 'ldap informations'), + ) parser.add_argument( '--clipper-field', default=None, @@ -35,6 +43,7 @@ class Command(BaseCommand): def handle(self, *args, **options): fake = options.get("fake", False) + keep_usernames = options.get("keep_usernames", False) if options.get('use_socialaccounts', False): accounts = {account.uid: account.user for account in @@ -55,7 +64,7 @@ class Command(BaseCommand): else: accounts = None - logs = install_longterm_adapter(fake, accounts) + logs = install_longterm_adapter(fake, accounts, keep_usernames) self.stdout.write("Social accounts created : %d" % len(logs["created"])) diff --git a/allauth_ens/tests.py b/allauth_ens/tests.py index 1824b60..61bb3a4 100644 --- a/allauth_ens/tests.py +++ b/allauth_ens/tests.py @@ -333,7 +333,7 @@ class LongTermClipperTests(CASTestCase): self.assertEqual(user1.username, "test@12") self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12') - def test_longterm_installer_from_allauth_command_username(self): + def test_longterm_installer_from_allauth_command_using_username(self): self._setup_ldap(12) with self.settings( SOCIALACCOUNT_ADAPTER='allauth.socialaccount.' @@ -362,6 +362,35 @@ class LongTermClipperTests(CASTestCase): self.assertEqual(user1.username, "test@12") self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12') + def test_longterm_installer_from_allauth_command_keeping_username(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() + + with captured_stdout() as stdout: + command = InstallLongterm() + command.handle(keep_usernames=True) + + output = stdout.getvalue() + self.assertIn('test -> test', output) + + r = self.client_cas_login(self.client, provider_id="clipper", + username='test') + user1 = r.context["user"] + nsa1 = SocialAccount.objects.count() + conn = user1.socialaccount_set.get(provider='clipper') + self.assertEqual(user1.id, user0.id) + self.assertEqual(nsa1, nsa0) + self.assertEqual(user1.username, "test") + self.assertEqual(conn.extra_data['ldap']['entrance_year'], '12') + def test_longterm_installer_from_allauth_command_socialaccounts(self): self._setup_ldap(12) with self.settings( From a30d3866c55f28bfb5575d5548a57aa64a96b5e0 Mon Sep 17 00:00:00 2001 From: Evarin Date: Sun, 6 Jan 2019 11:47:16 +0100 Subject: [PATCH 4/5] Install options explained in readme --- README.rst | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 33ec1c7..c169e73 100644 --- a/README.rst +++ b/README.rst @@ -216,11 +216,23 @@ Customize behaviour, and for instance attribute a default entrance year. 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``. - + Description + 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``. + + Install_longterm options + - ``--use-socialaccounts``: Use the existing SocialAccounts rather than all the Users. Useful if you are already using Allauth and don't want ``install_longterm`` to mess with the non-clipper authentications. + - ``--keep-usernames``: Do not apply the username template (e.g. ``clipper@promo``) to the existing accounts, only populate the SocialAccounts with LDAP informations. Useful if you don't want to change the usernames of previous users, but do want such a template for future accounts. + - ``--clipper-field ``: Use a special field rather than the username to get the clipper username (for LDAP lookup and SocialAccount creation/update). This parameter is compatible with ForeignKeys (e.g. ``profile.clipper``). Note: ``--use-socialaccounts`` will ignore the ``--clipper-field`` parameter. + - ``--fake``: Do not modify the database. Use it to test there is no conflict, and be sure the changes are the ones expected. This command does not check for uniqueness errors, so there it may succeed and the actual command fail eventually. + + Typical use cases + - *Django-cas-ng -> Longterm*: Use ``install_longterm`` without parameters, or maybe ``--keep-usernames``. If you had a custom username handling, ``--clipper_field`` may be useful. + - *Allauth -> Longterm*: Use ``install_longterm`` with ``--use-socialaccounts``, and maybe ``--keep-usernames``. + + ********* Demo Site ********* From 884020cc20311e79de964fef7e5aa1472e202af2 Mon Sep 17 00:00:00 2001 From: Evarin Date: Sun, 6 Jan 2019 12:08:07 +0100 Subject: [PATCH 5/5] Make flake8 happy --- allauth_ens/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/allauth_ens/utils.py b/allauth_ens/utils.py index 02d4c10..7da3c6c 100644 --- a/allauth_ens/utils.py +++ b/allauth_ens/utils.py @@ -116,8 +116,7 @@ def remove_email(user, email): # Prefer a verified mail. new_primary = ( - others.filter(verified=True).last() or - others.last() + others.filter(verified=True).last() or others.last() ) if new_primary: