Skip to content

Commit

Permalink
feat: add oidc support
Browse files Browse the repository at this point in the history
Signed-off-by: Julien Veyssier <[email protected]>
  • Loading branch information
julien-nc authored and AndyScherzinger committed Dec 29, 2024
1 parent d9b1f3a commit 6a44d96
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 12 deletions.
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Config.php parameters to operate the server in master mode:
// The user disovery module might require additional config paramters you can find in
// the documentation of the module
'gss.user.discovery.module' => '\OCA\GlobalSiteSelector\UserDiscoveryModules\UserDiscoverySAML',
// or 'gss.user.discovery.module' => '\OCA\GlobalSiteSelector\UserDiscoveryModules\UserDiscoveryOIDC'
// define a allow list for automatic login to other instance to let browsers handle the redirect properly
'gss.master.csp-allow' => ['*.myorg.com', 'node3.otherorg.com'],
Expand Down Expand Up @@ -80,13 +81,23 @@ specific use case:

#### UserDiscoverySAML

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

````
'gss.discovery.saml.slave.mapping' => 'idp-parameter'
````

#### UserDiscoveryOIDC

This module is similar to UserDiscoverySAML.
It reads the location from an OIDC token attribute which contains
the exact URL to the slave target server. The attribute can be defined this way:

````
'gss.discovery.oidc.slave.mapping' => 'token-attribute'
````

#### ManualUserMapping

This allows you to maintain a custom json file which maps a specific key word
Expand Down
15 changes: 12 additions & 3 deletions lib/Controller/MasterController.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,22 @@ public function autoLogout(?string $jwt) {
if ($jwt !== null) {
$key = $this->gss->getJwtKey();
$decoded = (array)JWT::decode($jwt, new Key($key, Application::JWT_ALGORITHM));
$idp = $decoded['saml.idp'] ?? null;

$logoutUrl = $this->urlGenerator->linkToRoute('user_saml.SAML.singleLogoutService');
// saml idp ID
$samlIdp = $decoded['saml.idp'] ?? null;
// oidc provider ID
$oidcProviderId = $decoded['oidc.providerId'] ?? '';

if (class_exists('\OCA\User_SAML\UserBackend')) {
$logoutUrl = $this->urlGenerator->linkToRoute('user_saml.SAML.singleLogoutService');
} elseif (class_exists('\OCA\UserOIDC\User\Backend')) {
$logoutUrl = $this->urlGenerator->linkToRoute('user_oidc.login.singleLogoutService');
}
if (!empty($logoutUrl)) {
$token = [
'logout' => 'logout',
'idp' => $idp,
'idp' => $samlIdp,
'oidcProviderId' => $oidcProviderId,
'exp' => time() + 300, // expires after 5 minutes
];

Expand Down
11 changes: 9 additions & 2 deletions lib/Controller/SlaveController.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ public function autoLogin(string $jwt): RedirectResponse {
$this->logger->debug('uid: ' . $uid . ', options: ' . json_encode($options));

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

$user = $this->userManager->get($uid);
Expand All @@ -108,6 +109,12 @@ public function autoLogin(string $jwt): RedirectResponse {
Slave::SAML_IDP,
$options['saml']['idp'] ?? null
);
$this->config->setUserValue(
$user->getUID(),
Application::APP_ID,
Slave::OIDC_PROVIDER_ID,
$options['oidc']['providerId'] ?? ''
);

$result = true;
} else {
Expand Down
31 changes: 26 additions & 5 deletions lib/Master.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ public function handleLoginRequest(
$userDiscoveryModule = $this->config->getSystemValueString('gss.user.discovery.module', '');
$this->logger->debug('handleLoginRequest: discovery module is: ' . $userDiscoveryModule);

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

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

$this->logger->debug('handleLoginRequest: backend is SAML.', ['options' => $options]);
} elseif (class_exists('\OCA\UserOIDC\Controller\LoginController')
&& class_exists('\OCA\UserOIDC\User\Backend')
&& $backend instanceof \OCA\UserOIDC\User\Backend
&& method_exists($backend, 'getUserData')
) {
// TODO double check if we need to behave the same when saml or oidc is used
$isSamlOrOidc = true;
$this->logger->debug('handleLoginRequest: backend is OIDC');

$options['backend'] = 'oidc';
$options['userData'] = $backend->getUserData();
$uid = $options['userData']['formatted']['uid'];
$password = '';
$discoveryData['oidc'] = $options['userData']['raw'];
// we only send the formatted user data to the slave
$options['userData'] = $options['userData']['formatted'];
$options['oidc'] = [
'providerId' => $this->session->get(\OCA\UserOIDC\Controller\LoginController::PROVIDERID)
];

$this->logger->debug('handleLoginRequest: backend is OIDC.', ['options' => $options]);
} else {
$this->logger->debug('handleLoginRequest: backend is not SAML');
$this->logger->debug('handleLoginRequest: backend is not SAML or OIDC');
}

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

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

// if not we fall-back to a initial user deployment method, if configured
Expand Down
6 changes: 6 additions & 0 deletions lib/Slave.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

class Slave {
public const SAML_IDP = 'saml_idp';
public const OIDC_PROVIDER_ID = 'oidc_provider_id';

private IUserManager $userManager;
private IClientService $clientService;
Expand Down Expand Up @@ -280,6 +281,11 @@ public function handleLogoutRequest(IUser $user) {
Application::APP_ID,
self::SAML_IDP,
null),
'oidc.providerId' => $this->config->getUserValue(
$user->getUID(),
Application::APP_ID,
self::OIDC_PROVIDER_ID,
null),
'exp' => time() + 300 // expires after 5 minute
];

Expand Down
64 changes: 64 additions & 0 deletions lib/UserDiscoveryModules/UserDiscoveryOIDC.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2024 Julien Veyssier <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\GlobalSiteSelector\UserDiscoveryModules;

use OCP\IConfig;

/**
* Class UserDiscoveryOIDC
*
* Discover initial user location with a dedicated OIDC attribute
*
* Therefore you have to define two values in the config.php file:
*
* 'gss.discovery.oidc.slave.mapping' => 'token-attribute'
* 'gss.user.discovery.module' => '\OCA\GlobalSiteSelector\UserDiscoveryModules\UserDiscoveryOIDC'
*
* @package OCA\GlobalSiteSelector\UserDiscoveryModule
*/
class UserDiscoveryOIDC implements IUserDiscoveryModule {
private string $tokenLocationAttribute;

public function __construct(IConfig $config) {
$this->tokenLocationAttribute = $config->getSystemValueString('gss.discovery.oidc.slave.mapping', '');
}


/**
* read user location from OIDC token attribute
*
* @param array $data OIDC attributes to read the location from
*
* @return string
*/
public function getLocation(array $data): string {
$location = '';
if (!empty($this->tokenLocationAttribute) && isset($data['oidc'][$this->tokenLocationAttribute])) {
$location = $data['oidc'][$this->tokenLocationAttribute];
}

return $location;
}
}

0 comments on commit 6a44d96

Please sign in to comment.