Skip to content

Commit 7c56172

Browse files
committed
feat (2fa): Add IStatelessProvider interface
Signed-off-by: Michał Roszak <m.roszakos@gmail.com>
1 parent 8032ad8 commit 7c56172

8 files changed

Lines changed: 66 additions & 4 deletions

File tree

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@
629629
- zorn-v <zorn7@yandex.ru>
630630
- zulan <git@zulan.net>
631631
- Łukasz Buśko <busko.lukasz@pm.me>
632+
- Michał Roszak <m.roszakos@gmail.com>
632633
- Nextcloud GmbH
633634
- ownCloud GmbH
634635
- ownCloud, Inc.

lib/composer/composer/LICENSE

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
Copyright (c) Nils Adermann, Jordi Boggiano
32

43
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
1817
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1918
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2019
THE SOFTWARE.
21-

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@
185185
'OCP\\Authentication\\TwoFactorAuth\\IProvidesIcons' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvidesIcons.php',
186186
'OCP\\Authentication\\TwoFactorAuth\\IProvidesPersonalSettings' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvidesPersonalSettings.php',
187187
'OCP\\Authentication\\TwoFactorAuth\\IRegistry' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IRegistry.php',
188+
'OCP\\Authentication\\TwoFactorAuth\\IStatelessProvider' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php',
188189
'OCP\\Authentication\\TwoFactorAuth\\RegistryEvent' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/RegistryEvent.php',
189190
'OCP\\Authentication\\TwoFactorAuth\\TwoFactorException' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/TwoFactorException.php',
190191
'OCP\\Authentication\\TwoFactorAuth\\TwoFactorProviderChallengeFailed' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderChallengeFailed.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
226226
'OCP\\Authentication\\TwoFactorAuth\\IProvidesIcons' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvidesIcons.php',
227227
'OCP\\Authentication\\TwoFactorAuth\\IProvidesPersonalSettings' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvidesPersonalSettings.php',
228228
'OCP\\Authentication\\TwoFactorAuth\\IRegistry' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IRegistry.php',
229+
'OCP\\Authentication\\TwoFactorAuth\\IStatelessProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IStatelessProvider.php',
229230
'OCP\\Authentication\\TwoFactorAuth\\RegistryEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/RegistryEvent.php',
230231
'OCP\\Authentication\\TwoFactorAuth\\TwoFactorException' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/TwoFactorException.php',
231232
'OCP\\Authentication\\TwoFactorAuth\\TwoFactorProviderChallengeFailed' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderChallengeFailed.php',

lib/private/Authentication/TwoFactorAuth/ProviderManager.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use OCP\Authentication\TwoFactorAuth\IDeactivatableByAdmin;
1414
use OCP\Authentication\TwoFactorAuth\IProvider;
1515
use OCP\Authentication\TwoFactorAuth\IRegistry;
16+
use OCP\Authentication\TwoFactorAuth\IStatelessProvider;
1617
use OCP\IUser;
1718

