feat(auth): Add IAlternativeLoginProvider

IAlternativeLogin has a fatal flaw in that it is not possible to
register multiple alternative login dynamically but only statically.

IAlternativeLoginProvider fixes this limitation as the provider can then
provider multiple IAlternativeLogin.

Signed-off-by: Carl Schwan <carlschwan@kde.org>
This commit is contained in:
Carl Schwan 2026-04-14 15:51:48 +02:00
parent 14472cb8cc
commit f4cae8c222
No known key found for this signature in database
GPG key ID: 02325448204E452A
6 changed files with 107 additions and 1 deletions

View file

@ -168,6 +168,7 @@ return array(
'OCP\\Authentication\\Exceptions\\PasswordUnavailableException' => $baseDir . '/lib/public/Authentication/Exceptions/PasswordUnavailableException.php',
'OCP\\Authentication\\Exceptions\\WipeTokenException' => $baseDir . '/lib/public/Authentication/Exceptions/WipeTokenException.php',
'OCP\\Authentication\\IAlternativeLogin' => $baseDir . '/lib/public/Authentication/IAlternativeLogin.php',
'OCP\\Authentication\\IAlternativeLoginProvider' => $baseDir . '/lib/public/Authentication/IAlternativeLoginProvider.php',
'OCP\\Authentication\\IApacheBackend' => $baseDir . '/lib/public/Authentication/IApacheBackend.php',
'OCP\\Authentication\\IProvideUserSecretBackend' => $baseDir . '/lib/public/Authentication/IProvideUserSecretBackend.php',
'OCP\\Authentication\\LoginCredentials\\ICredentials' => $baseDir . '/lib/public/Authentication/LoginCredentials/ICredentials.php',

View file

@ -209,6 +209,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Authentication\\Exceptions\\PasswordUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/PasswordUnavailableException.php',
'OCP\\Authentication\\Exceptions\\WipeTokenException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/WipeTokenException.php',
'OCP\\Authentication\\IAlternativeLogin' => __DIR__ . '/../../..' . '/lib/public/Authentication/IAlternativeLogin.php',
'OCP\\Authentication\\IAlternativeLoginProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/IAlternativeLoginProvider.php',
'OCP\\Authentication\\IApacheBackend' => __DIR__ . '/../../..' . '/lib/public/Authentication/IApacheBackend.php',
'OCP\\Authentication\\IProvideUserSecretBackend' => __DIR__ . '/../../..' . '/lib/public/Authentication/IProvideUserSecretBackend.php',
'OCP\\Authentication\\LoginCredentials\\ICredentials' => __DIR__ . '/../../..' . '/lib/public/Authentication/LoginCredentials/ICredentials.php',

View file

@ -16,6 +16,7 @@ use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\Services\InitialStateProvider;
use OCP\Authentication\IAlternativeLogin;
use OCP\Authentication\IAlternativeLoginProvider;
use OCP\Calendar\ICalendarProvider;
use OCP\Calendar\Resource\IBackend as IResourceBackend;
use OCP\Calendar\Room\IBackend as IRoomBackend;
@ -44,6 +45,7 @@ use OCP\Teams\ITeamResourceProvider;
use OCP\TextProcessing\IProvider as ITextProcessingProvider;
use OCP\Translation\ITranslationProvider;
use OCP\UserMigration\IMigrator as IUserMigrator;
use Override;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Throwable;
@ -95,6 +97,9 @@ class RegistrationContext {
/** @var ServiceRegistration<IAlternativeLogin>[] */
private $alternativeLogins = [];
/** @var ServiceRegistration<IAlternativeLoginProvider>[] */
private array $alternativeLoginProviders = [];
/** @var ServiceRegistration<InitialStateProvider>[] */
private $initialStates = [];
@ -251,6 +256,14 @@ class RegistrationContext {
);
}
#[Override]
public function registerAlternativeLoginProvider(string $class): void {
$this->context->registerAlternativeLoginProvider(
$this->appId,
$class
);
}
public function registerInitialStateProvider(string $class): void {
$this->context->registerInitialState(
$this->appId,
@ -495,6 +508,10 @@ class RegistrationContext {
$this->alternativeLogins[] = new ServiceRegistration($appId, $class);
}
public function registerAlternativeLoginProvider(string $appId, string $class): void {
$this->alternativeLoginProviders[] = new ServiceRegistration($appId, $class);
}
public function registerInitialState(string $appId, string $class): void {
$this->initialStates[] = new ServiceRegistration($appId, $class);
}
@ -828,6 +845,13 @@ class RegistrationContext {
return $this->alternativeLogins;
}
/**
* @return ServiceRegistration<IAlternativeLoginProvider>[]
*/
public function getAlternativeLoginProviders(): array {
return $this->alternativeLoginProviders;
}
/**
* @return ServiceRegistration<InitialStateProvider>[]
*/

View file

@ -19,6 +19,7 @@ use OC\SystemConfig;
use OCP\App\AppPathNotFoundException;
use OCP\App\IAppManager;
use OCP\Authentication\IAlternativeLogin;
use OCP\Authentication\IAlternativeLoginProvider;
use OCP\BackgroundJob\IJobList;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IAppConfig;
@ -41,6 +42,8 @@ use function OCP\Log\logger;
* upgrading and removing apps.
*/
class OC_App {
/** @var list<array{name: string, href: string, class: string}> */
private static array $altLogin = [];
private static array $alreadyRegistered = [];
public const supportedApp = 300;
@ -300,12 +303,55 @@ class OC_App {
}
/**
* @return array
* @return list<array{name: string, href: string, class: string}>
*/
public static function getAlternativeLogIns(): array {
/** @var Coordinator $bootstrapCoordinator */
$bootstrapCoordinator = Server::get(Coordinator::class);
foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLoginProviders() as $registration) {
if (!in_array(IAlternativeLoginProvider::class, class_implements($registration->getService()), true)) {
Server::get(LoggerInterface::class)->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
'option' => $registration->getService(),
'interface' => IAlternativeLoginProvider::class,
'app' => $registration->getAppId(),
]);
continue;
}
try {
/** @var IAlternativeLoginProvider $provider */
$provider = Server::get($registration->getService());
} catch (ContainerExceptionInterface $e) {
Server::get(LoggerInterface::class)->error('Alternative login option {option} can not be initialized.',
[
'exception' => $e,
'option' => $registration->getService(),
'app' => $registration->getAppId(),
]);
continue;
}
foreach ($provider->getAlternativeLogins() as $alternativeLogin) {
try {
$alternativeLogin->load();
self::$altLogin[] = [
'name' => $alternativeLogin->getLabel(),
'href' => $alternativeLogin->getLink(),
'class' => $alternativeLogin->getClass(),
];
} catch (Throwable $e) {
Server::get(LoggerInterface::class)->error('Alternative login option {option} had an error while loading.',
[
'exception' => $e,
'option' => $registration->getService(),
'app' => $registration->getAppId(),
]);
}
}
}
foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
Server::get(LoggerInterface::class)->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [

View file

@ -161,9 +161,21 @@ interface IRegistrationContext {
* @return void
*
* @since 20.0.0
* @deprecated 34.0.0 Use registerAlternativeLoginProvider instead.
*/
public function registerAlternativeLogin(string $class): void;
/**
* Register an alternative login options provider
*
* It is allowed to register more than one option per app.
*
* @param class-string<\OCP\Authentication\IAlternativeLoginProvider> $class
*
* @since 34.0.0
*/
public function registerAlternativeLoginProvider(string $class): void;
/**
* Register an initialstate provider
*

View file

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\Authentication;
/**
* Provider exposing one or multiple IAlternativeLogin.
*
* @since 34.0.0
*/
interface IAlternativeLoginProvider {
/**
* @return list<IAlternativeLogin>
* @since 34.0.0
*/
public function getAlternativeLogins(): array;
}