Skip to content

Commit d763da7

Browse files
committed
Merge #25 [V33] Event based provisioning
2 parents e626d35 + d482982 commit d763da7

10 files changed

Lines changed: 1177 additions & 1 deletion

lib/AppInfo/Application.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public function register(IRegistrationContext $context): void {
9090

9191
public function boot(IBootContext $context): void {
9292
$context->injectFn(\Closure::fromCallable([$this->backend, 'injectSession']));
93-
$context->injectFn(\Closure::fromCallable([$this, 'checkLoginToken']));
93+
// $context->injectFn(\Closure::fromCallable([$this, 'checkLoginToken']));
9494
/** @var IUserSession $userSession */
9595
$userSession = $this->getContainer()->get(IUserSession::class);
9696
if ($userSession->isLoggedIn()) {

lib/Controller/LoginController.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use OCA\UserOIDC\Service\LdapService;
2525
use OCA\UserOIDC\Service\OIDCService;
2626
use OCA\UserOIDC\Service\ProviderService;
27+
use OCA\UserOIDC\Service\ProvisioningDeniedException;
2728
use OCA\UserOIDC\Service\ProvisioningService;
2829
use OCA\UserOIDC\Service\SettingsService;
2930
use OCA\UserOIDC\Service\TokenService;
@@ -573,6 +574,24 @@ public function code(string $state = '', string $code = '', string $scope = '',
573574
}
574575

575576
if ($autoProvisionAllowed) {
577+
$user = null;
578+
579+
try {
580+
// use potential user from other backend, create it in our backend if it does not exist
581+
$user = $this->provisioningService->provisionUser($userId, $providerId, $idTokenPayload, $existingUser);
582+
} catch (ProvisioningDeniedException $denied) {
583+
// TODO: MagentaCLOUD should upstream the exception handling
584+
$redirectUrl = $denied->getRedirectUrl();
585+
if ($redirectUrl === null) {
586+
$message = $this->l10n->t('Failed to provision user');
587+
return $this->build403TemplateResponse($message, Http::STATUS_BAD_REQUEST, ['reason' => $denied->getMessage()]);
588+
} else {
589+
// error response is a redirect, e.g. to a booking site
590+
// so that you can immediately get the registration page
591+
return new RedirectResponse($redirectUrl);
592+
}
593+
}
594+
576595
if (!$softAutoProvisionAllowed && $existingUser !== null && $existingUser->getBackendClassName() !== Application::APP_ID) {
577596
// if soft auto-provisioning is disabled,
578597
// we refuse login for a user that already exists in another backend
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
/*
3+
* @copyright Copyright (c) 2023 T-Systems International
4+
*
5+
* @author B. Rederlechner <bernd.rederlechner@t-systems.com>
6+
*
7+
* @license GNU AGPL version 3 or any later version
8+
*
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace OCA\UserOIDC\Event;
14+
15+
use OCP\EventDispatcher\Event;
16+
17+
/**
18+
* Event to provide custom mapping logic based on the OIDC token data
19+
* In order to avoid further processing the event propagation should be stopped
20+
* in the listener after processing as the value might get overwritten afterwards
21+
* by other listeners through $event->stopPropagation();
22+
*/
23+
class UserAccountChangeEvent extends Event {
24+
25+
/** @var string */
26+
private $uid;
27+
28+
/** @var string|null */
29+
private $displayname;
30+
31+
/** @var string|null */
32+
private $mainEmail;
33+
34+
/** @var string|null */
35+
private $quota;
36+
37+
/** @var object */
38+
private $claims;
39+
40+
/** @var UserAccountChangeResult */
41+
private $result;
42+
43+
public function __construct(
44+
string $uid,
45+
?string $displayname,
46+
?string $mainEmail,
47+
?string $quota,
48+
object $claims,
49+
bool $accessAllowed = false
50+
) {
51+
parent::__construct();
52+
$this->uid = $uid;
53+
$this->displayname = $displayname;
54+
$this->mainEmail = $mainEmail;
55+
$this->quota = $quota;
56+
$this->claims = $claims;
57+
$this->result = new UserAccountChangeResult($accessAllowed, 'default');
58+
}
59+
60+
/**
61+
* Get the user ID (UID) associated with the event.
62+
*
63+
* @return string
64+
*/
65+
public function getUid(): string {
66+
return $this->uid;
67+
}
68+
69+
/**
70+
* Get the display name for the account.
71+
*
72+
* @return string|null
73+
*/
74+
public function getDisplayName(): ?string {
75+
return $this->displayname;
76+
}
77+
78+
/**
79+
* Get the primary email address.
80+
*
81+
* @return string|null
82+
*/
83+
public function getMainEmail(): ?string {
84+
return $this->mainEmail;
85+
}
86+
87+
/**
88+
* Get the quota assigned to the account.
89+
*
90+
* @return string|null
91+
*/
92+
public function getQuota(): ?string {
93+
return $this->quota;
94+
}
95+
96+
/**
97+
* Get the OIDC claims associated with the event.
98+
*
99+
* @return object
100+
*/
101+
public function getClaims(): object {
102+
return $this->claims;
103+
}
104+
105+
/**
106+
* Get the current result object.
107+
*
108+
* @return UserAccountChangeResult
109+
*/
110+
public function getResult(): UserAccountChangeResult {
111+
return $this->result;
112+
}
113+
114+
/**
115+
* Replace the result object with a new one.
116+
*
117+
* @param bool $accessAllowed Whether access should be allowed
118+
* @param string $reason Optional reason for the decision
119+
* @param string|null $redirectUrl Optional redirect URL
120+
* @return void
121+
*/
122+
public function setResult(bool $accessAllowed, string $reason = '', ?string $redirectUrl = null): void {
123+
$this->result = new UserAccountChangeResult($accessAllowed, $reason, $redirectUrl);
124+
}
125+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
/*
3+
* @copyright Copyright (c) 2023 T-Systems International
4+
*
5+
* @author B. Rederlechner <bernd.rederlechner@t-systems.com>
6+
*
7+
* @license GNU AGPL version 3 or any later version
8+
*
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace OCA\UserOIDC\Event;
14+
15+
/**
16+
* Represents the result of an account change event decision.
17+
* Used to signal whether access is allowed and optional redirect/reason info.
18+
*/
19+
class UserAccountChangeResult {
20+
21+
/** @var bool */
22+
private $accessAllowed;
23+
24+
/** @var string */
25+
private $reason;
26+
27+
/** @var string|null */
28+
private $redirectUrl;
29+
30+
public function __construct(bool $accessAllowed, string $reason = '', ?string $redirectUrl = null) {
31+
$this->accessAllowed = $accessAllowed;
32+
$this->redirectUrl = $redirectUrl;
33+
$this->reason = $reason;
34+
}
35+
36+
/**
37+
* Whether access for this user is allowed.
38+
*
39+
* @return bool
40+
*/
41+
public function isAccessAllowed(): bool {
42+
return $this->accessAllowed;
43+
}
44+
45+
/**
46+
* Set whether access for this user is allowed.
47+
*
48+
* @param bool $accessAllowed
49+
* @return void
50+
*/
51+
public function setAccessAllowed(bool $accessAllowed): void {
52+
$this->accessAllowed = $accessAllowed;
53+
}
54+
55+
/**
56+
* Returns the optional alternate redirect URL.
57+
*
58+
* @return string|null
59+
*/
60+
public function getRedirectUrl(): ?string {
61+
return $this->redirectUrl;
62+
}
63+
64+
/**
65+
* Sets the optional alternate redirect URL.
66+
*
67+
* @param string|null $redirectUrl
68+
* @return void
69+
*/
70+
public function setRedirectUrl(?string $redirectUrl): void {
71+
$this->redirectUrl = $redirectUrl;
72+
}
73+
74+
/**
75+
* Returns the decision reason.
76+
*
77+
* @return string
78+
*/
79+
public function getReason(): string {
80+
return $this->reason;
81+
}
82+
83+
/**
84+
* Sets the decision reason.
85+
*
86+
* @param string $reason
87+
* @return void
88+
*/
89+
public function setReason(string $reason): void {
90+
$this->reason = $reason;
91+
}
92+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\UserOIDC\Exception;
10+
11+
use Exception;
12+
13+
class AttributeValueException extends Exception {
14+
15+
public function __construct(
16+
$message = '',
17+
$code = 0,
18+
$previous = null,
19+
private ?string $error = null,
20+
private ?string $errorDescription = null,
21+
) {
22+
parent::__construct($message, $code, $previous);
23+
}
24+
25+
public function getError(): ?string {
26+
return $this->error;
27+
}
28+
29+
public function getErrorDescription(): ?string {
30+
return $this->errorDescription;
31+
}
32+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2023, T-Systems International
7+
*
8+
* @author B. Rederlechner <bernd.rederlechner@t-Systems.com>
9+
*
10+
* @license AGPL-3.0
11+
*
12+
* This code is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License, version 3,
14+
* as published by the Free Software Foundation.
15+
*
16+
* This program is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
* GNU Affero General Public License for more details.
20+
*
21+
* You should have received a copy of the GNU Affero General Public License, version 3,
22+
* along with this program. If not, see <http://www.gnu.org/licenses/>
23+
*
24+
*/
25+
26+
namespace OCA\UserOIDC\Service;
27+
28+
/**
29+
* Exception if the precondition of the config update method isn't met
30+
* @since 1.4.0
31+
*/
32+
class ProvisioningDeniedException extends \Exception {
33+
private $redirectUrl;
34+
35+
/**
36+
* Exception constructor including an option redirect url.
37+
*
38+
* @param string $message The error message. It will be not revealed to the
39+
* the user (unless the hint is empty) and thus
40+
* should be not translated.
41+
* @param string $hint A useful message that is presented to the end
42+
* user. It should be translated, but must not
43+
* contain sensitive data.
44+
* @param int $code Set default to 403 (Forbidden)
45+
* @param \Exception|null $previous
46+
*/
47+
public function __construct(string $message, ?string $redirectUrl = null, int $code = 403, ?\Exception $previous = null) {
48+
parent::__construct($message, $code, $previous);
49+
$this->redirectUrl = $redirectUrl;
50+
}
51+
52+
/**
53+
* Read optional failure redirect if available
54+
* @return string|null
55+
*/
56+
public function getRedirectUrl(): ?string {
57+
return $this->redirectUrl;
58+
}
59+
60+
/**
61+
* Include redirect in string serialisation.
62+
*
63+
* @return string
64+
*/
65+
public function __toString(): string {
66+
$redirect = $this->redirectUrl ?? '<no redirect>';
67+
return __CLASS__ . ": [{$this->code}]: {$this->message} ({$redirect})\n";
68+
}
69+
}

0 commit comments

Comments
 (0)