1819
class ProviderManager {
@@ -42,7 +43,9 @@ private function getProvider(string $providerId, IUser $user): IProvider {
4243
public function tryEnableProviderFor(string $providerId, IUser $user): bool {
4344
$provider = $this->getProvider($providerId, $user);
4445

45-
if ($provider instanceof IActivatableByAdmin) {
46+
if ($provider instanceof IActivatableByAdmin
47+
&& !($provider instanceof IStatelessProvider)
48+
) {
4649
$provider->enableFor($user);
4750
$this->providerRegistry->enableProviderFor($provider, $user);
4851
return true;
@@ -61,7 +64,9 @@ public function tryEnableProviderFor(string $providerId, IUser $user): bool {
6164
public function tryDisableProviderFor(string $providerId, IUser $user): bool {
6265
$provider = $this->getProvider($providerId, $user);
6366

64-
if ($provider instanceof IDeactivatableByAdmin) {
67+
if ($provider instanceof IDeactivatableByAdmin
68+
&& !($provider instanceof IStatelessProvider)
69+
) {
6570
$provider->disableFor($user);
6671
$this->providerRegistry->disableProviderFor($provider, $user);
6772
return true;

lib/private/Authentication/TwoFactorAuth/Registry.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use OC\Authentication\TwoFactorAuth\Db\ProviderUserAssignmentDao;
1212
use OCP\Authentication\TwoFactorAuth\IProvider;
1313
use OCP\Authentication\TwoFactorAuth\IRegistry;
14+
use OCP\Authentication\TwoFactorAuth\IStatelessProvider;
1415
use OCP\Authentication\TwoFactorAuth\RegistryEvent;
1516
use OCP\Authentication\TwoFactorAuth\TwoFactorProviderDisabled;
1617
use OCP\Authentication\TwoFactorAuth\TwoFactorProviderForUserRegistered;
@@ -31,6 +32,10 @@ public function getProviderStates(IUser $user): array {
3132
}
3233

3334
public function enableProviderFor(IProvider $provider, IUser $user) {
35+
if ($provider instanceof IStatelessProvider) {
36+
return;
37+
}
38+
3439
$this->assignmentDao->persist($provider->getId(), $user->getUID(), 1);
3540

3641
$event = new RegistryEvent($provider, $user);
@@ -39,6 +44,10 @@ public function enableProviderFor(IProvider $provider, IUser $user) {
3944
}
4045

4146
public function disableProviderFor(IProvider $provider, IUser $user) {
47+
if ($provider instanceof IStatelessProvider) {
48+
return;
49+
}
50+
4251
$this->assignmentDao->persist($provider->getId(), $user->getUID(), 0);
4352

4453
$event = new RegistryEvent($provider, $user);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCP\Authentication\TwoFactorAuth;
11+
12+
use OCP\AppFramework\Attribute\Implementable;
13+
14+
/**
15+
* Marks the 2FA provider stateless. That means the state of 2FA activation
16+
* for user will be checked dynamically and not stored in the database.
17+
*
18+
* @since 34.0.0
19+
*/
20+
#[Implementable(since: '34.0.0')]
21+
interface IStatelessProvider extends IProvider {
22+
}

tests/lib/Authentication/TwoFactorAuth/RegistryTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use OC\Authentication\TwoFactorAuth\Registry;
1414
use OCP\Authentication\TwoFactorAuth\IProvider;
1515
use OCP\Authentication\TwoFactorAuth\IRegistry;
16+
use OCP\Authentication\TwoFactorAuth\IStatelessProvider;
1617
use OCP\Authentication\TwoFactorAuth\RegistryEvent;
1718
use OCP\Authentication\TwoFactorAuth\TwoFactorProviderDisabled;
1819
use OCP\Authentication\TwoFactorAuth\TwoFactorProviderForUserRegistered;
@@ -76,6 +77,18 @@ public function testEnableProvider(): void {
7677
$this->registry->enableProviderFor($provider, $user);
7778
}
7879

80+
public function testEnableStatelessProvider(): void {
81+
$user = $this->createMock(IUser::class);
82+
$provider = $this->createMock(IStatelessProvider::class);
83+
84+
$this->dao->expects($this->never())->method('persist');
85+
86+
$this->dispatcher->expects($this->never())->method('dispatch');
87+
$this->dispatcher->expects($this->never())->method('dispatchTyped');
88+
89+
$this->registry->enableProviderFor($provider, $user);
90+
}
91+
7992
public function testDisableProvider(): void {
8093
$user = $this->createMock(IUser::class);
8194
$provider = $this->createMock(IProvider::class);
@@ -103,6 +116,18 @@ public function testDisableProvider(): void {
103116
$this->registry->disableProviderFor($provider, $user);
104117
}
105118

119+
public function testDisableStatelessProvider(): void {
120+
$user = $this->createMock(IUser::class);
121+
$provider = $this->createMock(IStatelessProvider::class);
122+
123+
$this->dao->expects($this->never())->method('persist');
124+
125+
$this->dispatcher->expects($this->never())->method('dispatch');
126+
$this->dispatcher->expects($this->never())->method('dispatchTyped');
127+
128+
$this->registry->disableProviderFor($provider, $user);
129+
}
130+
106131
public function testDeleteUserData(): void {
107132
$user = $this->createMock(IUser::class);
108133
$user->expects($this->once())->method('getUID')->willReturn('user123');

0 commit comments

Comments
 (0)