Skip to content

Commit 5c11f52

Browse files
committed
MC-22950: Enable 2FA by default for Admins
- New implementation for u2f keys
1 parent 35a81d6 commit 5c11f52

File tree

16 files changed

+758
-832
lines changed

16 files changed

+758
-832
lines changed

TwoFactorAuth/Block/Provider/U2fKey/Auth.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ class Auth extends Template
2626
*/
2727
private $session;
2828

29+
/**
30+
* @var array
31+
*/
32+
private $authenticateData;
33+
2934
/**
3035
* @param Template\Context $context
3136
* @param Session $session
@@ -57,9 +62,20 @@ public function getJsLayout()
5762
$this->jsLayout['components']['tfa-auth']['touchImageUrl'] =
5863
$this->getViewFileUrl('Magento_TwoFactorAuth::images/u2f/touch.png');
5964

60-
$this->jsLayout['components']['tfa-auth']['authenticateData'] =
61-
$this->u2fKey->getAuthenticateData($this->session->getUser());
62-
65+
$this->jsLayout['components']['tfa-auth']['authenticateData'] = $this->generateAuthenticateData();
6366
return parent::getJsLayout();
6467
}
68+
69+
/**
70+
* Get the data needed to authenticate a webauthn request
71+
*
72+
* @return array
73+
*/
74+
public function generateAuthenticateData(): array
75+
{
76+
$authenticateData = $this->u2fKey->getAuthenticateData($this->session->getUser());
77+
$this->session->setTfaU2fChallenge($authenticateData['credentialRequestOptions']['challenge']);
78+
79+
return $authenticateData;
80+
}
6581
}

TwoFactorAuth/Block/Provider/U2fKey/Configure.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Magento\TwoFactorAuth\Block\Provider\U2fKey;
99

1010
use Magento\Backend\Block\Template;
11+
use Magento\Backend\Model\Auth\Session;
1112
use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey;
1213

1314
/**
@@ -20,19 +21,27 @@ class Configure extends Template
2021
*/
2122
private $u2fKey;
2223

24+
/**
25+
* @var Session
26+
*/
27+
private $session;
28+
2329
/**
2430
* @param Template\Context $context
2531
* @param U2fKey $u2fKey
32+
* @param Session $session
2633
* @param array $data
2734
*/
2835
public function __construct(
2936
Template\Context $context,
3037
U2fKey $u2fKey,
38+
Session $session,
3139
array $data = []
3240
) {
3341

3442
parent::__construct($context, $data);
3543
$this->u2fKey = $u2fKey;
44+
$this->session = $session;
3645
}
3746

3847
/**
@@ -50,7 +59,7 @@ public function getJsLayout()
5059
$this->getViewFileUrl('Magento_TwoFactorAuth::images/u2f/touch.png');
5160

5261
$this->jsLayout['components']['tfa-configure']['registerData'] =
53-
$this->u2fKey->getRegisterData();
62+
$this->u2fKey->getRegisterData($this->session->getUser());
5463

5564
return parent::getJsLayout();
5665
}

TwoFactorAuth/Controller/Adminhtml/U2f/Auth.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use Magento\TwoFactorAuth\Api\UserConfigManagerInterface;
1616
use Magento\TwoFactorAuth\Controller\Adminhtml\AbstractAction;
1717
use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey;
18-
use Magento\User\Model\User;
1918

2019
/**
2120
* UbiKey authentication controller

TwoFactorAuth/Controller/Adminhtml/U2f/Authpost.php

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
use Magento\User\Model\User;
2323

2424
/**
25-
* UbiKey Authentication post controller
25+
* U2f key Authentication post controller
26+
*
2627
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2728
* @SuppressWarnings(PHPMD.CamelCaseMethodName)
2829
*/
@@ -92,12 +93,21 @@ public function execute()
9293
$result = $this->jsonFactory->create();
9394

