From c404c665afbad63c9285ed48a67ede10c79b33cd Mon Sep 17 00:00:00 2001 From: aynsix Date: Mon, 9 Dec 2024 18:16:55 +0300 Subject: [PATCH 1/2] openid add group mapping --- config/configuration.sample.yml | 8 +++ doc/others/openid-sso.md | 23 ++++++-- .../Authentication/Provider/Openid.php | 56 +++++++++++++++---- lib/conf.d/configuration.yml | 8 +++ 4 files changed, 79 insertions(+), 16 deletions(-) diff --git a/config/configuration.sample.yml b/config/configuration.sample.yml index b466b237cf..7e722ea6dd 100644 --- a/config/configuration.sample.yml +++ b/config/configuration.sample.yml @@ -229,6 +229,14 @@ authentication: debug: false auto-logout: false auto-connect-idp-name: null + groupmask: "/phraseanet_([^,]+)/i" + fieldmap: + id: sub + login: email + firstname: given_name + lastname: family_name + email: email + groups: group registration-fields: - name: company diff --git a/doc/others/openid-sso.md b/doc/others/openid-sso.md index 6e8f325c27..5055025a2e 100644 --- a/doc/others/openid-sso.md +++ b/doc/others/openid-sso.md @@ -32,6 +32,14 @@ authentication: # logout with phraseanet and also logout with keycloak auto-logout: true auto-connect-idp-name: null + groupmask: "/cn=phraseanet_([^,]+),cn=users,ou=alchemy$/i" + fieldmap: + id: sub + login: email + firstname: given_name + lastname: family_name + email: email + groups: group ``` @@ -47,16 +55,19 @@ authentication: set the 'Valid post logout redirect URIs' field with `https://{phraseanet-host}/login/logout/` eg: https://phraseanet.phrasea.local/login/logout/ -- Choose a client > client scopes > '.... dedicated' - - add a 'groups' mapper if not exist, > Add mapper > by configuration - +- if not exist create a client scope with mapper type Group Membership `Mapper type` => Group Membership - `Name` => groups - `Token Claim Name` => groups + `Name` => group + `Token Claim Name` => group `Full group path` => off `Add to userinfo` => on +- Add the created client scope to the client + + Choose a client > client scopes > Add client scope > choose the scope + + + #### token expiration - we can define token expiration in keycloak diff --git a/lib/Alchemy/Phrasea/Authentication/Provider/Openid.php b/lib/Alchemy/Phrasea/Authentication/Provider/Openid.php index 89b17cbb95..1aa865136a 100644 --- a/lib/Alchemy/Phrasea/Authentication/Provider/Openid.php +++ b/lib/Alchemy/Phrasea/Authentication/Provider/Openid.php @@ -343,20 +343,26 @@ public function onCallback(Request $request) $this->debug(); - $userName = $data['preferred_username']; + $usegroups = isset($this->config['usegroups']) ? $this->config['usegroups'] : false; + $idKey = isset($this->config['fieldmap']['id']) ? $this->config['fieldmap']['id'] : 'sub'; + $loginKey = isset($this->config['fieldmap']['login']) ? $this->config['fieldmap']['login'] : 'email'; + $firstnameKey = isset($this->config['fieldmap']['firstname']) ? $this->config['fieldmap']['firstname'] : 'given_name'; + $lastnameKey = isset($this->config['fieldmap']['lastname']) ? $this->config['fieldmap']['lastname'] : 'family_name'; + $emailKey = isset($this->config['fieldmap']['email']) ? $this->config['fieldmap']['email'] : 'email'; + $groupsKey = isset($this->config['fieldmap']['groups']) ? $this->config['fieldmap']['groups'] : 'groups'; + $distantUserId = $data['sub']; - if (!\Swift_Validate::email($userName) && isset($data['email'])) { - $userName = $data['email'];// login to be an email + if (!\Swift_Validate::email($data[$loginKey]) && isset($data['email'])) { + $loginKey = 'email';// login to be an email } - $usegroups = isset($this->config['usegroups']) ? $this->config['usegroups'] : false; $userUA = $this->CreateUser([ - 'id' => $distantUserId = $data['sub'], - 'login' => $userName, - 'firstname' => isset($data['given_name']) ? $data['given_name'] : '', - 'lastname' => isset($data['family_name']) ? $data['family_name'] : '' , - 'email' => isset($data['email']) ? $data['email'] : '', - '_groups' => isset($data['groups']) && $usegroups ? $data['groups'] : '' + 'id' => $data[$idKey], + 'login' => $userName = $data[$loginKey], + 'firstname' => isset($data[$firstnameKey]) ? $data[$firstnameKey] : '', + 'lastname' => isset($data[$lastnameKey]) ? $data[$lastnameKey] : '' , + 'email' => isset($data[$emailKey]) ? $data[$emailKey] : '', + '_groups' => isset($data[$groupsKey]) && $usegroups ? $this->filterGroups($data[$groupsKey]) : '' ]); $userAuthProviderRepository = $this->getUsrAuthProviderRepository(); @@ -715,6 +721,36 @@ private function CreateUser(Array $data) return $ret; } + private function filterGroups($groups) + { + $this->debug(sprintf("filtering openid groups :\n%s", print_r($groups, true))); + + $ret = []; + if ($this->config['groupmask']) { + $this->debug(sprintf("filtering groups with regexp : \"%s\"", $this->config['groupmask'])); + foreach ($groups as $grp) { + $matches = []; + $retpreg = preg_match_all($this->config['groupmask'], $grp, $matches, PREG_SET_ORDER); + + $this->debug(sprintf("preg_match('%s', '%s', ...)\n - returned %s \n - matches = %s " + , $this->config['groupmask'], $grp + , print_r($retpreg, true), print_r($matches, true))); + + foreach ($matches as $match) { + if (count($match)>0 && isset($match[1]) && !array_key_exists($match[1], $ret)) { + $ret[] = $match[1]; + } + } + } + } else { + $this->debug(sprintf("no groupmask defined, openid groups ignored")); + } + + $this->debug(sprintf("filtered groups :\n%s", print_r($ret, true))); + + return empty($ret) ? '' : $ret ; + } + /** diff --git a/lib/conf.d/configuration.yml b/lib/conf.d/configuration.yml index ba8aa39090..7f06e1a99b 100644 --- a/lib/conf.d/configuration.yml +++ b/lib/conf.d/configuration.yml @@ -246,6 +246,14 @@ authentication: debug: false auto-logout: false auto-connect-idp-name: null + groupmask: "/phraseanet_([^,]+)/i" + fieldmap: + id: sub + login: email + firstname: given_name + lastname: family_name + email: email + groups: group registration-fields: - name: company From baa0b17b65155e097bf70c21d8376c1efec71894 Mon Sep 17 00:00:00 2001 From: aynsix Date: Tue, 10 Dec 2024 18:16:06 +0300 Subject: [PATCH 2/2] add patch --- lib/classes/patch/4111PHRAS4106.php | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/classes/patch/4111PHRAS4106.php b/lib/classes/patch/4111PHRAS4106.php index 040c6cb594..592971c2bf 100644 --- a/lib/classes/patch/4111PHRAS4106.php +++ b/lib/classes/patch/4111PHRAS4106.php @@ -50,13 +50,36 @@ public function apply(base $appbox, Application $app) $conf = $app['conf']; foreach ($app['conf']->get(['authentication', 'providers'], []) as $providerId => $data) { if ($data['type'] === "openid") { - if(!isset($data['options']['usegroups'])) { + if (!isset($data['options']['usegroups'])) { $data['options']['usegroups'] = false; $providerConfig[$providerId] = $data; $conf->merge(['authentication', 'providers'], $providerConfig); } + + if (!isset($data['options']['fieldmap'])) { + $data['options']['fieldmap'] = [ + 'id' => 'sub', + 'login' => 'email', + 'firstname' => 'given_name', + 'lastname' => 'family_name', + 'email' => 'email', + 'groups' => 'group', + ]; + + $providerConfig[$providerId] = $data; + + $conf->merge(['authentication', 'providers'], $providerConfig); + } + + if (!isset($data['options']['groupmask'])) { + $data['options']['groupmask'] = "/phraseanet_([^,]+)/i"; + + $providerConfig[$providerId] = $data; + + $conf->merge(['authentication', 'providers'], $providerConfig); + } } }