Skip to content

Commit 365564c

Browse files
committed
Merge #14 Add event based provisioning V31
2 parents 3fb916b + 3ddcfa9 commit 365564c

File tree

10 files changed

+1086
-6
lines changed

10 files changed

+1086
-6
lines changed

lib/AppInfo/Application.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ public function register(IRegistrationContext $context): void {
6060

6161
public function boot(IBootContext $context): void {
6262
$context->injectFn(\Closure::fromCallable([$this->backend, 'injectSession']));
63-
$context->injectFn(\Closure::fromCallable([$this, 'checkLoginToken']));
6463
/** @var IUserSession $userSession */
6564
$userSession = $this->getContainer()->get(IUserSession::class);
6665
if ($userSession->isLoggedIn()) {

lib/Controller/LoginController.php

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use OCA\UserOIDC\Service\DiscoveryService;
2424
use OCA\UserOIDC\Service\LdapService;
2525
use OCA\UserOIDC\Service\ProviderService;
26+
use OCA\UserOIDC\Service\ProvisioningDeniedException;
2627
use OCA\UserOIDC\Service\ProvisioningService;
2728
use OCA\UserOIDC\Service\TokenService;
2829
use OCA\UserOIDC\User\Backend;
@@ -500,14 +501,31 @@ public function code(string $state = '', string $code = '', string $scope = '',
500501
}
501502

502503
if ($autoProvisionAllowed) {
503-
if (!$softAutoProvisionAllowed && $userFromOtherBackend !== null) {
504+
// TODO: (proposal) refactor all provisioning strategies into event handlers
505+
$user = null;
506+
507+
try {
508+
// use potential user from other backend, create it in our backend if it does not exist
509+
$user = $this->provisioningService->provisionUser($userId, $providerId, $idTokenPayload, $userFromOtherBackend);
510+
} catch (ProvisioningDeniedException $denied) {
511+
// TODO: MagentaCLOUD should upstream the exception handling
512+
$redirectUrl = $denied->getRedirectUrl();
513+
if ($redirectUrl === null) {
514+
$message = $this->l10n->t('Failed to provision user');
515+
return $this->build403TemplateResponse($message, Http::STATUS_BAD_REQUEST, ['reason' => $denied->getMessage()]);
516+
} else {
517+
// error response is a redirect, e.g. to a booking site
518+
// so that you can immediately get the registration page
519+
return new RedirectResponse($redirectUrl);
520+
}
521+
}
522+
523+
if (!$softAutoProvisionAllowed && $userFromOtherBackend !== null && $user === null) {
504524
// if soft auto-provisioning is disabled,
505525
// we refuse login for a user that already exists in another backend
506526
$message = $this->l10n->t('User conflict');
507527
return $this->build403TemplateResponse($message, Http::STATUS_BAD_REQUEST, ['reason' => 'non-soft auto provision, user conflict'], false);
508528
}
509-
// use potential user from other backend, create it in our backend if it does not exist
510-
$user = $this->provisioningService->provisionUser($userId, $providerId, $idTokenPayload, $userFromOtherBackend);
511529
} else {
512530
// when auto provision is disabled, we assume the user has been created by another user backend (or manually)
513531
$user = $userFromOtherBackend;
@@ -806,7 +824,7 @@ public function telekomBackChannelLogout(string $logout_token = '') {
806824
* @return JSONResponse
807825
*/
808826
private function getBackchannelLogoutErrorResponse(
809-
string $error, string $description, array $throttleMetadata = [],
827+
string $error, string $description, array $throttleMetadata = [], ?bool $throttle = null,
810828
): JSONResponse {
811829
$this->logger->debug('Backchannel logout error. ' . $error . ' ; ' . $description);
812830
return new JSONResponse(
@@ -827,4 +845,4 @@ private function toCodeChallenge(string $data): string {
827845
$s = str_replace('/', '_', $s); // 63rd char of encoding
828846
return $s;
829847
}
830-
}
848+
}

lib/Db/UserMapper.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,25 @@
1414
use OCP\Cache\CappedMemoryCache;
1515
use OCP\IConfig;
1616
use OCP\IDBConnection;
17+
use Psr\Log\LoggerInterface;
1718

1819
/**
1920
* @extends QBMapper<User>
2021
*/
2122
class UserMapper extends QBMapper {
2223

2324
private CappedMemoryCache $userCache;
25+
private LoggerInterface $logger;
2426

2527
public function __construct(
2628
IDBConnection $db,
29+
LoggerInterface $logger,
2730
private LocalIdService $idService,
2831
private IConfig $config,
2932
) {
3033
parent::__construct($db, 'user_oidc', User::class);
3134
$this->userCache = new CappedMemoryCache();
35+
$this->logger = $logger;
3236
}
3337

3438
/**
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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+
private $uid;
25+
private $displayname;
26+
private $mainEmail;
27+
private $quota;
28+
private $claims;
29+
private $result;
30+
31+
32+
public function __construct(string $uid, ?string $displayname, ?string $mainEmail, ?string $quota, object $claims, bool $accessAllowed = false) {
33+
parent::__construct();
34+
$this->uid = $uid;
35+
$this->displayname = $displayname;
36+
$this->mainEmail = $mainEmail;
37+
$this->quota = $quota;
38+
$this->claims = $claims;
39+
$this->result = new UserAccountChangeResult($accessAllowed, 'default');
40+
}
41+
42+
/**
43+
* @return get event username (uid)
44+
*/
45+
public function getUid(): string {
46+
return $this->uid;
47+
}
48+
49+
/**
50+
* @return get event displayname
51+
*/
52+
public function getDisplayName(): ?string {
53+
return $this->displayname;
54+
}
55+
56+
/**
57+
* @return get event main email
58+
*/
59+
public function getMainEmail(): ?string {
60+
return $this->mainEmail;
61+
}
62+
63+
/**
64+
* @return get event quota
65+
*/
66+
public function getQuota(): ?string {
67+
return $this->quota;
68+
}
69+
70+
/**
71+
* @return array the array of claim values associated with the event
72+
*/
73+
public function getClaims(): object {
74+
return $this->claims;
75+
}
76+
77+
/**
78+
* @return value for the logged in user attribute
79+
*/
80+
public function getResult(): UserAccountChangeResult {
81+
return $this->result;
82+
}
83+
84+
public function setResult(bool $accessAllowed, string $reason = '', ?string $redirectUrl = null) : void {
85+
$this->result = new UserAccountChangeResult($accessAllowed, $reason, $redirectUrl);
86+
}
87+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
* Event to provide custom mapping logic based on the OIDC token data
17+
* In order to avoid further processing the event propagation should be stopped
18+
* in the listener after processing as the value might get overwritten afterwards
19+
* by other listeners through $event->stopPropagation();
20+
*/
21+
class UserAccountChangeResult {
22+
23+
/** @var bool */
24+
private $accessAllowed;
25+
/** @var string */
26+
private $reason;
27+
/** @var string */
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+
* @return value for the logged in user attribute
38+
*/
39+
public function isAccessAllowed(): bool {
40+
return $this->accessAllowed;
41+
}
42+
43+
public function setAccessAllowed(bool $accessAllowed): void {
44+
$this->accessAllowed = $accessAllowed;
45+
}
46+
47+
/**
48+
* @return get optional alternate redirect address
49+
*/
50+
public function getRedirectUrl(): ?string {
51+
return $this->redirectUrl;
52+
}
53+
54+
/**
55+
* @return set optional alternate redirect address
56+
*/
57+
public function setRedirectUrl(?string $redirectUrl): void {
58+
$this->redirectUrl = $redirectUrl;
59+
}
60+
61+
/**
62+
* @return get decision reason
63+
*/
64+
public function getReason(): string {
65+
return $this->reason;
66+
}
67+
68+
/**
69+
* @return set decision reason
70+
*/
71+
public function setReason(string $reason): void {
72+
$this->reason = $reason;
73+
}
74+
}
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)