Skip to content

Commit 32aa1db

Browse files
authored
PHRAS-4108 openid : add claims mapping and groups filtering (#4563)
* openid add group mapping * add migration patch for configuration injection
1 parent 4e9414b commit 32aa1db

File tree

5 files changed

+103
-17
lines changed

5 files changed

+103
-17
lines changed

config/configuration.sample.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,14 @@ authentication:
229229
debug: false
230230
auto-logout: false
231231
auto-connect-idp-name: null
232+
groupmask: "/phraseanet_([^,]+)/i"
233+
fieldmap:
234+
id: sub
235+
login: email
236+
firstname: given_name
237+
lastname: family_name
238+
email: email
239+
groups: group
232240
registration-fields:
233241
-
234242
name: company

doc/others/openid-sso.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ authentication:
3232
# logout with phraseanet and also logout with keycloak
3333
auto-logout: true
3434
auto-connect-idp-name: null
35+
groupmask: "/cn=phraseanet_([^,]+),cn=users,ou=alchemy$/i"
36+
fieldmap:
37+
id: sub
38+
login: email
39+
firstname: given_name
40+
lastname: family_name
41+
email: email
42+
groups: group
3543

3644
```
3745

@@ -47,16 +55,19 @@ authentication:
4755

4856
set the 'Valid post logout redirect URIs' field with `https://{phraseanet-host}/login/logout/` eg: https://phraseanet.phrasea.local/login/logout/
4957

50-
- Choose a client > client scopes > '.... dedicated'
51-
52-
add a 'groups' mapper if not exist, > Add mapper > by configuration
53-
58+
- if not exist create a client scope with mapper type Group Membership
5459
`Mapper type` => Group Membership
55-
`Name` => groups
56-
`Token Claim Name` => groups
60+
`Name` => group
61+
`Token Claim Name` => group
5762
`Full group path` => off
5863
`Add to userinfo` => on
5964

65+
- Add the created client scope to the client
66+
67+
Choose a client > client scopes > Add client scope > choose the scope
68+
69+
70+
6071
#### token expiration
6172
- we can define token expiration in keycloak
6273

lib/Alchemy/Phrasea/Authentication/Provider/Openid.php

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -343,20 +343,26 @@ public function onCallback(Request $request)
343343

344344
$this->debug();
345345

346-
$userName = $data['preferred_username'];
346+
$usegroups = isset($this->config['usegroups']) ? $this->config['usegroups'] : false;
347+
$idKey = isset($this->config['fieldmap']['id']) ? $this->config['fieldmap']['id'] : 'sub';
348+
$loginKey = isset($this->config['fieldmap']['login']) ? $this->config['fieldmap']['login'] : 'email';
349+
$firstnameKey = isset($this->config['fieldmap']['firstname']) ? $this->config['fieldmap']['firstname'] : 'given_name';
350+
$lastnameKey = isset($this->config['fieldmap']['lastname']) ? $this->config['fieldmap']['lastname'] : 'family_name';
351+
$emailKey = isset($this->config['fieldmap']['email']) ? $this->config['fieldmap']['email'] : 'email';
352+
$groupsKey = isset($this->config['fieldmap']['groups']) ? $this->config['fieldmap']['groups'] : 'groups';
353+
$distantUserId = $data['sub'];
347354

348-
if (!\Swift_Validate::email($userName) && isset($data['email'])) {
349-
$userName = $data['email'];// login to be an email
355+
if (!\Swift_Validate::email($data[$loginKey]) && isset($data['email'])) {
356+
$loginKey = 'email';// login to be an email
350357
}
351358

352-
$usegroups = isset($this->config['usegroups']) ? $this->config['usegroups'] : false;
353359
$userUA = $this->CreateUser([
354-
'id' => $distantUserId = $data['sub'],
355-
'login' => $userName,
356-
'firstname' => isset($data['given_name']) ? $data['given_name'] : '',
357-
'lastname' => isset($data['family_name']) ? $data['family_name'] : '' ,
358-
'email' => isset($data['email']) ? $data['email'] : '',
359-
'_groups' => isset($data['groups']) && $usegroups ? $data['groups'] : ''
360+
'id' => $data[$idKey],
361+
'login' => $userName = $data[$loginKey],
362+
'firstname' => isset($data[$firstnameKey]) ? $data[$firstnameKey] : '',
363+
'lastname' => isset($data[$lastnameKey]) ? $data[$lastnameKey] : '' ,
364+
'email' => isset($data[$emailKey]) ? $data[$emailKey] : '',
365+
'_groups' => isset($data[$groupsKey]) && $usegroups ? $this->filterGroups($data[$groupsKey]) : ''
360366
]);
361367

362368
$userAuthProviderRepository = $this->getUsrAuthProviderRepository();
@@ -715,6 +721,36 @@ private function CreateUser(Array $data)
715721
return $ret;
716722
}
717723

724+
private function filterGroups($groups)
725+
{
726+
$this->debug(sprintf("filtering openid groups :\n%s", print_r($groups, true)));
727+
728+
$ret = [];
729+
if ($this->config['groupmask']) {
730+
$this->debug(sprintf("filtering groups with regexp : \"%s\"", $this->config['groupmask']));
731+
foreach ($groups as $grp) {
732+
$matches = [];
733+
$retpreg = preg_match_all($this->config['groupmask'], $grp, $matches, PREG_SET_ORDER);
734+
735+
$this->debug(sprintf("preg_match('%s', '%s', ...)\n - returned %s \n - matches = %s "
736+
, $this->config['groupmask'], $grp
737+
, print_r($retpreg, true), print_r($matches, true)));
738+
739+
foreach ($matches as $match) {
740+
if (count($match)>0 && isset($match[1]) && !array_key_exists($match[1], $ret)) {
741+
$ret[] = $match[1];
742+
}
743+
}
744+
}
745+
} else {
746+
$this->debug(sprintf("no groupmask defined, openid groups ignored"));
747+
}
748+
749+
$this->debug(sprintf("filtered groups :\n%s", print_r($ret, true)));
750+
751+
return empty($ret) ? '' : $ret ;
752+
}
753+
718754

719755

720756
/**

lib/classes/patch/4111PHRAS4106.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,36 @@ public function apply(base $appbox, Application $app)
5050
$conf = $app['conf'];
5151
foreach ($app['conf']->get(['authentication', 'providers'], []) as $providerId => $data) {
5252
if ($data['type'] === "openid") {
53-
if(!isset($data['options']['usegroups'])) {
53+
if (!isset($data['options']['usegroups'])) {
5454
$data['options']['usegroups'] = false;
5555

5656
$providerConfig[$providerId] = $data;
5757

5858
$conf->merge(['authentication', 'providers'], $providerConfig);
5959
}
60+
61+
if (!isset($data['options']['fieldmap'])) {
62+
$data['options']['fieldmap'] = [
63+
'id' => 'sub',
64+
'login' => 'email',
65+
'firstname' => 'given_name',
66+
'lastname' => 'family_name',
67+
'email' => 'email',
68+
'groups' => 'group',
69+
];
70+
71+
$providerConfig[$providerId] = $data;
72+
73+
$conf->merge(['authentication', 'providers'], $providerConfig);
74+
}
75+
76+
if (!isset($data['options']['groupmask'])) {
77+
$data['options']['groupmask'] = "/phraseanet_([^,]+)/i";
78+
79+
$providerConfig[$providerId] = $data;
80+
81+
$conf->merge(['authentication', 'providers'], $providerConfig);
82+
}
6083
}
6184
}
6285

lib/conf.d/configuration.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,14 @@ authentication:
246246
debug: false
247247
auto-logout: false
248248
auto-connect-idp-name: null
249+
groupmask: "/phraseanet_([^,]+)/i"
250+
fieldmap:
251+
id: sub
252+
login: email
253+
firstname: given_name
254+
lastname: family_name
255+
email: email
256+
groups: group
249257
registration-fields:
250258
-
251259
name: company

0 commit comments

Comments
 (0)