From 79013e0a131cf9e7ae4a5362509584560f97938c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Roszak?= Date: Fri, 19 Dec 2025 16:52:56 +0100 Subject: [PATCH 1/6] 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 + .../TwoFactorAuth/ProviderManager.php | 9 +++++-- .../Authentication/TwoFactorAuth/Registry.php | 9 +++++++ .../TwoFactorAuth/IStatelessProvider.php | 21 ++++++++++++++++ .../TwoFactorAuth/RegistryTest.php | 25 +++++++++++++++++++ 5 files changed, 63 insertions(+), 2 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/private/Authentication/TwoFactorAuth/ProviderManager.php b/lib/private/Authentication/TwoFactorAuth/ProviderManager.php index 5ce4c5981546f..dedf2cd604e30 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 { @@ -47,7 +48,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; @@ -66,7 +69,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 544f60c4f97ec..6d9142e606875 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; @@ -37,6 +38,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); @@ -45,6 +50,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..cbd359666ab6a --- /dev/null +++ b/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php @@ -0,0 +1,21 @@ +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); @@ -108,6 +121,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'); From 739bd3d77b23836fc9f8258bd458b8a942c85ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Roszak?= Date: Sat, 27 Dec 2025 15:38:45 +0100 Subject: [PATCH 2/6] refactor (2fa): Change IStatelessProvider definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Roszak --- .../Authentication/TwoFactorAuth/IStatelessProvider.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php b/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php index cbd359666ab6a..4591a47b5ff47 100644 --- a/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php +++ b/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php @@ -9,13 +9,14 @@ namespace OCP\Authentication\TwoFactorAuth; -use OCP\IUser; +use OCP\AppFramework\Attribute\Implementable; /** * Marks the 2FA provider stateless. That means the state of 2FA activation * for user will be checked dynamically and not stored in the database. + * + * @since 33.0.0 */ +#[Implementable(since: '33.0.0')] interface IStatelessProvider extends IProvider { - - public function isTwoFactorAuthEnabledForUser(IUser $user): bool; } From e509c3dc24795250820b2ec4737feba389c45c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Roszak?= Date: Tue, 6 Jan 2026 12:14:33 +0100 Subject: [PATCH 3/6] refactor (2fa): Correct IStatelessProvider license MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Roszak --- .../Authentication/TwoFactorAuth/IStatelessProvider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php b/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php index 4591a47b5ff47..237739d3440e9 100644 --- a/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php +++ b/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php @@ -3,8 +3,8 @@ declare(strict_types=1); /** - * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-only + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Authentication\TwoFactorAuth; From 7ebc509fb66981bfc52028c5ad178a00ae09693d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Roszak?= Date: Mon, 2 Mar 2026 17:10:53 +0100 Subject: [PATCH 4/6] refactor (2fa): Change NC target version in IStatelessProvider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Roszak --- .../Authentication/TwoFactorAuth/IStatelessProvider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php b/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php index 237739d3440e9..79109dace19e5 100644 --- a/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php +++ b/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php @@ -15,8 +15,8 @@ * Marks the 2FA provider stateless. That means the state of 2FA activation * for user will be checked dynamically and not stored in the database. * - * @since 33.0.0 + * @since 34.0.0 */ -#[Implementable(since: '33.0.0')] +#[Implementable(since: '34.0.0')] interface IStatelessProvider extends IProvider { } From b97494dfc428f9c541e6f9ad9dc509dc53e252f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Roszak?= Date: Mon, 2 Mar 2026 17:13:17 +0100 Subject: [PATCH 5/6] fix: update autoloaders with IStatelessProvider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Roszak --- lib/composer/composer/LICENSE | 2 ++ lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + 3 files changed, 4 insertions(+) diff --git a/lib/composer/composer/LICENSE b/lib/composer/composer/LICENSE index 62ecfd8d0046b..f27399a042d95 100644 --- a/lib/composer/composer/LICENSE +++ b/lib/composer/composer/LICENSE @@ -1,3 +1,4 @@ + Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy @@ -17,3 +18,4 @@ 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 0b6dd29f2040b..b11bca1ff63c2 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -181,6 +181,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 9c34d2cd4debd..89a1569fdb03d 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -222,6 +222,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', From 4fe13320eaad1ec8e3dfdbe00889e004814ee358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Roszak?= Date: Tue, 3 Mar 2026 09:59:34 +0100 Subject: [PATCH 6/6] refactor: revert lib/composer/composer/LICENSE line changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Roszak --- lib/composer/composer/LICENSE | 2 -- 1 file changed, 2 deletions(-) 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. -