provisionning: factorize user creation (#59135)

This commit is contained in:
Benjamin Dauvergne 2021-08-26 16:31:39 +02:00
parent 2daa7b3170
commit 8dc951282a
1 changed files with 71 additions and 62 deletions

View File

@ -17,10 +17,11 @@
import hashlib
import logging
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.db import IntegrityError
from django.db.models.query import Q
from django.db.transaction import atomic
from mellon.models import Issuer
from hobo.agent.common.models import Role, UserExtraAttributes
from hobo.multitenant.utils import provision_user_groups
@ -50,6 +51,64 @@ def user_str(user):
return s
def get_issuer(entity_id):
issuer, _ = Issuer.objects.get_or_create(entity_id=entity_id)
return issuer
def provision_user(entity_id, o, tries=0):
updated = set()
attributes = {
'first_name': o['first_name'][:30],
'last_name': o['last_name'][:150],
'email': o['email'][:254],
'username': o['uuid'][:150],
'is_superuser': o['is_superuser'],
'is_staff': o['is_superuser'],
'is_active': o.get('is_active', True),
}
excluded_attrs = ['roles', 'password']
extra_attributes = {k: v for k, v in o.items() if k not in excluded_attrs}
User = get_user_model()
try:
user = User.objects.get(
saml_identifiers__name_id=o['uuid'], saml_identifiers__issuer__entity_id=entity_id
)
user_extra_attributes = UserExtraAttributes.objects.get(user=user)
except User.DoesNotExist:
user = User()
user_extra_attributes = UserExtraAttributes(user=user, data=extra_attributes)
for key, value in attributes.items():
if getattr(user, key) != value:
setattr(user, key, value)
updated.add(key)
for key in extra_attributes:
if extra_attributes[key] != user_extra_attributes.data.get(key):
updated.add(key)
if not user.id: # user is new
issuer = get_issuer(entity_id)
try:
with atomic(savepoint=False):
user.save()
user.saml_identifiers.create(issuer=issuer, name_id=o['uuid'])
user_extra_attributes.save()
logger.info('provisionned new user %s', user_str(user))
except IntegrityError:
if tries > 0:
raise
return provision_user(user, o, tries=tries + 1)
elif updated:
user.save()
user_extra_attributes.save()
logger.info('updated user %s(%s)', user_str(user), updated)
return user
class NotificationProcessing:
@classmethod
def check_valid_notification(cls, notification):
@ -80,71 +139,21 @@ class NotificationProcessing:
@classmethod
def provision_user(cls, issuer, action, data, full=False):
from django.contrib.auth import get_user_model
from mellon.models import UserSAMLIdentifier
from mellon.models_utils import get_issuer
assert not full # provisionning all users is dangerous, we prefer deprovision
User = get_user_model()
assert not full # provisionning all users is dangerous, we prefer deprovision
uuids = set()
for o in data:
try:
with atomic():
if action == 'provision':
new = False
updated = set()
attributes = {
'first_name': o['first_name'][:30],
'last_name': o['last_name'][:150],
'email': o['email'][:254],
'username': o['uuid'][:150],
'is_superuser': o['is_superuser'],
'is_staff': o['is_superuser'],
'is_active': o.get('is_active', True),
}
assert cls.check_valid_user(o)
try:
mellon_user = UserSAMLIdentifier.objects.get(
issuer__entity_id=issuer, name_id=o['uuid']
)
user = mellon_user.user
except UserSAMLIdentifier.DoesNotExist:
try:
user = User.objects.get(
Q(username=o['uuid'][:30]) | Q(username=o['uuid'][:150])
)
except User.DoesNotExist:
# temp user object
user = User.objects.create(**attributes)
new = True
saml_issuer = get_issuer(issuer)
mellon_user = UserSAMLIdentifier.objects.create(
user=user, issuer=saml_issuer, name_id=o['uuid']
)
excluded_attrs = ['roles', 'password']
UserExtraAttributes.objects.update_or_create(
user=user,
defaults={'data': {k: v for k, v in o.items() if k not in excluded_attrs}},
)
if new:
logger.info('provisionned new user %s', user_str(user))
else:
for key, value in attributes.items():
if getattr(user, key) != value:
setattr(user, key, value)
updated.add(key)
if updated:
user.save()
logger.info('updated user %s(%s)', user_str(user), updated)
role_uuids = [role['uuid'] for role in o.get('roles', [])]
provision_user_groups(user, role_uuids)
elif action == 'deprovision':
assert 'uuid' in o
uuids.add(o['uuid'])
except IntegrityError:
raise TryAgain
for o in data:
if action == 'provision':
assert cls.check_valid_user(o)
user = provision_user(issuer, o)
role_uuids = [role['uuid'] for role in o.get('roles', [])]
provision_user_groups(user, role_uuids)
elif action == 'deprovision':
assert 'uuid' in o
uuids.add(o['uuid'])
if (full and action == 'provision') or (action == 'deprovision'):
if action == 'deprovision':
qs = User.objects.filter(saml_identifiers__name_id__in=uuids)