9495
try {
95-
$this->u2fKey->verify($this->getUser(), $this->dataObjectFactory->create([
96-
'data' => $this->getRequest()->getParams(),
97-
]));
98-
$this->tfaSession->grantAccess();
99-
100-
$res = ['success' => true];
96+
$challenge = $this->session->getTfaU2fChallenge();
97+
if (!empty($challenge)) {
98+
$this->u2fKey->verify($this->getUser(), $this->dataObjectFactory->create([
99+
'data' => [
100+
'publicKeyCredential' => $this->getRequest()->getParams()['publicKeyCredential'],
101+
'originalChallenge' => $challenge
102+
]
103+
]));
104+
$this->tfaSession->grantAccess();
105+
$this->session->unsTfaU2fChallenge();
106+
107+
$res = ['success' => true];
108+
} else {
109+
$res = ['success' => false];
110+
}
101111
} catch (Exception $e) {
102112
$this->alert->event(
103113
'Magento_TwoFactorAuth',

TwoFactorAuth/Controller/Adminhtml/U2f/Configurepost.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
namespace Magento\TwoFactorAuth\Controller\Adminhtml\U2f;
99

10-
use Exception;
1110
use Magento\Backend\App\Action\Context;
1211
use Magento\Backend\Model\Auth\Session;
1312
use Magento\Framework\App\Action\HttpPostActionInterface;
@@ -21,7 +20,8 @@
2120
use Magento\TwoFactorAuth\Model\UserConfig\HtmlAreaTokenVerifier;
2221

2322
/**
24-
* UbiKey configuration post controller
23+
* U2f key configuration post controller
24+
*
2525
* @SuppressWarnings(PHPMD.CamelCaseMethodName)
2626
*/
2727
class Configurepost extends AbstractConfigureAction implements HttpPostActionInterface
@@ -94,10 +94,9 @@ public function execute()
9494
$result = $this->jsonFactory->create();
9595

9696
try {
97-
$request = $this->getRequest()->getParam('request');
98-
$response = $this->getRequest()->getParam('response');
97+
$data = $this->getRequest()->getParam('publicKeyCredential');
9998

100-
$this->u2fKey->registerDevice($this->getUser(), $request, $response);
99+
$this->u2fKey->registerDevice($this->getUser(), $data);
101100
$this->tfaSession->grantAccess();
102101

103102
$this->alert->event(

TwoFactorAuth/Model/Provider/Engine/U2fKey.php

Lines changed: 28 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,11 @@
1010
use Magento\Framework\DataObject;
1111
use Magento\Framework\Exception\LocalizedException;
1212
use Magento\Framework\Exception\NoSuchEntityException;
13-
use Magento\Store\Model\Store;
1413
use Magento\Store\Model\StoreManagerInterface;
14+
use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey\WebAuthn;
1515
use Magento\User\Api\Data\UserInterface;
1616
use Magento\TwoFactorAuth\Api\UserConfigManagerInterface;
1717
use Magento\TwoFactorAuth\Api\EngineInterface;
18-
use stdClass;
19-
use u2flib_server\Error;
20-
use u2flib_server\Registration;
21-
use u2flib_server\U2F;
2218

2319
/**
2420
* UbiKey engine
@@ -42,92 +38,72 @@ class U2fKey implements EngineInterface
4238
*/
4339
private $storeManager;
4440

41+
/**
42+
* @var WebAuthn
43+
*/
44+
private $webAuthn;
45+
4546
/**
4647
* @param StoreManagerInterface $storeManager
47-
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
4848
* @param UserConfigManagerInterface $userConfigManager
49+
* @param WebAuthn $webAuthn
4950
*/
5051
public function __construct(
5152
StoreManagerInterface $storeManager,
52-
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
53-
UserConfigManagerInterface $userConfigManager
53+
UserConfigManagerInterface $userConfigManager,
54+
WebAuthn $webAuthn
5455
) {
5556
$this->userConfigManager = $userConfigManager;
5657
$this->storeManager = $storeManager;
57-
}
58-
59-
/**
60-
* Converts array to object
61-
* @param array $hash
62-
* @return stdClass
63-
*/
64-
private function hashToObject(array $hash): stdClass
65-
{
66-
// @codingStandardsIgnoreStart
67-
$object = new stdClass();
68-
// @codingStandardsIgnoreEnd
69-
foreach ($hash as $key => $value) {
70-
$object->$key = $value;
71-
}
72-
73-
return $object;
58+
$this->webAuthn = $webAuthn;
7459
}
7560

7661
/**
7762
* @inheritDoc
7863
*/
7964
public function verify(UserInterface $user, DataObject $request): bool
8065
{
81-
$u2f = $this->getU2f();
82-
8366
$registration = $this->getRegistration($user);
8467
if ($registration === null) {
8568
throw new LocalizedException(__('Missing registration data'));
8669
}
8770

88-
$requests = [$this->hashToObject($request->getData('request')[0])];
89-
$registrations = [$this->hashToObject($registration)];
90-
$response = $this->hashToObject($request->getData('response'));
91-
92-
// it triggers an error in case of auth failure
93-
$u2f->doAuthenticate($requests, $registrations, $response);
71+
$this->webAuthn->assertCredentialDataIsValid(
72+
$request->getData('publicKeyCredential'),
73+
$registration['public_keys'],
74+
$request->getData('originalChallenge')
75+
);
9476

9577
return true;
9678
}
9779

9880
/**
9981
* Create the registration challenge
82+
*
83+
* @param UserInterface $user
10084
* @return array
10185
* @throws LocalizedException
102-
* @throws Error
10386
*/
104-
public function getRegisterData(): array
87+
public function getRegisterData(UserInterface $user): array
10588
{
106-
$u2f = $this->getU2f();
107-
return $u2f->getRegisterData();
89+
return $this->webAuthn->getRegisterData($user);
10890
}
10991

11092
/**
11193
* Get authenticate data
94+
*
11295
* @param UserInterface $user
11396
* @return array
11497
* @throws LocalizedException
115-
* @throws Error
11698
*/
11799
public function getAuthenticateData(UserInterface $user): array
118100
{
119-
$u2f = $this->getU2f();
120-
121-
$registration = $this->getRegistration($user);
122-
if ($registration === null) {
123-
throw new LocalizedException(__('Missing registration data'));
124-
}
125-
126-
return $u2f->getAuthenticateData([$this->hashToObject($registration)]);
101+
return $this->webAuthn->getAuthenticateData($this->getRegistration($user)['public_keys']);
127102
}
128103

129104
/**
130105
* Get registration information
106+
*
131107
* @param UserInterface $user
132108
* @return array
133109
* @throws NoSuchEntityException
@@ -145,34 +121,22 @@ private function getRegistration(UserInterface $user): array
145121

146122
/**
147123
* Register a new key
124+
*
148125
* @param UserInterface $user
149-
* @param array $request
150-
* @param array $response
151-
* @return Registration
152-
* @throws LocalizedException
126+
* @param array $data
153127
* @throws NoSuchEntityException
154-
* @throws Error
128+
* @throws \Magento\Framework\Validation\ValidationException
155129
*/
156-
public function registerDevice(UserInterface $user, array $request, array $response): Registration
130+
public function registerDevice(UserInterface $user, array $data): void
157131
{
158-
// Must convert to object
159-
$request = $this->hashToObject($request);
160-
$response = $this->hashToObject($response);
161-
162-
$u2f = $this->getU2f();
163-
$res = $u2f->doRegister($request, $response);
132+
$publicKey = $this->webAuthn->getPublicKeyFromRegistrationData($data);
164133

165134
$this->userConfigManager->addProviderConfig((int) $user->getId(), static::CODE, [
166135
'registration' => [
167-
'certificate' => $res->certificate,
168-
'keyHandle' => $res->keyHandle,
169-
'publicKey' => $res->publicKey,
170-
'counter' => $res->counter,
136+
'public_keys' => [$publicKey]
171137
]
172138
]);
173139
$this->userConfigManager->activateProviderConfiguration((int) $user->getId(), static::CODE);
174-
175-
return $res;
176140
}
177141

178142
/**
@@ -182,27 +146,4 @@ public function isEnabled(): bool
182146
{
183147
return true;
184148
}
185-
186-
/**
187-
* @return U2F
188-
* @throws LocalizedException
189-
* @throws Error
190-
*/
191-
private function getU2f(): U2F
192-
{
193-
/** @var Store $store */
194-
$store = $this->storeManager->getStore(Store::ADMIN_CODE);
195-
196-
$baseUrl = $store->getBaseUrl();
197-
if (preg_match('/^(https?:\/\/.+?)\//', $baseUrl, $matches)) {
198-
$domain = $matches[1];
199-
} else {
200-
throw new LocalizedException(__('Unexpected error while parsing domain name'));
201-
}
202-
203-
/** @var U2F $u2f */
204-
// @codingStandardsIgnoreStart
205-
return new U2F($domain);
206-
// @codingStandardsIgnoreEnd
207-
}
208149
}

0 commit comments

Comments
 (0)