From 7c5617230316e02f09583d46d269dfb957e54b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Roszak?= Date: Thu, 26 Mar 2026 17:24:12 +0100 Subject: [PATCH] feat (2fa): Add IStatelessProvider interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Roszak --- AUTHORS | 1 + lib/composer/composer/LICENSE | 2 -- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + .../TwoFactorAuth/ProviderManager.php | 9 +++++-- .../Authentication/TwoFactorAuth/Registry.php | 9 +++++++ .../TwoFactorAuth/IStatelessProvider.php | 22 ++++++++++++++++ .../TwoFactorAuth/RegistryTest.php | 25 +++++++++++++++++++ 8 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php diff --git a/AUTHORS b/AUTHORS index fe478401fddb4..6af579f94a9f8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -629,6 +629,7 @@ - zorn-v - zulan - Łukasz Buśko + - Michał Roszak - Nextcloud GmbH - ownCloud GmbH - ownCloud, Inc. diff --git a/lib/composer/composer/LICENSE b/lib/composer/composer/LICENSE index f27399a042d95..62ecfd8d0046b 100644 --- a/lib/composer/composer/LICENSE +++ b/lib/composer/composer/LICENSE @@ -1,4 +1,3 @@ - Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy @@ -18,4 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 35992e16837d2..1fe046d1f0dfe 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -185,6 +185,7 @@ 'OCP\\Authentication\\TwoFactorAuth\\IProvidesIcons' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvidesIcons.php', 'OCP\\Authentication\\TwoFactorAuth\\IProvidesPersonalSettings' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvidesPersonalSettings.php', 'OCP\\Authentication\\TwoFactorAuth\\IRegistry' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IRegistry.php', + 'OCP\\Authentication\\TwoFactorAuth\\IStatelessProvider' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php', 'OCP\\Authentication\\TwoFactorAuth\\RegistryEvent' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/RegistryEvent.php', 'OCP\\Authentication\\TwoFactorAuth\\TwoFactorException' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/TwoFactorException.php', 'OCP\\Authentication\\TwoFactorAuth\\TwoFactorProviderChallengeFailed' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderChallengeFailed.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 79c4de8f32767..36c0c7c02f7b2 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -226,6 +226,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Authentication\\TwoFactorAuth\\IProvidesIcons' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvidesIcons.php', 'OCP\\Authentication\\TwoFactorAuth\\IProvidesPersonalSettings' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvidesPersonalSettings.php', 'OCP\\Authentication\\TwoFactorAuth\\IRegistry' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IRegistry.php', + 'OCP\\Authentication\\TwoFactorAuth\\IStatelessProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php', 'OCP\\Authentication\\TwoFactorAuth\\RegistryEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/RegistryEvent.php', 'OCP\\Authentication\\TwoFactorAuth\\TwoFactorException' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/TwoFactorException.php', 'OCP\\Authentication\\TwoFactorAuth\\TwoFactorProviderChallengeFailed' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderChallengeFailed.php', diff --git a/lib/private/Authentication/TwoFactorAuth/ProviderManager.php b/lib/private/Authentication/TwoFactorAuth/ProviderManager.php index f82f666ac9db9..6ed9c7156660f 100644 --- a/lib/private/Authentication/TwoFactorAuth/ProviderManager.php +++ b/lib/private/Authentication/TwoFactorAuth/ProviderManager.php @@ -13,6 +13,7 @@ use OCP\Authentication\TwoFactorAuth\IDeactivatableByAdmin; use OCP\Authentication\TwoFactorAuth\IProvider; use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\Authentication\TwoFactorAuth\IStatelessProvider; use OCP\IUser; class ProviderManager { @@ -42,7 +43,9 @@ private function getProvider(string $providerId, IUser $user): IProvider { public function tryEnableProviderFor(string $providerId, IUser $user): bool { $provider = $this->getProvider($providerId, $user); - if ($provider instanceof IActivatableByAdmin) { + if ($provider instanceof IActivatableByAdmin + && !($provider instanceof IStatelessProvider) + ) { $provider->enableFor($user); $this->providerRegistry->enableProviderFor($provider, $user); return true; @@ -61,7 +64,9 @@ public function tryEnableProviderFor(string $providerId, IUser $user): bool { public function tryDisableProviderFor(string $providerId, IUser $user): bool { $provider = $this->getProvider($providerId, $user); - if ($provider instanceof IDeactivatableByAdmin) { + if ($provider instanceof IDeactivatableByAdmin + && !($provider instanceof IStatelessProvider) + ) { $provider->disableFor($user); $this->providerRegistry->disableProviderFor($provider, $user); return true; diff --git a/lib/private/Authentication/TwoFactorAuth/Registry.php b/lib/private/Authentication/TwoFactorAuth/Registry.php index 6534daa41cac9..89ccaa7671230 100644 --- a/lib/private/Authentication/TwoFactorAuth/Registry.php +++ b/lib/private/Authentication/TwoFactorAuth/Registry.php @@ -11,6 +11,7 @@ use OC\Authentication\TwoFactorAuth\Db\ProviderUserAssignmentDao; use OCP\Authentication\TwoFactorAuth\IProvider; use OCP\Authentication\TwoFactorAuth\IRegistry; +use OCP\Authentication\TwoFactorAuth\IStatelessProvider; use OCP\Authentication\TwoFactorAuth\RegistryEvent; use OCP\Authentication\TwoFactorAuth\TwoFactorProviderDisabled; use OCP\Authentication\TwoFactorAuth\TwoFactorProviderForUserRegistered; @@ -31,6 +32,10 @@ public function getProviderStates(IUser $user): array { } public function enableProviderFor(IProvider $provider, IUser $user) { + if ($provider instanceof IStatelessProvider) { + return; + } + $this->assignmentDao->persist($provider->getId(), $user->getUID(), 1); $event = new RegistryEvent($provider, $user); @@ -39,6 +44,10 @@ public function enableProviderFor(IProvider $provider, IUser $user) { } public function disableProviderFor(IProvider $provider, IUser $user) { + if ($provider instanceof IStatelessProvider) { + return; + } + $this->assignmentDao->persist($provider->getId(), $user->getUID(), 0); $event = new RegistryEvent($provider, $user); diff --git a/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php b/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php new file mode 100644 index 0000000000000..79109dace19e5 --- /dev/null +++ b/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php @@ -0,0 +1,22 @@ +registry->enableProviderFor($provider, $user); } + public function testEnableStatelessProvider(): void { + $user = $this->createMock(IUser::class); + $provider = $this->createMock(IStatelessProvider::class); + + $this->dao->expects($this->never())->method('persist'); + + $this->dispatcher->expects($this->never())->method('dispatch'); + $this->dispatcher->expects($this->never())->method('dispatchTyped'); + + $this->registry->enableProviderFor($provider, $user); + } + public function testDisableProvider(): void { $user = $this->createMock(IUser::class); $provider = $this->createMock(IProvider::class); @@ -103,6 +116,18 @@ public function testDisableProvider(): void { $this->registry->disableProviderFor($provider, $user); } + public function testDisableStatelessProvider(): void { + $user = $this->createMock(IUser::class); + $provider = $this->createMock(IStatelessProvider::class); + + $this->dao->expects($this->never())->method('persist'); + + $this->dispatcher->expects($this->never())->method('dispatch'); + $this->dispatcher->expects($this->never())->method('dispatchTyped'); + + $this->registry->disableProviderFor($provider, $user); + } + public function testDeleteUserData(): void { $user = $this->createMock(IUser::class); $user->expects($this->once())->method('getUID')->willReturn('user123');