Coverage for src/backend/InvenTree/InvenTree/sso.py: 15%

65 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 17:47 +0000

1"""Helper functions for Single Sign On functionality.""" 

2 

3import json 

4 

5from django.contrib.auth.models import Group 

6from django.db.models.signals import post_save 

7from django.dispatch import receiver 

8 

9import structlog 

10from allauth.socialaccount.models import SocialAccount, SocialLogin 

11 

12from common.settings import get_global_setting 

13 

14logger = structlog.get_logger('inventree') 

15 

16 

17def get_provider_app(provider): 

18 """Return the SocialApp object for the given provider.""" 

19 from allauth.socialaccount.models import SocialApp 

20 

21 try: 

22 apps = SocialApp.objects.filter(provider__iexact=provider.id) 

23 except SocialApp.DoesNotExist: 

24 logger.warning("SSO SocialApp not found for provider '%s'", provider.id) 

25 return None 

26 

27 if apps.count() > 1: 

28 logger.warning("Multiple SocialApps found for provider '%s'", provider.id) 

29 

30 if apps.count() == 0: 

31 logger.warning("SSO SocialApp not found for provider '%s'", provider.id) 

32 

33 return apps.first() 

34 

35 

36def check_provider(provider): 

37 """Check if the given provider is correctly configured. 

38 

39 To be correctly configured, the following must be true: 

40 

41 - Provider must either have a registered SocialApp 

42 - Must have at least one site enabled 

43 """ 

44 import allauth.app_settings 

45 

46 # First, check that the provider is enabled 

47 app = get_provider_app(provider) 

48 

49 if not app: 

50 return False 

51 

52 if allauth.app_settings.SITES_ENABLED: 

53 # At least one matching site must be specified 

54 if not app.sites.exists(): 

55 logger.error('SocialApp %s has no sites configured', app) 

56 return False 

57 

58 # At this point, we assume that the provider is correctly configured 

59 return True 

60 

61 

62def provider_display_name(provider): 

63 """Return the 'display name' for the given provider.""" 

64 if app := get_provider_app(provider): 

65 return app.name 

66 

67 # Fallback value if app not found 

68 return provider.name 

69 

70 

71def ensure_sso_groups(sender, sociallogin: SocialLogin, **kwargs): 

72 """Sync groups from IdP each time a SSO user logs on. 

73 

74 This event listener is registered in the apps ready method. 

75 """ 

76 if not get_global_setting('LOGIN_ENABLE_SSO_GROUP_SYNC'): 

77 return 

78 

79 group_key = get_global_setting('SSO_GROUP_KEY') 

80 group_map = json.loads(get_global_setting('SSO_GROUP_MAP')) 

81 # map SSO groups to InvenTree groups 

82 group_names = [] 

83 for sso_group in sociallogin.account.extra_data.get('userinfo', {}).get( 

84 group_key, [] 

85 ): 

86 if mapped_name := group_map.get(sso_group): 

87 group_names.append(mapped_name) 

88 

89 # ensure user has groups 

90 user = sociallogin.account.user 

91 

92 for group_name in group_names: 

93 try: 

94 user.groups.get(name=group_name) 

95 except Group.DoesNotExist: 

96 # user not in group yet 

97 try: 

98 group = Group.objects.get(name=group_name) 

99 except Group.DoesNotExist: 

100 logger.info(f'Creating group {group_name} as it did not exist') 

101 group = Group(name=group_name) 

102 group.save() 

103 logger.info(f'Adding group {group_name} to user {user}') 

104 user.groups.add(group) 

105 

106 # remove groups not listed by SSO if not disabled 

107 if get_global_setting('SSO_REMOVE_GROUPS'): 

108 for group in user.groups.all(): 

109 if group.name not in group_names: 

110 logger.info(f'Removing group {group.name} from {user}') 

111 user.groups.remove(group) 

112 

113 

114@receiver(post_save, sender=SocialAccount) 

115def on_social_account_created(sender, instance: SocialAccount, created: bool, **kwargs): 

116 """Sync SSO groups when new SocialAccount is added. 

117 

118 Since the allauth `social_account_added` signal is not sent for some reason, this 

119 signal is simulated using post_save signals. The issue has been reported as 

120 https://github.com/pennersr/django-allauth/issues/3834 

121 """ 

122 if created: 

123 ensure_sso_groups(None, SocialLogin(account=instance))