Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public function register(IRegistrationContext $context): void {

public function boot(IBootContext $context): void {
$context->injectFn(\Closure::fromCallable([$this->backend, 'injectSession']));
$context->injectFn(\Closure::fromCallable([$this, 'checkLoginToken']));
// $context->injectFn(\Closure::fromCallable([$this, 'checkLoginToken']));
/** @var IUserSession $userSession */
$userSession = $this->getContainer()->get(IUserSession::class);
if ($userSession->isLoggedIn()) {
Expand Down
19 changes: 19 additions & 0 deletions lib/Controller/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use OCA\UserOIDC\Service\LdapService;
use OCA\UserOIDC\Service\OIDCService;
use OCA\UserOIDC\Service\ProviderService;
use OCA\UserOIDC\Service\ProvisioningDeniedException;
use OCA\UserOIDC\Service\ProvisioningService;
use OCA\UserOIDC\Service\SettingsService;
use OCA\UserOIDC\Service\TokenService;
Expand Down Expand Up @@ -554,6 +555,24 @@ public function code(string $state = '', string $code = '', string $scope = '',
}

if ($autoProvisionAllowed) {
$user = null;

try {
// use potential user from other backend, create it in our backend if it does not exist
$user = $this->provisioningService->provisionUser($userId, $providerId, $idTokenPayload, $existingUser);
} catch (ProvisioningDeniedException $denied) {
// TODO: MagentaCLOUD should upstream the exception handling
$redirectUrl = $denied->getRedirectUrl();
if ($redirectUrl === null) {
$message = $this->l10n->t('Failed to provision user');
return $this->build403TemplateResponse($message, Http::STATUS_BAD_REQUEST, ['reason' => $denied->getMessage()]);
} else {
// error response is a redirect, e.g. to a booking site
// so that you can immediately get the registration page
return new RedirectResponse($redirectUrl);
}
}

if (!$softAutoProvisionAllowed && $existingUser !== null && $existingUser->getBackendClassName() !== Application::APP_ID) {
// if soft auto-provisioning is disabled,
// we refuse login for a user that already exists in another backend
Expand Down
125 changes: 125 additions & 0 deletions lib/Event/UserAccountChangeEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php
/*
* @copyright Copyright (c) 2023 T-Systems International
*
* @author B. Rederlechner <bernd.rederlechner@t-systems.com>
*
* @license GNU AGPL version 3 or any later version
*
*/

declare(strict_types=1);

namespace OCA\UserOIDC\Event;

use OCP\EventDispatcher\Event;

/**
* Event to provide custom mapping logic based on the OIDC token data
* In order to avoid further processing the event propagation should be stopped
* in the listener after processing as the value might get overwritten afterwards
* by other listeners through $event->stopPropagation();
*/
class UserAccountChangeEvent extends Event {

/** @var string */
private $uid;

/** @var string|null */
private $displayname;

/** @var string|null */
private $mainEmail;

/** @var string|null */
private $quota;

/** @var object */
private $claims;

/** @var UserAccountChangeResult */
private $result;

public function __construct(
string $uid,
?string $displayname,
?string $mainEmail,
?string $quota,
object $claims,
bool $accessAllowed = false
) {
parent::__construct();
$this->uid = $uid;
$this->displayname = $displayname;
$this->mainEmail = $mainEmail;
$this->quota = $quota;
$this->claims = $claims;
$this->result = new UserAccountChangeResult($accessAllowed, 'default');
}

/**
* Get the user ID (UID) associated with the event.
*
* @return string
*/
public function getUid(): string {
return $this->uid;
}

/**
* Get the display name for the account.
*
* @return string|null
*/
public function getDisplayName(): ?string {
return $this->displayname;
}

/**
* Get the primary email address.
*
* @return string|null
*/
public function getMainEmail(): ?string {
return $this->mainEmail;
}

/**
* Get the quota assigned to the account.
*
* @return string|null
*/
public function getQuota(): ?string {
return $this->quota;
}

/**
* Get the OIDC claims associated with the event.
*
* @return object
*/
public function getClaims(): object {
return $this->claims;
}

/**
* Get the current result object.
*
* @return UserAccountChangeResult
*/
public function getResult(): UserAccountChangeResult {
return $this->result;
}

/**
* Replace the result object with a new one.
*
* @param bool $accessAllowed Whether access should be allowed
* @param string $reason Optional reason for the decision
* @param string|null $redirectUrl Optional redirect URL
* @return void
*/
public function setResult(bool $accessAllowed, string $reason = '', ?string $redirectUrl = null): void {
$this->result = new UserAccountChangeResult($accessAllowed, $reason, $redirectUrl);
}
}
92 changes: 92 additions & 0 deletions lib/Event/UserAccountChangeResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php
/*
* @copyright Copyright (c) 2023 T-Systems International
*
* @author B. Rederlechner <bernd.rederlechner@t-systems.com>
*
* @license GNU AGPL version 3 or any later version
*
*/

declare(strict_types=1);

namespace OCA\UserOIDC\Event;

/**
* Represents the result of an account change event decision.
* Used to signal whether access is allowed and optional redirect/reason info.
*/
class UserAccountChangeResult {

/** @var bool */
private $accessAllowed;

/** @var string */
private $reason;

/** @var string|null */
private $redirectUrl;

public function __construct(bool $accessAllowed, string $reason = '', ?string $redirectUrl = null) {
$this->accessAllowed = $accessAllowed;
$this->redirectUrl = $redirectUrl;
$this->reason = $reason;
}

/**
* Whether access for this user is allowed.
*
* @return bool
*/
public function isAccessAllowed(): bool {
return $this->accessAllowed;
}

/**
* Set whether access for this user is allowed.
*
* @param bool $accessAllowed
* @return void
*/
public function setAccessAllowed(bool $accessAllowed): void {
$this->accessAllowed = $accessAllowed;
}

/**
* Returns the optional alternate redirect URL.
*
* @return string|null
*/
public function getRedirectUrl(): ?string {
return $this->redirectUrl;
}

/**
* Sets the optional alternate redirect URL.
*
* @param string|null $redirectUrl
* @return void
*/
public function setRedirectUrl(?string $redirectUrl): void {
$this->redirectUrl = $redirectUrl;
}

/**
* Returns the decision reason.
*
* @return string
*/
public function getReason(): string {
return $this->reason;
}

/**
* Sets the decision reason.
*
* @param string $reason
* @return void
*/
public function setReason(string $reason): void {
$this->reason = $reason;
}
}
32 changes: 32 additions & 0 deletions lib/Exception/AttributeValueException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\UserOIDC\Exception;

use Exception;

class AttributeValueException extends Exception {

public function __construct(
$message = '',
$code = 0,
$previous = null,
private ?string $error = null,
private ?string $errorDescription = null,
) {
parent::__construct($message, $code, $previous);
}

public function getError(): ?string {
return $this->error;
}

public function getErrorDescription(): ?string {
return $this->errorDescription;
}
}
69 changes: 69 additions & 0 deletions lib/Service/ProvisioningDeniedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2023, T-Systems International
*
* @author B. Rederlechner <bernd.rederlechner@t-Systems.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace OCA\UserOIDC\Service;

/**
* Exception if the precondition of the config update method isn't met
* @since 1.4.0
*/
class ProvisioningDeniedException extends \Exception {
private $redirectUrl;

/**
* Exception constructor including an option redirect url.
*
* @param string $message The error message. It will be not revealed to the
* the user (unless the hint is empty) and thus
* should be not translated.
* @param string $hint A useful message that is presented to the end
* user. It should be translated, but must not
* contain sensitive data.
* @param int $code Set default to 403 (Forbidden)
* @param \Exception|null $previous
*/
public function __construct(string $message, ?string $redirectUrl = null, int $code = 403, ?\Exception $previous = null) {
parent::__construct($message, $code, $previous);
$this->redirectUrl = $redirectUrl;
}

/**
* Read optional failure redirect if available
* @return string|null
*/
public function getRedirectUrl(): ?string {
return $this->redirectUrl;
}

/**
* Include redirect in string serialisation.
*
* @return string
*/
public function __toString(): string {
$redirect = $this->redirectUrl ?? '<no redirect>';
return __CLASS__ . ": [{$this->code}]: {$this->message} ({$redirect})\n";
}
}
Loading
Loading