Skip to content

Commit ea74e6d

Browse files
committed
feat: add oidc support
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
1 parent d9b1f3a commit ea74e6d

File tree

6 files changed

+115
-12
lines changed

6 files changed

+115
-12
lines changed

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Config.php parameters to operate the server in master mode:
3939
// The user disovery module might require additional config paramters you can find in
4040
// the documentation of the module
4141
'gss.user.discovery.module' => '\OCA\GlobalSiteSelector\UserDiscoveryModules\UserDiscoverySAML',
42+
// or 'gss.user.discovery.module' => '\OCA\GlobalSiteSelector\UserDiscoveryModules\UserDiscoveryOIDC'
4243
4344
// define a allow list for automatic login to other instance to let browsers handle the redirect properly
4445
'gss.master.csp-allow' => ['*.myorg.com', 'node3.otherorg.com'],
@@ -80,13 +81,23 @@ specific use case:
8081

8182
#### UserDiscoverySAML
8283

83-
This modules reads the location directly from a parameter of the IDP which contain
84-
the exact URL to the server. The name of the parameter can be defined this way:
84+
This modules reads the location directly from a parameter of the IDP which contains
85+
the exact URL to the slave target server. The name of the parameter can be defined this way:
8586

8687
````
8788
'gss.discovery.saml.slave.mapping' => 'idp-parameter'
8889
````
8990

91+
#### UserDiscoveryOIDC
92+
93+
This module is similar to UserDiscoverySAML.
94+
It reads the location from an OIDC token attribute which contains
95+
the exact URL to the slave target server. The attribute can be defined this way:
96+
97+
````
98+
'gss.discovery.oidc.slave.mapping' => 'token-attribute'
99+
````
100+
90101
#### ManualUserMapping
91102

92103
This allows you to maintain a custom json file which maps a specific key word

