Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add oidc support #168

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
49 changes: 49 additions & 0 deletions lib/UserDiscoveryModules/UserDiscoveryOIDC.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

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;
}
}
Loading