doublons-cd91: add script to handle saml/local duplicates
This commit is contained in:
parent
5c010343f7
commit
f94d8d5657
|
@ -0,0 +1,107 @@
|
|||
import collections
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import connection, transaction
|
||||
from django.utils import timezone
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
fh = logging.FileHandler('authentic_fusion.log')
|
||||
fh.setLevel(logging.DEBUG)
|
||||
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.DEBUG)
|
||||
|
||||
logger.addHandler(fh)
|
||||
logger.addHandler(ch)
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
domain_url = ''
|
||||
if hasattr(connection, 'tenant') and hasattr(connection.tenant, 'domain_url'):
|
||||
domain_url = 'https://%s' % connection.tenant.domain_url
|
||||
|
||||
|
||||
logger.info('=== Starting fusion at %s ===', timezone.now().strftime('%Y-%m-%dT%H:%M:%S'))
|
||||
|
||||
|
||||
agent_users = (
|
||||
User.objects.filter(email__icontains='@cd-essonne.fr', is_active=True)
|
||||
.distinct()
|
||||
.order_by('first_name')
|
||||
.prefetch_related('saml_identifiers')
|
||||
)
|
||||
|
||||
agent_users_by_email = collections.defaultdict(list)
|
||||
for user in agent_users:
|
||||
agent_users_by_email[user.email.lower()].append(user)
|
||||
|
||||
|
||||
def get_user_detail(user):
|
||||
return f'{user.get_full_name()} {user.email} {user.uuid} {domain_url}{user.get_absolute_url()}'
|
||||
|
||||
|
||||
users_to_keep = []
|
||||
for email, users in agent_users_by_email.items():
|
||||
if len(users) == 1:
|
||||
continue
|
||||
|
||||
if len(users) == 3:
|
||||
logger.info('* SKIPPING USER %s (more than 2 duplicates)', get_user_detail(users[0]))
|
||||
continue
|
||||
|
||||
if users[0].saml_identifiers.all() and users[1].saml_identifiers.all():
|
||||
logger.info('* SKIPPING USER %s (duplicates are both saml accounts)', get_user_detail(users[0]))
|
||||
continue
|
||||
|
||||
if not users[0].saml_identifiers.all() and not users[1].saml_identifiers.all():
|
||||
logger.info('* SKIPPING USER %s (duplicates are both local accounts)', get_user_detail(users[0]))
|
||||
continue
|
||||
|
||||
roles = {}
|
||||
for user in users:
|
||||
for role in user.roles.all():
|
||||
roles[role.id] = role
|
||||
|
||||
user_to_keep = [x for x in users if x.saml_identifiers.all()][0]
|
||||
users_to_disable = [x for x in users if not x.saml_identifiers.all()]
|
||||
|
||||
user_to_keep._roles_to_add = roles
|
||||
user_to_keep._duplicated_users = users_to_disable
|
||||
users_to_keep.append(user_to_keep)
|
||||
|
||||
|
||||
def do_fusion(users):
|
||||
disabled_users_uuid_by_user_uuid = collections.defaultdict(list)
|
||||
for user in users:
|
||||
logger.info('* Processing user %s', get_user_detail(user))
|
||||
|
||||
for role in sorted(user._roles_to_add.values(), key=lambda x: x.name.lower()):
|
||||
logger.info('Adding role %s', role)
|
||||
user.roles.add(role)
|
||||
|
||||
for duplicated_user in user._duplicated_users:
|
||||
logger.info('Disabling duplicate %s', get_user_detail(duplicated_user))
|
||||
disabled_users_uuid_by_user_uuid[user.uuid].append(duplicated_user.uuid)
|
||||
duplicated_user.mark_as_inactive(reason='Désactivation automatique des doublons')
|
||||
|
||||
result = json.dumps(disabled_users_uuid_by_user_uuid)
|
||||
logger.info('Result %s', result)
|
||||
|
||||
with open('authentic_fusion_result.json', 'w') as f:
|
||||
f.write(result)
|
||||
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
do_fusion(users_to_keep)
|
||||
|
||||
if len(sys.argv) < 2 or sys.argv[1] != '--proceed=true':
|
||||
raise ValueError
|
||||
logger.info('=== Success ===')
|
||||
except ValueError:
|
||||
logger.info('=== Did nothing ===')
|
|
@ -159,3 +159,167 @@ def test_authentic_fusion(db, caplog):
|
|||
saml_user_recent_connection_no_roles_2.refresh_from_db()
|
||||
assert saml_user_recent_connection_no_roles_2.is_active is True
|
||||
assert set(saml_user_recent_connection_no_roles_2.roles.all()) == {role1, role2}
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2022-04-19 14:00')
|
||||
def test_authentic_fusion_local_account(db, caplog):
|
||||
role1 = Role.objects.create(name='role1')
|
||||
role2 = Role.objects.create(name='role2')
|
||||
role3 = Role.objects.create(name='role3')
|
||||
|
||||
# duplicated users, but not agents, should ignore
|
||||
User.objects.create(first_name='Normal', last_name='User', email='normal.user@gmail.com')
|
||||
User.objects.create(first_name='Normal', last_name='User', email='normal.user@gmail.com')
|
||||
|
||||
# two local duplicates, should ignore
|
||||
User.objects.create(first_name='Agent', last_name='No SAML', email='agent.no.saml@cd-essonne.fr')
|
||||
User.objects.create(first_name='Agent', last_name='No SAML', email='agent.no.saml@cd-essonne.fr')
|
||||
|
||||
# three local duplicates, should ignore
|
||||
User.objects.create(first_name='Agent', last_name='3 dups', email='Agent3dups@cd-essonne.fr')
|
||||
User.objects.create(first_name='Agent', last_name='3 dups', email='aGent3dups@cd-essonne.fr')
|
||||
User.objects.create(first_name='Agent', last_name='3 dups', email='agEnt3dups@cd-essonne.fr')
|
||||
|
||||
# agent with saml link, no duplicate, should ignore
|
||||
issuer = Issuer.objects.create(entity_id='https://idp1.example.com/', slug='idp1')
|
||||
saml_user = User.objects.create(
|
||||
first_name='Agent',
|
||||
last_name='No duplicate',
|
||||
email='agent.no.dup@cd-essonne.fr',
|
||||
)
|
||||
deactivated_saml_user = User.objects.create(
|
||||
first_name='Agent',
|
||||
last_name='No duplicate (deactivated)',
|
||||
email='agent.no.dup@cd-essonne.fr',
|
||||
is_active=False,
|
||||
)
|
||||
UserSAMLIdentifier.objects.create(user=saml_user, issuer=issuer, name_id='anodup')
|
||||
UserSAMLIdentifier.objects.create(user=deactivated_saml_user, issuer=issuer, name_id='adeact')
|
||||
|
||||
# two saml duplicates, should ignore
|
||||
saml_user_old_connection = User.objects.create(
|
||||
id=42,
|
||||
uuid='uuid:42/agent@cd-essonne.fr',
|
||||
first_name='Agent',
|
||||
last_name='Duplicated',
|
||||
email='agent@cd-essonne.fr',
|
||||
last_login=now() - datetime.timedelta(days=10),
|
||||
)
|
||||
UserSAMLIdentifier.objects.create(user=saml_user_old_connection, issuer=issuer, name_id='aduplicated')
|
||||
saml_user_old_connection_no_roles = User.objects.create(
|
||||
id=43,
|
||||
uuid='uuid:43/agent@cd-essonne.fr',
|
||||
first_name='Agent',
|
||||
last_name='Duplicated',
|
||||
email='agent@cd-Essonne.fr',
|
||||
last_login=now() - datetime.timedelta(days=5),
|
||||
)
|
||||
UserSAMLIdentifier.objects.create(
|
||||
user=saml_user_old_connection_no_roles, issuer=issuer, name_id='Aduplicated'
|
||||
)
|
||||
|
||||
# local and saml duplicates, should process
|
||||
saml_user = User.objects.create(
|
||||
id=45,
|
||||
uuid='uuid:45/agent2@cd-essonne.fr',
|
||||
first_name='Agent',
|
||||
last_name='Duplicated 2',
|
||||
email='agent2@cd-essonne.fr',
|
||||
)
|
||||
UserSAMLIdentifier.objects.create(user=saml_user, issuer=issuer, name_id='Aduplicated2')
|
||||
|
||||
local_user = User.objects.create(
|
||||
id=46,
|
||||
uuid='uuid:46/agent2@cd-essonne.fr',
|
||||
first_name='Agent',
|
||||
last_name='Duplicated 2',
|
||||
email='agent2@cd-essonne.fr',
|
||||
)
|
||||
local_user.roles.add(role2)
|
||||
|
||||
# again
|
||||
local_user2 = User.objects.create(
|
||||
id=47,
|
||||
uuid='uuid:47/agent3@cd-essonne.fr',
|
||||
first_name='Agent',
|
||||
last_name='Duplicated 3',
|
||||
email='agent3@cd-essonne.fr',
|
||||
)
|
||||
local_user.roles.add(role1)
|
||||
|
||||
saml_user2 = User.objects.create(
|
||||
id=48,
|
||||
uuid='uuid:48/agent3@cd-essonne.fr',
|
||||
first_name='Agent',
|
||||
last_name='Duplicated 3',
|
||||
email='agent3@cd-essonne.fr',
|
||||
)
|
||||
UserSAMLIdentifier.objects.create(user=saml_user2, issuer=issuer, name_id='Aduplicated3')
|
||||
saml_user2.roles.add(role2)
|
||||
|
||||
assert User.objects.count() == 15
|
||||
assert User.objects.filter(is_active=True).count() == 14
|
||||
|
||||
call_command('runscript', 'tests/authentic_fusion_local.py')
|
||||
|
||||
log_messages = caplog.messages
|
||||
assert log_messages == [
|
||||
'=== Starting fusion at 2022-04-19T14:00:00 ===',
|
||||
'* SKIPPING USER Agent Duplicated agent@cd-essonne.fr '
|
||||
'uuid:42/agent@cd-essonne.fr /manage/users/42/ (duplicates are both saml '
|
||||
'accounts)',
|
||||
'* SKIPPING USER Agent No SAML agent.no.saml@cd-essonne.fr '
|
||||
'06231a68f4bc483293faad5fb3105ddd /manage/users/544/ (duplicates are both '
|
||||
'local accounts)',
|
||||
'* SKIPPING USER Agent 3 dups Agent3dups@cd-essonne.fr '
|
||||
'653c422dd6d94654a0c1bd6cd708a93c /manage/users/546/ (more than 2 duplicates)',
|
||||
'* Processing user Agent Duplicated 2 agent2@cd-essonne.fr '
|
||||
'uuid:45/agent2@cd-essonne.fr /manage/users/45/',
|
||||
'Adding role role1',
|
||||
'Adding role role2',
|
||||
'Disabling duplicate Agent Duplicated 2 agent2@cd-essonne.fr '
|
||||
'uuid:46/agent2@cd-essonne.fr /manage/users/46/',
|
||||
'* Processing user Agent Duplicated 3 agent3@cd-essonne.fr '
|
||||
'uuid:48/agent3@cd-essonne.fr /manage/users/48/',
|
||||
'Adding role role2',
|
||||
'Disabling duplicate Agent Duplicated 3 agent3@cd-essonne.fr '
|
||||
'uuid:47/agent3@cd-essonne.fr /manage/users/47/',
|
||||
'Result {"uuid:45/agent2@cd-essonne.fr": ["uuid:46/agent2@cd-essonne.fr"], '
|
||||
'"uuid:48/agent3@cd-essonne.fr": ["uuid:47/agent3@cd-essonne.fr"]}',
|
||||
'=== Did nothing ===',
|
||||
]
|
||||
|
||||
# no changes in db
|
||||
assert User.objects.count() == 15
|
||||
assert User.objects.filter(is_active=True).count() == 14
|
||||
assert saml_user_recent_connection_no_roles_2.roles.count() == 0
|
||||
|
||||
caplog.clear()
|
||||
call_command('runscript', 'tests/authentic_fusion_local.py', '--proceed=true')
|
||||
|
||||
assert log_messages[:-1] == caplog.messages[:-1]
|
||||
assert caplog.messages[-1] == '=== Success ==='
|
||||
|
||||
assert User.objects.count() == 11
|
||||
assert User.objects.filter(is_active=True).count() == 7
|
||||
|
||||
assert User.objects.filter(email='normal.user@gmail.com', is_active=True).count() == 2
|
||||
assert User.objects.filter(email='agent.no.saml@cd-essonne.fr', is_active=True).count() == 2
|
||||
assert User.objects.filter(email='agent.no.dup@cd-essonne.fr', is_active=True).count() == 1
|
||||
|
||||
saml_user_old_connection.refresh_from_db()
|
||||
assert saml_user_old_connection.is_active is False
|
||||
|
||||
saml_user_old_connection_no_roles.refresh_from_db()
|
||||
assert saml_user_old_connection_no_roles.is_active is False
|
||||
|
||||
saml_user_recent_connection.refresh_from_db()
|
||||
assert saml_user_recent_connection.is_active is True
|
||||
assert set(saml_user_recent_connection.roles.all()) == {role1, role2, role3}
|
||||
|
||||
saml_user_old_connection_2.refresh_from_db()
|
||||
assert saml_user_old_connection_2.is_active is False
|
||||
|
||||
saml_user_recent_connection_no_roles_2.refresh_from_db()
|
||||
assert saml_user_recent_connection_no_roles_2.is_active is True
|
||||
assert set(saml_user_recent_connection_no_roles_2.roles.all()) == {role1, role2}
|
||||
|
|
Loading…
Reference in New Issue