From 77a171f2f3a226eda292e0aa40bb73e69e18753b Mon Sep 17 00:00:00 2001 From: Lars Meijers Date: Fri, 8 Mar 2024 16:43:42 +0100 Subject: [PATCH] oidc keycloak adaptions --- dojo/context_processors.py | 3 +++ .../0209_alter_dojo_group_social_provider.py | 18 +++++++++++++ dojo/group/utils.py | 5 ++-- dojo/models.py | 2 ++ dojo/pipeline.py | 20 +++++++++++++++ dojo/settings/settings.dist.py | 25 ++++++++++++++----- dojo/templates/dojo/login.html | 2 +- dojo/user/views.py | 2 +- 8 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 dojo/db_migrations/0209_alter_dojo_group_social_provider.py diff --git a/dojo/context_processors.py b/dojo/context_processors.py index c0bbb25046..9da80ff0b8 100644 --- a/dojo/context_processors.py +++ b/dojo/context_processors.py @@ -17,6 +17,9 @@ def globalize_vars(request): "AZUREAD_TENANT_OAUTH2_GET_GROUPS": settings.AZUREAD_TENANT_OAUTH2_GET_GROUPS, "AZUREAD_TENANT_OAUTH2_GROUPS_FILTER": settings.AZUREAD_TENANT_OAUTH2_GROUPS_FILTER, "AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS": settings.AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS, + "KEYCLOAK_TENANT_OAUTH2_GET_GROUPS": settings.AZUREAD_TENANT_OAUTH2_GET_GROUPS, + "KEYCLOAK_TENANT_OAUTH2_GROUPS_FILTER": settings.AZUREAD_TENANT_OAUTH2_GROUPS_FILTER, + "KEYCLOAK_TENANT_OAUTH2_CLEANUP_GROUPS": settings.AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS, "KEYCLOAK_ENABLED": settings.KEYCLOAK_OAUTH2_ENABLED, "SOCIAL_AUTH_KEYCLOAK_LOGIN_BUTTON_TEXT": settings.SOCIAL_AUTH_KEYCLOAK_LOGIN_BUTTON_TEXT, "GITHUB_ENTERPRISE_ENABLED": settings.GITHUB_ENTERPRISE_OAUTH2_ENABLED, diff --git a/dojo/db_migrations/0209_alter_dojo_group_social_provider.py b/dojo/db_migrations/0209_alter_dojo_group_social_provider.py new file mode 100644 index 0000000000..74d4fa4192 --- /dev/null +++ b/dojo/db_migrations/0209_alter_dojo_group_social_provider.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2024-04-11 12:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0208_merge_acunetix'), + ] + + operations = [ + migrations.AlterField( + model_name='dojo_group', + name='social_provider', + field=models.CharField(blank=True, choices=[('AzureAD', 'AzureAD'), ('Remote', 'Remote'), ('Keycloak', 'Keycloak')], 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 2ddf5e57a2..c142433cd4 100644 --- a/dojo/group/utils.py +++ b/dojo/group/utils.py @@ -2,8 +2,7 @@ from django.contrib.auth.models import Group from django.db.models.signals import post_save, post_delete from django.dispatch import receiver -from dojo.models import Dojo_Group, Dojo_Group_Member, Role -from django.conf import settings +from dojo.models import Dojo_Group, Dojo_Group_Member, Role, Dojo_User def get_auth_group_name(group, attempt=0): @@ -41,7 +40,7 @@ def group_post_save_handler(sender, **kwargs): group.save() user = get_current_user() - if user and not settings.AZUREAD_TENANT_OAUTH2_GET_GROUPS: + if user and isinstance(user, Dojo_User): # Add the current user as the owner of the group member = Dojo_Group_Member() member.user = user diff --git a/dojo/models.py b/dojo/models.py index a59af55ee9..6e84592b8d 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -250,9 +250,11 @@ class UserContactInfo(models.Model): class Dojo_Group(models.Model): AZURE = 'AzureAD' REMOTE = 'Remote' + KEYCLOAK = 'Keycloak' SOCIAL_CHOICES = ( (AZURE, _('AzureAD')), (REMOTE, _('Remote')), + (KEYCLOAK, _('Keycloak')), ) name = models.CharField(max_length=255, unique=True) description = models.CharField(max_length=4000, null=True, blank=True) diff --git a/dojo/pipeline.py b/dojo/pipeline.py index f5405af9ee..9227360211 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -7,6 +7,7 @@ from django.conf import settings from dojo.models import Product, Product_Member, Product_Type, Role, Dojo_Group, Dojo_Group_Member from social_core.backends.azuread_tenant import AzureADTenantOAuth2 +from social_core.backends.open_id_connect import OpenIdConnectAuth from social_core.backends.google import GoogleOAuth2 from dojo.authorization.roles_permissions import Permissions, Roles from dojo.product.queries import get_authorized_products @@ -65,6 +66,25 @@ def modify_permissions(backend, uid, user=None, social=None, *args, **kwargs): pass +def update_keycloak_groups(backend, uid, user=None, social=None, *args, **kwargs): + if settings.KEYCLOAK_OAUTH2_ENABLED and settings.KEYCLOAK_TENANT_OAUTH2_GET_GROUPS and isinstance(backend, OpenIdConnectAuth): + group_names = [] + if 'groups' not in kwargs['response'] or kwargs['response']['groups'] == "": + logger.warning("No groups in response. Stopping to update groups of user based on azureAD") + return + group_ids = kwargs['response']['groups'] + for group_from_response in group_ids: + if settings.KEYCLOAK_TENANT_OAUTH2_GROUPS_FILTER == "" or re.search(settings.KEYCLOAK_TENANT_OAUTH2_GROUPS_FILTER, group_from_response): + group_names.append(group_from_response) + else: + logger.debug("Skipping group " + group_from_response + " due to KEYCLOAK_TENANT_OAUTH2_GROUPS_FILTER " + settings.KEYCLOAK_TENANT_OAUTH2_GROUPS_FILTER) + + if len(group_names) > 0: + assign_user_to_groups(user, group_names, 'Keycloak') + if settings.KEYCLOAK_TENANT_OAUTH2_CLEANUP_GROUPS: + cleanup_old_groups_for_user(user, group_names) + + def update_azure_groups(backend, uid, user=None, social=None, *args, **kwargs): if settings.AZUREAD_TENANT_OAUTH2_ENABLED and settings.AZUREAD_TENANT_OAUTH2_GET_GROUPS and isinstance(backend, AzureADTenantOAuth2): # In some wild cases, there could be two social auth users diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index a53c737f2d..ba2fa755ca 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -124,6 +124,9 @@ DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_GET_GROUPS=(bool, False), DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_GROUPS_FILTER=(str, ''), DD_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS=(bool, True), + DD_SOCIAL_AUTH_KEYCLOAK_OAUTH2_GET_GROUPS=(bool, False), + DD_SOCIAL_AUTH_KEYCLOAK_OAUTH2_CLEANUP_GROUPS=(bool, True), + DD_SOCIAL_AUTH_KEYCLOAK_OAUTH2_GROUPS_FILTER=(str, ''), DD_SOCIAL_AUTH_GITLAB_OAUTH2_ENABLED=(bool, False), DD_SOCIAL_AUTH_GITLAB_PROJECT_AUTO_IMPORT=(bool, False), DD_SOCIAL_AUTH_GITLAB_PROJECT_IMPORT_TAGS=(bool, False), @@ -134,6 +137,7 @@ DD_SOCIAL_AUTH_GITLAB_API_URL=(str, 'https://gitlab.com'), DD_SOCIAL_AUTH_GITLAB_SCOPE=(list, ['read_user', 'openid']), DD_SOCIAL_AUTH_KEYCLOAK_OAUTH2_ENABLED=(bool, False), + DD_SOCIAL_AUTH_OIDC_OIDC_ENDPOINT=(str, ''), DD_SOCIAL_AUTH_KEYCLOAK_KEY=(str, ''), DD_SOCIAL_AUTH_KEYCLOAK_SECRET=(str, ''), DD_SOCIAL_AUTH_KEYCLOAK_PUBLIC_KEY=(str, ''), @@ -533,7 +537,8 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param 'dojo.okta.OktaOAuth2', 'social_core.backends.azuread_tenant.AzureADTenantOAuth2', 'social_core.backends.gitlab.GitLabOAuth2', - 'social_core.backends.keycloak.KeycloakOAuth2', + # 'social_core.backends.keycloak.KeycloakOAuth2', + 'social_core.backends.open_id_connect.OpenIdConnectAuth', 'social_core.backends.github_enterprise.GithubEnterpriseOAuth2', 'dojo.remote_user.RemoteUserBackend', 'django.contrib.auth.backends.RemoteUserBackend', @@ -569,6 +574,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_keycloak_groups', 'dojo.pipeline.update_product_access', ) @@ -630,13 +636,20 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param SOCIAL_AUTH_TRAILING_SLASH = env('DD_SOCIAL_AUTH_TRAILING_SLASH') KEYCLOAK_OAUTH2_ENABLED = env('DD_SOCIAL_AUTH_KEYCLOAK_OAUTH2_ENABLED') -SOCIAL_AUTH_KEYCLOAK_KEY = env('DD_SOCIAL_AUTH_KEYCLOAK_KEY') -SOCIAL_AUTH_KEYCLOAK_SECRET = env('DD_SOCIAL_AUTH_KEYCLOAK_SECRET') -SOCIAL_AUTH_KEYCLOAK_PUBLIC_KEY = env('DD_SOCIAL_AUTH_KEYCLOAK_PUBLIC_KEY') -SOCIAL_AUTH_KEYCLOAK_AUTHORIZATION_URL = env('DD_SOCIAL_AUTH_KEYCLOAK_AUTHORIZATION_URL') -SOCIAL_AUTH_KEYCLOAK_ACCESS_TOKEN_URL = env('DD_SOCIAL_AUTH_KEYCLOAK_ACCESS_TOKEN_URL') +SOCIAL_AUTH_OIDC_OIDC_ENDPOINT = env('DD_SOCIAL_AUTH_OIDC_OIDC_ENDPOINT') +SOCIAL_AUTH_OIDC_KEY = env('DD_SOCIAL_AUTH_KEYCLOAK_KEY') +SOCIAL_AUTH_OIDC_SECRET = env('DD_SOCIAL_AUTH_KEYCLOAK_SECRET') SOCIAL_AUTH_KEYCLOAK_LOGIN_BUTTON_TEXT = env('DD_SOCIAL_AUTH_KEYCLOAK_LOGIN_BUTTON_TEXT') +KEYCLOAK_TENANT_OAUTH2_GET_GROUPS = env('DD_SOCIAL_AUTH_KEYCLOAK_OAUTH2_GET_GROUPS') +KEYCLOAK_TENANT_OAUTH2_CLEANUP_GROUPS = env('DD_SOCIAL_AUTH_KEYCLOAK_OAUTH2_CLEANUP_GROUPS') +KEYCLOAK_TENANT_OAUTH2_GROUPS_FILTER = env('DD_SOCIAL_AUTH_KEYCLOAK_OAUTH2_GROUPS_FILTER') + +# SOCIAL_AUTH_KEYCLOAK_PUBLIC_KEY = env('DD_SOCIAL_AUTH_KEYCLOAK_PUBLIC_KEY') +# SOCIAL_AUTH_KEYCLOAK_AUTHORIZATION_URL = env('DD_SOCIAL_AUTH_KEYCLOAK_AUTHORIZATION_URL') +# SOCIAL_AUTH_KEYCLOAK_ACCESS_TOKEN_URL = env('DD_SOCIAL_AUTH_KEYCLOAK_ACCESS_TOKEN_URL') + + GITHUB_ENTERPRISE_OAUTH2_ENABLED = env('DD_SOCIAL_AUTH_GITHUB_ENTERPRISE_OAUTH2_ENABLED') SOCIAL_AUTH_GITHUB_ENTERPRISE_URL = env('DD_SOCIAL_AUTH_GITHUB_ENTERPRISE_URL') SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL = env('DD_SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL') diff --git a/dojo/templates/dojo/login.html b/dojo/templates/dojo/login.html index 55da0d7f7d..42ac84368a 100644 --- a/dojo/templates/dojo/login.html +++ b/dojo/templates/dojo/login.html @@ -88,7 +88,7 @@

{% trans "Login" %}

{% if KEYCLOAK_ENABLED is True %}
{% endif %} diff --git a/dojo/user/views.py b/dojo/user/views.py index 7e3d41938b..a3b5c3bf30 100644 --- a/dojo/user/views.py +++ b/dojo/user/views.py @@ -127,7 +127,7 @@ def login_view(request): elif settings.GITLAB_OAUTH2_ENABLED: social_auth = 'gitlab' elif settings.KEYCLOAK_OAUTH2_ENABLED: - social_auth = 'keycloak' + social_auth = 'oidc' elif settings.AUTH0_OAUTH2_ENABLED: social_auth = 'auth0' elif settings.GITHUB_ENTERPRISE_OAUTH2_ENABLED: