ctl: always retry provisionning when detecting duplicates (#75777)

This commit is contained in:
Benjamin Dauvergne 2023-03-24 11:55:13 +01:00
parent 2bb316e2ed
commit e01c6b65cc
1 changed files with 52 additions and 40 deletions

View File

@ -16,7 +16,9 @@
import json
import os
import random
import sys
import time
from quixote import get_publisher
@ -142,7 +144,6 @@ class CmdHoboNotify(Command):
@classmethod
def provision_user(cls, publisher, issuer, action, data, full=False):
formdef = UserFieldsFormDef(publisher=publisher)
User = publisher.user_class
if full:
@ -152,45 +153,7 @@ class CmdHoboNotify(Command):
try:
o = json_encode_helper(o, publisher.site_charset)
if action == 'provision':
if not cls.check_valid_user(o):
raise ValueError('invalid user')
uuid = o['uuid']
users = User.get_users_with_name_identifier(uuid)
if len(users) > 1:
raise Exception('duplicate users')
if users:
user = users[0]
else:
user = User(uuid)
user.form_data = user.form_data or {}
for field in formdef.fields:
if not field.id.startswith('_'):
continue
field_value = o.get(field.id[1:])
if field.convert_value_from_anything:
try:
field_value = field.convert_value_from_anything(field_value)
except ValueError as e:
publisher.record_error(exception=e, context='[PROVISIONNING]', notify=True)
continue
user.form_data[field.id] = field_value
user.name_identifiers = [uuid]
# reset roles
user.is_active = o.get('is_active', True)
user.is_admin = o.get('is_superuser', False)
user.roles = []
for role_ref in o.get('roles', []):
role = get_publisher().role_class.resolve(role_ref['uuid'])
if role and role.id not in user.roles:
user.add_roles([role.id])
user.set_attributes_from_formdata(user.form_data)
user.store()
# verify we did not produce a doublon
users = User.get_users_with_name_identifier(uuid)
for doublon in users:
if int(doublon.id) < int(user.id): # we are not the first so backoff
user.remove_self()
break
cls.create_or_update_user(publisher, o)
elif action == 'deprovision':
if 'uuid' not in o:
raise KeyError('user without uuid')
@ -200,5 +163,54 @@ class CmdHoboNotify(Command):
except Exception as e:
publisher.record_error(exception=e, context='[PROVISIONNING]', notify=True)
@classmethod
def create_or_update_user(cls, publisher, o, retry=0):
User = publisher.user_class
formdef = UserFieldsFormDef(publisher=publisher)
if retry > 3:
raise Exception('user provisionning failed after %s tries.' % retry)
if retry > 0:
time.sleep(random.random() * 2 * retry)
if not cls.check_valid_user(o):
raise ValueError('invalid user')
uuid = o['uuid']
users = User.get_users_with_name_identifier(uuid)
if len(users) > 1:
raise Exception('duplicate users')
if users:
user = users[0]
else:
user = User(uuid)
user.form_data = user.form_data or {}
for field in formdef.fields:
if not field.id.startswith('_'):
continue
field_value = o.get(field.id[1:])
if field.convert_value_from_anything:
try:
field_value = field.convert_value_from_anything(field_value)
except ValueError as e:
publisher.record_error(exception=e, context='[PROVISIONNING]', notify=True)
continue
user.form_data[field.id] = field_value
user.name_identifiers = [uuid]
# reset roles
user.is_active = o.get('is_active', True)
user.is_admin = o.get('is_superuser', False)
user.roles = []
for role_ref in o.get('roles', []):
role = get_publisher().role_class.resolve(role_ref['uuid'])
if role and role.id not in user.roles:
user.add_roles([role.id])
user.set_attributes_from_formdata(user.form_data)
user.store()
# verify we did not produce a doublon
users = User.get_users_with_name_identifier(uuid)
if len(users) > 1:
user.remove_self()
cls.create_or_update_user(publisher, o, retry=retry + 1)
CmdHoboNotify.register()