Coverage for distro_tracker/signon/distro_tracker.py: 77%

33 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2025-01-12 09:15 +0000

1# Copyright 2024 The Debusine Developers 

2# See the COPYRIGHT file at the top-level directory of this distribution 

3# 

4# This file is part of Distro Tracker. It is subject to the license terms 

5# in the LICENSE file found in the top-level directory of this 

6# distribution and at https://deb.li/DTLicense. No part of Distro Tracker, 

7# including this file, may be copied, modified, propagated, or distributed 

8# except according to the terms contained in the LICENSE file. 

9"""Distro Tracker extensions to Signon.""" 

10 

11import logging 

12 

13from django_email_accounts.models import User, UserEmail 

14from django.core.exceptions import ValidationError 

15from django.contrib.auth.hashers import make_password 

16 

17from distro_tracker.signon.models import Identity 

18from distro_tracker.signon.signon import Signon 

19from distro_tracker.signon.utils import split_full_name 

20 

21log = logging.getLogger(__name__) 

22 

23 

24class DistroTrackerSignon(Signon): 

25 """ 

26 Distro Tracker specific extension to Signon. 

27 

28 Activate it in django settings with:: 

29 

30 SIGNON_CLASS = "distro_tracker.signon.distro_tracker.DistroTrackerSignon" # noqa: E501 

31 """ 

32 

33 def _lookup_user_from_identity(self, identity: Identity) -> User | None: 

34 """Lookup an existing user from claims in an Identity.""" 

35 try: 

36 user_email = UserEmail.objects.get( 

37 email__iexact=identity.claims["email"] 

38 ) 

39 return user_email.user 

40 except UserEmail.DoesNotExist: 

41 return None 

42 

43 def create_user_from_identity(self, identity: Identity) -> User | None: 

44 email = identity.claims["email"] 

45 

46 user_email, _ = UserEmail.objects.get_or_create( 

47 email__iexact=email, defaults={'email': email} 

48 ) 

49 if not user_email.user: 

50 first_name, last_name = split_full_name(identity.claims["name"]) 

51 

52 # Django does not run validators on create_user, so garbage in the 

53 # claims can either create garbage users, or cause database 

54 # transaction errors that will invalidate the current transaction. 

55 # 

56 # See: https://stackoverflow.com/questions/67442439/why-django-does-not-validate-email-in-customuser-model # noqa: E501 

57 

58 # Instead of calling create_user, I instead have to replicate what 

59 # it does here and call validation explicitly before save. 

60 

61 # This is the equivalent of the following, with validation: 

62 # user = User.objects.create_user( 

63 # main_email=email, 

64 # first_name=first_name, 

65 # last_name=last_name, 

66 # ) 

67 user = User( 

68 main_email=email, 

69 first_name=first_name, 

70 last_name=last_name, 

71 is_active=True, 

72 ) 

73 user.password = make_password(None) 

74 try: 

75 user.clean_fields() 

76 except ValidationError as e: 

77 log.warning( 

78 "%s: cannot create a local user", 

79 identity, 

80 exc_info=e, 

81 ) 

82 return None 

83 user.save() 

84 user_email.user = user 

85 user_email.save() 

86 else: 

87 user = user_email.user 

88 

89 return user