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
8 changes: 8 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
use OCA\UserOIDC\Listener\TimezoneHandlingListener;
use OCA\UserOIDC\Listener\TokenInvalidatedListener;
use OCA\UserOIDC\Service\ID4MeService;
use OCA\UserOIDC\Service\ProvisioningEventService;
use OCA\UserOIDC\Service\ProvisioningService;
use OCA\UserOIDC\Service\RequestClassificationService;
use OCA\UserOIDC\Service\SettingsService;
use OCA\UserOIDC\Service\TokenService;
Expand All @@ -36,6 +38,7 @@
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\IUserSession;
use Psr\Container\ContainerInterface;
use Throwable;

class Application extends App implements IBootstrap {
Expand All @@ -50,6 +53,11 @@ public function __construct(array $urlParams = []) {
}

public function register(IRegistrationContext $context): void {
// override registration of provisioning service to use event-based solution
$this->getContainer()->registerService(ProvisioningService::class, function (ContainerInterface $c): ProvisioningService {
return $c->get(ProvisioningEventService::class);
});

/** @var IUserManager $userManager */
$userManager = $this->getContainer()->get(IUserManager::class);

Expand Down
16 changes: 15 additions & 1 deletion 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 @@ -654,8 +655,21 @@ public function code(string $state = '', string $code = '', string $scope = '',
$message = $this->l10n->t('User conflict');
return $this->build403TemplateResponse($message, Http::STATUS_BAD_REQUEST, ['reason' => 'non-soft auto provision, user conflict'], false);
}

// use potential user from other backend, create it in our backend if it does not exist
$provisioningResult = $this->provisioningService->provisionUser($userId, $providerId, $idTokenPayload, $existingUser);
try {
$provisioningResult = $this->provisioningService->provisionUser($userId, $providerId, $idTokenPayload, $existingUser);
} catch (ProvisioningDeniedException $denied) {
$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()]);
}

return new RedirectResponse($redirectUrl);
}

$user = $provisioningResult['user'];
if ($existingUser === null && $user !== null) {
// we know we just created a user
Expand Down
59 changes: 59 additions & 0 deletions lib/Event/UserAccountChangeEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 T-Systems International
* SPDX-License-Identifier: AGPL-3.0-only
*/

namespace OCA\UserOIDC\Event;

use OCP\EventDispatcher\Event;

/**
* Event to allow custom account provisioning decisions based on OIDC token data.
*/
class UserAccountChangeEvent extends Event {
private UserAccountChangeResult $result;

public function __construct(
private string $uid,
private ?string $displayName,
private ?string $mainEmail,
private ?string $quota,
private object $claims,
) {
parent::__construct();

$this->result = new UserAccountChangeResult();
}

public function getUid(): string {
return $this->uid;
}

public function getDisplayName(): ?string {
return $this->displayName;
}

public function getMainEmail(): ?string {
return $this->mainEmail;
}

public function getQuota(): ?string {
return $this->quota;
}

public function getClaims(): object {
return $this->claims;
}

public function getResult(): UserAccountChangeResult {
return $this->result;
}

public function setResult(bool $accessAllowed, string $reason = '', ?string $redirectUrl = null): void {
$this->result = new UserAccountChangeResult($accessAllowed, $reason, $redirectUrl);
}
}
50 changes: 50 additions & 0 deletions lib/Event/UserAccountChangeResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 T-Systems International
* SPDX-License-Identifier: AGPL-3.0-only
*/

namespace OCA\UserOIDC\Event;

/**
* Represents the result of an account change event decision.
*/
class UserAccountChangeResult {
public function __construct(
private ?bool $accessAllowed = null,
private string $reason = '',
private ?string $redirectUrl = null,
) {
}

public function hasDecision(): bool {
return $this->accessAllowed !== null;
}

public function isAccessAllowed(): bool {
return $this->accessAllowed === true;
}

public function setAccessAllowed(bool $accessAllowed): void {
$this->accessAllowed = $accessAllowed;
}

public function getReason(): string {
return $this->reason;
}

public function setReason(string $reason): void {
$this->reason = $reason;
}

public function getRedirectUrl(): ?string {
return $this->redirectUrl;
}

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

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 T-Systems International
* SPDX-License-Identifier: AGPL-3.0-only
*/

namespace OCA\UserOIDC\Service;

class ProvisioningDeniedException extends \Exception {
public function __construct(
string $message,
private ?string $redirectUrl = null,
int $code = 403,
?\Throwable $previous = null,
) {
parent::__construct($message, $code, $previous);
}

public function getRedirectUrl(): ?string {
return $this->redirectUrl;
}

public function __toString(): string {
$redirect = $this->redirectUrl ?? '<no redirect>';

return self::class . ": [{$this->code}]: {$this->message} ({$redirect})\n";
}
}
Loading
Loading