lib/Controller/MasterController.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,22 @@ public function autoLogout(?string $jwt) {
6767
if ($jwt !== null) {
6868
$key = $this->gss->getJwtKey();
6969
$decoded = (array)JWT::decode($jwt, new Key($key, Application::JWT_ALGORITHM));
70-
$idp = $decoded['saml.idp'] ?? null;
7170

72-
$logoutUrl = $this->urlGenerator->linkToRoute('user_saml.SAML.singleLogoutService');
71+
// saml idp ID
72+
$samlIdp = $decoded['saml.idp'] ?? null;
73+
// oidc provider ID
74+
$oidcProviderId = $decoded['oidc.providerId'] ?? '';
75+
76+
if (class_exists('\OCA\User_SAML\UserBackend')) {
77+
$logoutUrl = $this->urlGenerator->linkToRoute('user_saml.SAML.singleLogoutService');
78+
} elseif (class_exists('\OCA\UserOIDC\User\Backend')) {
79+
$logoutUrl = $this->urlGenerator->linkToRoute('user_oidc.login.singleLogoutService');
80+
}
7381
if (!empty($logoutUrl)) {
7482
$token = [
7583
'logout' => 'logout',
76-
'idp' => $idp,
84+
'idp' => $samlIdp,
85+
'oidcProviderId' => $oidcProviderId,
7786
'exp' => time() + 300, // expires after 5 minutes
7887
];
7988

lib/Controller/SlaveController.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ public function autoLogin(string $jwt): RedirectResponse {
9090
$this->logger->debug('uid: ' . $uid . ', options: ' . json_encode($options));
9191

9292
$target = $options['target'];
93-
if (($options['backend'] ?? '') === 'saml') {
94-
$this->logger->debug('saml enabled');
93+
$backend = $options['backend'] ?? '';
94+
if ($backend === 'saml' || $backend === 'oidc') {
95+
$this->logger->debug('saml or oidc enabled: ' . $backend);
9596
$this->autoprovisionIfNeeded($uid, $options);
9697

9798
$user = $this->userManager->get($uid);
@@ -108,6 +109,12 @@ public function autoLogin(string $jwt): RedirectResponse {
108109
Slave::SAML_IDP,
109110
$options['saml']['idp'] ?? null
110111
);
112+
$this->config->setUserValue(
113+
$user->getUID(),
114+
Application::APP_ID,
115+
Slave::OIDC_PROVIDER_ID,
116+
$options['oidc']['providerId'] ?? ''
117+
);
111118

112119
$result = true;
113120
} else {

lib/Master.php

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,10 @@ public function handleLoginRequest(
104104
$userDiscoveryModule = $this->config->getSystemValueString('gss.user.discovery.module', '');
105105
$this->logger->debug('handleLoginRequest: discovery module is: ' . $userDiscoveryModule);
106106

107-
$isSaml = false;
107+
$isSamlOrOidc = false;
108108
if (class_exists('\OCA\User_SAML\UserBackend')
109109
&& $backend instanceof \OCA\User_SAML\UserBackend) {
110-
$isSaml = true;
110+
$isSamlOrOidc = true;
111111
$this->logger->debug('handleLoginRequest: backend is SAML');
112112

113113
$options['backend'] = 'saml';
@@ -122,8 +122,29 @@ public function handleLoginRequest(
122122
];
123123

124124
$this->logger->debug('handleLoginRequest: backend is SAML.', ['options' => $options]);
125+
} elseif (class_exists('\OCA\UserOIDC\Controller\LoginController')
126+
&& class_exists('\OCA\UserOIDC\User\Backend')
127+
&& $backend instanceof \OCA\UserOIDC\User\Backend
128+
&& method_exists($backend, 'getUserData')
129+
) {
130+
// TODO double check if we need to behave the same when saml or oidc is used
131+
$isSamlOrOidc = true;
132+
$this->logger->debug('handleLoginRequest: backend is OIDC');
133+
134+
$options['backend'] = 'oidc';
135+
$options['userData'] = $backend->getUserData();
136+
$uid = $options['userData']['formatted']['uid'];
137+
$password = '';
138+
$discoveryData['oidc'] = $options['userData']['raw'];
139+
// we only send the formatted user data to the slave
140+
$options['userData'] = $options['userData']['formatted'];
141+
$options['oidc'] = [
142+
'providerId' => $this->session->get(\OCA\UserOIDC\Controller\LoginController::PROVIDERID)
143+
];
144+
145+
$this->logger->debug('handleLoginRequest: backend is OIDC.', ['options' => $options]);
125146
} else {
126-
$this->logger->debug('handleLoginRequest: backend is not SAML');
147+
$this->logger->debug('handleLoginRequest: backend is not SAML or OIDC');
127148
}
128149

129150
$this->logger->debug('handleLoginRequest: uid is: ' . $uid);
@@ -141,8 +162,8 @@ public function handleLoginRequest(
141162
}
142163

143164
// first ask the lookup server if we already know the user
144-
// is from SAML, only search on userId, ignore email.
145-
$location = $this->queryLookupServer($uid, $isSaml);
165+
// is from SAML or OIDC, only search on userId, ignore email.
166+
$location = $this->queryLookupServer($uid, $isSamlOrOidc);
146167
$this->logger->debug('handleLoginRequest: location according to lookup server: ' . $location);
147168

148169
// if not we fall-back to a initial user deployment method, if configured

lib/Slave.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
class Slave {
2121
public const SAML_IDP = 'saml_idp';
22+
public const OIDC_PROVIDER_ID = 'oidc_provider_id';
2223

2324
private IUserManager $userManager;
2425
private IClientService $clientService;
@@ -280,6 +281,11 @@ public function handleLogoutRequest(IUser $user) {
280281
Application::APP_ID,
281282
self::SAML_IDP,
282283
null),
284+
'oidc.providerId' => $this->config->getUserValue(
285+
$user->getUID(),
286+
Application::APP_ID,
287+
self::OIDC_PROVIDER_ID,
288+
null),
283289
'exp' => time() + 300 // expires after 5 minute
284290
];
285291

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\GlobalSiteSelector\UserDiscoveryModules;
11+
12+
use OCP\IConfig;
13+
14+
/**
15+
* Class UserDiscoveryOIDC
16+
*
17+
* Discover initial user location with a dedicated OIDC attribute
18+
*
19+
* Therefore you have to define two values in the config.php file:
20+
*
21+
* 'gss.discovery.oidc.slave.mapping' => 'token-attribute'
22+
* 'gss.user.discovery.module' => '\OCA\GlobalSiteSelector\UserDiscoveryModules\UserDiscoveryOIDC'
23+
*
24+
* @package OCA\GlobalSiteSelector\UserDiscoveryModule
25+
*/
26+
class UserDiscoveryOIDC implements IUserDiscoveryModule {
27+
private string $tokenLocationAttribute;
28+
29+
public function __construct(IConfig $config) {
30+
$this->tokenLocationAttribute = $config->getSystemValueString('gss.discovery.oidc.slave.mapping', '');
31+
}
32+
33+
34+
/**
35+
* read user location from OIDC token attribute
36+
*
37+
* @param array $data OIDC attributes to read the location from
38+
*
39+
* @return string
40+
*/
41+
public function getLocation(array $data): string {
42+
$location = '';
43+
if (!empty($this->tokenLocationAttribute) && isset($data['oidc'][$this->tokenLocationAttribute])) {
44+
$location = $data['oidc'][$this->tokenLocationAttribute];
45+
}
46+
47+
return $location;
48+
}
49+
}

0 commit comments

Comments
 (0)