Skip to content

Commit 4330404

Browse files
committed
Merge branch '16.next-cake5' into feature/gb-1170
2 parents 8ee3a3e + 92e6dbc commit 4330404

File tree

6 files changed

+98
-3
lines changed

6 files changed

+98
-3
lines changed

.semver

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
22
:major: 16
33
:minor: 0
4-
:patch: 0
4+
:patch: 1
55
:special: ''

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ Changelog
22
=========
33
Releases for CakePHP 5
44
-------------
5+
* 16.0.1
6+
* add event for skipping two-factor authentication verification
57
* 16.0.0
68
* Require CakePHP ^5.3
79
* Added `src/Plugin.php` class which extends `UsersPlugin` for backward compatibility.

Docs/Documentation/Two-Factor-Authenticator.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,32 @@ the QR code shown (image 1).
5252

5353
<img src="OneTimePasswordAuthenticator/App.png?raw=true" width="300"/>
5454

55+
56+
Skipping 2FA Verification
57+
-------------------------
58+
You can conditionally skip the Two-Factor Authentication verify step for specific users
59+
or scenarios by listening to the `UsersPlugin::EVENT_2FA_SKIP_VERIFY` event.
60+
61+
If the event returns `true`, the verification is skipped, and the user is logged in immediately.
62+
The event receives the `user` data as a payload.
63+
64+
**Example: Skipping 2FA based on User Role**
65+
66+
In your `src/Application.php` or a dedicated Event Listener:
67+
68+
```php
69+
use Cake\Event\EventInterface;
70+
use CakeDC\Users\UsersPlugin;
71+
72+
// ...
73+
74+
$this->getEventManager()->on(UsersPlugin::EVENT_2FA_SKIP_VERIFY, function (EventInterface $event) {
75+
$user = $event->getData('user');
76+
77+
// Check if the user has the 'admin' role
78+
if (!empty($user['role']) && $user['role'] === 'admin') {
79+
// Return true to skip the 2FA verification for this user role
80+
$event->setResult(true);
81+
}
82+
});
83+
```

src/Controller/Traits/OneTimePasswordVerifyTrait.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Cake\Core\Configure;
1717
use CakeDC\Auth\Authentication\AuthenticationService;
1818
use CakeDC\Auth\Authenticator\TwoFactorAuthenticator;
19+
use CakeDC\Users\UsersPlugin;
1920

2021
trait OneTimePasswordVerifyTrait
2122
{
@@ -43,6 +44,15 @@ public function verify()
4344
$temporarySession = $this->getRequest()->getSession()->read(
4445
AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY,
4546
);
47+
48+
$event = $this->dispatchEvent(UsersPlugin::EVENT_2FA_SKIP_VERIFY, ['user' => $temporarySession]);
49+
if ($event->getResult() === true) {
50+
$this->getRequest()->getSession()->delete(AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY);
51+
$this->getRequest()->getSession()->write(TwoFactorAuthenticator::USER_SESSION_KEY, $temporarySession);
52+
53+
return $this->redirect($loginAction);
54+
}
55+
4656
$secretVerified = $temporarySession['secret_verified'] ?? null;
4757
// showing QR-code until shared secret is verified
4858
if (!$secretVerified) {
@@ -55,7 +65,10 @@ public function verify()
5565
$temporarySession['email'],
5666
$secret,
5767
);
58-
$this->set(['secretDataUri' => $secretDataUri]);
68+
$this->set([
69+
'secretDataUri' => $secretDataUri,
70+
'secret' => $secret,
71+
]);
5972
}
6073

6174
if ($this->getRequest()->is('post')) {

src/UsersPlugin.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ class UsersPlugin extends BasePlugin
4848
public const EVENT_AFTER_RESEND_TOKEN_VALIDATION = 'Users.Global.afterResendTokenValidation';
4949
public const EVENT_AFTER_EMAIL_TOKEN_VALIDATION = 'Users.Global.afterEmailTokenValidation';
5050

51+
public const EVENT_2FA_SKIP_VERIFY = 'Users.TwoFactor.skipVerify';
52+
5153
public const DEPRECATED_MESSAGE_U2F =
5254
'U2F is no longer supported by chrome, we suggest using Webauthn as a replacement';
5355

tests/TestCase/Controller/Traits/OneTimePasswordVerifyTraitTest.php

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@
1414
namespace CakeDC\Users\Test\TestCase\Controller\Traits;
1515

1616
use Cake\Core\Configure;
17+
use Cake\Event\Event;
1718
use Cake\Http\ServerRequest;
1819
use Cake\ORM\TableRegistry;
20+
use CakeDC\Auth\Authentication\AuthenticationService;
21+
use CakeDC\Auth\Authenticator\TwoFactorAuthenticator;
1922
use CakeDC\Auth\Controller\Component\OneTimePasswordAuthenticatorComponent;
23+
use CakeDC\Users\UsersPlugin;
2024

2125
class OneTimePasswordVerifyTraitTest extends BaseTrait
2226
{
@@ -147,7 +151,7 @@ public function testVerifyGetShowQR()
147151
->will($this->returnValue('newDataUriGenerated'));
148152
$this->Trait->expects($this->once())
149153
->method('set')
150-
->with(['secretDataUri' => 'newDataUriGenerated']);
154+
->with(['secretDataUri' => 'newDataUriGenerated', 'secret' => 'newSecret']);
151155

152156
$this->Trait->verify();
153157
$user = $this->Trait->getUsersTable()->findById('00000000-0000-0000-0000-000000000001')->firstOrFail();
@@ -277,4 +281,49 @@ public function testVerifyGetDoesNotGenerateNewSecret()
277281
$session->read(),
278282
);
279283
}
284+
285+
/**
286+
* testVerifySkipEventCheck
287+
*/
288+
public function testVerifySkipEventCheck()
289+
{
290+
Configure::write('OneTimePasswordAuthenticator.login', true);
291+
$request = $this->getMockBuilder('Cake\Http\ServerRequest')
292+
->onlyMethods(['is', 'getData', 'getSession'])
293+
->addMethods(['allow'])
294+
->getMock();
295+
$this->Trait->setRequest($request);
296+
297+
$userData = [
298+
'id' => 1,
299+
'secret_verified' => 1,
300+
'email' => 'test@example.com',
301+
];
302+
$session = $this->_mockSession([
303+
'temporarySession' => $userData,
304+
]);
305+
306+
$eventMock = $this->getMockBuilder(Event::class)
307+
->disableOriginalConstructor()
308+
->getMock();
309+
$eventMock->method('getResult')->willReturn(true);
310+
311+
$this->Trait->expects($this->once())
312+
->method('dispatchEvent')
313+
->with(UsersPlugin::EVENT_2FA_SKIP_VERIFY, ['user' => $userData])
314+
->willReturn($eventMock);
315+
316+
$this->Trait->expects($this->once())
317+
->method('redirect');
318+
$this->Trait->verify();
319+
320+
$this->assertNull(
321+
$session->read(AuthenticationService::TWO_FACTOR_VERIFY_SESSION_KEY),
322+
);
323+
324+
$this->assertEquals(
325+
$userData,
326+
$session->read(TwoFactorAuthenticator::USER_SESSION_KEY),
327+
);
328+
}
280329
}

0 commit comments

Comments
 (0)