From 9d780d7322819b31a49c9d6c7c73b3c35f958330 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 9 Apr 2026 19:45:54 +0200 Subject: [PATCH 1/5] Removed superfluous OIDC client --- .../sync/openid_connect.client.windows_aad.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 config/sync/openid_connect.client.windows_aad.yml diff --git a/config/sync/openid_connect.client.windows_aad.yml b/config/sync/openid_connect.client.windows_aad.yml deleted file mode 100644 index 471e77834..000000000 --- a/config/sync/openid_connect.client.windows_aad.yml +++ /dev/null @@ -1,18 +0,0 @@ -uuid: 7a551b1d-f957-45bc-a54e-11d227cce0d8 -langcode: en -status: false -dependencies: { } -id: windows_aad -label: windows_aad -plugin: generic -settings: - client_id: "file:///settings.local.php#$config['openid_connect.client.windows_aad']['settings']['client_id']" - client_secret: "file:///settings.local.php#$config['openid_connect.client.windows_aad']['settings']['client_secret']" - issuer_url: '' - authorization_endpoint: "file:///settings.local.php#$config['openid_connect.client.windows_aad]['settings']['client_id']" - token_endpoint: "file:///settings.local.php#$config['openid_connect.client.windows_aad]['settings'][token_endpoint']" - userinfo_endpoint: '' - end_session_endpoint: '' - scopes: - - openid - - email From b44bc7b8a1c6d8b41dffc704d50a6cb2ddf99a4c Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 9 Apr 2026 21:13:25 +0200 Subject: [PATCH 2/5] Updated OIDC module and development setup # Conflicts: # composer.lock --- config/sync/openid_connect.client.generic.yml | 9 +- config/sync/openid_connect.settings.yml | 8 +- docker-compose.oidc.yml | 34 +++++ docker-compose.override.yml | 123 +----------------- 4 files changed, 47 insertions(+), 127 deletions(-) create mode 100644 docker-compose.oidc.yml diff --git a/config/sync/openid_connect.client.generic.yml b/config/sync/openid_connect.client.generic.yml index b1f0647a7..5d4afb4c2 100644 --- a/config/sync/openid_connect.client.generic.yml +++ b/config/sync/openid_connect.client.generic.yml @@ -9,11 +9,14 @@ settings: client_id: client-id client_secret: '[client-secret]' iss_allowed_domains: '' + prompt: + - login issuer_url: '' - authorization_endpoint: 'https://idp-citizen.os2loop.local.itkdev.dk/connect/authorize' - token_endpoint: 'https://idp-citizen.os2loop.local.itkdev.dk/connect/token' + authorization_endpoint: 'https://idp-employee.os2loop.local.itkdev.dk/oauth2/authorize' + # This URL makes sense only for the phpfpm service. + token_endpoint: 'http://idp-employee:9400/oauth2/token' userinfo_endpoint: '' - end_session_endpoint: 'https://idp-citizen.os2loop.local.itkdev.dk/connect/endsession' + end_session_endpoint: 'https://idp-employee.os2loop.local.itkdev.dk/oauth2/end_session' scopes: - openid - email diff --git a/config/sync/openid_connect.settings.yml b/config/sync/openid_connect.settings.yml index 3f0a95e0d..d60a88b58 100644 --- a/config/sync/openid_connect.settings.yml +++ b/config/sync/openid_connect.settings.yml @@ -2,13 +2,12 @@ always_save_userinfo: true connect_existing_users: true override_registration_settings: true end_session_enabled: true -user_login_display: above +user_login_display: replace redirect_login: /user redirect_logout: / userinfo_mappings: - timezone: zoneinfo - os2loop_user_family_name: family_name - os2loop_user_given_name: given_name + os2loop_user_city: family_name + os2loop_user_external_list: given_name role_mappings: os2loop_user_administrator: - administrator @@ -28,3 +27,4 @@ role_mappings: - post_author os2loop_user_user_administrator: - user_administrator +autostart_login: true diff --git a/docker-compose.oidc.yml b/docker-compose.oidc.yml new file mode 100644 index 000000000..86d2fff74 --- /dev/null +++ b/docker-compose.oidc.yml @@ -0,0 +1,34 @@ +services: + idp-employee: + image: ghcr.io/geigerzaehler/oidc-provider-mock:latest + networks: + - app + - frontend + labels: + - "traefik.enable=true" + - "traefik.docker.network=frontend" + - "traefik.http.routers.idp-employee_${COMPOSE_PROJECT_NAME:?}.rule=Host(`idp-employee.${COMPOSE_DOMAIN:?}`)" + - "traefik.http.services.idp-employee_${COMPOSE_PROJECT_NAME:?}.loadbalancer.server.port=9400" + command: + [ + "--user-claims", + '{"sub": "user", "email": "user@example.com", "groups": ["authenticated"]}', + "--user-claims", + '{"sub": "administrator", "email": "administrator@example.com", "groups": ["os2loop_user_administrator"]}', + "--user-claims", + '{"sub": "user_administrator", "email": "user_administrator@example.com", "groups": ["os2loop_user_user_administrator"]}', + "--user-claims", + '{"sub": "manager", "email": "manager@example.com", "groups": ["os2loop_user_manager"]}', + "--user-claims", + '{"sub": "documentation_coordinator", "email": "documentation_coordinator@example.com", "groups": ["os2loop_user_documentation_coordinator"]}', + "--user-claims", + '{"sub": "document_collection_editor", "email": "document_collection_editor@example.com", "groups": ["os2loop_user_document_collection_editor"]}', + "--user-claims", + '{"sub": "document_author", "email": "document_author@example.com", "groups": ["os2loop_user_document_author"]}', + "--user-claims", + '{"sub": "external_sources_editor", "email": "external_sources_editor@example.com", "groups": ["os2loop_user_external_sources_editor"]}', + "--user-claims", + '{"sub": "post_author", "email": "post_author@example.com", "groups": ["os2loop_user_post_author"]}', + "--user-claims", + '{"sub": "read_only", "email": "read_only@example.com", "groups": ["os2loop_user_read_only"]}', + ] diff --git a/docker-compose.override.yml b/docker-compose.override.yml index d602498b8..8a6fc4a59 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -1,3 +1,6 @@ +include: + - docker-compose.oidc.yml + services: node: image: node:20 @@ -16,123 +19,3 @@ services: environment: # Match PHP_MAX_EXECUTION_TIME above - NGINX_FASTCGI_READ_TIMEOUT=300 - - idp-citizen: - image: ghcr.io/soluto/oidc-server-mock:0.8.6 - profiles: - - oidc - - test - # Let this container be accessible both internally and externally on the same domain. - container_name: idp-citizen.${COMPOSE_DOMAIN} - networks: - - app - - frontend - ports: - # https://github.com/Soluto/oidc-server-mock?tab=readme-ov-file#https - # - '80' - - "443" - volumes: - - .:/tmp/config:ro - labels: - - "traefik.enable=true" - - "traefik.docker.network=frontend" - - "traefik.http.routers.${COMPOSE_PROJECT_NAME}_idp-citizen.rule=Host(`idp-citizen.${COMPOSE_DOMAIN}`)" - - "traefik.http.services.${COMPOSE_PROJECT_NAME}_idp-citizen.loadbalancer.server.port=443" - - "traefik.http.services.${COMPOSE_PROJECT_NAME}_idp-citizen.loadbalancer.server.scheme=https" - - "traefik.http.routers.${COMPOSE_PROJECT_NAME}_idp-citizen.middlewares=redirect-to-https" - - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - - environment: - # https://github.com/Soluto/oidc-server-mock?tab=readme-ov-file#https - ASPNETCORE_URLS: https://+:443;http://+:80 - ASPNETCORE_Kestrel__Certificates__Default__Password: mock - ASPNETCORE_Kestrel__Certificates__Default__Path: /tmp/config/.docker/oidc-server-mock/cert/docker.pfx - - ASPNETCORE_ENVIRONMENT: Development - SERVER_OPTIONS_INLINE: | - AccessTokenJwtType: JWT - Discovery: - ShowKeySet: true - Authentication: - CookieSameSiteMode: Lax - CheckSessionCookieSameSiteMode: Lax - - LOGIN_OPTIONS_INLINE: | - { - "AllowRememberLogin": false - } - - LOGOUT_OPTIONS_INLINE: | - { - "AutomaticRedirectAfterSignOut": true - } - - CLIENTS_CONFIGURATION_INLINE: | - - ClientId: client-id - ClientSecrets: [client-secret] - Description: Mock IdP - AllowedGrantTypes: - # - client_credentials - # - implicit - - authorization_code - # https://github.com/Soluto/oidc-server-mock/issues/46#issuecomment-704963181 - RequireClientSecret: false - AllowAccessTokensViaBrowser: true - # https://github.com/Soluto/oidc-server-mock/issues/26#issuecomment-705022941 - AlwaysIncludeUserClaimsInIdToken: true - AllowedScopes: - - openid - - profile - - email - ClientClaimsPrefix: '' - RedirectUris: - - '*' - # https://github.com/Soluto/oidc-server-mock/issues/60 - PostLogoutRedirectUris: - - '*' - # https://github.com/Soluto/oidc-server-mock/issues/46#issuecomment-704845375 - RequirePkce: false - - # Needed to set custom claim types in "profile" - # https://github.com/Soluto/oidc-server-mock/issues/123#issuecomment-1427129278 - # https://github.com/Soluto/oidc-server-mock/blob/master/README.md#simple-configuration - # https://docs.docker.com/compose/compose-file/compose-file-v3/#environment - OVERRIDE_STANDARD_IDENTITY_RESOURCES: "true" - IDENTITY_RESOURCES_INLINE: | - # https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes#standard-claims - - Name: openid - ClaimTypes: - - sub - - Name: email - ClaimTypes: - - email - - Name: profile - ClaimTypes: - # Add your custom claims here - - dk_ssn - - name - - email - - zip - - uuid - - USERS_CONFIGURATION_INLINE: | - - SubjectId: 1 - Username: citizen1 - Password: citizen1 - Claims: - # Claims added here must be defined above in IDENTITY_RESOURCES_INLINE - - Type: dk_ssn - Value: '1111111111' - ValueType: string - - Type: name - Value: 'Anders And' - ValueType: string - - Type: email - Value: admin@example.com - ValueType: string - - Type: zip - Value: '1111' - ValueType: string - - Type: uuid - Value: '11111111-1111-1111-1111-111111111111' - ValueType: string From ddf6764f41438450cf2aab7f44ff7af8fdfac9a7 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 9 Apr 2026 21:13:48 +0200 Subject: [PATCH 3/5] Cleaned up OIDC settings --- .../modules/os2loop_user_login/README.md | 16 +++--- .../os2loop_user_login.module | 10 ---- .../src/Form/SettingsForm.php | 41 +++++++++------- .../os2loop_user_login/src/Helper/Helper.php | 49 ++++++------------- 4 files changed, 44 insertions(+), 72 deletions(-) diff --git a/web/profiles/custom/os2loop/modules/os2loop_user_login/README.md b/web/profiles/custom/os2loop/modules/os2loop_user_login/README.md index 18e8c4b55..52f624f4a 100644 --- a/web/profiles/custom/os2loop/modules/os2loop_user_login/README.md +++ b/web/profiles/custom/os2loop/modules/os2loop_user_login/README.md @@ -7,14 +7,10 @@ Go to Administration › Configuration › OS2Loop › OS2Loop user login settin ## OpenID Connect -The modules [OpenID Connect](https://www.drupal.org/project/openid_connect) and -[OpenID Connect Microsoft Azure Active Directory -client](https://www.drupal.org/project/openid_connect_windows_aad) are used for -OpenID Connect login. *Note*: Eventhough it's called “OpenID Connect Microsoft -Azure Active Directory client” it also work with other OpenID Connect identity -providers. - -In the default configuration both login methods assume that the identitity +The module [OpenID Connect](https://www.drupal.org/project/openid_connect) is +used for OpenID Connect login. + +In the default configuration the login method assumes that the identitity provider returns a `name` claim which is used as the Drupal user name and that a `groups` claim is a list of groups that can be mapped to Drupal roles. @@ -83,12 +79,16 @@ $config['openid_connect.client.generic']['settings']['authorization_endpoint'] = $config['openid_connect.client.generic']['settings']['token_endpoint'] = …; // Get this from your OpenID Connect Discovery endpoint // Optional $config['openid_connect.client.generic']['settings']['end_session_endpoint'] = …; // Get this from your OpenID Connect Discovery endpoint + +// Disable "Autostart login process" +$config['openid_connect.settings']['autostart_login'] = false; ``` Check your overwrites by running ```sh vendor/bin/drush config:get --include-overridden openid_connect.client.generic +vendor/bin/drush config:get --include-overridden openid_connect.settings ``` #### Groups to roles mapping diff --git a/web/profiles/custom/os2loop/modules/os2loop_user_login/os2loop_user_login.module b/web/profiles/custom/os2loop/modules/os2loop_user_login/os2loop_user_login.module index 9f700a40d..578693d48 100644 --- a/web/profiles/custom/os2loop/modules/os2loop_user_login/os2loop_user_login.module +++ b/web/profiles/custom/os2loop/modules/os2loop_user_login/os2loop_user_login.module @@ -5,18 +5,8 @@ * The module file for os2loop_user_login. */ -use Drupal\Core\Form\FormStateInterface; use Drupal\user\UserInterface; -/** - * Implements hook_form_alter(). - * - * @see \Drupal\os2loop_user_login\Helper\Helper::alterForm() - */ -function os2loop_user_login_form_alter(&$form, FormStateInterface $form_state, $form_id) { - Drupal::service('os2loop_user_login.helper')->alterForm($form, $form_state, $form_id); -} - /** * Implements hook_menu_local_tasks_alter(). * diff --git a/web/profiles/custom/os2loop/modules/os2loop_user_login/src/Form/SettingsForm.php b/web/profiles/custom/os2loop/modules/os2loop_user_login/src/Form/SettingsForm.php index 5dd65143b..96942ef5c 100644 --- a/web/profiles/custom/os2loop/modules/os2loop_user_login/src/Form/SettingsForm.php +++ b/web/profiles/custom/os2loop/modules/os2loop_user_login/src/Form/SettingsForm.php @@ -73,37 +73,43 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['show_drupal_login'] = [ '#type' => 'checkbox', '#title' => $this->t('Show Drupal login'), - '#default_value' => $config->get('show_drupal_login'), + '#default_value' => FALSE, + '#disabled' => TRUE, '#description' => $this->t( - 'Show Drupal (username and password) login on user login page. If not enabled, the login form will still be visible if #drupal-login is appended to the url (@login_url).', + 'This option has been removed. This is now controlled by the "@config_title" setting in the OpenID Connect settings.', [ - '@login_url' => Url::fromRoute('user.login', [], [ - 'absolute' => TRUE, - 'fragment' => 'drupal-login', - ])->toString(), - ]), + '@config_title' => $this->t('OpenID buttons display in user login form'), + ':config_url' => Url::fromRoute('openid_connect.admin_settings')->toString(), + ], + ), ]; $form['show_oidc_login'] = [ '#type' => 'checkbox', '#title' => $this->t('Show OpenID Connect login'), - '#default_value' => $config->get('show_oidc_login'), + '#default_value' => FALSE, + '#disabled' => TRUE, '#description' => $this->t( - 'Show OpenID Connect login button on user login page. Set up proper OpenID Connect configuration before enabling this.', + 'This option has been removed. This is now controlled by the "@config_title" setting in the OpenID Connect settings.', [ - '@config_url' => Url::fromRoute('openid_connect.admin_settings')->toString(), - ] + '@config_title' => $this->t('OpenID buttons display in user login form'), + ':config_url' => Url::fromRoute('openid_connect.admin_settings')->toString(), + ], ), ]; - $options['oidc'] = $this->t('OpenID Connect'); $form['default_login_method'] = [ '#type' => 'select', '#title' => $this->t('Default login method'), - '#options' => $options, - '#empty_value' => '', - '#default_value' => $config->get('default_login_method'), - '#description' => $this->t('The default login method to use. If specified, anonymous users will automatically be logged in with this method.'), + '#default_value' => FALSE, + '#disabled' => TRUE, + '#description' => $this->t( + 'This option has been removed. This is now controlled by the "@config_title" setting in the OpenID Connect settings.', + [ + '@config_title' => $this->t('Autostart login process'), + ':config_url' => Url::fromRoute('openid_connect.admin_settings')->toString(), + ], + ), ]; $form['hide_logout_menu_item'] = [ @@ -121,9 +127,6 @@ public function buildForm(array $form, FormStateInterface $form_state) { */ public function submitForm(array &$form, FormStateInterface $form_state) { $this->configFactory->getEditable(static::SETTINGS_NAME) - ->set('show_drupal_login', $form_state->getValue('show_drupal_login')) - ->set('show_oidc_login', $form_state->getValue('show_oidc_login')) - ->set('default_login_method', $form_state->getValue('default_login_method')) ->set('hide_logout_menu_item', $form_state->getValue('hide_logout_menu_item')) ->save(); diff --git a/web/profiles/custom/os2loop/modules/os2loop_user_login/src/Helper/Helper.php b/web/profiles/custom/os2loop/modules/os2loop_user_login/src/Helper/Helper.php index 20d81f120..2fd7cafc2 100644 --- a/web/profiles/custom/os2loop/modules/os2loop_user_login/src/Helper/Helper.php +++ b/web/profiles/custom/os2loop/modules/os2loop_user_login/src/Helper/Helper.php @@ -5,7 +5,6 @@ use Drupal\Core\Entity\EntityFieldManager; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Path\CurrentPathStack; use Drupal\Core\Session\AccountInterface; @@ -28,6 +27,13 @@ class Helper { */ private $config; + /** + * The OpenID Connect config. + * + * @var \Drupal\Core\Config\ImmutableConfig + */ + private $openIdConnectConfig; + /** * The entity type manager. * @@ -75,6 +81,7 @@ class Helper { */ public function __construct(Settings $settings, ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, EntityFieldManager $entity_field_manager, MessengerInterface $messenger, RequestStack $requestStack, CurrentPathStack $currentPathStack) { $this->config = $settings->getConfig(SettingsForm::SETTINGS_NAME); + $this->openIdConnectConfig = $settings->getConfig('openid_connect.settings'); $this->moduleHandler = $module_handler; $this->entityTypeManager = $entity_type_manager; $this->entityFieldManager = $entity_field_manager; @@ -83,39 +90,6 @@ public function __construct(Settings $settings, ModuleHandlerInterface $module_h $this->currentPathStack = $currentPathStack; } - /** - * Implements hook_form_alter(). - * - * Show different login options depending on the site configuration. - */ - public function alterForm(&$form, FormStateInterface $form_state, $form_id) { - if ('openid_connect_login_form' === $form_id) { - if (!$this->config->get('show_oidc_login')) { - $form['#access'] = FALSE; - } - } - elseif ('user_login_form' === $form_id) { - if (!$this->config->get('show_drupal_login')) { - $form['#attached']['library'][] = 'os2loop_user_login/user-login-form'; - - // Wrap default Drupal login form in an element with a known id - // (drupal-login) so we can visually hide it. - foreach ($form as $key => $value) { - if (0 !== strpos($key, '#')) { - $form['drupal_login'][$key] = array_merge($value); - unset($form[$key]); - } - } - $form['drupal_login'] += [ - '#type' => 'fieldset', - '#title' => $this->t('Drupal login'), - '#weight' => 100, - '#attributes' => ['id' => 'drupal-login'], - ]; - } - } - } - /** * Implements hook_preprocess_block(). */ @@ -138,7 +112,12 @@ public function preprocessBlock(array &$variables) { return; } - $defaultLoginMethod = $this->config->get('default_login_method'); + // The OpenID Connect module's "Autostart login process" triggers only on + // login, register or password reset pages. We need to trigger it in the + // userlogin block as well and use JavaScript to submit the (OIDC) login + // form if found on the page + // (cf. @os2loop_theme/templates/block/block--user-login-block.html.twig). + $defaultLoginMethod = TRUE === $this->openIdConnectConfig->get('autostart_login') ? 'oidc' : NULL; switch ($defaultLoginMethod) { case 'oidc': $variables['default_login_form_id'] = 'openid-connect-login-form'; From ed1d09b4b332231b06595bd1a0f4aa6610a74273 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 9 Apr 2026 21:24:24 +0200 Subject: [PATCH 4/5] Updated changelog ^ Conflicts: ^ CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64a4b5676..4ebeda9ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- [PR-385](https://github.com/itk-dev/os2loop/pull/385) + Cleaned up OpenID Connect settings and removed some obsolete custom settings - [PR-384](https://github.com/itk-dev/os2loop/pull/384) Set access permission on search view - [PR-383](https://github.com/itk-dev/os2loop/pull/383) From cd967c39175aea970fe197c61a87249908cca82f Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 10 Apr 2026 09:37:56 +0200 Subject: [PATCH 5/5] Updated config --- config/sync/openid_connect.client.generic.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/config/sync/openid_connect.client.generic.yml b/config/sync/openid_connect.client.generic.yml index 5d4afb4c2..d31117759 100644 --- a/config/sync/openid_connect.client.generic.yml +++ b/config/sync/openid_connect.client.generic.yml @@ -9,14 +9,13 @@ settings: client_id: client-id client_secret: '[client-secret]' iss_allowed_domains: '' - prompt: - - login issuer_url: '' authorization_endpoint: 'https://idp-employee.os2loop.local.itkdev.dk/oauth2/authorize' - # This URL makes sense only for the phpfpm service. token_endpoint: 'http://idp-employee:9400/oauth2/token' userinfo_endpoint: '' end_session_endpoint: 'https://idp-employee.os2loop.local.itkdev.dk/oauth2/end_session' scopes: - openid - email + prompt: + - login