From 944478f10c146114fc4d65b1a653f8165dd4f88f Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Tue, 21 Oct 2025 16:50:19 +0200 Subject: [PATCH 01/15] Implement OIDC groups mapping --- dojo/pipeline.py | 26 ++++++++++++++++++++++++++ dojo/settings/settings.dist.py | 7 +++++++ 2 files changed, 33 insertions(+) diff --git a/dojo/pipeline.py b/dojo/pipeline.py index 8aaea4079bb..66e6e79ff40 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -7,6 +7,7 @@ from django.conf import settings from social_core.backends.azuread_tenant import AzureADTenantOAuth2 from social_core.backends.google import GoogleOAuth2 +from social_core.backends.open_id_connect import OpenIdConnectAuth from dojo.authorization.roles_permissions import Permissions, Roles from dojo.models import Dojo_Group, Dojo_Group_Member, Product, Product_Member, Product_Type, Role @@ -106,6 +107,31 @@ def update_azure_groups(backend, uid, user=None, social=None, *args, **kwargs): cleanup_old_groups_for_user(user, group_names) +def update_oidc_groups(backend, uid, user=None, social=None, *args, **kwargs): + if settings.OIDC_AUTH_ENABLED and settings.DD_SOCIAL_AUTH_OIDC_GET_GROUPS and isinstance(backend, OpenIdConnectAuth): + response = kwargs.get("response", {}) + group_names = response.get("groups", []) + + if not group_names: + logger.warning("No 'groups' claim found in Dex OIDC response. Skipping group assignment.") + return + logger.debug(f"Dex OIDC groups received: {group_names}") + filtered_group_names = [] + group_filter = getattr(settings, "OIDC_GROUPS_FILTER", None) + for group_name in group_names: + try: + if group_filter and not re.search(group_filter, group_name): + logger.debug(f"Skipping group '{group_name}' due to OIDC_GROUPS_FILTER: {group_filter}") + continue + filtered_group_names.append(group_name) + except Exception as e: + logger.error(f"Error processing group '{group_name}': {e}") + if filtered_group_names: + assign_user_to_groups(user, filtered_group_names, Dojo_Group.OIDC) + if getattr(settings, "OIDC_CLEANUP_GROUPS", False): + cleanup_old_groups_for_user(user, filtered_group_names) + + def is_group_id(group): return bool(re.search(r"^[a-zA-Z0-9]{8,}-[a-zA-Z0-9]{4,}-[a-zA-Z0-9]{4,}-[a-zA-Z0-9]{4,}-[a-zA-Z0-9]{12,}$", group)) diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 2ef1835b08e..9edec9604a0 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -118,6 +118,9 @@ DD_SOCIAL_AUTH_REDIRECT_IS_HTTPS=(bool, False), # If true, the redirect after login will use the HTTPS protocol DD_SOCIAL_AUTH_TRAILING_SLASH=(bool, True), DD_SOCIAL_AUTH_OIDC_AUTH_ENABLED=(bool, False), + DD_SOCIAL_AUTH_OIDC_GET_GROUPS=(bool, False), + DD_SOCIAL_AUTH_OIDC_GROUPS_FILTER=(str, ""), + DD_SOCIAL_AUTH_OIDC_CLEANUP_GROUPS=(bool, True), DD_SOCIAL_AUTH_OIDC_OIDC_ENDPOINT=(str, ""), DD_SOCIAL_AUTH_OIDC_ID_KEY=(str, ""), DD_SOCIAL_AUTH_OIDC_KEY=(str, ""), @@ -583,6 +586,7 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param "social_core.pipeline.social_auth.load_extra_data", "social_core.pipeline.user.user_details", "dojo.pipeline.update_azure_groups", + "dojo.pipeline.update_oidc_groups", "dojo.pipeline.update_product_access", ) @@ -641,6 +645,9 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param # Mandatory settings OIDC_AUTH_ENABLED = env("DD_SOCIAL_AUTH_OIDC_AUTH_ENABLED") +OIDC_GET_GROUPS = env("DD_SOCIAL_AUTH_OIDC_GET_GROUPS") +OIDC_GROUPS_FILTER = env("DD_SOCIAL_AUTH_OIDC_GROUPS_FILTER") +OIDC_CLEANUP_GROUPS = env("DD_SOCIAL_AUTH_OIDC_CLEANUP_GROUPS") SOCIAL_AUTH_OIDC_OIDC_ENDPOINT = env("DD_SOCIAL_AUTH_OIDC_OIDC_ENDPOINT") SOCIAL_AUTH_OIDC_KEY = env("DD_SOCIAL_AUTH_OIDC_KEY") SOCIAL_AUTH_OIDC_SECRET = env("DD_SOCIAL_AUTH_OIDC_SECRET") From 5a55f3307fc90cfb0bb7baa5bbc5a231df7324b0 Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Tue, 21 Oct 2025 16:53:41 +0200 Subject: [PATCH 02/15] typo --- dojo/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dojo/pipeline.py b/dojo/pipeline.py index 66e6e79ff40..b90ef5a72ab 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -108,7 +108,7 @@ def update_azure_groups(backend, uid, user=None, social=None, *args, **kwargs): def update_oidc_groups(backend, uid, user=None, social=None, *args, **kwargs): - if settings.OIDC_AUTH_ENABLED and settings.DD_SOCIAL_AUTH_OIDC_GET_GROUPS and isinstance(backend, OpenIdConnectAuth): + if settings.OIDC_AUTH_ENABLED and settings.OIDC_GET_GROUPS and isinstance(backend, OpenIdConnectAuth): response = kwargs.get("response", {}) group_names = response.get("groups", []) From fa302687e0ffcddb4915e33b122313a19e5cb303 Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Tue, 21 Oct 2025 17:14:08 +0200 Subject: [PATCH 03/15] update --- dojo/pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dojo/pipeline.py b/dojo/pipeline.py index b90ef5a72ab..e94c0c14cd8 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -113,9 +113,9 @@ def update_oidc_groups(backend, uid, user=None, social=None, *args, **kwargs): group_names = response.get("groups", []) if not group_names: - logger.warning("No 'groups' claim found in Dex OIDC response. Skipping group assignment.") + logger.warning("No 'groups' claim found in OIDC response. Skipping group assignment.") return - logger.debug(f"Dex OIDC groups received: {group_names}") + logger.debug(f"OIDC groups received: {group_names}") filtered_group_names = [] group_filter = getattr(settings, "OIDC_GROUPS_FILTER", None) for group_name in group_names: From 4de0546b7f6261ec68e1c931e76b8e8230e964aa Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Thu, 30 Oct 2025 22:09:25 +0100 Subject: [PATCH 04/15] update --- dojo/settings/settings.dist.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 9edec9604a0..3c790288ea3 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -134,6 +134,7 @@ DD_SOCIAL_AUTH_OIDC_USERINFO_URL=(str, ""), DD_SOCIAL_AUTH_OIDC_JWKS_URI=(str, ""), DD_SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT=(str, "Login with OIDC"), + DD_SOCIAL_AUTH_OIDC_SCOPE=(list, ['openid', 'profile', 'email', 'groups']), DD_SOCIAL_AUTH_AUTH0_OAUTH2_ENABLED=(bool, False), DD_SOCIAL_AUTH_AUTH0_KEY=(str, ""), DD_SOCIAL_AUTH_AUTH0_SECRET=(str, ""), @@ -648,6 +649,7 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param OIDC_GET_GROUPS = env("DD_SOCIAL_AUTH_OIDC_GET_GROUPS") OIDC_GROUPS_FILTER = env("DD_SOCIAL_AUTH_OIDC_GROUPS_FILTER") OIDC_CLEANUP_GROUPS = env("DD_SOCIAL_AUTH_OIDC_CLEANUP_GROUPS") +SOCIAL_AUTH_OIDC_SCOPE = env("DD_SOCIAL_AUTH_OIDC_SCOPE") SOCIAL_AUTH_OIDC_OIDC_ENDPOINT = env("DD_SOCIAL_AUTH_OIDC_OIDC_ENDPOINT") SOCIAL_AUTH_OIDC_KEY = env("DD_SOCIAL_AUTH_OIDC_KEY") SOCIAL_AUTH_OIDC_SECRET = env("DD_SOCIAL_AUTH_OIDC_SECRET") From d8d2826ea3bdb8f3b52d4a0bcc5a6be25f911911 Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Thu, 30 Oct 2025 22:13:43 +0100 Subject: [PATCH 05/15] update --- dojo/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dojo/models.py b/dojo/models.py index 282a8c4d667..d853297bf4f 100644 --- a/dojo/models.py +++ b/dojo/models.py @@ -273,9 +273,11 @@ class UserContactInfo(models.Model): class Dojo_Group(models.Model): AZURE = "AzureAD" REMOTE = "Remote" + OIDC = "OIDC" SOCIAL_CHOICES = ( (AZURE, _("AzureAD")), (REMOTE, _("Remote")), + (OIDC, _("OIDC")), ) name = models.CharField(max_length=255, unique=True) description = models.CharField(max_length=4000, null=True, blank=True) From 2952f38a9fdbb57879387d92386d2b7929f5177a Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Thu, 30 Oct 2025 22:30:03 +0100 Subject: [PATCH 06/15] update --- dojo/group/utils.py | 33 +++++++++++++++++++++------------ dojo/pipeline.py | 7 +++++-- dojo/settings/settings.dist.py | 2 +- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/dojo/group/utils.py b/dojo/group/utils.py index d2245dac2a6..52825914773 100644 --- a/dojo/group/utils.py +++ b/dojo/group/utils.py @@ -1,10 +1,13 @@ +import logging + from crum import get_current_user -from django.conf import settings -from django.contrib.auth.models import Group +from django.contrib.auth.models import AnonymousUser, Group from django.db.models.signals import post_delete, post_save from django.dispatch import receiver -from dojo.models import Dojo_Group, Dojo_Group_Member, Role +from dojo.models import Dojo_Group, Dojo_Group_Member, Dojo_User, Role + +logger = logging.getLogger(__name__) def get_auth_group_name(group, attempt=0): @@ -32,15 +35,21 @@ def group_post_save_handler(sender, **kwargs): group.auth_group = auth_group group.save() user = get_current_user() - if user and not settings.AZUREAD_TENANT_OAUTH2_GET_GROUPS: - # Add the current user as the owner of the group - member = Dojo_Group_Member() - member.user = user - member.group = group - member.role = Role.objects.get(is_owner=True) - member.save() - # Add user to authentication group as well - auth_group.user_set.add(user) + if not user or isinstance(user, AnonymousUser): + logger.debug("Skipping group_post_save_handler: user is anonymous or missing.") + return + if not isinstance(user, Dojo_User): + try: + user = Dojo_User.objects.get(pk=user.pk) + except Dojo_User.DoesNotExist: + logger.error(f"Group post-save: No Dojo_User found for user with pk '{user.pk}'.") + return + member = Dojo_Group_Member() + member.user = user + member.group = group + member.role = Role.objects.get(is_owner=True) + member.save() + auth_group.user_set.add(user) @receiver(post_delete, sender=Dojo_Group) diff --git a/dojo/pipeline.py b/dojo/pipeline.py index e94c0c14cd8..ea514dd5e2b 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -111,7 +111,6 @@ def update_oidc_groups(backend, uid, user=None, social=None, *args, **kwargs): if settings.OIDC_AUTH_ENABLED and settings.OIDC_GET_GROUPS and isinstance(backend, OpenIdConnectAuth): response = kwargs.get("response", {}) group_names = response.get("groups", []) - if not group_names: logger.warning("No 'groups' claim found in OIDC response. Skipping group assignment.") return @@ -126,7 +125,7 @@ def update_oidc_groups(backend, uid, user=None, social=None, *args, **kwargs): filtered_group_names.append(group_name) except Exception as e: logger.error(f"Error processing group '{group_name}': {e}") - if filtered_group_names: + if len(filtered_group_names) > 0: assign_user_to_groups(user, filtered_group_names, Dojo_Group.OIDC) if getattr(settings, "OIDC_CLEANUP_GROUPS", False): cleanup_old_groups_for_user(user, filtered_group_names) @@ -209,6 +208,10 @@ def sanitize_username(username): def create_user(strategy, details, backend, user=None, *args, **kwargs): if not settings.SOCIAL_AUTH_CREATE_USER: return None +<<<<<<< HEAD username = details.get(settings.SOCIAL_AUTH_CREATE_USER_MAPPING) details["username"] = sanitize_username(username) +======= + details["username"] = sanitize_username(details.get("email")) +>>>>>>> 0b9d5238e5 (update) return social_core.pipeline.user.create_user(strategy, details, backend, user, args, kwargs) diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 3c790288ea3..0d3f77d7b5e 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -134,7 +134,7 @@ DD_SOCIAL_AUTH_OIDC_USERINFO_URL=(str, ""), DD_SOCIAL_AUTH_OIDC_JWKS_URI=(str, ""), DD_SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT=(str, "Login with OIDC"), - DD_SOCIAL_AUTH_OIDC_SCOPE=(list, ['openid', 'profile', 'email', 'groups']), + DD_SOCIAL_AUTH_OIDC_SCOPE=(list, ["openid", "profile", "email", "groups"]), DD_SOCIAL_AUTH_AUTH0_OAUTH2_ENABLED=(bool, False), DD_SOCIAL_AUTH_AUTH0_KEY=(str, ""), DD_SOCIAL_AUTH_AUTH0_SECRET=(str, ""), From 8303b2262a59960aa6624f52bf89de59913ed67b Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Thu, 30 Oct 2025 23:49:38 +0100 Subject: [PATCH 07/15] update --- .../0245_alter_dojo_group_social_provider.py | 18 ++++++++++++++++++ dojo/group/utils.py | 18 +++++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 dojo/db_migrations/0245_alter_dojo_group_social_provider.py diff --git a/dojo/db_migrations/0245_alter_dojo_group_social_provider.py b/dojo/db_migrations/0245_alter_dojo_group_social_provider.py new file mode 100644 index 00000000000..29dbc26d168 --- /dev/null +++ b/dojo/db_migrations/0245_alter_dojo_group_social_provider.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.13 on 2025-10-30 22:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0244_pghistory_indices'), + ] + + operations = [ + migrations.AlterField( + model_name='dojo_group', + name='social_provider', + field=models.CharField(blank=True, choices=[('AzureAD', 'AzureAD'), ('Remote', 'Remote'), ('OIDC', 'OIDC')], help_text='Group imported from a social provider.', max_length=10, null=True, verbose_name='Social Authentication Provider'), + ), + ] diff --git a/dojo/group/utils.py b/dojo/group/utils.py index 52825914773..ca90ce7a5e6 100644 --- a/dojo/group/utils.py +++ b/dojo/group/utils.py @@ -1,6 +1,7 @@ import logging from crum import get_current_user +from django.conf import settings from django.contrib.auth.models import AnonymousUser, Group from django.db.models.signals import post_delete, post_save from django.dispatch import receiver @@ -29,7 +30,6 @@ def group_post_save_handler(sender, **kwargs): created = kwargs.pop("created") group = kwargs.pop("instance") if created: - # Create authentication group auth_group = Group(name=get_auth_group_name(group)) auth_group.save() group.auth_group = auth_group @@ -44,12 +44,16 @@ def group_post_save_handler(sender, **kwargs): except Dojo_User.DoesNotExist: logger.error(f"Group post-save: No Dojo_User found for user with pk '{user.pk}'.") return - member = Dojo_Group_Member() - member.user = user - member.group = group - member.role = Role.objects.get(is_owner=True) - member.save() - auth_group.user_set.add(user) + if not settings.AZUREAD_TENANT_OAUTH2_GET_GROUPS: + # Add the current user as the owner of the group + member = Dojo_Group_Member() + member.user = user + member.group = group + member.role = Role.objects.get(is_owner=True) + member.save() + + # Add user to authentication group as well + auth_group.user_set.add(user) @receiver(post_delete, sender=Dojo_Group) From 7fc68a68ddac30dd09b07d4fa5f01d3fcdeb098a Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Thu, 30 Oct 2025 23:53:20 +0100 Subject: [PATCH 08/15] update --- dojo/group/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dojo/group/utils.py b/dojo/group/utils.py index ca90ce7a5e6..16d269d20eb 100644 --- a/dojo/group/utils.py +++ b/dojo/group/utils.py @@ -30,6 +30,7 @@ def group_post_save_handler(sender, **kwargs): created = kwargs.pop("created") group = kwargs.pop("instance") if created: + # Create authentication group auth_group = Group(name=get_auth_group_name(group)) auth_group.save() group.auth_group = auth_group @@ -51,7 +52,6 @@ def group_post_save_handler(sender, **kwargs): member.group = group member.role = Role.objects.get(is_owner=True) member.save() - # Add user to authentication group as well auth_group.user_set.add(user) From 5d0f0d1a81bcfceae9daefca984816bc73bcb59a Mon Sep 17 00:00:00 2001 From: manuelsommer <47991713+manuel-sommer@users.noreply.github.com> Date: Fri, 31 Oct 2025 00:30:32 +0100 Subject: [PATCH 09/15] Aktualisieren von pipeline.py --- dojo/pipeline.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dojo/pipeline.py b/dojo/pipeline.py index ea514dd5e2b..c07074848ad 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -208,10 +208,14 @@ def sanitize_username(username): def create_user(strategy, details, backend, user=None, *args, **kwargs): if not settings.SOCIAL_AUTH_CREATE_USER: return None +<<<<<<< HEAD <<<<<<< HEAD username = details.get(settings.SOCIAL_AUTH_CREATE_USER_MAPPING) details["username"] = sanitize_username(username) ======= details["username"] = sanitize_username(details.get("email")) >>>>>>> 0b9d5238e5 (update) +======= + details["username"] = sanitize_username(details.get("username")) +>>>>>>> b9d07c6a56 (Aktualisieren von pipeline.py) return social_core.pipeline.user.create_user(strategy, details, backend, user, args, kwargs) From 9cc4dcd7502789b6fc7daa1548c660e15c5c0d7b Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Fri, 31 Oct 2025 07:35:03 +0100 Subject: [PATCH 10/15] add unittest --- unittests/test_pipeline.py | 109 +++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 unittests/test_pipeline.py diff --git a/unittests/test_pipeline.py b/unittests/test_pipeline.py new file mode 100644 index 00000000000..02200180edf --- /dev/null +++ b/unittests/test_pipeline.py @@ -0,0 +1,109 @@ + +import unittest +from unittest.mock import ANY, MagicMock, patch + +from social_core.backends.azuread_tenant import AzureADTenantOAuth2 +from social_core.backends.open_id_connect import OpenIdConnectAuth + +from dojo.models import Dojo_Group +from dojo.pipeline import update_azure_groups, update_oidc_groups + + +class TestUpdateOIDCGroups(unittest.TestCase): + + @patch("dojo.pipeline.settings") + @patch("dojo.pipeline.assign_user_to_groups") + @patch("dojo.pipeline.cleanup_old_groups_for_user") + def test_update_oidc_groups_with_valid_groups(self, mock_cleanup, mock_assign, mock_settings): + mock_settings.OIDC_AUTH_ENABLED = True + mock_settings.OIDC_GET_GROUPS = True + mock_settings.OIDC_GROUPS_FILTER = ".*" + mock_settings.OIDC_CLEANUP_GROUPS = True + mock_backend = MagicMock(spec=OpenIdConnectAuth) + mock_user = MagicMock() + response = {"groups": ["admin", "user"]} + update_oidc_groups(mock_backend, uid="123", user=mock_user, response=response) + mock_assign.assert_called_once_with(mock_user, ["admin", "user"], ANY) + mock_cleanup.assert_called_once_with(mock_user, ["admin", "user"]) + + @patch("dojo.pipeline.settings") + def test_update_oidc_groups_with_no_groups(self, mock_settings): + mock_settings.OIDC_AUTH_ENABLED = True + mock_settings.OIDC_GET_GROUPS = True + mock_backend = MagicMock(spec=OpenIdConnectAuth) + mock_user = MagicMock() + response = {"groups": []} + with patch("dojo.pipeline.logger.warning") as mock_logger: + update_oidc_groups(mock_backend, uid="123", user=mock_user, response=response) + mock_logger.assert_called_once_with("No 'groups' claim found in OIDC response. Skipping group assignment.") + + @patch("dojo.pipeline.settings") + @patch("dojo.pipeline.assign_user_to_groups") + def test_update_oidc_groups_with_filter(self, mock_assign, mock_settings): + mock_settings.OIDC_AUTH_ENABLED = True + mock_settings.OIDC_GET_GROUPS = True + mock_settings.OIDC_GROUPS_FILTER = "^admin$" + mock_settings.OIDC_CLEANUP_GROUPS = False + mock_backend = MagicMock(spec=OpenIdConnectAuth) + mock_user = MagicMock() + response = {"groups": ["admin", "user", "guest"]} + update_oidc_groups(mock_backend, uid="123", user=mock_user, response=response) + mock_assign.assert_called_once_with(mock_user, ["admin"], ANY) + + +class TestUpdateAzureGroups(unittest.TestCase): + + @patch("dojo.pipeline.settings") + @patch("dojo.pipeline.assign_user_to_groups") + @patch("dojo.pipeline.cleanup_old_groups_for_user") + @patch("dojo.pipeline.requests.get") + def test_update_azure_groups_with_group_ids(self, mock_requests_get, mock_cleanup, mock_assign, mock_settings): + mock_settings.AZUREAD_TENANT_OAUTH2_ENABLED = True + mock_settings.AZUREAD_TENANT_OAUTH2_GET_GROUPS = True + mock_settings.AZUREAD_TENANT_OAUTH2_GROUPS_FILTER = None + mock_settings.AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS = True + mock_settings.REQUESTS_TIMEOUT = 5 + mock_backend = MagicMock(spec=AzureADTenantOAuth2) + mock_user = MagicMock() + mock_social = MagicMock() + mock_social.extra_data = { + "access_token": "fake-token", + "resource": "https://graph.microsoft.com", + } + mock_user.social_auth.order_by.return_value.first.return_value = mock_social + mock_response = {"groups": ["group-id-1", "group-id-2"]} + mock_requests_get.return_value.json.return_value = {"displayName": "GroupName"} + mock_requests_get.return_value.raise_for_status = MagicMock() + with patch("dojo.pipeline.is_group_id", return_value=True): + update_azure_groups(mock_backend, uid="123", user=mock_user, response=mock_response) + mock_assign.assert_called_once_with(mock_user, ["GroupName", "GroupName"], Dojo_Group.AZURE) + mock_cleanup.assert_called_once_with(mock_user, ["GroupName", "GroupName"]) + + @patch("dojo.pipeline.settings") + def test_update_azure_groups_with_no_groups(self, mock_settings): + mock_settings.AZUREAD_TENANT_OAUTH2_ENABLED = True + mock_settings.AZUREAD_TENANT_OAUTH2_GET_GROUPS = True + mock_backend = MagicMock(spec=AzureADTenantOAuth2) + mock_user = MagicMock() + mock_user.social_auth.order_by.return_value.first.return_value = MagicMock() + mock_response = {"groups": []} + with patch("dojo.pipeline.logger.warning") as mock_logger: + update_azure_groups(mock_backend, uid="123", user=mock_user, response=mock_response) + mock_logger.assert_called_once_with("No groups in response. Stopping to update groups of user based on azureAD") + + @patch("dojo.pipeline.settings") + @patch("dojo.pipeline.assign_user_to_groups") + def test_update_azure_groups_with_group_name_and_filter(self, mock_assign, mock_settings): + mock_settings.AZUREAD_TENANT_OAUTH2_ENABLED = True + mock_settings.AZUREAD_TENANT_OAUTH2_GET_GROUPS = True + mock_settings.AZUREAD_TENANT_OAUTH2_GROUPS_FILTER = "^admin$" + mock_settings.AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS = False + mock_backend = MagicMock(spec=AzureADTenantOAuth2) + mock_user = MagicMock() + mock_social = MagicMock() + mock_social.extra_data = {"access_token": "fake-token", "resource": "https://graph.microsoft.com"} + mock_user.social_auth.order_by.return_value.first.return_value = mock_social + mock_response = {"groups": ["admin", "user", "guest"]} + with patch("dojo.pipeline.is_group_id", return_value=False): + update_azure_groups(mock_backend, uid="123", user=mock_user, response=mock_response) + mock_assign.assert_called_once_with(mock_user, ["admin"], Dojo_Group.AZURE) From bc5562b64568af5620d747a7e04ee0678b00264a Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Fri, 31 Oct 2025 07:46:38 +0100 Subject: [PATCH 11/15] add docs --- .../customize_dojo/user_management/configure_sso.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/content/en/customize_dojo/user_management/configure_sso.md b/docs/content/en/customize_dojo/user_management/configure_sso.md index da6c9ca5f33..4329e820c3f 100644 --- a/docs/content/en/customize_dojo/user_management/configure_sso.md +++ b/docs/content/en/customize_dojo/user_management/configure_sso.md @@ -569,6 +569,17 @@ You can also optionally set the following variables: Once these variables have been set, restart DefectDojo. Log In With OIDC should now be added to the DefectDojo login page. +### Group synchronization options: +You can set the following variables to parse the OIDC groups: + + {{< highlight python >}} + DD_SOCIAL_AUTH_OIDC_GET_GROUPS=True, # Enable group synchronization from OIDC claims + DD_SOCIAL_AUTH_OIDC_GROUPS_FILTER='', # Optional regex to filter group names + DD_SOCIAL_AUTH_OIDC_CLEANUP_GROUPS=True, # Remove user from groups not present in OIDC claim + {{< /highlight >}} + +Once these variables have been set, restart DefectDojo. + ## SAML Configuration DefectDojo Pro users can follow this guide to set up a SAML configuration using the DefectDojo UI. Open-Source users can set up SAML via environment variables, using the following [guide](./#open-source-saml). From 626f63fddc1481b8677b77e5f5873bda5368063b Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Mon, 3 Nov 2025 20:57:36 +0100 Subject: [PATCH 12/15] update --- ...l_provider.py => 0247_alter_dojo_group_social_provider.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename dojo/db_migrations/{0245_alter_dojo_group_social_provider.py => 0247_alter_dojo_group_social_provider.py} (80%) diff --git a/dojo/db_migrations/0245_alter_dojo_group_social_provider.py b/dojo/db_migrations/0247_alter_dojo_group_social_provider.py similarity index 80% rename from dojo/db_migrations/0245_alter_dojo_group_social_provider.py rename to dojo/db_migrations/0247_alter_dojo_group_social_provider.py index 29dbc26d168..e337e63c9fd 100644 --- a/dojo/db_migrations/0245_alter_dojo_group_social_provider.py +++ b/dojo/db_migrations/0247_alter_dojo_group_social_provider.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.13 on 2025-10-30 22:37 +# Generated by Django 5.1.13 on 2025-11-03 19:56 from django.db import migrations, models @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('dojo', '0244_pghistory_indices'), + ('dojo', '0246_endpoint_idx_ep_product_lower_host_and_more'), ] operations = [ From 8b8162ff0571b30eaa5c0bcaa7e8c84adf8660a0 Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Wed, 17 Dec 2025 11:33:30 +0100 Subject: [PATCH 13/15] fix --- dojo/pipeline.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/dojo/pipeline.py b/dojo/pipeline.py index c07074848ad..9ed07c54ac1 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -208,14 +208,6 @@ def sanitize_username(username): def create_user(strategy, details, backend, user=None, *args, **kwargs): if not settings.SOCIAL_AUTH_CREATE_USER: return None -<<<<<<< HEAD -<<<<<<< HEAD username = details.get(settings.SOCIAL_AUTH_CREATE_USER_MAPPING) details["username"] = sanitize_username(username) -======= - details["username"] = sanitize_username(details.get("email")) ->>>>>>> 0b9d5238e5 (update) -======= - details["username"] = sanitize_username(details.get("username")) ->>>>>>> b9d07c6a56 (Aktualisieren von pipeline.py) return social_core.pipeline.user.create_user(strategy, details, backend, user, args, kwargs) From 8ab5edb0eea72fbe3bad11f428bb3a504fb304dc Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Wed, 17 Dec 2025 11:35:25 +0100 Subject: [PATCH 14/15] fix db migration --- ...ial_provider.py => 0249_alter_dojo_group_social_provider.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename dojo/db_migrations/{0247_alter_dojo_group_social_provider.py => 0249_alter_dojo_group_social_provider.py} (88%) diff --git a/dojo/db_migrations/0247_alter_dojo_group_social_provider.py b/dojo/db_migrations/0249_alter_dojo_group_social_provider.py similarity index 88% rename from dojo/db_migrations/0247_alter_dojo_group_social_provider.py rename to dojo/db_migrations/0249_alter_dojo_group_social_provider.py index e337e63c9fd..f3a191a1805 100644 --- a/dojo/db_migrations/0247_alter_dojo_group_social_provider.py +++ b/dojo/db_migrations/0249_alter_dojo_group_social_provider.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('dojo', '0246_endpoint_idx_ep_product_lower_host_and_more'), + ('dojo', '0248_alter_general_survey_expiration'), ] operations = [ From 9386653b5202e36e42f5a8b4d2f2ed9a98a0ad2d Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Wed, 17 Dec 2025 11:45:33 +0100 Subject: [PATCH 15/15] update --- ...ial_provider.py => 0252_alter_dojo_group_social_provider.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename dojo/db_migrations/{0249_alter_dojo_group_social_provider.py => 0252_alter_dojo_group_social_provider.py} (90%) diff --git a/dojo/db_migrations/0249_alter_dojo_group_social_provider.py b/dojo/db_migrations/0252_alter_dojo_group_social_provider.py similarity index 90% rename from dojo/db_migrations/0249_alter_dojo_group_social_provider.py rename to dojo/db_migrations/0252_alter_dojo_group_social_provider.py index f3a191a1805..7a5d2b938cf 100644 --- a/dojo/db_migrations/0249_alter_dojo_group_social_provider.py +++ b/dojo/db_migrations/0252_alter_dojo_group_social_provider.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('dojo', '0248_alter_general_survey_expiration'), + ('dojo', '0251_usercontactinfo_reset_timestamps'), ] operations = [