Projet

Général

Profil

0001-auth_fc-get-a-lock-on-the-sub-during-account-creatio.patch

Paul Marillonnet, 14 septembre 2022 09:11

Télécharger (6,19 ko)

Voir les différences:

Subject: [PATCH] auth_fc: get a lock on the sub during account creation
 (#65411)

 src/authentic2/custom_user/managers.py | 14 +++++
 src/authentic2_auth_fc/views.py        | 75 ++++++++++++++------------
 2 files changed, 55 insertions(+), 34 deletions(-)
src/authentic2/custom_user/managers.py
174 174
            raise self.model.MultipleObjectsReturned
175 175
        return users[0]
176 176

  
177
    def filter_by_email(self, email):
178
        """
179
        Prevents unicode normalization collision attacks (see
180
        https://nvd.nist.gov/vuln/detail/CVE-2019-19844)
181
        """
182
        users = []
183
        for user in self.filter(email__iexact=email, is_active=True):
184
            if (
185
                unicodedata.normalize('NFKC', user.email).casefold()
186
                == unicodedata.normalize('NFKC', email).casefold()
187
            ):
188
                users.append(user)
189
        return users
190

  
177 191

  
178 192
class UserManager(BaseUserManager):
179 193
    def _create_user(self, username, email, password, is_staff, is_superuser, **extra_fields):
src/authentic2_auth_fc/views.py
333 333

  
334 334
    def link(self, request):
335 335
        '''Request an access grant code and associate it to the current user'''
336

  
337
        Lock.lock_identifier(identifier=self.sub)
338

  
336 339
        try:
337 340
            self.fc_account, created = models.FcAccount.objects.get_or_create(
338 341
                sub=self.sub,
......
450 453
            user = User.objects.create(ou=get_default_ou())
451 454
            created = True
452 455

  
453
        try:
454
            if created:
455
                user.set_unusable_password()
456
                user.save()
457

  
458
            # As we intercept IntegrityError and we can never be sure if we are
459
            # in a transaction or not, we must use one to prevent later SQL
460
            # queries to fail.
461
            with transaction.atomic():
456
        if created:
457
            user.set_unusable_password()
458
            user.save()
459

  
460
        # As we intercept IntegrityError and we can never be sure if we are
461
        # in a transaction or not, we must use one to prevent later SQL
462
        # queries to fail.
463
        with transaction.atomic():
464

  
465
            Lock.lock_identifier(identifier=sub)
466

  
467
            # reshuffle existing accounts order
468
            Lock.lock_email(email=user.email)
469
            for account in models.FcAccount.objects.filter(user=user).order_by('-order'):
470
                account.order += 1
471
                account.save()
472

  
473
            if not models.FcAccount.objects.filter(sub=sub).count():
462 474
                models.FcAccount.objects.create(
463 475
                    user=user,
464 476
                    sub=sub,
......
466 478
                    token=json.dumps(token),
467 479
                    user_info=json.dumps(user_info),
468 480
                )
469
        except IntegrityError:
470
            # uniqueness check failed, as the user is new, it can only mean that the sub is not unique
471
            # let's try again
472
            if created:
473
                user.delete()
474
            return utils_misc.authenticate(request, sub=sub, token=token, user_info=user_info), False
475
        except Exception:
476
            # if anything unexpected happen and user was created, delete it and re-raise
477
            if created:
478
                user.delete()
479
            raise
481
        if created:
482
            logger.info('auth_fc: new account "%s" created with FranceConnect sub "%s"', user, sub)
483
            hooks.call_hooks('event', name='fc-create', user=user, sub=sub, request=request)
484
            # FC account creation does not rely on the registration_completion generic view.
485
            # Registration event has to be recorded here:
486
            request.journal.record('user.registration', user=user, how='france-connect')
480 487
        else:
481
            if created:
482
                logger.info('auth_fc: new account "%s" created with FranceConnect sub "%s"', user, sub)
483
                hooks.call_hooks('event', name='fc-create', user=user, sub=sub, request=request)
484
                # FC account creation does not rely on the registration_completion generic view.
485
                # Registration event has to be recorded here:
486
                request.journal.record('user.registration', user=user, how='france-connect')
487
            else:
488
                logger.info('auth_fc: existing account "%s" linked to FranceConnect sub "%s"', user, sub)
489
                hooks.call_hooks('event', name='fc-link', user=user, sub=sub, request=request)
488
            logger.info('auth_fc: existing account "%s" linked to FranceConnect sub "%s"', user, sub)
489
            hooks.call_hooks('event', name='fc-link', user=user, sub=sub, request=request)
490 490

  
491 491
        authenticated_user = utils_misc.authenticate(request, sub=sub, user_info=user_info, token=token)
492 492
        return authenticated_user, created
......
528 528
        if not a2_app_settings.A2_EMAIL_IS_UNIQUE:
529 529
            qs = qs.filter(ou=ou)
530 530

  
531
        Lock.lock_email(email)
532
        try:
533
            user = qs.get_by_email(email)
534
        except User.DoesNotExist:
535
            return User.objects.create(ou=ou, email=email), True
531
        with transaction.atomic():
532
            Lock.lock_email(email)
533

  
534
            users = qs.filter_by_email(email)
535
            if not users:
536
                return User.objects.create(ou=ou, email=email), True
537

  
538
        if len(users) > 1:
539
            # oops, something went wrong there, several users with the same
540
            # email within the same organization unit, aborting.
541
            return None, False
536 542

  
543
        user = users[0]
537 544
        if user.ou != ou:
538 545
            raise UserOutsideDefaultOu
539 546
        return user, False
540
-