From 3e2c2c522591492fcbdc4013f99142b471cb5227 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 27 Mar 2020 16:10:56 -0500 Subject: [PATCH 01/58] MC-30536: Enable 2FA for web API - Base behavior - Google implementation --- .../Api/Data/GoogleConfigureInterface.php | 75 +++++++++ .../Api/GoogleAuthenticateInterface.php | 24 +++ .../Api/GoogleConfigureInterface.php | 27 ++++ TwoFactorAuth/Api/TfaInterface.php | 5 + TwoFactorAuth/Model/EmailUserNotifier.php | 48 +++++- .../Provider/Engine/Google/Authenticate.php | 125 +++++++++++++++ .../Engine/Google/ConfigurationData.php | 84 ++++++++++ .../Provider/Engine/Google/Configure.php | 107 +++++++++++++ .../Plugin/BlockAdminTokenCreation.php | 149 ++++++++++++++++++ TwoFactorAuth/etc/adminhtml/system.xml | 5 + TwoFactorAuth/etc/di.xml | 2 + TwoFactorAuth/etc/webapi.xml | 30 +++- TwoFactorAuth/etc/webapi_rest/di.xml | 13 ++ TwoFactorAuth/etc/webapi_soap/di.xml | 13 ++ 14 files changed, 692 insertions(+), 15 deletions(-) create mode 100644 TwoFactorAuth/Api/Data/GoogleConfigureInterface.php create mode 100644 TwoFactorAuth/Api/GoogleAuthenticateInterface.php create mode 100644 TwoFactorAuth/Api/GoogleConfigureInterface.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/Google/ConfigurationData.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/Google/Configure.php create mode 100644 TwoFactorAuth/Plugin/BlockAdminTokenCreation.php create mode 100644 TwoFactorAuth/etc/webapi_rest/di.xml create mode 100644 TwoFactorAuth/etc/webapi_soap/di.xml diff --git a/TwoFactorAuth/Api/Data/GoogleConfigureInterface.php b/TwoFactorAuth/Api/Data/GoogleConfigureInterface.php new file mode 100644 index 00000000..04deeac0 --- /dev/null +++ b/TwoFactorAuth/Api/Data/GoogleConfigureInterface.php @@ -0,0 +1,75 @@ +scopeConfig = $scopeConfig; $this->transportBuilder = $transportBuilder; $this->storeManager = $storeManager; $this->logger = $logger; $this->url = $url; + $this->appState = $appState; } /** @@ -75,12 +86,25 @@ public function __construct( * @param User $user * @param string $token * @param string $emailTemplateId + * @param bool $useWebApiUrl * @return void - * @throws NotificationExceptionInterface */ - private function sendConfigRequired(User $user, string $token, string $emailTemplateId): void - { + private function sendConfigRequired( + User $user, + string $token, + string $emailTemplateId, + bool $useWebApiUrl = false + ): void { try { + $userUrl = $this->scopeConfig->getValue(TfaInterface::XML_PATH_WEBAPI_CONFIG_EMAIL_URL); + if ($useWebApiUrl && $userUrl) { + $url = $userUrl . + (parse_url($userUrl, PHP_URL_QUERY) ? '&' : '?') . + http_build_query(['tfat' => $token, 'user_id' => $user->getId()]); + } else { + $url = $this->url->getUrl('tfa/tfa/index', ['tfat' => $token]); + } + $transport = $this->transportBuilder ->setTemplateIdentifier($emailTemplateId) ->setTemplateOptions([ @@ -92,7 +116,7 @@ private function sendConfigRequired(User $user, string $token, string $emailTemp 'username' => $user->getFirstName() . ' ' . $user->getLastName(), 'token' => $token, 'store_name' => $this->storeManager->getStore()->getFrontendName(), - 'url' => $this->url->getUrl('tfa/tfa/index', ['tfat' => $token]) + 'url' => $url ] ) ->setFromByScope( @@ -112,7 +136,7 @@ private function sendConfigRequired(User $user, string $token, string $emailTemp */ public function sendUserConfigRequestMessage(User $user, string $token): void { - $this->sendConfigRequired($user, $token, 'tfa_admin_user_config_required'); + $this->sendConfigRequired($user, $token, 'tfa_admin_user_config_required', $this->isWebapi()); } /** @@ -120,6 +144,16 @@ public function sendUserConfigRequestMessage(User $user, string $token): void */ public function sendAppConfigRequestMessage(User $user, string $token): void { - $this->sendConfigRequired($user, $token, 'tfa_admin_app_config_required'); + $this->sendConfigRequired($user, $token, 'tfa_admin_app_config_required', false); + } + + /** + * Determine if the environment is webapi or not + * + * @return bool + */ + private function isWebapi(): bool + { + return in_array($this->appState->getAreaCode(), [Area::AREA_WEBAPI_REST, Area::AREA_WEBAPI_SOAP]); } } diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php new file mode 100644 index 00000000..dc7bc11c --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php @@ -0,0 +1,125 @@ +google = $google; + $this->userResource = $userResource; + $this->userFactory = $userFactory; + $this->tfa = $tfa; + $this->dataObjectFactory = $dataObjectFactory; + $this->alert = $alert; + $this->tokenFactory = $tokenFactory; + } + + /** + * Get an admin token by authenticating using google + * + * @param int $userId + * @param string $otp + * @return string + */ + public function getToken(int $userId, string $otp): string + { + if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } + + $user = $this->userFactory->create(); + $this->userResource->load($user, $userId); + + if ($this->google->verify($user, $this->dataObjectFactory->create([ + 'data' => [ + 'tfa_code' => $otp + ], + ])) + ) { + if ($this->tfa->getProvider(Google::CODE)->isActive($userId)) { + $this->tfa->getProvider(Google::CODE)->activate((int)$user->getId()); + } + + $this->alert->event( + 'Magento_TwoFactorAuth', + 'New Google Authenticator code issued', + AlertInterface::LEVEL_INFO, + $user->getUserName() + ); + + return $this->tokenFactory->create()->createAdminToken($userId)->getToken(); + } else { + throw new AuthorizationException(__('Invalid code.')); + } + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/ConfigurationData.php b/TwoFactorAuth/Model/Provider/Engine/Google/ConfigurationData.php new file mode 100644 index 00000000..baf65f02 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/Google/ConfigurationData.php @@ -0,0 +1,84 @@ +_get(self::QR_CODE_URL); + } + + /** + * Set value for qr code url + * + * @param string $value + * @return void + */ + public function setQrCodeUrl(string $value): void + { + $this->setData(self::QR_CODE_URL, $value); + } + + /** + * Get value for secret code + * + * @return string + */ + public function getSecretCode(): string + { + return (string)$this->_get(self::SECRET_CODE); + } + + /** + * Set value for secret code + * + * @param string $value + * @return void + */ + public function setSecretCode(string $value): void + { + $this->setData(self::SECRET_CODE, $value); + } + + /** + * Retrieve existing extension attributes object or create a new one + * + * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation + * + * @return \Magento\TwoFactorAuth\Api\Data\GoogleConfigureExtensionInterface|null + */ + public function getExtensionAttributes(): ?GoogleConfigureExtensionInterface + { + return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object + * + * @param \Magento\TwoFactorAuth\Api\Data\GoogleConfigureExtensionInterface $extensionAttributes + */ + public function setExtensionAttributes(GoogleConfigureExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php new file mode 100644 index 00000000..9beaffa3 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php @@ -0,0 +1,107 @@ +configurationDataFactory = $configurationDataFactory; + $this->google = $google; + $this->tokenManager = $tokenManager; + $this->userResource = $userResource; + $this->userFactory = $userFactory; + $this->tfa = $tfa; + } + + /** + * @inheritDoc + */ + public function getConfigurationData(int $userId, string $tfat): GoogleConfigurationData + { + if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } + + if (!$this->tokenManager->isValidFor($userId, $tfat)) { + throw new AuthorizationException( + __('Invalid tfat token') + ); + } + $user = $this->userFactory->create(); + $this->userResource->load($user, $userId); + + return $this->configurationDataFactory->create( + [ + 'data' => [ + GoogleConfigurationData::QR_CODE_URL => + 'data:image/png;base64,' . base64_encode($this->google->getQrCodeAsPng($user)), + GoogleConfigurationData::SECRET_CODE => $this->google->getSecretCode($user) + ] + ] + ); + } +} diff --git a/TwoFactorAuth/Plugin/BlockAdminTokenCreation.php b/TwoFactorAuth/Plugin/BlockAdminTokenCreation.php new file mode 100644 index 00000000..5414b8bb --- /dev/null +++ b/TwoFactorAuth/Plugin/BlockAdminTokenCreation.php @@ -0,0 +1,149 @@ +credentialsValidator = $credentialsValidator; + $this->tfa = $tfa; + $this->configRequestManager = $configRequestManager; + $this->auth = $auth; + $this->requestThrottler = $requestThrottler; + $this->adminUser = $adminUser; + } + + /** + * Intercept valid login attempt for use with tfa + * + * @param AdminTokenService $subject + * @param string $username + * @param string $password + * @return bool|null + * @throws WebapiException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeCreateAdminAccessToken( + AdminTokenService $subject, + string $username, + string $password + ): void { + $user = $this->getUserWithCredentials($username, $password); + if (!$user || !$user->getId()) { + // Default behavior + return; + } + + if (!$this->configRequestManager->isConfigurationRequiredFor((int)$user->getId())) { + throw new WebapiException( + __( + 'Please use the 2fa provider-specific endpoints to obtain a token.' + ) + ); + } elseif (empty($this->tfa->getUserProviders((int)$user->getId()))) { + // It is expected that available 2fa providers are selected via db or admin ui + throw new WebapiException( + __('Please ask an administrator with sufficient access to configure 2FA first') + ); + } + + try { + $this->configRequestManager->sendConfigRequestTo($user); + } catch (AuthorizationException|NotificationExceptionInterface $exception) { + throw new WebapiException( + __('Failed to send the message. Please contact the administrator') + ); + } + + throw new WebapiException( + __( + 'You are required to configure personal Two-Factor Authorization in order to login. ' + . 'Please check your email.' + ) + ); + } + + /** + * Check if the given credentials are a valid admin account + * + * @param string $username + * @param string $password + * @return User|null + */ + private function getUserWithCredentials(string $username, string $password): ?User + { + try { + $this->credentialsValidator->validate($username, $password); + $this->requestThrottler->throttle($username, RequestThrottler::USER_TYPE_ADMIN); + + return $this->adminUser->login($username, $password); + } catch (\Exception $e) { + return null; + } + } +} diff --git a/TwoFactorAuth/etc/adminhtml/system.xml b/TwoFactorAuth/etc/adminhtml/system.xml index 94180a24..4d72c3aa 100644 --- a/TwoFactorAuth/etc/adminhtml/system.xml +++ b/TwoFactorAuth/etc/adminhtml/system.xml @@ -30,6 +30,11 @@ Two-factor authorization providers for admin users to use during login Magento\TwoFactorAuth\Model\Config\Backend\ForceProviders + + + This can be used to override the default email configuration link that is sent when using the Magento Web API's to authenticate. + + + diff --git a/TwoFactorAuth/etc/webapi.xml b/TwoFactorAuth/etc/webapi.xml index 917171ac..f5aaa9cd 100644 --- a/TwoFactorAuth/etc/webapi.xml +++ b/TwoFactorAuth/etc/webapi.xml @@ -9,58 +9,72 @@ xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd"> - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + diff --git a/TwoFactorAuth/etc/webapi_rest/di.xml b/TwoFactorAuth/etc/webapi_rest/di.xml new file mode 100644 index 00000000..f7f6f0f8 --- /dev/null +++ b/TwoFactorAuth/etc/webapi_rest/di.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/TwoFactorAuth/etc/webapi_soap/di.xml b/TwoFactorAuth/etc/webapi_soap/di.xml new file mode 100644 index 00000000..f7f6f0f8 --- /dev/null +++ b/TwoFactorAuth/etc/webapi_soap/di.xml @@ -0,0 +1,13 @@ + + + + + + + From 34ded5bec59f99d5fcfa07279cd78069f21af982 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Tue, 31 Mar 2020 15:34:39 -0500 Subject: [PATCH 02/58] MC-30536: Enable 2FA for web API - Basic webapi override plugin - Google endpoints - Test coverage --- .../Api/Data/GoogleAuthenticateInterface.php | 52 +++++ TwoFactorAuth/Api/Data/TfaTokenInterface.php | 52 +++++ .../Api/GoogleAuthenticateInterface.php | 6 +- .../Api/GoogleConfigureInterface.php | 6 +- .../Engine/Google/AuthenticateData.php | 57 ++++++ .../Engine/Google/ConfigurationData.php | 3 +- TwoFactorAuth/Model/Data/TfaToken.php | 57 ++++++ .../Model/Provider/Engine/Google.php | 4 +- .../Provider/Engine/Google/Authenticate.php | 9 +- .../Provider/Engine/Google/Configure.php | 6 +- .../Model/UserConfig/SignedTokenManager.php | 2 +- .../Test/Api/AdminIntegrationTokenTest.php | 184 ++++++++++++++++++ .../Test/Api/GoogleAuthenticateTest.php | 143 ++++++++++++++ .../Test/Api/GoogleConfigureTest.php | 119 +++++++++++ TwoFactorAuth/etc/di.xml | 2 + TwoFactorAuth/etc/webapi.xml | 11 +- 16 files changed, 689 insertions(+), 24 deletions(-) create mode 100644 TwoFactorAuth/Api/Data/GoogleAuthenticateInterface.php create mode 100644 TwoFactorAuth/Api/Data/TfaTokenInterface.php create mode 100644 TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php rename TwoFactorAuth/Model/{ => Data}/Provider/Engine/Google/ConfigurationData.php (96%) create mode 100644 TwoFactorAuth/Model/Data/TfaToken.php create mode 100644 TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php create mode 100644 TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php create mode 100644 TwoFactorAuth/Test/Api/GoogleConfigureTest.php diff --git a/TwoFactorAuth/Api/Data/GoogleAuthenticateInterface.php b/TwoFactorAuth/Api/Data/GoogleAuthenticateInterface.php new file mode 100644 index 00000000..93cbece1 --- /dev/null +++ b/TwoFactorAuth/Api/Data/GoogleAuthenticateInterface.php @@ -0,0 +1,52 @@ +_get(self::OTP); + } + + /** + * @inheritDoc + */ + public function setOtp(string $value): void + { + $this->setData(self::OTP, $value); + } + + /** + * Retrieve existing extension attributes object or create a new one + * + * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation + * + * @return \Magento\TwoFactorAuth\Api\Data\GoogleAuthenticateExtensionInterface|null + */ + public function getExtensionAttributes(): ?GoogleAuthenticateExtensionInterface + { + return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object + * + * @param \Magento\TwoFactorAuth\Api\Data\GoogleAuthenticateExtensionInterface $extensionAttributes + */ + public function setExtensionAttributes(GoogleAuthenticateExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/ConfigurationData.php b/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php similarity index 96% rename from TwoFactorAuth/Model/Provider/Engine/Google/ConfigurationData.php rename to TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php index baf65f02..d498ae1d 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google/ConfigurationData.php +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php @@ -6,7 +6,7 @@ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Model\Provider\Engine\Google; +namespace Magento\TwoFactorAuth\Model\Data\Provider\Engine\Google; use Magento\Framework\Api\AbstractExtensibleObject; use Magento\TwoFactorAuth\Api\Data\GoogleConfigureExtensionInterface; @@ -17,7 +17,6 @@ */ class ConfigurationData extends AbstractExtensibleObject implements GoogleConfigureInterface { - /** * Get value for qr code url * diff --git a/TwoFactorAuth/Model/Data/TfaToken.php b/TwoFactorAuth/Model/Data/TfaToken.php new file mode 100644 index 00000000..849aab59 --- /dev/null +++ b/TwoFactorAuth/Model/Data/TfaToken.php @@ -0,0 +1,57 @@ +_getData(static::TOKEN); + } + + /** + * @inheritDoc + */ + public function setToken(string $value): void + { + $this->setData(static::TOKEN, $value); + } + + /** + * Retrieve existing extension attributes object or create a new one + * + * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation + * + * @return \Magento\TwoFactorAuth\Api\Data\TfaTokenExtensionInterface|null + */ + public function getExtensionAttributes(): ?TfaTokenExtensionInterface + { + return $this->_getData(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object + * + * @param \Magento\TwoFactorAuth\Api\Data\TfaTokenExtensionInterface $extensionAttributes + */ + public function setExtensionAttributes(TfaTokenExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/Google.php b/TwoFactorAuth/Model/Provider/Engine/Google.php index cfa85474..aa3a7819 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google.php @@ -142,9 +142,9 @@ public function verify(UserInterface $user, DataObject $request): bool } $totp = $this->getTotp($user); - $totp->now(); + $config = $this->configManager->getProviderConfig((int)$user->getId(), static::CODE); - return $totp->verify($token); + return $totp->verify($token, null, $config['window'] ?? null); } /** diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php index dc7bc11c..19138234 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php @@ -11,6 +11,7 @@ use Magento\Framework\DataObjectFactory; use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Webapi\Exception as WebApiException; +use Magento\TwoFactorAuth\Api\Data\GoogleAuthenticateInterface as GoogleAuthenticateInterfaceData; use Magento\TwoFactorAuth\Api\GoogleAuthenticateInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\AlertInterface; @@ -88,10 +89,12 @@ public function __construct( * Get an admin token by authenticating using google * * @param int $userId - * @param string $otp + * @param GoogleAuthenticateInterfaceData $data * @return string + * @throws AuthorizationException + * @throws WebApiException */ - public function getToken(int $userId, string $otp): string + public function getToken(int $userId, GoogleAuthenticateInterfaceData $data): string { if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { throw new WebApiException(__('Provider is not allowed.')); @@ -102,7 +105,7 @@ public function getToken(int $userId, string $otp): string if ($this->google->verify($user, $this->dataObjectFactory->create([ 'data' => [ - 'tfa_code' => $otp + 'tfa_code' => $data->getOtp() ], ])) ) { diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php index 9beaffa3..28de13a4 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php @@ -11,10 +11,12 @@ use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Webapi\Exception as WebApiException; use Magento\TwoFactorAuth\Api\Data\GoogleConfigureInterface as GoogleConfigurationData; +use Magento\TwoFactorAuth\Api\Data\TfaTokenInterface; use Magento\TwoFactorAuth\Api\GoogleConfigureInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; +use Magento\TwoFactorAuth\Model\Data\Provider\Engine\Google\ConfigurationDataFactory; use Magento\User\Model\ResourceModel\User; use Magento\User\Model\UserFactory; @@ -80,13 +82,13 @@ public function __construct( /** * @inheritDoc */ - public function getConfigurationData(int $userId, string $tfat): GoogleConfigurationData + public function getConfigurationData(int $userId, TfaTokenInterface $tfaToken): GoogleConfigurationData { if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { throw new WebApiException(__('Provider is not allowed.')); } - if (!$this->tokenManager->isValidFor($userId, $tfat)) { + if (!$this->tokenManager->isValidFor($userId, $tfaToken->getToken())) { throw new AuthorizationException( __('Invalid tfat token') ); diff --git a/TwoFactorAuth/Model/UserConfig/SignedTokenManager.php b/TwoFactorAuth/Model/UserConfig/SignedTokenManager.php index d0d3e93c..0bda1904 100644 --- a/TwoFactorAuth/Model/UserConfig/SignedTokenManager.php +++ b/TwoFactorAuth/Model/UserConfig/SignedTokenManager.php @@ -64,8 +64,8 @@ public function issueFor(int $userId): string public function isValidFor(int $userId, string $token): bool { $isValid = false; - [$encodedData, $signatureProvided] = explode('.', base64_decode($token)); try { + [$encodedData, $signatureProvided] = explode('.', base64_decode($token)); $data = $this->json->unserialize($encodedData); if (array_key_exists('user_id', $data) && array_key_exists('tfa_configuration', $data) diff --git a/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php new file mode 100644 index 00000000..68d6223b --- /dev/null +++ b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php @@ -0,0 +1,184 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->userConfig = $objectManager->get(UserConfigManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + $this->config = $objectManager->get(Config::class); + } + + /** + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testDefaultBehaviorForInvalidCredentials() + { + $serviceInfo = $this->buildServiceInfo(); + + try { + $this->_webApiCall( + $serviceInfo, + ['username' => 'customRoleUser', 'password' => 'bad'] + ); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.', + $response['message'] + ); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testUserWithConfigured2fa() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(Google::CODE)->activate($userId); + $serviceInfo = $this->buildServiceInfo(); + + try { + $this->_webApiCall( + $serviceInfo, + ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] + ); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Please use the 2fa provider-specific endpoints to obtain a token.', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers duo_security + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testUserWithAvailableButUnconfigured2fa() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(Google::CODE)->activate($userId); + $serviceInfo = $this->buildServiceInfo(); + + try { + $this->_webApiCall( + $serviceInfo, + ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] + ); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame( + 'You are required to configure personal Two-Factor Authorization in order to login. ' + . 'Please check your email.', + $response['message'] + ); + } + } + + /** + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testNoAvailable2faProviders() + { + $this->config->setDataByPath('twofactorauth/general/force_providers', ''); + $this->config->save(); + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(Google::CODE)->activate($userId); + $serviceInfo = $this->buildServiceInfo(); + + try { + $this->_webApiCall( + $serviceInfo, + ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] + ); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame( + 'Please ask an administrator with sufficient access to configure 2FA first', + $response['message'] + ); + } + } + + /** + * @return array + */ + private function buildServiceInfo(): array + { + return [ + 'rest' => [ + // Ensure the default auth is invalidated + 'token' => 'invalid', + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => Request::HTTP_METHOD_POST + ], + 'soap' => [ + // Ensure the default auth is invalidated + 'token' => 'invalid', + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . self::OPERATION + ] + ]; + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php new file mode 100644 index 00000000..5a9c92e5 --- /dev/null +++ b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php @@ -0,0 +1,143 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->google = $objectManager->get(Google::class); + $this->userConfig = $objectManager->get(UserConfigManagerInterface::class); + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers duo_security + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testUnavailableProvider() + { + $userId = $this->getUserId(); + $serviceInfo = $this->buildServiceInfo($userId); + + try { + $this->_webApiCall($serviceInfo, ['data' => ['otp' => 'foo']]); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame($response['message'], 'Provider is not allowed.'); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testInvalidToken() + { + $userId = $this->getUserId(); + $serviceInfo = $this->buildServiceInfo($userId); + + try { + $this->_webApiCall($serviceInfo, ['data' => ['otp' => 'foo']]); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Invalid code.', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testValidToken() + { + $userId = $this->getUserId(); + $otp = $this->getUserOtp(); + $serviceInfo = $this->buildServiceInfo($userId); + + $response = $this->_webApiCall($serviceInfo, ['data' => ['otp' => $otp]]); + self::assertNotEmpty($response); + self::assertRegExp('/^[a-z0-9]{32}$/', $response); + } + + /** + * @param int $userId + * @return array + */ + private function buildServiceInfo(int $userId): array + { + return [ + 'rest' => [ + // Ensure the default auth is invalidated + 'token' => 'invalid', + 'resourcePath' => self::RESOURCE_PATH . '/' . $userId, + 'httpMethod' => Request::HTTP_METHOD_POST + ], + 'soap' => [ + // Ensure the default auth is invalidated + 'token' => 'invalid', + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . self::OPERATION + ] + ]; + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + + return (int)$user->getId(); + } + + private function getUserOtp(): string + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + $totp = new TOTP($user->getEmail(), $this->google->getSecretCode($user)); + + // Enable longer window of valid tokens to prevent test race condition + $this->userConfig->addProviderConfig((int)$user->getId(), Google::CODE, ['window' => 120]); + + return $totp->now(); + } +} diff --git a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php new file mode 100644 index 00000000..e2c8e3b6 --- /dev/null +++ b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php @@ -0,0 +1,119 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testInvalidTfat() + { + $serviceInfo = $this->buildServiceInfo($this->getUserId()); + + try { + $this->_webApiCall($serviceInfo, ['tfaToken' => ['token' => 'abc']]); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Invalid tfat token', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers duo_security + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testUnavailableProvider() + { + $userId = $this->getUserId(); + $token = $this->tokenManager->issueFor($userId); + $serviceInfo = $this->buildServiceInfo($userId); + + try { + $this->_webApiCall($serviceInfo, ['tfaToken' => ['token' => $token]]); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Provider is not allowed.', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testValidRequest() + { + $userId = $this->getUserId(); + $token = $this->tokenManager->issueFor($userId); + $serviceInfo = $this->buildServiceInfo($userId); + + $response = $this->_webApiCall($serviceInfo, ['tfaToken' => ['token' => $token]]); + self::assertNotEmpty($response['qr_code_url']); + self::assertStringStartsWith('data:image/png', $response['qr_code_url']); + self::assertNotEmpty($response['secret_code']); + } + + /** + * @param int $userId + * @return array + */ + private function buildServiceInfo(int $userId): array + { + return [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '/' . $userId, + 'httpMethod' => Request::HTTP_METHOD_POST + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . self::OPERATION + ] + ]; + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/etc/di.xml b/TwoFactorAuth/etc/di.xml index e52e0898..f82b6565 100644 --- a/TwoFactorAuth/etc/di.xml +++ b/TwoFactorAuth/etc/di.xml @@ -26,6 +26,8 @@ + + diff --git a/TwoFactorAuth/etc/webapi.xml b/TwoFactorAuth/etc/webapi.xml index f5aaa9cd..fcb3b961 100644 --- a/TwoFactorAuth/etc/webapi.xml +++ b/TwoFactorAuth/etc/webapi.xml @@ -57,21 +57,14 @@ - - - - - - - - + - + From 094297832d66ccc3eccae55fe53a5863d630f022 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Wed, 1 Apr 2020 15:33:39 -0500 Subject: [PATCH 03/58] MC-30536: Enable 2FA for web API - Refactored endpoints - Added activation endpoint --- .../Api/AdminTokenServiceInterface.php | 29 +++ .../Api/Data/AdminTokenResponseInterface.php | 95 +++++++++ .../Api/Data/GoogleAuthenticateInterface.php | 52 ----- TwoFactorAuth/Api/Data/TfaTokenInterface.php | 52 ----- .../Api/GoogleAuthenticateInterface.php | 7 +- .../Api/GoogleConfigureInterface.php | 14 +- TwoFactorAuth/Api/ProviderInterface.php | 14 ++ TwoFactorAuth/Api/TfatActionsInterface.php | 33 ++++ .../Model/AdminAccessTokenService.php | 163 ++++++++++++++++ .../Model/Data/AdminTokenResponse.php | 82 ++++++++ TwoFactorAuth/Model/Data/TfaToken.php | 57 ------ .../Provider/Engine/Google/Authenticate.php | 53 +++-- .../Provider/Engine/Google/Configure.php | 88 ++++++++- TwoFactorAuth/Model/TfatActions.php | 86 +++++++++ TwoFactorAuth/Model/UserAuthenticator.php | 72 +++++++ .../Plugin/BlockAdminTokenCreation.php | 149 -------------- .../Test/Api/AdminIntegrationTokenTest.php | 54 +++--- TwoFactorAuth/Test/Api/GoogleActivateTest.php | 181 ++++++++++++++++++ .../Test/Api/GoogleAuthenticateTest.php | 105 ++++++++-- .../Test/Api/GoogleConfigureTest.php | 35 +++- TwoFactorAuth/etc/di.xml | 4 +- TwoFactorAuth/etc/webapi.xml | 30 ++- TwoFactorAuth/etc/webapi_rest/di.xml | 13 -- TwoFactorAuth/etc/webapi_soap/di.xml | 13 -- 24 files changed, 1062 insertions(+), 419 deletions(-) create mode 100644 TwoFactorAuth/Api/AdminTokenServiceInterface.php create mode 100644 TwoFactorAuth/Api/Data/AdminTokenResponseInterface.php delete mode 100644 TwoFactorAuth/Api/Data/GoogleAuthenticateInterface.php delete mode 100644 TwoFactorAuth/Api/Data/TfaTokenInterface.php create mode 100644 TwoFactorAuth/Api/TfatActionsInterface.php create mode 100644 TwoFactorAuth/Model/AdminAccessTokenService.php create mode 100644 TwoFactorAuth/Model/Data/AdminTokenResponse.php delete mode 100644 TwoFactorAuth/Model/Data/TfaToken.php create mode 100644 TwoFactorAuth/Model/TfatActions.php create mode 100644 TwoFactorAuth/Model/UserAuthenticator.php delete mode 100644 TwoFactorAuth/Plugin/BlockAdminTokenCreation.php create mode 100644 TwoFactorAuth/Test/Api/GoogleActivateTest.php delete mode 100644 TwoFactorAuth/etc/webapi_rest/di.xml delete mode 100644 TwoFactorAuth/etc/webapi_soap/di.xml diff --git a/TwoFactorAuth/Api/AdminTokenServiceInterface.php b/TwoFactorAuth/Api/AdminTokenServiceInterface.php new file mode 100644 index 00000000..7738408f --- /dev/null +++ b/TwoFactorAuth/Api/AdminTokenServiceInterface.php @@ -0,0 +1,29 @@ +tfa = $tfa; + $this->configRequestManager = $configRequestManager; + $this->auth = $auth; + $this->userAuthenticator = $userAuthenticator; + $this->adminTokenService = $adminTokenService; + $this->requestThrottler = $requestThrottler; + $this->responseFactory = $responseFactory; + } + + /** + * Create access token for admin given the admin credentials. + * + * @param string $username + * @param string $password + * @return \Magento\TwoFactorAuth\Api\Data\AdminTokenResponseInterface + */ + public function createAdminAccessToken(string $username, string $password): AdminTokenResponseInterface + { + try { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + } catch (\InvalidArgumentException $e) { + $this->requestThrottler->logAuthenticationFailure($username, RequestThrottler::USER_TYPE_ADMIN); + throw new AuthenticationException( + __( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + ) + ); + } + $this->requestThrottler->resetAuthenticationFailuresCount($username, RequestThrottler::USER_TYPE_ADMIN); + + $userId = (int)$user->getId(); + + if (!$this->configRequestManager->isConfigurationRequiredFor($userId)) { + return $this->responseFactory->create( + [ + 'data' => [ + AdminTokenResponseInterface::USER_ID => $userId, + AdminTokenResponseInterface::MESSAGE => + (string)__('Please use the 2fa provider-specific endpoints to obtain a token.'), + AdminTokenResponseInterface::ACTIVE_PROVIDERS => array_filter( + $this->tfa->getUserProviders($userId), + function ($provider) use ($userId) { + return $provider->isActive($userId) ? $provider : null; + } + ), + ] + ] + ); + } elseif (empty($this->tfa->getUserProviders($userId))) { + // It is expected that available 2fa providers are selected via db or admin ui + throw new WebapiException( + __('Please ask an administrator with sufficient access to configure 2FA first') + ); + } + + try { + $this->configRequestManager->sendConfigRequestTo($user); + } catch (AuthorizationException|NotificationExceptionInterface $exception) { + throw new WebapiException( + __('Failed to send the message. Please contact the administrator') + ); + } + + return $this->responseFactory->create( + [ + 'data' => [ + AdminTokenResponseInterface::USER_ID => $userId, + AdminTokenResponseInterface::MESSAGE => + (string)__('You are required to configure personal Two-Factor Authorization in order to login. ' + . 'Please check your email.'), + AdminTokenResponseInterface::ACTIVE_PROVIDERS => array_filter( + $this->tfa->getUserProviders($userId), + function ($provider) use ($userId) { + return $provider->isActive($userId) ? $provider : null; + } + ) + ] + ] + ); + } +} diff --git a/TwoFactorAuth/Model/Data/AdminTokenResponse.php b/TwoFactorAuth/Model/Data/AdminTokenResponse.php new file mode 100644 index 00000000..956ca842 --- /dev/null +++ b/TwoFactorAuth/Model/Data/AdminTokenResponse.php @@ -0,0 +1,82 @@ +_get(self::USER_ID); + } + + /** + * @inheritDoc + */ + public function setUserId(int $value): void + { + $this->setData(self::USER_ID, $value); + } + + /** + * @inheritDoc + */ + public function getMessage(): string + { + return (string)$this->_get(self::MESSAGE); + } + + /** + * @inheritDoc + */ + public function setMessage(string $value): void + { + $this->setData(self::MESSAGE, $value); + } + + /** + * @inheritDoc + */ + public function getActiveProviders(): array + { + return $this->_get(self::ACTIVE_PROVIDERS); + } + + /** + * @inheritDoc + */ + public function setActiveProviders(array $value): void + { + $this->setData(self::ACTIVE_PROVIDERS, $value); + } + + /** + * @inheritdoc + */ + public function getExtensionAttributes(): ?AdminTokenResponseExtensionInterface + { + return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * @inheritDoc + */ + public function setExtensionAttributes(AdminTokenResponseExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Data/TfaToken.php b/TwoFactorAuth/Model/Data/TfaToken.php deleted file mode 100644 index 849aab59..00000000 --- a/TwoFactorAuth/Model/Data/TfaToken.php +++ /dev/null @@ -1,57 +0,0 @@ -_getData(static::TOKEN); - } - - /** - * @inheritDoc - */ - public function setToken(string $value): void - { - $this->setData(static::TOKEN, $value); - } - - /** - * Retrieve existing extension attributes object or create a new one - * - * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation - * - * @return \Magento\TwoFactorAuth\Api\Data\TfaTokenExtensionInterface|null - */ - public function getExtensionAttributes(): ?TfaTokenExtensionInterface - { - return $this->_getData(self::EXTENSION_ATTRIBUTES_KEY); - } - - /** - * Set an extension attributes object - * - * @param \Magento\TwoFactorAuth\Api\Data\TfaTokenExtensionInterface $extensionAttributes - */ - public function setExtensionAttributes(TfaTokenExtensionInterface $extensionAttributes): void - { - $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); - } -} diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php index 19138234..bfe9c8b4 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php @@ -11,13 +11,11 @@ use Magento\Framework\DataObjectFactory; use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Webapi\Exception as WebApiException; -use Magento\TwoFactorAuth\Api\Data\GoogleAuthenticateInterface as GoogleAuthenticateInterfaceData; use Magento\TwoFactorAuth\Api\GoogleAuthenticateInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; -use Magento\User\Model\ResourceModel\User; -use Magento\User\Model\UserFactory; +use Magento\TwoFactorAuth\Model\UserAuthenticator; use Magento\Integration\Model\Oauth\TokenFactory as TokenModelFactory; /** @@ -30,16 +28,6 @@ class Authenticate implements GoogleAuthenticateInterface */ private $google; - /** - * @var User - */ - private $userFactory; - - /** - * @var User - */ - private $userResource; - /** * @var TfaInterface */ @@ -49,70 +37,75 @@ class Authenticate implements GoogleAuthenticateInterface * @var DataObjectFactory */ private $dataObjectFactory; + /** * @var AlertInterface */ private $alert; + /** * @var TokenModelFactory */ private $tokenFactory; + /** + * @var UserAuthenticator + */ + private $userAuthenticator; + /** * @param Google $google - * @param User $userResource - * @param UserFactory $userFactory * @param TfaInterface $tfa * @param DataObjectFactory $dataObjectFactory * @param AlertInterface $alert * @param TokenModelFactory $tokenFactory + * @param UserAuthenticator $userAuthenticator */ public function __construct( Google $google, - User $userResource, - UserFactory $userFactory, TfaInterface $tfa, DataObjectFactory $dataObjectFactory, AlertInterface $alert, - TokenModelFactory $tokenFactory + TokenModelFactory $tokenFactory, + UserAuthenticator $userAuthenticator ) { $this->google = $google; - $this->userResource = $userResource; - $this->userFactory = $userFactory; $this->tfa = $tfa; $this->dataObjectFactory = $dataObjectFactory; $this->alert = $alert; $this->tokenFactory = $tokenFactory; + $this->userAuthenticator = $userAuthenticator; } /** * Get an admin token by authenticating using google * - * @param int $userId - * @param GoogleAuthenticateInterfaceData $data + * @param string $username + * @param string $password + * @param string $otp * @return string * @throws AuthorizationException * @throws WebApiException */ - public function getToken(int $userId, GoogleAuthenticateInterfaceData $data): string + public function getToken(string $username, string $password, string $otp): string { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $userId = (int)$user->getId(); + if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { throw new WebApiException(__('Provider is not allowed.')); } - $user = $this->userFactory->create(); - $this->userResource->load($user, $userId); + if (!$this->tfa->getProviderByCode(Google::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is not configured.')); + } if ($this->google->verify($user, $this->dataObjectFactory->create([ 'data' => [ - 'tfa_code' => $data->getOtp() + 'tfa_code' => $otp ], ])) ) { - if ($this->tfa->getProvider(Google::CODE)->isActive($userId)) { - $this->tfa->getProvider(Google::CODE)->activate((int)$user->getId()); - } - $this->alert->event( 'Magento_TwoFactorAuth', 'New Google Authenticator code issued', diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php index 28de13a4..f2a5f6bb 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php @@ -8,13 +8,17 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\Google; +use Magento\Framework\DataObjectFactory; use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Webapi\Exception as WebApiException; +use Magento\Integration\Model\Oauth\TokenFactory as TokenModelFactory; +use Magento\TwoFactorAuth\Api\Data\GoogleAuthenticateInterface; use Magento\TwoFactorAuth\Api\Data\GoogleConfigureInterface as GoogleConfigurationData; use Magento\TwoFactorAuth\Api\Data\TfaTokenInterface; use Magento\TwoFactorAuth\Api\GoogleConfigureInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; +use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\TwoFactorAuth\Model\Data\Provider\Engine\Google\ConfigurationDataFactory; use Magento\User\Model\ResourceModel\User; @@ -55,6 +59,21 @@ class Configure implements GoogleConfigureInterface */ private $tfa; + /** + * @var DataObjectFactory + */ + private $dataObjectFactory; + + /** + * @var AlertInterface + */ + private $alert; + + /** + * @var TokenModelFactory + */ + private $tokenFactory; + /** * @param ConfigurationDataFactory $configurationDataFactory * @param Google $google @@ -62,6 +81,9 @@ class Configure implements GoogleConfigureInterface * @param User $userResource * @param UserFactory $userFactory * @param TfaInterface $tfa + * @param DataObjectFactory $dataObjectFactory + * @param AlertInterface $alert + * @param TokenModelFactory $tokenFactory */ public function __construct( ConfigurationDataFactory $configurationDataFactory, @@ -69,7 +91,10 @@ public function __construct( UserConfigTokenManagerInterface $tokenManager, User $userResource, UserFactory $userFactory, - TfaInterface $tfa + TfaInterface $tfa, + DataObjectFactory $dataObjectFactory, + AlertInterface $alert, + TokenModelFactory $tokenFactory ) { $this->configurationDataFactory = $configurationDataFactory; $this->google = $google; @@ -77,18 +102,25 @@ public function __construct( $this->userResource = $userResource; $this->userFactory = $userFactory; $this->tfa = $tfa; + $this->dataObjectFactory = $dataObjectFactory; + $this->alert = $alert; + $this->tokenFactory = $tokenFactory; } /** * @inheritDoc */ - public function getConfigurationData(int $userId, TfaTokenInterface $tfaToken): GoogleConfigurationData + public function getConfigurationData(int $userId, string $tfaToken): GoogleConfigurationData { if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { throw new WebApiException(__('Provider is not allowed.')); } - if (!$this->tokenManager->isValidFor($userId, $tfaToken->getToken())) { + if ($this->tfa->getProviderByCode(Google::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is already configured.')); + } + + if (!$this->tokenManager->isValidFor($userId, $tfaToken)) { throw new AuthorizationException( __('Invalid tfat token') ); @@ -106,4 +138,54 @@ public function getConfigurationData(int $userId, TfaTokenInterface $tfaToken): ] ); } + + /** + * Activate the provider + * + * @param int $userId + * @param string $tfaToken + * @param string $otp + * @return string + * @throws AuthorizationException + * @throws WebApiException + */ + public function activate(int $userId, string $tfaToken, string $otp): string + { + if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } + + if ($this->tfa->getProviderByCode(Google::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is already configured.')); + } + + if (!$this->tokenManager->isValidFor($userId, $tfaToken)) { + throw new AuthorizationException( + __('Invalid tfat token') + ); + } + + $user = $this->userFactory->create(); + $this->userResource->load($user, $userId); + + if ($this->google->verify($user, $this->dataObjectFactory->create([ + 'data' => [ + 'tfa_code' => $otp + ], + ])) + ) { + $this->tfa->getProvider(Google::CODE)->activate((int)$user->getId()); + + $this->alert->event( + 'Magento_TwoFactorAuth', + 'New Google Authenticator code issued', + AlertInterface::LEVEL_INFO, + $user->getUserName() + ); + + return $this->tokenFactory->create()->createAdminToken($userId)->getToken(); + } else { + throw new AuthorizationException(__('Invalid code.')); + } + } } diff --git a/TwoFactorAuth/Model/TfatActions.php b/TwoFactorAuth/Model/TfatActions.php new file mode 100644 index 00000000..cfef9cbc --- /dev/null +++ b/TwoFactorAuth/Model/TfatActions.php @@ -0,0 +1,86 @@ +tokenManager = $tokenManager; + $this->tfa = $tfa; + } + + /** + * Get list of providers available for the user + * + * @param int $userId + * @param string $tfaToken + * @return \Magento\TwoFactorAuth\Api\ProviderInterface[] + * @throws AuthorizationException + */ + public function getUserProviders(int $userId, string $tfaToken): array + { + $this->validateTfat($userId, $tfaToken); + + return $this->tfa->getUserProviders($userId); + } + + /** + * Get list of providers requiring activation + * + * @param int $userId + * @param string $tfaToken + * @return \Magento\TwoFactorAuth\Api\ProviderInterface[] + * @throws AuthorizationException + */ + public function getProvidersToActivate(int $userId, string $tfaToken): array + { + $this->validateTfat($userId, $tfaToken); + + return $this->tfa->getProvidersToActivate($userId); + } + + /** + * Validate the given 2fa token + * + * @param int $userId + * @param string $tfat + * @throws AuthorizationException + */ + private function validateTfat(int $userId, string $tfat): void + { + if (!$this->tokenManager->isValidFor($userId, $tfat)) { + throw new AuthorizationException(__('Invalid token.')); + } + } +} diff --git a/TwoFactorAuth/Model/UserAuthenticator.php b/TwoFactorAuth/Model/UserAuthenticator.php new file mode 100644 index 00000000..56a0c246 --- /dev/null +++ b/TwoFactorAuth/Model/UserAuthenticator.php @@ -0,0 +1,72 @@ +credentialsValidator = $credentialsValidator; + $this->userFactory = $userFactory; + $this->requestThrottler = $requestThrottler; + } + + /** + * Get a user with credentials while enforcing throttling + * + * @param string $username + * @param string $password + * @return User + */ + public function authenticateWithCredentials(string $username, string $password): User + { + $this->credentialsValidator->validate($username, $password); + $this->requestThrottler->throttle($username, RequestThrottler::USER_TYPE_ADMIN); + + $user = $this->userFactory->create(); + $user->login($username, $password); + + if (!$user->getId()) { + throw new \InvalidArgumentException('Invalid credentials'); + } + + return $user; + } +} diff --git a/TwoFactorAuth/Plugin/BlockAdminTokenCreation.php b/TwoFactorAuth/Plugin/BlockAdminTokenCreation.php deleted file mode 100644 index 5414b8bb..00000000 --- a/TwoFactorAuth/Plugin/BlockAdminTokenCreation.php +++ /dev/null @@ -1,149 +0,0 @@ -credentialsValidator = $credentialsValidator; - $this->tfa = $tfa; - $this->configRequestManager = $configRequestManager; - $this->auth = $auth; - $this->requestThrottler = $requestThrottler; - $this->adminUser = $adminUser; - } - - /** - * Intercept valid login attempt for use with tfa - * - * @param AdminTokenService $subject - * @param string $username - * @param string $password - * @return bool|null - * @throws WebapiException - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeCreateAdminAccessToken( - AdminTokenService $subject, - string $username, - string $password - ): void { - $user = $this->getUserWithCredentials($username, $password); - if (!$user || !$user->getId()) { - // Default behavior - return; - } - - if (!$this->configRequestManager->isConfigurationRequiredFor((int)$user->getId())) { - throw new WebapiException( - __( - 'Please use the 2fa provider-specific endpoints to obtain a token.' - ) - ); - } elseif (empty($this->tfa->getUserProviders((int)$user->getId()))) { - // It is expected that available 2fa providers are selected via db or admin ui - throw new WebapiException( - __('Please ask an administrator with sufficient access to configure 2FA first') - ); - } - - try { - $this->configRequestManager->sendConfigRequestTo($user); - } catch (AuthorizationException|NotificationExceptionInterface $exception) { - throw new WebapiException( - __('Failed to send the message. Please contact the administrator') - ); - } - - throw new WebapiException( - __( - 'You are required to configure personal Two-Factor Authorization in order to login. ' - . 'Please check your email.' - ) - ); - } - - /** - * Check if the given credentials are a valid admin account - * - * @param string $username - * @param string $password - * @return User|null - */ - private function getUserWithCredentials(string $username, string $password): ?User - { - try { - $this->credentialsValidator->validate($username, $password); - $this->requestThrottler->throttle($username, RequestThrottler::USER_TYPE_ADMIN); - - return $this->adminUser->login($username, $password); - } catch (\Exception $e) { - return null; - } - } -} diff --git a/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php index 68d6223b..5f87f31e 100644 --- a/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php +++ b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php @@ -12,6 +12,7 @@ use Magento\TestFramework\Bootstrap as TestBootstrap; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\TwoFactorAuth\Api\Data\AdminTokenResponseInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\User\Model\UserFactory; @@ -85,44 +86,41 @@ public function testUserWithConfigured2fa() $this->tfa->getProviderByCode(Google::CODE)->activate($userId); $serviceInfo = $this->buildServiceInfo(); - try { - $this->_webApiCall( - $serviceInfo, - ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] - ); - self::fail('Endpoint should have thrown an exception'); - } catch (\Throwable $exception) { - $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); - self::assertSame('Please use the 2fa provider-specific endpoints to obtain a token.', $response['message']); - } + $response = $this->_webApiCall( + $serviceInfo, + ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] + ); + self::assertSame($userId, (int)$response[AdminTokenResponseInterface::USER_ID]); + self::assertSame( + 'Please use the 2fa provider-specific endpoints to obtain a token.', + $response[AdminTokenResponseInterface::MESSAGE] + ); + self::assertCount(1, $response[AdminTokenResponseInterface::ACTIVE_PROVIDERS]); + self::assertSame('google', $response[AdminTokenResponseInterface::ACTIVE_PROVIDERS][0]['code']); } /** - * @magentoConfigFixture twofactorauth/general/force_providers duo_security + * @magentoConfigFixture twofactorauth/general/force_providers google,duo_security * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php */ - public function testUserWithAvailableButUnconfigured2fa() + public function testUserWithAvailableUnconfigured2fa() { $userId = $this->getUserId(); $this->tfa->getProviderByCode(Google::CODE)->activate($userId); $serviceInfo = $this->buildServiceInfo(); - try { - $this->_webApiCall( - $serviceInfo, - ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] - ); - self::fail('Endpoint should have thrown an exception'); - } catch (\Throwable $exception) { - $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); - self::assertSame( - 'You are required to configure personal Two-Factor Authorization in order to login. ' - . 'Please check your email.', - $response['message'] - ); - } + $response = $this->_webApiCall( + $serviceInfo, + ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] + ); + self::assertSame($userId, (int)$response[AdminTokenResponseInterface::USER_ID]); + self::assertSame( + 'You are required to configure personal Two-Factor Authorization in order to login. ' + . 'Please check your email.', + $response[AdminTokenResponseInterface::MESSAGE] + ); + self::assertCount(1, $response[AdminTokenResponseInterface::ACTIVE_PROVIDERS]); + self::assertSame('google', $response[AdminTokenResponseInterface::ACTIVE_PROVIDERS][0]['code']); } /** diff --git a/TwoFactorAuth/Test/Api/GoogleActivateTest.php b/TwoFactorAuth/Test/Api/GoogleActivateTest.php new file mode 100644 index 00000000..2100380d --- /dev/null +++ b/TwoFactorAuth/Test/Api/GoogleActivateTest.php @@ -0,0 +1,181 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + $this->userFactory = $objectManager->get(UserFactory::class); + $this->google = $objectManager->get(Google::class); + $this->userConfig = $objectManager->get(UserConfigManagerInterface::class); + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testInvalidTfat() + { + $serviceInfo = $this->buildServiceInfo($this->getUserId()); + + try { + $this->_webApiCall($serviceInfo, ['tfaToken' => 'abc', 'otp' => 'invalid']); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Invalid tfat token', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers duo_security + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testUnavailableProvider() + { + $userId = $this->getUserId(); + $token = $this->tokenManager->issueFor($userId); + $serviceInfo = $this->buildServiceInfo($userId); + + try { + $this->_webApiCall($serviceInfo, ['tfaToken' => $token, 'otp' => 'invalid']); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Provider is not allowed.', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testAlreadyActivatedProvider() + { + $userId = $this->getUserId(); + $token = $this->tokenManager->issueFor($userId); + $serviceInfo = $this->buildServiceInfo($userId); + $otp = $this->getUserOtp(); + $this->tfa->getProviderByCode(Google::CODE) + ->activate($userId); + + try { + $this->_webApiCall($serviceInfo, ['tfaToken' => $token, 'otp' => $otp]); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Provider is already configured.', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testActivate() + { + $userId = $this->getUserId(); + $token = $this->tokenManager->issueFor($userId); + $otp = $this->getUserOtp(); + $serviceInfo = $this->buildServiceInfo($userId); + + $response = $this->_webApiCall( + $serviceInfo, + [ + 'tfaToken' => $token, + 'otp' => $otp + ] + ); + self::assertNotEmpty($response); + self::assertRegExp('/^[a-z0-9]{32}$/', $response); + } + + private function getUserOtp(): string + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + $totp = new TOTP($user->getEmail(), $this->google->getSecretCode($user)); + + // Enable longer window of valid tokens to prevent test race condition + $this->userConfig->addProviderConfig((int)$user->getId(), Google::CODE, ['window' => 120]); + + return $totp->now(); + } + + /** + * @param int $userId + * @return array + */ + private function buildServiceInfo(int $userId): array + { + return [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '/' . $userId, + 'httpMethod' => Request::HTTP_METHOD_POST + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . self::OPERATION + ] + ]; + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php index 5a9c92e5..200caedb 100644 --- a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php @@ -37,12 +37,42 @@ class GoogleAuthenticateTest extends WebapiAbstract */ private $userConfig; + /** + * @var TfaInterface + */ + private $tfa; + protected function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->userFactory = $objectManager->get(UserFactory::class); $this->google = $objectManager->get(Google::class); $this->userConfig = $objectManager->get(UserConfigManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + } + + /** + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testInvalidCredentialsProvider() + { + $serviceInfo = $this->buildServiceInfo(); + + try { + $this->_webApiCall( + $serviceInfo, + [ + 'username' => 'customRoleUser', + 'password' => 'bad', + 'otp' => 'foo' + ] + ); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Invalid credentials', $response['message']); + } } /** @@ -51,16 +81,22 @@ protected function setUp() */ public function testUnavailableProvider() { - $userId = $this->getUserId(); - $serviceInfo = $this->buildServiceInfo($userId); + $serviceInfo = $this->buildServiceInfo(); try { - $this->_webApiCall($serviceInfo, ['data' => ['otp' => 'foo']]); + $this->_webApiCall( + $serviceInfo, + [ + 'username' => 'customRoleUser', + 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, + 'otp' => 'foo' + ] + ); self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); self::assertEmpty(json_last_error()); - self::assertSame($response['message'], 'Provider is not allowed.'); + self::assertSame('Provider is not allowed.', $response['message']); } } @@ -71,10 +107,19 @@ public function testUnavailableProvider() public function testInvalidToken() { $userId = $this->getUserId(); - $serviceInfo = $this->buildServiceInfo($userId); + $serviceInfo = $this->buildServiceInfo(); + $this->tfa->getProviderByCode(Google::CODE) + ->activate($userId); try { - $this->_webApiCall($serviceInfo, ['data' => ['otp' => 'foo']]); + $this->_webApiCall( + $serviceInfo, + [ + 'username' => 'customRoleUser', + 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, + 'otp' => 'bad' + ] + ); self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); @@ -83,6 +128,34 @@ public function testInvalidToken() } } + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testNotConfiguredProvider() + { + $userId = $this->getUserId(); + $serviceInfo = $this->buildServiceInfo(); + $this->tfa->getProviderByCode(Google::CODE) + ->resetConfiguration($userId); + + try { + $this->_webApiCall( + $serviceInfo, + [ + 'username' => 'customRoleUser', + 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, + 'otp' => 'foo' + ] + ); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Provider is not configured.', $response['message']); + } + } + /** * @magentoConfigFixture twofactorauth/general/force_providers google * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php @@ -91,24 +164,32 @@ public function testValidToken() { $userId = $this->getUserId(); $otp = $this->getUserOtp(); - $serviceInfo = $this->buildServiceInfo($userId); - - $response = $this->_webApiCall($serviceInfo, ['data' => ['otp' => $otp]]); + $serviceInfo = $this->buildServiceInfo(); + $this->tfa->getProviderByCode(Google::CODE) + ->activate($userId); + + $response = $this->_webApiCall( + $serviceInfo, + [ + 'username' => 'customRoleUser', + 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, + 'otp' => $otp + ] + ); self::assertNotEmpty($response); self::assertRegExp('/^[a-z0-9]{32}$/', $response); } /** - * @param int $userId * @return array */ - private function buildServiceInfo(int $userId): array + private function buildServiceInfo(): array { return [ 'rest' => [ // Ensure the default auth is invalidated 'token' => 'invalid', - 'resourcePath' => self::RESOURCE_PATH . '/' . $userId, + 'resourcePath' => self::RESOURCE_PATH, 'httpMethod' => Request::HTTP_METHOD_POST ], 'soap' => [ diff --git a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php index e2c8e3b6..59907fc4 100644 --- a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php +++ b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php @@ -10,6 +10,7 @@ use Magento\Framework\Webapi\Rest\Request; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\User\Model\UserFactory; class GoogleConfigureTest extends WebapiAbstract @@ -29,11 +30,17 @@ class GoogleConfigureTest extends WebapiAbstract */ private $tokenManager; + /** + * @var TfaInterface + */ + private $tfa; + protected function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->userFactory = $objectManager->get(UserFactory::class); $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); } /** @@ -45,7 +52,7 @@ public function testInvalidTfat() $serviceInfo = $this->buildServiceInfo($this->getUserId()); try { - $this->_webApiCall($serviceInfo, ['tfaToken' => ['token' => 'abc']]); + $this->_webApiCall($serviceInfo, ['tfaToken' => 'abc']); self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); @@ -65,7 +72,7 @@ public function testUnavailableProvider() $serviceInfo = $this->buildServiceInfo($userId); try { - $this->_webApiCall($serviceInfo, ['tfaToken' => ['token' => $token]]); + $this->_webApiCall($serviceInfo, ['tfaToken' => $token]); self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); @@ -74,6 +81,28 @@ public function testUnavailableProvider() } } + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $token = $this->tokenManager->issueFor($userId); + $serviceInfo = $this->buildServiceInfo($userId); + $this->tfa->getProviderByCode(Google::CODE) + ->activate($userId); + + try { + $this->_webApiCall($serviceInfo, ['tfaToken' => $token]); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Provider is already configured.', $response['message']); + } + } + /** * @magentoConfigFixture twofactorauth/general/force_providers google * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php @@ -84,7 +113,7 @@ public function testValidRequest() $token = $this->tokenManager->issueFor($userId); $serviceInfo = $this->buildServiceInfo($userId); - $response = $this->_webApiCall($serviceInfo, ['tfaToken' => ['token' => $token]]); + $response = $this->_webApiCall($serviceInfo, ['tfaToken' => $token]); self::assertNotEmpty($response['qr_code_url']); self::assertStringStartsWith('data:image/png', $response['qr_code_url']); self::assertNotEmpty($response['secret_code']); diff --git a/TwoFactorAuth/etc/di.xml b/TwoFactorAuth/etc/di.xml index f82b6565..a9f33643 100644 --- a/TwoFactorAuth/etc/di.xml +++ b/TwoFactorAuth/etc/di.xml @@ -23,11 +23,13 @@ + - + + diff --git a/TwoFactorAuth/etc/webapi.xml b/TwoFactorAuth/etc/webapi.xml index fcb3b961..2a11f56f 100644 --- a/TwoFactorAuth/etc/webapi.xml +++ b/TwoFactorAuth/etc/webapi.xml @@ -8,6 +8,13 @@ + + + + + + + @@ -57,6 +64,20 @@ + + + + + + + + + + + + + + @@ -64,10 +85,17 @@ - + + + + + + + + diff --git a/TwoFactorAuth/etc/webapi_rest/di.xml b/TwoFactorAuth/etc/webapi_rest/di.xml deleted file mode 100644 index f7f6f0f8..00000000 --- a/TwoFactorAuth/etc/webapi_rest/di.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - diff --git a/TwoFactorAuth/etc/webapi_soap/di.xml b/TwoFactorAuth/etc/webapi_soap/di.xml deleted file mode 100644 index f7f6f0f8..00000000 --- a/TwoFactorAuth/etc/webapi_soap/di.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - From 4bd9c55fd5729640e04aae97404a3d37cd0f0195 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 2 Apr 2020 14:34:40 -0500 Subject: [PATCH 04/58] MC-30536: Enable 2FA for web API - Added authy configuration endpoint --- TwoFactorAuth/Api/AuthyConfigureInterface.php | 42 ++++ .../Api/Data/AuthyDeviceInterface.php | 105 +++++++++ ...thyRegistrationPromptResponseInterface.php | 76 +++++++ .../Api/GoogleConfigureInterface.php | 2 - .../Data/Provider/Engine/Authy/Device.php | 90 ++++++++ .../Engine/Authy/RegistrationResponse.php | 74 ++++++ .../Model/Provider/Engine/Authy/Configure.php | 167 ++++++++++++++ .../Provider/Engine/Google/Authenticate.php | 8 +- .../Provider/Engine/Google/Configure.php | 63 +++--- TwoFactorAuth/Test/Api/GoogleActivateTest.php | 2 +- .../Test/Api/GoogleConfigureTest.php | 2 +- .../Provider/Engine/Authy/ConfigureTest.php | 210 ++++++++++++++++++ TwoFactorAuth/etc/di.xml | 3 + TwoFactorAuth/etc/webapi.xml | 7 + 14 files changed, 808 insertions(+), 43 deletions(-) create mode 100644 TwoFactorAuth/Api/AuthyConfigureInterface.php create mode 100644 TwoFactorAuth/Api/Data/AuthyDeviceInterface.php create mode 100644 TwoFactorAuth/Api/Data/AuthyRegistrationPromptResponseInterface.php create mode 100644 TwoFactorAuth/Model/Data/Provider/Engine/Authy/Device.php create mode 100644 TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php create mode 100644 TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php diff --git a/TwoFactorAuth/Api/AuthyConfigureInterface.php b/TwoFactorAuth/Api/AuthyConfigureInterface.php new file mode 100644 index 00000000..1e51ddf9 --- /dev/null +++ b/TwoFactorAuth/Api/AuthyConfigureInterface.php @@ -0,0 +1,42 @@ +_getData(self::COUNTRY); + } + + /** + * @inheritDoc + */ + public function setCountry(string $value): void + { + $this->setData(self::COUNTRY, $value); + } + + /** + * @inheritDoc + */ + public function getPhoneNumber(): string + { + return $this->_getData(self::PHONE); + } + + /** + * @inheritDoc + */ + public function setPhoneNumber(string $value): void + { + $this->setData(self::PHONE, $value); + } + + /** + * @inheritDoc + */ + public function getMethod(): string + { + return $this->_getData(self::METHOD); + } + + /** + * @inheritDoc + */ + public function setMethod(string $value): void + { + $this->setData(self::METHOD, $value); + } + + /** + * Retrieve existing extension attributes object or create a new one + * + * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation + * + * @return \Magento\TwoFactorAuth\Api\Data\AuthyDeviceExtensionInterface|null + */ + public function getExtensionAttributes(): ?AuthyDeviceExtensionInterface + { + return $this->_getData(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object + * + * @param \Magento\TwoFactorAuth\Api\Data\AuthyDeviceExtensionInterface $extensionAttributes + * @return void + */ + public function setExtensionAttributes(AuthyDeviceExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php b/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php new file mode 100644 index 00000000..e16ec542 --- /dev/null +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php @@ -0,0 +1,74 @@ +_get(self::MESSAGE); + } + + /** + * @inheritDoc + */ + public function setMessage(string $value): void + { + $this->setData(self::MESSAGE, $value); + } + + /** + * @inheritDoc + */ + public function getExpirationSeconds(): int + { + return (int)$this->_get(self::EXPIRATION_SECONDS); + } + + /** + * @inheritDoc + */ + public function setExpirationSeconds(int $value): void + { + $this->setData(self::EXPIRATION_SECONDS, $value); + } + + /** + * Retrieve existing extension attributes object or create a new one + * + * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation + * + * @return \Magento\TwoFactorAuth\Api\Data\AuthyRegistrationPromptResponseExtensionInterface|null + */ + public function getExtensionAttributes(): ?AuthyRegistrationPromptResponseExtensionInterface + { + return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object + * + * @param \Magento\TwoFactorAuth\Api\Data\AuthyRegistrationPromptResponseExtensionInterface $extensionAttributes + */ + public function setExtensionAttributes( + AuthyRegistrationPromptResponseExtensionInterface $extensionAttributes + ): void { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php new file mode 100644 index 00000000..e32ce092 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php @@ -0,0 +1,167 @@ +alert = $alert; + $this->verification = $verification; + $this->tfa = $tfa; + $this->userResource = $userResource; + $this->userFactory = $userFactory; + $this->responseFactory = $responseFactory; + $this->tokenManager = $tokenManager; + } + + /** + * @inheritDoc + */ + public function sendDeviceRegistrationPrompt( + int $userId, + string $tfaToken, + AuthyDeviceInterface $deviceData + ): ResponseInterface { + $user = $this->validateAndGetUser($userId, $tfaToken); + + $response = []; + $this->verification->request( + $user, + $deviceData->getCountry(), + $deviceData->getPhoneNumber(), + $deviceData->getMethod(), + $response + ); + + $this->alert->event( + 'Magento_TwoFactorAuth', + 'New authy verification request via ' . $deviceData->getMethod(), + AlertInterface::LEVEL_INFO, + $user->getUserName() + ); + + return $this->responseFactory->create( + [ + 'data' => [ + ResponseInterface::MESSAGE => $response['message'], + ResponseInterface::EXPIRATION_SECONDS => (int)$response['seconds_to_expire'], + ] + ] + ); + } + + /** + * Activate the provider and get an admin token + * + * @param int $userId + * @param string $tfaToken + * @param string $otp + * @return string + */ + public function activate(int $userId, string $tfaToken, string $otp): string + { + return 'foo'; + } + + /** + * Validate input params and get a user + * + * @param int $userId + * @param string $tfaToken + * @return UserInterface + * @throws AuthorizationException + * @throws WebApiException + */ + private function validateAndGetUser(int $userId, string $tfaToken): UserInterface + { + if (!$this->tfa->getProviderIsAllowed($userId, Authy::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif ($this->tfa->getProviderByCode(Authy::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is already configured.')); + } elseif (!$this->tokenManager->isValidFor($userId, $tfaToken)) { + throw new AuthorizationException( + __('Invalid tfa token') + ); + } + + $user = $this->userFactory->create(); + $this->userResource->load($user, $userId); + + return $user; + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php index bfe9c8b4..2298d3ec 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php @@ -94,13 +94,9 @@ public function getToken(string $username, string $password, string $otp): strin if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { throw new WebApiException(__('Provider is not allowed.')); - } - - if (!$this->tfa->getProviderByCode(Google::CODE)->isActive($userId)) { + } elseif (!$this->tfa->getProviderByCode(Google::CODE)->isActive($userId)) { throw new WebApiException(__('Provider is not configured.')); - } - - if ($this->google->verify($user, $this->dataObjectFactory->create([ + } elseif ($this->google->verify($user, $this->dataObjectFactory->create([ 'data' => [ 'tfa_code' => $otp ], diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php index f2a5f6bb..3cfa8f58 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php @@ -12,15 +12,14 @@ use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Webapi\Exception as WebApiException; use Magento\Integration\Model\Oauth\TokenFactory as TokenModelFactory; -use Magento\TwoFactorAuth\Api\Data\GoogleAuthenticateInterface; use Magento\TwoFactorAuth\Api\Data\GoogleConfigureInterface as GoogleConfigurationData; -use Magento\TwoFactorAuth\Api\Data\TfaTokenInterface; use Magento\TwoFactorAuth\Api\GoogleConfigureInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\TwoFactorAuth\Model\Data\Provider\Engine\Google\ConfigurationDataFactory; +use Magento\User\Api\Data\UserInterface; use Magento\User\Model\ResourceModel\User; use Magento\User\Model\UserFactory; @@ -112,21 +111,7 @@ public function __construct( */ public function getConfigurationData(int $userId, string $tfaToken): GoogleConfigurationData { - if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { - throw new WebApiException(__('Provider is not allowed.')); - } - - if ($this->tfa->getProviderByCode(Google::CODE)->isActive($userId)) { - throw new WebApiException(__('Provider is already configured.')); - } - - if (!$this->tokenManager->isValidFor($userId, $tfaToken)) { - throw new AuthorizationException( - __('Invalid tfat token') - ); - } - $user = $this->userFactory->create(); - $this->userResource->load($user, $userId); + $user = $this->validateAndGetUser($userId, $tfaToken); return $this->configurationDataFactory->create( [ @@ -151,22 +136,7 @@ public function getConfigurationData(int $userId, string $tfaToken): GoogleConfi */ public function activate(int $userId, string $tfaToken, string $otp): string { - if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { - throw new WebApiException(__('Provider is not allowed.')); - } - - if ($this->tfa->getProviderByCode(Google::CODE)->isActive($userId)) { - throw new WebApiException(__('Provider is already configured.')); - } - - if (!$this->tokenManager->isValidFor($userId, $tfaToken)) { - throw new AuthorizationException( - __('Invalid tfat token') - ); - } - - $user = $this->userFactory->create(); - $this->userResource->load($user, $userId); + $user = $this->validateAndGetUser($userId, $tfaToken); if ($this->google->verify($user, $this->dataObjectFactory->create([ 'data' => [ @@ -188,4 +158,31 @@ public function activate(int $userId, string $tfaToken, string $otp): string throw new AuthorizationException(__('Invalid code.')); } } + + /** + * Validate input params and get a user + * + * @param int $userId + * @param string $tfaToken + * @return UserInterface + * @throws AuthorizationException + * @throws WebApiException + */ + private function validateAndGetUser(int $userId, string $tfaToken): UserInterface + { + if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif ($this->tfa->getProviderByCode(Google::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is already configured.')); + } elseif (!$this->tokenManager->isValidFor($userId, $tfaToken)) { + throw new AuthorizationException( + __('Invalid tfa token') + ); + } + + $user = $this->userFactory->create(); + $this->userResource->load($user, $userId); + + return $user; + } } diff --git a/TwoFactorAuth/Test/Api/GoogleActivateTest.php b/TwoFactorAuth/Test/Api/GoogleActivateTest.php index 2100380d..b3c65793 100644 --- a/TwoFactorAuth/Test/Api/GoogleActivateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleActivateTest.php @@ -71,7 +71,7 @@ public function testInvalidTfat() } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); self::assertEmpty(json_last_error()); - self::assertSame('Invalid tfat token', $response['message']); + self::assertSame('Invalid tfa token', $response['message']); } } diff --git a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php index 59907fc4..5eabd99f 100644 --- a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php +++ b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php @@ -57,7 +57,7 @@ public function testInvalidTfat() } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); self::assertEmpty(json_last_error()); - self::assertSame('Invalid tfat token', $response['message']); + self::assertSame('Invalid tfa token', $response['message']); } } diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php new file mode 100644 index 00000000..3a4e1b44 --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php @@ -0,0 +1,210 @@ +verification = $this->createMock(Verification::class); + $this->userFactory = $objectManager->get(UserFactory::class); + $this->deviceDataFactory = $objectManager->get(AuthyDeviceInterfaceFactory::class); + $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + $this->model = $objectManager->create( + Configure::class, + [ + 'verification' => $this->verification + ] + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @expectedException \Magento\Framework\Exception\AuthorizationException + * @expectedExceptionMessage Invalid tfa token + */ + public function testInvalidTfat() + { + $userId = $this->getUserId(); + $this->verification + ->expects($this->never()) + ->method('request'); + $this->model->sendDeviceRegistrationPrompt( + $userId, + 'abc', + $this->deviceDataFactory->create( + [ + 'data' => [ + AuthyDeviceInterface::COUNTRY => '1', + AuthyDeviceInterface::PHONE => '555-555-5555', + AuthyDeviceInterface::METHOD => AuthyDeviceInterface::METHOD_SMS, + ] + ] + ) + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is already configured. + */ + public function testAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(Authy::CODE) + ->activate($userId); + $this->verification + ->expects($this->never()) + ->method('request'); + $this->model->sendDeviceRegistrationPrompt( + $userId, + 'abc', + $this->deviceDataFactory->create( + [ + 'data' => [ + AuthyDeviceInterface::COUNTRY => '1', + AuthyDeviceInterface::PHONE => '555-555-5555', + AuthyDeviceInterface::METHOD => AuthyDeviceInterface::METHOD_SMS, + ] + ] + ) + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testUnavailableProvider() + { + $userId = $this->getUserId(); + $this->verification + ->expects($this->never()) + ->method('request'); + $this->model->sendDeviceRegistrationPrompt( + $userId, + 'abc', + $this->deviceDataFactory->create( + [ + 'data' => [ + AuthyDeviceInterface::COUNTRY => '1', + AuthyDeviceInterface::PHONE => '555-555-5555', + AuthyDeviceInterface::METHOD => AuthyDeviceInterface::METHOD_SMS, + ] + ] + ) + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testValidRequest() + { + $userId = $this->getUserId(); + + $this->verification + ->method('request') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + '4', + '555-555-5555', + AuthyDeviceInterface::METHOD_SMS, + $this->anything() + ) + ->willReturnCallback( + function ($userId, $country, $phone, $method, &$response) { + // These keys come from authy api not our model + $response['message'] = 'foo'; + $response['seconds_to_expire'] = 123; + } + ); + + $result = $this->model->sendDeviceRegistrationPrompt( + $userId, + $this->tokenManager->issueFor($userId), + $this->deviceDataFactory->create( + [ + 'data' => [ + AuthyDeviceInterface::COUNTRY => '4', + AuthyDeviceInterface::PHONE => '555-555-5555', + AuthyDeviceInterface::METHOD => AuthyDeviceInterface::METHOD_SMS, + ] + ] + ) + ); + + self::assertSame('foo', $result->getMessage()); + self::assertSame(123, $result->getExpirationSeconds()); + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/etc/di.xml b/TwoFactorAuth/etc/di.xml index a9f33643..15ef7ce1 100644 --- a/TwoFactorAuth/etc/di.xml +++ b/TwoFactorAuth/etc/di.xml @@ -30,6 +30,9 @@ + + + diff --git a/TwoFactorAuth/etc/webapi.xml b/TwoFactorAuth/etc/webapi.xml index 2a11f56f..18f6b597 100644 --- a/TwoFactorAuth/etc/webapi.xml +++ b/TwoFactorAuth/etc/webapi.xml @@ -98,4 +98,11 @@ + + + + + + + From b0f67d03f335fc4dfaddfeedb5aaaa8e91237174 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 2 Apr 2020 19:09:17 -0500 Subject: [PATCH 05/58] MC-30536: Enable 2FA for web API - Authy activation endpoint --- .../Model/Provider/Engine/Authy/Configure.php | 47 ++++++- .../Provider/Engine/Authy/ConfigureTest.php | 118 +++++++++++++++++- TwoFactorAuth/etc/webapi.xml | 7 ++ 3 files changed, 165 insertions(+), 7 deletions(-) diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php index e32ce092..19547087 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php @@ -10,6 +10,7 @@ use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Webapi\Exception as WebApiException; +use Magento\Integration\Model\Oauth\TokenFactory as TokenModelFactory; use Magento\TwoFactorAuth\Api\AuthyConfigureInterface; use Magento\TwoFactorAuth\Api\Data\AuthyDeviceInterface; use Magento\TwoFactorAuth\Api\TfaInterface; @@ -62,6 +63,16 @@ class Configure implements AuthyConfigureInterface */ private $responseFactory; + /** + * @var TokenModelFactory + */ + private $tokenFactory; + + /** + * @var Authy + */ + private $authy; + /** * @param AlertInterface $alert * @param Verification $verification @@ -70,6 +81,8 @@ class Configure implements AuthyConfigureInterface * @param UserFactory $userFactory * @param ResponseFactory $responseFactory * @param UserConfigTokenManagerInterface $tokenManager + * @param TokenModelFactory $tokenFactory + * @param Authy $authy */ public function __construct( AlertInterface $alert, @@ -78,7 +91,9 @@ public function __construct( User $userResource, UserFactory $userFactory, ResponseFactory $responseFactory, - UserConfigTokenManagerInterface $tokenManager + UserConfigTokenManagerInterface $tokenManager, + TokenModelFactory $tokenFactory, + Authy $authy ) { $this->alert = $alert; $this->verification = $verification; @@ -87,6 +102,8 @@ public function __construct( $this->userFactory = $userFactory; $this->responseFactory = $responseFactory; $this->tokenManager = $tokenManager; + $this->tokenFactory = $tokenFactory; + $this->authy = $authy; } /** @@ -135,7 +152,33 @@ public function sendDeviceRegistrationPrompt( */ public function activate(int $userId, string $tfaToken, string $otp): string { - return 'foo'; + $user = $this->validateAndGetUser($userId, $tfaToken); + + try { + $this->verification->verify($user, $otp); + $this->authy->enroll($user); + + $this->alert->event( + 'Magento_TwoFactorAuth', + 'Authy identity verified', + AlertInterface::LEVEL_INFO, + $user->getUserName() + ); + + return $this->tokenFactory->create() + ->createAdminToken($userId) + ->getToken(); + } catch (\Throwable $e) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'Authy identity verification failure', + AlertInterface::LEVEL_ERROR, + $user->getUserName(), + $e->getMessage() + ); + + throw $e; + } } /** diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php index 3a4e1b44..3528fa13 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php @@ -48,6 +48,11 @@ class ConfigureTest extends TestCase */ private $tfa; + /** + * @var Authy|MockObject + */ + private $authy; + /** * @var UserConfigTokenManagerInterface */ @@ -61,10 +66,12 @@ protected function setUp() $this->deviceDataFactory = $objectManager->get(AuthyDeviceInterfaceFactory::class); $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); $this->tfa = $objectManager->get(TfaInterface::class); + $this->authy = $this->createMock(Authy::class); $this->model = $objectManager->create( Configure::class, [ - 'verification' => $this->verification + 'verification' => $this->verification, + 'authy' => $this->authy ] ); } @@ -76,7 +83,7 @@ protected function setUp() * @expectedException \Magento\Framework\Exception\AuthorizationException * @expectedExceptionMessage Invalid tfa token */ - public function testInvalidTfat() + public function testConfigureInvalidTfat() { $userId = $this->getUserId(); $this->verification @@ -104,7 +111,7 @@ public function testInvalidTfat() * @expectedException \Magento\Framework\Webapi\Exception * @expectedExceptionMessage Provider is already configured. */ - public function testAlreadyConfiguredProvider() + public function testConfigureAlreadyConfiguredProvider() { $userId = $this->getUserId(); $this->tfa->getProviderByCode(Authy::CODE) @@ -133,7 +140,7 @@ public function testAlreadyConfiguredProvider() * @expectedException \Magento\Framework\Webapi\Exception * @expectedExceptionMessage Provider is not allowed. */ - public function testUnavailableProvider() + public function testConfigureUnavailableProvider() { $userId = $this->getUserId(); $this->verification @@ -159,7 +166,7 @@ public function testUnavailableProvider() * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_custom_role.php */ - public function testValidRequest() + public function testConfigureValidRequest() { $userId = $this->getUserId(); @@ -200,6 +207,107 @@ function ($userId, $country, $phone, $method, &$response) { self::assertSame(123, $result->getExpirationSeconds()); } + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @expectedException \Magento\Framework\Exception\AuthorizationException + * @expectedExceptionMessage Invalid tfa token + */ + public function testActivateInvalidTfat() + { + $userId = $this->getUserId(); + $this->verification + ->expects($this->never()) + ->method('request'); + $this->authy + ->expects($this->never()) + ->method('enroll'); + $this->model->activate( + $userId, + 'abc', + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is already configured. + */ + public function testActivateAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(Authy::CODE) + ->activate($userId); + $this->authy + ->expects($this->never()) + ->method('enroll'); + $this->verification + ->expects($this->never()) + ->method('request'); + $this->model->activate( + $userId, + 'abc', + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testActivateUnavailableProvider() + { + $userId = $this->getUserId(); + $this->authy + ->expects($this->never()) + ->method('enroll'); + $this->verification + ->expects($this->never()) + ->method('request'); + $this->model->activate( + $userId, + 'abc', + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testActivateValidRequest() + { + $userId = $this->getUserId(); + $this->verification + ->method('verify') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + 'cba' + ); + $this->authy + ->expects($this->once()) + ->method('enroll') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ); + $this->model->activate( + $userId, + $this->tokenManager->issueFor($userId), + 'cba' + ); + } + private function getUserId(): int { $user = $this->userFactory->create(); diff --git a/TwoFactorAuth/etc/webapi.xml b/TwoFactorAuth/etc/webapi.xml index 18f6b597..11a096f3 100644 --- a/TwoFactorAuth/etc/webapi.xml +++ b/TwoFactorAuth/etc/webapi.xml @@ -105,4 +105,11 @@ + + + + + + + From 9b038a9c8f7cdd2910ca68405579cb283ab98e63 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 3 Apr 2020 11:02:46 -0500 Subject: [PATCH 06/58] MC-30536: Enable 2FA for web API - Authy endpoints --- .../Api/AuthyAuthenticateInterface.php | 56 +++++ .../Provider/Engine/Authy/Authenticate.php | 193 ++++++++++++++++++ TwoFactorAuth/etc/di.xml | 1 + TwoFactorAuth/etc/webapi.xml | 21 ++ 4 files changed, 271 insertions(+) create mode 100644 TwoFactorAuth/Api/AuthyAuthenticateInterface.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php diff --git a/TwoFactorAuth/Api/AuthyAuthenticateInterface.php b/TwoFactorAuth/Api/AuthyAuthenticateInterface.php new file mode 100644 index 00000000..235d1437 --- /dev/null +++ b/TwoFactorAuth/Api/AuthyAuthenticateInterface.php @@ -0,0 +1,56 @@ +userAuthenticator = $userAuthenticator; + $this->authy = $authy; + $this->alert = $alert; + $this->dataObjectFactory = $dataObjectFactory; + $this->tokenFactory = $tokenFactory; + $this->authyToken = $authyToken; + $this->tfa = $tfa; + $this->oneTouch = $oneTouch; + } + + /** + * @inheritDoc + */ + public function authenticateWithToken(string $username, string $password, string $otp): string + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + + if (!$this->tfa->getProviderIsAllowed((int)$user->getId(), Authy::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(Authy::CODE)->isActive((int)$user->getId())) { + throw new WebApiException(__('Provider is not configured.')); + } + + try { + $this->authy->verify($user, $this->dataObjectFactory->create([ + 'data' => [ + 'tfa_code' => $otp + ], + ])); + + return $this->tokenFactory->create() + ->createAdminToken((int)$user->getId()) + ->getToken(); + } catch (\Exception $e) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'Authy error', + AlertInterface::LEVEL_ERROR, + $user->getUserName(), + $e->getMessage() + ); + throw $e; + } + } + + /** + * @inheritDoc + */ + public function sendToken(string $username, string $password, string $via): bool + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + + if (!$this->tfa->getProviderIsAllowed((int)$user->getId(), Authy::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(Authy::CODE)->isActive((int)$user->getId())) { + throw new WebApiException(__('Provider is not configured.')); + } + + if ($via === 'onetouch') { + $this->oneTouch->request($user); + } else { + $this->authyToken->request($user, $via); + } + + return true; + } + + /** + * @inheritDoc + */ + public function authenticateWithOnetouch(string $username, string $password): string + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + + if (!$this->tfa->getProviderIsAllowed((int)$user->getId(), Authy::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(Authy::CODE)->isActive((int)$user->getId())) { + throw new WebApiException(__('Provider is not configured.')); + } + + try { + $res = $this->oneTouch->verify($user); + if ($res === 'approved') { + return $this->tokenFactory->create() + ->createAdminToken((int)$user->getId()) + ->getToken(); + } else { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'Authy onetouch auth denied', + AlertInterface::LEVEL_WARNING, + $user->getUserName() + ); + + throw new AuthorizationException(__('Onetouch prompt was denied or timed out.')); + } + } catch (\Exception $e) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'Authy onetouch error', + AlertInterface::LEVEL_ERROR, + $user->getUserName(), + $e->getMessage() + ); + + throw $e; + } + } +} diff --git a/TwoFactorAuth/etc/di.xml b/TwoFactorAuth/etc/di.xml index 15ef7ce1..34ac8661 100644 --- a/TwoFactorAuth/etc/di.xml +++ b/TwoFactorAuth/etc/di.xml @@ -33,6 +33,7 @@ + diff --git a/TwoFactorAuth/etc/webapi.xml b/TwoFactorAuth/etc/webapi.xml index 11a096f3..5c5893c4 100644 --- a/TwoFactorAuth/etc/webapi.xml +++ b/TwoFactorAuth/etc/webapi.xml @@ -112,4 +112,25 @@ + + + + + + + + + + + + + + + + + + + + + From f4a5f05c4f5a6bbbdca521029fa395bb86c7387d Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Mon, 6 Apr 2020 16:09:07 -0500 Subject: [PATCH 07/58] MC-30536: Enable 2FA for web API - Added u2f key endpoints --- .../Api/Data/U2fWebAuthnRequestInterface.php | 53 +++++ .../Api/U2fKeyAuthenticateInterface.php | 36 ++++ .../Api/U2fKeyConfigureInterface.php | 36 ++++ .../Model/AdminAccessTokenService.php | 32 +-- .../Engine/U2fkey/WebAuthnRequest.php | 51 +++++ .../Model/Provider/Engine/Authy/Configure.php | 81 ++------ .../Provider/Engine/Google/Configure.php | 70 ++----- .../Model/Provider/Engine/U2fKey.php | 5 + .../Provider/Engine/U2fKey/Authenticate.php | 186 ++++++++++++++++++ .../Provider/Engine/U2fKey/Configure.php | 164 +++++++++++++++ .../Model/Provider/Engine/U2fKey/WebAuthn.php | 33 +++- TwoFactorAuth/Model/UserAuthenticator.php | 80 +++++++- .../Test/Api/GoogleAuthenticateTest.php | 8 +- .../Engine/Authy/AuthenticateTest.php | 150 ++++++++++++++ .../Provider/Engine/Authy/ConfigureTest.php | 18 +- TwoFactorAuth/etc/adminhtml/system.xml | 11 +- TwoFactorAuth/etc/di.xml | 3 + TwoFactorAuth/etc/webapi.xml | 28 +++ 18 files changed, 870 insertions(+), 175 deletions(-) create mode 100644 TwoFactorAuth/Api/Data/U2fWebAuthnRequestInterface.php create mode 100644 TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php create mode 100644 TwoFactorAuth/Api/U2fKeyConfigureInterface.php create mode 100644 TwoFactorAuth/Model/Data/Provider/Engine/U2fkey/WebAuthnRequest.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php create mode 100644 TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php diff --git a/TwoFactorAuth/Api/Data/U2fWebAuthnRequestInterface.php b/TwoFactorAuth/Api/Data/U2fWebAuthnRequestInterface.php new file mode 100644 index 00000000..0c793783 --- /dev/null +++ b/TwoFactorAuth/Api/Data/U2fWebAuthnRequestInterface.php @@ -0,0 +1,53 @@ +tfa = $tfa; $this->configRequestManager = $configRequestManager; $this->auth = $auth; $this->userAuthenticator = $userAuthenticator; - $this->adminTokenService = $adminTokenService; - $this->requestThrottler = $requestThrottler; $this->responseFactory = $responseFactory; } @@ -97,18 +78,7 @@ public function __construct( */ public function createAdminAccessToken(string $username, string $password): AdminTokenResponseInterface { - try { - $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); - } catch (\InvalidArgumentException $e) { - $this->requestThrottler->logAuthenticationFailure($username, RequestThrottler::USER_TYPE_ADMIN); - throw new AuthenticationException( - __( - 'The account sign-in was incorrect or your account is disabled temporarily. ' - . 'Please wait and try again later.' - ) - ); - } - $this->requestThrottler->resetAuthenticationFailuresCount($username, RequestThrottler::USER_TYPE_ADMIN); + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); $userId = (int)$user->getId(); diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/U2fkey/WebAuthnRequest.php b/TwoFactorAuth/Model/Data/Provider/Engine/U2fkey/WebAuthnRequest.php new file mode 100644 index 00000000..e1a82fbd --- /dev/null +++ b/TwoFactorAuth/Model/Data/Provider/Engine/U2fkey/WebAuthnRequest.php @@ -0,0 +1,51 @@ +_getData(self::CREDENTIAL_REQUEST_OPTIONS_JSON); + } + + /** + * @inheritDoc + */ + public function setCredentialRequestOptionsJson(string $value): void + { + $this->setData(self::CREDENTIAL_REQUEST_OPTIONS_JSON, $value); + } + + /** + * @inheritDoc + */ + public function getExtensionAttributes(): ?U2FWebAuthnRequestExtensionInterface + { + return $this->_getData(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * @inheritDoc + */ + public function setExtensionAttributes(U2FWebAuthnRequestExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php index 19547087..a7b97d45 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php @@ -8,20 +8,14 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\Authy; -use Magento\Framework\Exception\AuthorizationException; -use Magento\Framework\Webapi\Exception as WebApiException; use Magento\Integration\Model\Oauth\TokenFactory as TokenModelFactory; use Magento\TwoFactorAuth\Api\AuthyConfigureInterface; use Magento\TwoFactorAuth\Api\Data\AuthyDeviceInterface; -use Magento\TwoFactorAuth\Api\TfaInterface; -use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Authy; -use Magento\User\Api\Data\UserInterface; -use Magento\User\Model\ResourceModel\User; -use Magento\User\Model\UserFactory; use Magento\TwoFactorAuth\Api\Data\AuthyRegistrationPromptResponseInterfaceFactory as ResponseFactory; use Magento\TwoFactorAuth\Api\Data\AuthyRegistrationPromptResponseInterface as ResponseInterface; +use Magento\TwoFactorAuth\Model\UserAuthenticator; /** * Configures authy @@ -38,26 +32,6 @@ class Configure implements AuthyConfigureInterface */ private $verification; - /** - * @var TfaInterface - */ - private $tfa; - - /** - * @var UserConfigTokenManagerInterface - */ - private $tokenManager; - - /** - * @var User - */ - private $userResource; - - /** - * @var UserFactory - */ - private $userFactory; - /** * @var AuthyRegistrationPromptResponseInterfaceFactory */ @@ -73,37 +47,33 @@ class Configure implements AuthyConfigureInterface */ private $authy; + /** + * @var UserAuthenticator + */ + private $userAuthenticator; + /** * @param AlertInterface $alert * @param Verification $verification - * @param TfaInterface $tfa - * @param User $userResource - * @param UserFactory $userFactory * @param ResponseFactory $responseFactory - * @param UserConfigTokenManagerInterface $tokenManager * @param TokenModelFactory $tokenFactory * @param Authy $authy + * @param UserAuthenticator $userAuthenticator */ public function __construct( AlertInterface $alert, Verification $verification, - TfaInterface $tfa, - User $userResource, - UserFactory $userFactory, ResponseFactory $responseFactory, - UserConfigTokenManagerInterface $tokenManager, TokenModelFactory $tokenFactory, - Authy $authy + Authy $authy, + UserAuthenticator $userAuthenticator ) { $this->alert = $alert; $this->verification = $verification; - $this->tfa = $tfa; - $this->userResource = $userResource; - $this->userFactory = $userFactory; $this->responseFactory = $responseFactory; - $this->tokenManager = $tokenManager; $this->tokenFactory = $tokenFactory; $this->authy = $authy; + $this->userAuthenticator = $userAuthenticator; } /** @@ -114,7 +84,7 @@ public function sendDeviceRegistrationPrompt( string $tfaToken, AuthyDeviceInterface $deviceData ): ResponseInterface { - $user = $this->validateAndGetUser($userId, $tfaToken); + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, Authy::CODE); $response = []; $this->verification->request( @@ -152,7 +122,7 @@ public function sendDeviceRegistrationPrompt( */ public function activate(int $userId, string $tfaToken, string $otp): string { - $user = $this->validateAndGetUser($userId, $tfaToken); + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, Authy::CODE); try { $this->verification->verify($user, $otp); @@ -180,31 +150,4 @@ public function activate(int $userId, string $tfaToken, string $otp): string throw $e; } } - - /** - * Validate input params and get a user - * - * @param int $userId - * @param string $tfaToken - * @return UserInterface - * @throws AuthorizationException - * @throws WebApiException - */ - private function validateAndGetUser(int $userId, string $tfaToken): UserInterface - { - if (!$this->tfa->getProviderIsAllowed($userId, Authy::CODE)) { - throw new WebApiException(__('Provider is not allowed.')); - } elseif ($this->tfa->getProviderByCode(Authy::CODE)->isActive($userId)) { - throw new WebApiException(__('Provider is already configured.')); - } elseif (!$this->tokenManager->isValidFor($userId, $tfaToken)) { - throw new AuthorizationException( - __('Invalid tfa token') - ); - } - - $user = $this->userFactory->create(); - $this->userResource->load($user, $userId); - - return $user; - } } diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php index 3cfa8f58..58f6d85f 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php @@ -15,13 +15,10 @@ use Magento\TwoFactorAuth\Api\Data\GoogleConfigureInterface as GoogleConfigurationData; use Magento\TwoFactorAuth\Api\GoogleConfigureInterface; use Magento\TwoFactorAuth\Api\TfaInterface; -use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\TwoFactorAuth\Model\Data\Provider\Engine\Google\ConfigurationDataFactory; -use Magento\User\Api\Data\UserInterface; -use Magento\User\Model\ResourceModel\User; -use Magento\User\Model\UserFactory; +use Magento\TwoFactorAuth\Model\UserAuthenticator; /** * Configure google provider @@ -38,21 +35,6 @@ class Configure implements GoogleConfigureInterface */ private $google; - /** - * @var UserConfigTokenManagerInterface - */ - private $tokenManager; - - /** - * @var User - */ - private $userFactory; - - /** - * @var User - */ - private $userResource; - /** * @var TfaInterface */ @@ -73,37 +55,36 @@ class Configure implements GoogleConfigureInterface */ private $tokenFactory; + /** + * @var UserAuthenticator + */ + private $userAuthenticator; + /** * @param ConfigurationDataFactory $configurationDataFactory * @param Google $google - * @param UserConfigTokenManagerInterface $tokenManager - * @param User $userResource - * @param UserFactory $userFactory * @param TfaInterface $tfa * @param DataObjectFactory $dataObjectFactory * @param AlertInterface $alert * @param TokenModelFactory $tokenFactory + * @param UserAuthenticator $userAuthenticator */ public function __construct( ConfigurationDataFactory $configurationDataFactory, Google $google, - UserConfigTokenManagerInterface $tokenManager, - User $userResource, - UserFactory $userFactory, TfaInterface $tfa, DataObjectFactory $dataObjectFactory, AlertInterface $alert, - TokenModelFactory $tokenFactory + TokenModelFactory $tokenFactory, + UserAuthenticator $userAuthenticator ) { $this->configurationDataFactory = $configurationDataFactory; $this->google = $google; - $this->tokenManager = $tokenManager; - $this->userResource = $userResource; - $this->userFactory = $userFactory; $this->tfa = $tfa; $this->dataObjectFactory = $dataObjectFactory; $this->alert = $alert; $this->tokenFactory = $tokenFactory; + $this->userAuthenticator = $userAuthenticator; } /** @@ -111,7 +92,7 @@ public function __construct( */ public function getConfigurationData(int $userId, string $tfaToken): GoogleConfigurationData { - $user = $this->validateAndGetUser($userId, $tfaToken); + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, Google::CODE); return $this->configurationDataFactory->create( [ @@ -136,7 +117,7 @@ public function getConfigurationData(int $userId, string $tfaToken): GoogleConfi */ public function activate(int $userId, string $tfaToken, string $otp): string { - $user = $this->validateAndGetUser($userId, $tfaToken); + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, Google::CODE); if ($this->google->verify($user, $this->dataObjectFactory->create([ 'data' => [ @@ -158,31 +139,4 @@ public function activate(int $userId, string $tfaToken, string $otp): string throw new AuthorizationException(__('Invalid code.')); } } - - /** - * Validate input params and get a user - * - * @param int $userId - * @param string $tfaToken - * @return UserInterface - * @throws AuthorizationException - * @throws WebApiException - */ - private function validateAndGetUser(int $userId, string $tfaToken): UserInterface - { - if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { - throw new WebApiException(__('Provider is not allowed.')); - } elseif ($this->tfa->getProviderByCode(Google::CODE)->isActive($userId)) { - throw new WebApiException(__('Provider is already configured.')); - } elseif (!$this->tokenManager->isValidFor($userId, $tfaToken)) { - throw new AuthorizationException( - __('Invalid tfa token') - ); - } - - $user = $this->userFactory->create(); - $this->userResource->load($user, $userId); - - return $user; - } } diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey.php index a6725caf..a4a6b313 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey.php @@ -21,6 +21,11 @@ */ class U2fKey implements EngineInterface { + /** + * The config path for the domain to use when issuing challenged from the web api + */ + const XML_PATH_WEBAPI_DOMAIN = 'twofactorauth/u2fkey/webapi_challenge_domain'; + /** * Engine code * diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php new file mode 100644 index 00000000..33be6742 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php @@ -0,0 +1,186 @@ +tfa = $tfa; + $this->u2fKey = $u2fKey; + $this->alert = $alert; + $this->tokenFactory = $tokenFactory; + $this->dataObjectFactory = $dataObjectFactory; + $this->userAuthenticator = $userAuthenticator; + $this->authnRequestInterfaceFactory = $authnRequestInterfaceFactory; + $this->json = $json; + $this->configManager = $configManager; + } + + /** + * @inheritDoc + */ + public function getAuthenticationData(string $username, string $password): U2FWebAuthnRequestInterface + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $userId = (int)$user->getId(); + + if (!$this->tfa->getProviderIsAllowed($userId, U2fKey::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(U2fKey::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is not configured.')); + } + + $data = $this->u2fKey->getAuthenticateData($user); + $this->configManager->addProviderConfig( + $userId, + U2fKey::CODE, + ['webapi_authentication_challenge' => $data['credentialRequestOptions']['challenge']] + ); + + $json = $this->json->serialize($data); + + return $this->authnRequestInterfaceFactory->create( + [ + 'data' => [ + U2FWebAuthnRequestInterface::CREDENTIAL_REQUEST_OPTIONS_JSON => $json + ] + ] + ); + } + + /** + * @inheritDoc + */ + public function verify(string $username, string $password, string $publicKeyCredentialJson): string + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $userId = (int)$user->getId(); + + $config = $this->configManager->getProviderConfig($userId, U2fKey::CODE); + if (!$this->tfa->getProviderIsAllowed($userId, U2fKey::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(U2fKey::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is not configured.')); + } elseif (empty($config['webapi_authentication_challenge'])) { + throw new WebApiException(__('U2f authentication prompt not sent.')); + } + + try { + $this->u2fKey->verify($user, $this->dataObjectFactory->create( + [ + 'data' => [ + 'publicKeyCredential' => $this->json->unserialize($publicKeyCredentialJson), + 'originalChallenge' => $config['webapi_authentication_challenge'] + ] + ] + )); + } catch (\Exception $e) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'U2F error', + AlertInterface::LEVEL_ERROR, + $user->getUserName(), + $e->getMessage() + ); + throw $e; + } + + $this->configManager->addProviderConfig( + $userId, + U2fKey::CODE, + ['webapi_authentication_challenge' => null] + ); + + return $this->tokenFactory->create() + ->createAdminToken($userId) + ->getToken(); + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php new file mode 100644 index 00000000..76d9bac0 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php @@ -0,0 +1,164 @@ +u2fKey = $u2fKey; + $this->userAuthenticator = $userAuthenticator; + $this->configManager = $configManager; + $this->authnInterfaceFactory = $authnInterfaceFactory; + $this->alert = $alert; + $this->tokenFactory = $tokenFactory; + $this->json = $json; + } + + /** + * @inheritDoc + */ + public function getRegistrationData(int $userId, string $tfaToken): U2FWebAuthnRequestInterface + { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, U2fKey::CODE); + + $data = $this->u2fKey->getRegisterData($user); + + $this->configManager->addProviderConfig( + $userId, + U2fKey::CODE, + [self::REGISTER_CHALLENGE_KEY => $data['publicKey']['challenge']] + ); + + return $this->authnInterfaceFactory->create( + [ + 'data' => [ + U2FWebAuthnRequestInterface::CREDENTIAL_REQUEST_OPTIONS_JSON => $this->json->serialize($data) + ] + ] + ); + } + + /** + * @inheritDoc + */ + public function activate(int $userId, string $tfaToken, string $publicKeyCredentialJson): string + { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, U2fKey::CODE); + + $config = $this->configManager->getProviderConfig($userId, U2fKey::CODE); + + if (empty($config[self::REGISTER_CHALLENGE_KEY])) { + throw new WebApiException(__('U2f key registration was not started.')); + } + + try { + $this->u2fKey->registerDevice( + $user, + [ + 'publicKeyCredential' => $this->json->unserialize($publicKeyCredentialJson), + 'challenge' => $config[self::REGISTER_CHALLENGE_KEY] + ] + ); + $this->alert->event( + 'Magento_TwoFactorAuth', + 'U2F New device registered', + AlertInterface::LEVEL_INFO, + $user->getUserName() + ); + } catch (\Exception $e) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'U2F error while adding device', + AlertInterface::LEVEL_ERROR, + $user->getUserName(), + $e->getMessage() + ); + throw $e; + } + + $this->configManager->addProviderConfig( + $userId, + U2fKey::CODE, + [self::REGISTER_CHALLENGE_KEY => null] + ); + + return $this->tokenFactory->create() + ->createAdminToken($userId) + ->getToken(); + } + +} diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php index 53d0c7a7..afab25e7 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php @@ -9,10 +9,14 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; use CBOR\CBOREncoder; +use Magento\Framework\App\Area; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\State; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Validation\ValidationException; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; +use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; use Magento\User\Api\Data\UserInterface; /** @@ -32,13 +36,29 @@ class WebAuthn */ private $storeManager; + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var State + */ + private $appState; + /** * @param StoreManagerInterface $storeManager + * @param ScopeConfigInterface $scopeConfig + * @param State $appState */ public function __construct( - StoreManagerInterface $storeManager + StoreManagerInterface $storeManager, + ScopeConfigInterface $scopeConfig, + State $appState ) { $this->storeManager = $storeManager; + $this->scopeConfig = $scopeConfig; + $this->appState = $appState; } /** @@ -74,7 +94,7 @@ public function assertCredentialDataIsValid( // Steps 7-9 if (rtrim(strtr(base64_encode($this->convertArrayToBytes($originalChallenge)), '+/', '-_'), '=') !== $credentialData['response']['clientData']['challenge'] - || 'https://' . $domain !== $credentialData['response']['clientData']['origin'] + || $domain !== parse_url($credentialData['response']['clientData']['origin'], \PHP_URL_HOST) || $credentialData['response']['clientData']['type'] !== 'webauthn.get' ) { throw new LocalizedException(__('Invalid U2F key.')); @@ -221,7 +241,7 @@ public function getPublicKeyFromRegistrationData(array $data): array if (rtrim(strtr(base64_encode($this->convertArrayToBytes($data['challenge'])), '+/', '-_'), '=') !== $credentialData['response']['clientData']['challenge'] - || 'https://' . $domain !== $credentialData['response']['clientData']['origin'] + || $domain !== parse_url($credentialData['response']['clientData']['origin'], \PHP_URL_HOST) || $credentialData['response']['clientData']['type'] !== 'webauthn.create' ) { throw new LocalizedException(__('Invalid U2F key.')); @@ -322,6 +342,13 @@ private function convertArrayToBytes(array $bytes): string */ private function getDomainName(): string { + $configValue = $this->scopeConfig->getValue(U2fKey::XML_PATH_WEBAPI_DOMAIN); + if ($configValue && + in_array($this->appState->getAreaCode(), [Area::AREA_WEBAPI_REST, Area::AREA_WEBAPI_SOAP]) + ) { + return $configValue; + } + $store = $this->storeManager->getStore(Store::ADMIN_CODE); $baseUrl = $store->getBaseUrl(); if (!preg_match('/^(https?:\/\/(?P.+?))\//', $baseUrl, $matches)) { diff --git a/TwoFactorAuth/Model/UserAuthenticator.php b/TwoFactorAuth/Model/UserAuthenticator.php index 56a0c246..08ca2cd0 100644 --- a/TwoFactorAuth/Model/UserAuthenticator.php +++ b/TwoFactorAuth/Model/UserAuthenticator.php @@ -8,8 +8,15 @@ namespace Magento\TwoFactorAuth\Model; +use Magento\Framework\DataObjectFactory; +use Magento\Framework\Exception\AuthenticationException; +use Magento\Framework\Exception\AuthorizationException; +use Magento\Framework\Webapi\Exception as WebApiException; use Magento\Integration\Model\CredentialsValidator; use Magento\Integration\Model\Oauth\Token\RequestThrottler; +use Magento\TwoFactorAuth\Api\TfaInterface; +use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; +use Magento\User\Model\ResourceModel\User as UserResource; use Magento\User\Model\User; use Magento\User\Model\UserFactory; @@ -33,19 +40,51 @@ class UserAuthenticator */ private $requestThrottler; + /** + * @var UserResource + */ + private $userResource; + + /** + * @var TfaInterface + */ + private $tfa; + + /** + * @var DataObjectFactory + */ + private $dataObjectFactory; + + /** + * @var UserConfigTokenManagerInterface + */ + private $tokenManager; + /** * @param CredentialsValidator $credentialsValidator * @param UserFactory $userFactory * @param RequestThrottler $requestThrottler + * @param UserResource $userResource + * @param UserConfigTokenManagerInterface $tokenManager + * @param TfaInterface $tfa + * @param DataObjectFactory $dataObjectFactory */ public function __construct( CredentialsValidator $credentialsValidator, UserFactory $userFactory, - RequestThrottler $requestThrottler + RequestThrottler $requestThrottler, + UserResource $userResource, + UserConfigTokenManagerInterface $tokenManager, + TfaInterface $tfa, + DataObjectFactory $dataObjectFactory ) { $this->credentialsValidator = $credentialsValidator; $this->userFactory = $userFactory; $this->requestThrottler = $requestThrottler; + $this->userResource = $userResource; + $this->tfa = $tfa; + $this->dataObjectFactory = $dataObjectFactory; + $this->tokenManager = $tokenManager; } /** @@ -64,9 +103,46 @@ public function authenticateWithCredentials(string $username, string $password): $user->login($username, $password); if (!$user->getId()) { - throw new \InvalidArgumentException('Invalid credentials'); + $this->requestThrottler->logAuthenticationFailure($username, RequestThrottler::USER_TYPE_ADMIN); + + throw new AuthenticationException( + __( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + ) + ); } + $this->requestThrottler->resetAuthenticationFailuresCount($username, RequestThrottler::USER_TYPE_ADMIN); + + return $user; + } + + /** + * Obtain a user with an id and a tfa token + * + * @param int $userId + * @param string $tfaToken + * @param string $providerCode + * @return User + * @throws AuthorizationException + * @throws WebApiException + */ + public function authenticateWithTokenAndProvider(int $userId, string $tfaToken, string $providerCode): User + { + if (!$this->tfa->getProviderIsAllowed($userId, $providerCode)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif ($this->tfa->getProviderByCode($providerCode)->isActive($userId)) { + throw new WebApiException(__('Provider is already configured.')); + } elseif (!$this->tokenManager->isValidFor($userId, $tfaToken)) { + throw new AuthorizationException( + __('Invalid tfa token') + ); + } + + $user = $this->userFactory->create(); + $this->userResource->load($user, $userId); + return $user; } } diff --git a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php index 200caedb..6b20da8e 100644 --- a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php @@ -54,7 +54,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php */ - public function testInvalidCredentialsProvider() + public function testInvalidCredentials() { $serviceInfo = $this->buildServiceInfo(); @@ -71,7 +71,11 @@ public function testInvalidCredentialsProvider() } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); self::assertEmpty(json_last_error()); - self::assertSame('Invalid credentials', $response['message']); + self::assertSame( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.', + $response['message'] + ); } } diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php new file mode 100644 index 00000000..1e023594 --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php @@ -0,0 +1,150 @@ +tfa = $objectManager->get(TfaInterface::class); + $this->authy = $this->createMock(Authy::class); + $this->userFactory = $objectManager->get(UserFactory::class); + $this->model = $objectManager->create( + Authenticate::class, + [ + 'authy' => $this->authy + ] + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthenticationException + */ + public function testAuthenticateInvalidCredentials() + { + $this->authy + ->expects($this->never()) + ->method('verify'); + $this->model->authenticateWithToken( + 'adminUser', + 'bad', + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not configured. + */ + public function testAuthenticateNotConfiguredProvider() + { + $this->authy + ->expects($this->never()) + ->method('verify'); + $this->model->authenticateWithToken( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testAuthenticateUnavailableProvider() + { + $this->authy + ->expects($this->never()) + ->method('verify'); + $this->model->authenticateWithToken( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testAuthenticateValidRequest() + { + $this->tfa->getProviderByCode(Authy::CODE) + ->activate($this->getUserId()); + $userId = $this->getUserId(); + $this->authy + ->expects($this->once()) + ->method('verify') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + $this->callback(function ($value) { + return $value->getData('tfa_code') === 'abc'; + }) + ); + $result = $this->model->authenticateWithToken( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'abc' + ); + + self::assertRegExp('/^[a-z0-9]{32}$/', $result); + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('adminUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php index 3528fa13..ece9d9cd 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php @@ -79,7 +79,7 @@ protected function setUp() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc - * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @magentoDataFixture Magento/User/_files/user_with_role.php * @expectedException \Magento\Framework\Exception\AuthorizationException * @expectedExceptionMessage Invalid tfa token */ @@ -107,7 +107,7 @@ public function testConfigureInvalidTfat() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc - * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @magentoDataFixture Magento/User/_files/user_with_role.php * @expectedException \Magento\Framework\Webapi\Exception * @expectedExceptionMessage Provider is already configured. */ @@ -136,7 +136,7 @@ public function testConfigureAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security - * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @magentoDataFixture Magento/User/_files/user_with_role.php * @expectedException \Magento\Framework\Webapi\Exception * @expectedExceptionMessage Provider is not allowed. */ @@ -164,7 +164,7 @@ public function testConfigureUnavailableProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc - * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @magentoDataFixture Magento/User/_files/user_with_role.php */ public function testConfigureValidRequest() { @@ -210,7 +210,7 @@ function ($userId, $country, $phone, $method, &$response) { /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc - * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @magentoDataFixture Magento/User/_files/user_with_role.php * @expectedException \Magento\Framework\Exception\AuthorizationException * @expectedExceptionMessage Invalid tfa token */ @@ -233,7 +233,7 @@ public function testActivateInvalidTfat() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc - * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @magentoDataFixture Magento/User/_files/user_with_role.php * @expectedException \Magento\Framework\Webapi\Exception * @expectedExceptionMessage Provider is already configured. */ @@ -257,7 +257,7 @@ public function testActivateAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security - * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @magentoDataFixture Magento/User/_files/user_with_role.php * @expectedException \Magento\Framework\Webapi\Exception * @expectedExceptionMessage Provider is not allowed. */ @@ -280,7 +280,7 @@ public function testActivateUnavailableProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc - * @magentoDataFixture Magento/User/_files/user_with_custom_role.php + * @magentoDataFixture Magento/User/_files/user_with_role.php */ public function testActivateValidRequest() { @@ -311,7 +311,7 @@ public function testActivateValidRequest() private function getUserId(): int { $user = $this->userFactory->create(); - $user->loadByUsername('customRoleUser'); + $user->loadByUsername('adminUser'); return (int)$user->getId(); } diff --git a/TwoFactorAuth/etc/adminhtml/system.xml b/TwoFactorAuth/etc/adminhtml/system.xml index 4d72c3aa..b5878177 100644 --- a/TwoFactorAuth/etc/adminhtml/system.xml +++ b/TwoFactorAuth/etc/adminhtml/system.xml @@ -32,7 +32,7 @@ - + This can be used to override the default email configuration link that is sent when using the Magento Web API's to authenticate. @@ -69,6 +69,15 @@ + + + + + This domain will be used when issuing and processing WebAuthn challenges via WebApi. The store domain will be used by default. + + diff --git a/TwoFactorAuth/etc/di.xml b/TwoFactorAuth/etc/di.xml index 34ac8661..87ca199a 100644 --- a/TwoFactorAuth/etc/di.xml +++ b/TwoFactorAuth/etc/di.xml @@ -34,6 +34,9 @@ + + + diff --git a/TwoFactorAuth/etc/webapi.xml b/TwoFactorAuth/etc/webapi.xml index 5c5893c4..1fe1386f 100644 --- a/TwoFactorAuth/etc/webapi.xml +++ b/TwoFactorAuth/etc/webapi.xml @@ -133,4 +133,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 042ca816abe12876929cf041e79ad64836c40059 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Tue, 7 Apr 2020 11:04:41 -0500 Subject: [PATCH 08/58] MC-30536: Enable 2FA for web API - Added u2fkey tests --- .../Provider/Engine/U2fKey/Authenticate.php | 10 +- .../Engine/U2fKey/AuthenticateTest.php | 271 ++++++++++++++++++ .../Provider/Engine/U2fKey/ConfigureTest.php | 270 +++++++++++++++++ 3 files changed, 547 insertions(+), 4 deletions(-) create mode 100644 TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php create mode 100644 TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php index 33be6742..c23d29b1 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php @@ -26,6 +26,8 @@ */ class Authenticate implements U2fKeyAuthenticateInterface { + private const AUTHENTICATION_CHALLENGE_KEY = 'webapi_authentication_challenge'; + /** * @var TfaInterface */ @@ -122,7 +124,7 @@ public function getAuthenticationData(string $username, string $password): U2FWe $this->configManager->addProviderConfig( $userId, U2fKey::CODE, - ['webapi_authentication_challenge' => $data['credentialRequestOptions']['challenge']] + [self::AUTHENTICATION_CHALLENGE_KEY => $data['credentialRequestOptions']['challenge']] ); $json = $this->json->serialize($data); @@ -149,7 +151,7 @@ public function verify(string $username, string $password, string $publicKeyCred throw new WebApiException(__('Provider is not allowed.')); } elseif (!$this->tfa->getProviderByCode(U2fKey::CODE)->isActive($userId)) { throw new WebApiException(__('Provider is not configured.')); - } elseif (empty($config['webapi_authentication_challenge'])) { + } elseif (empty($config[self::AUTHENTICATION_CHALLENGE_KEY])) { throw new WebApiException(__('U2f authentication prompt not sent.')); } @@ -158,7 +160,7 @@ public function verify(string $username, string $password, string $publicKeyCred [ 'data' => [ 'publicKeyCredential' => $this->json->unserialize($publicKeyCredentialJson), - 'originalChallenge' => $config['webapi_authentication_challenge'] + 'originalChallenge' => $config[self::AUTHENTICATION_CHALLENGE_KEY] ] ] )); @@ -176,7 +178,7 @@ public function verify(string $username, string $password, string $publicKeyCred $this->configManager->addProviderConfig( $userId, U2fKey::CODE, - ['webapi_authentication_challenge' => null] + [self::AUTHENTICATION_CHALLENGE_KEY => null] ); return $this->tokenFactory->create() diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php new file mode 100644 index 00000000..495d674f --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php @@ -0,0 +1,271 @@ +tfa = $objectManager->get(TfaInterface::class); + $this->u2fkey = $this->createMock(U2fKey::class); + $this->userFactory = $objectManager->get(UserFactory::class); + $this->model = $objectManager->create( + Authenticate::class, + [ + 'u2fKey' => $this->u2fkey + ] + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthenticationException + */ + public function testGetDataInvalidCredentials() + { + $this->u2fkey + ->expects($this->never()) + ->method('getAuthenticateData'); + $this->model->getAuthenticationData( + 'adminUser', + 'bad' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not configured. + */ + public function testGetDataNotConfiguredProvider() + { + $this->u2fkey + ->expects($this->never()) + ->method('getAuthenticateData'); + $this->model->getAuthenticationData( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testGetDataUnavailableProvider() + { + $this->u2fkey + ->expects($this->never()) + ->method('getAuthenticateData'); + $this->model->getAuthenticationData( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthenticationException + */ + public function testVerifyInvalidCredentials() + { + $this->u2fkey + ->expects($this->never()) + ->method('verify'); + $this->model->verify( + 'adminUser', + 'bad', + 'I identify as JSON' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not configured. + */ + public function testVerifyNotConfiguredProvider() + { + $this->u2fkey + ->expects($this->never()) + ->method('verify'); + $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'I identify as JSON' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testVerifyUnavailableProvider() + { + $this->u2fkey + ->expects($this->never()) + ->method('verify'); + $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'I identify as JSON' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testGetDataValidRequest() + { + $this->tfa->getProviderByCode(U2fKey::CODE) + ->activate($this->getUserId()); + $userId = $this->getUserId(); + + $data = ['credentialRequestOptions' => ['challenge' => [1, 2, 3]]]; + $this->u2fkey + ->expects($this->once()) + ->method('getAuthenticateData') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ) + ->willReturn($data); + + $result = $this->model->getAuthenticationData( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + + self::assertInstanceOf(U2FWebAuthnRequestInterface::class, $result); + self::assertSame(json_encode($data), $result->getCredentialRequestOptionsJson()); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testVerifyValidRequest() + { + $this->tfa->getProviderByCode(U2fKey::CODE) + ->activate($this->getUserId()); + $userId = $this->getUserId(); + + $this->u2fkey + ->expects($this->once()) + ->method('getAuthenticateData') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ) + ->willReturn(['credentialRequestOptions' => ['challenge' => [3, 2, 1]]]); + $this->model->getAuthenticationData('adminUser', Bootstrap::ADMIN_PASSWORD); + + $verifyData = ['foo' => 'bar']; + $this->u2fkey + ->expects($this->once()) + ->method('verify') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + $this->callback(function ($data) use ($verifyData) { + return $data->getData('publicKeyCredential') === $verifyData + // Assert the previously issued challenge is used for verification + && $data->getData('originalChallenge') === [3, 2, 1]; + }) + ); + + $token = $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + json_encode($verifyData) + ); + self::assertRegExp('/^[a-z0-9]{32}$/', $token); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Something + */ + public function testVerifyThrowsExceptionRequest() + { + $this->tfa->getProviderByCode(U2fKey::CODE) + ->activate($this->getUserId()); + $userId = $this->getUserId(); + + $this->u2fkey + ->method('getAuthenticateData') + ->willReturn(['credentialRequestOptions' => ['challenge' => [4, 5, 6]]]); + $this->model->getAuthenticationData('adminUser', Bootstrap::ADMIN_PASSWORD); + + $this->u2fkey + ->method('verify') + ->willThrowException(new \InvalidArgumentException('Something')); + + $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + json_encode(['foo' => 'bar']) + ); + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('adminUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php new file mode 100644 index 00000000..a73af69a --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php @@ -0,0 +1,270 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + $this->u2fkey = $this->createMock(U2fKey::class); + $this->model = $objectManager->create( + Configure::class, + [ + 'u2fKey' => $this->u2fkey + ] + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthorizationException + * @expectedExceptionMessage Invalid tfa token + */ + public function testGetRegistrationDataInvalidTfat() + { + $userId = $this->getUserId(); + $this->u2fkey + ->expects($this->never()) + ->method('getRegisterData'); + $this->model->getRegistrationData( + $userId, + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is already configured. + */ + public function testGetRegistrationDataAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(U2fKey::CODE) + ->activate($userId); + $this->u2fkey + ->expects($this->never()) + ->method('getRegisterData'); + $this->model->getRegistrationData( + $userId, + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testGetRegistrationDataUnavailableProvider() + { + $userId = $this->getUserId(); + $this->u2fkey + ->expects($this->never()) + ->method('getRegisterData'); + $this->model->getRegistrationData( + $userId, + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthorizationException + * @expectedExceptionMessage Invalid tfa token + */ + public function testActivateInvalidTfat() + { + $userId = $this->getUserId(); + $this->u2fkey + ->expects($this->never()) + ->method('registerDevice'); + $this->model->activate( + $userId, + 'abc', + 'I identify as JSON' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is already configured. + */ + public function testActivateAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(U2fKey::CODE) + ->activate($userId); + $this->u2fkey + ->expects($this->never()) + ->method('registerDevice'); + $this->model->activate( + $userId, + 'abc', + 'I identify as JSON' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testActivateUnavailableProvider() + { + $userId = $this->getUserId(); + $this->u2fkey + ->expects($this->never()) + ->method('registerDevice'); + $this->model->activate( + $userId, + 'abc', + 'I identify as JSON' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testGetRegistrationDataValidRequest() + { + $userId = $this->getUserId(); + $data = ['publicKey' => ['challenge' => [1, 2, 3]]]; + + $this->u2fkey + ->method('getRegisterData') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ) + ->willReturn($data); + + $result = $this->model->getRegistrationData( + $userId, + $this->tokenManager->issueFor($userId) + ); + + self::assertInstanceOf(U2FWebAuthnRequestInterface::class, $result); + self::assertSame(json_encode($data), $result->getCredentialRequestOptionsJson()); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testActivateValidRequest() + { + $userId = $this->getUserId(); + $tfat = $this->tokenManager->issueFor($userId); + + $this->u2fkey + ->method('getRegisterData') + ->willReturn(['publicKey' => ['challenge' => [3, 2, 1]]]); + $this->model->getRegistrationData($userId, $tfat); + + $activateData = ['foo' => 'bar']; + $this->u2fkey + ->method('registerDevice') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + [ + 'publicKeyCredential' => $activateData, + // Asserts the previously issued challenge was used for verification + 'challenge' => [3, 2, 1] + ] + ); + + $token = $this->model->activate($userId, $tfat, json_encode($activateData)); + + self::assertRegExp('/^[a-z0-9]{32}$/', $token); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Something + */ + public function testActivateInvalidKeyDataThrowsException() + { + $userId = $this->getUserId(); + $tfat = $this->tokenManager->issueFor($userId); + + $this->u2fkey + ->method('getRegisterData') + ->willReturn(['publicKey' => ['challenge' => [3, 2, 1]]]); + $this->model->getRegistrationData($userId, $tfat); + + $this->u2fkey + ->method('registerDevice') + ->willThrowException(new \InvalidArgumentException('Something')); + + $this->model->activate($userId, $tfat, json_encode(['foo' => 'bar'])); + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('adminUser'); + + return (int)$user->getId(); + } +} From 78b685286cacc347bceb502d83c1d8c5ea9c261d Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Tue, 7 Apr 2020 15:14:16 -0500 Subject: [PATCH 09/58] MC-30536: Enable 2FA for web API - duo endpoints - duo tests --- TwoFactorAuth/Api/Data/DuoDataInterface.php | 75 +++++ .../Api/DuoAuthenticateInterface.php | 39 +++ TwoFactorAuth/Api/DuoConfigureInterface.php | 39 +++ .../Data/Provider/Engine/DuoSecurity/Data.php | 74 +++++ .../Engine/DuoSecurity/Authenticate.php | 162 ++++++++++ .../Provider/Engine/DuoSecurity/Configure.php | 110 +++++++ .../Engine/DuoSecurity/AuthenticateTest.php | 293 ++++++++++++++++++ .../Engine/DuoSecurity/ConfigureTest.php | 293 ++++++++++++++++++ .../Engine/U2fKey/AuthenticateTest.php | 4 +- .../Provider/Engine/U2fKey/ConfigureTest.php | 4 +- TwoFactorAuth/etc/di.xml | 3 + TwoFactorAuth/etc/webapi.xml | 28 ++ 12 files changed, 1122 insertions(+), 2 deletions(-) create mode 100644 TwoFactorAuth/Api/Data/DuoDataInterface.php create mode 100644 TwoFactorAuth/Api/DuoAuthenticateInterface.php create mode 100644 TwoFactorAuth/Api/DuoConfigureInterface.php create mode 100644 TwoFactorAuth/Model/Data/Provider/Engine/DuoSecurity/Data.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php create mode 100644 TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php create mode 100644 TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php diff --git a/TwoFactorAuth/Api/Data/DuoDataInterface.php b/TwoFactorAuth/Api/Data/DuoDataInterface.php new file mode 100644 index 00000000..2c94c163 --- /dev/null +++ b/TwoFactorAuth/Api/Data/DuoDataInterface.php @@ -0,0 +1,75 @@ +_getData(self::SIGNATURE); + } + + /** + * @inheritDoc + */ + public function setSignature(string $value): void + { + $this->setData(self::SIGNATURE, $value); + } + + /** + * @inheritDoc + */ + public function getApiHostname(): string + { + return (string)$this->_getData(self::API_HOSTNAME); + } + + /** + * @inheritDoc + */ + public function setApiHostname(string $value): void + { + $this->setData(self::API_HOSTNAME, $value); + } + + /** + * Retrieve existing extension attributes object or create a new one + * + * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation + * + * @return \Magento\TwoFactorAuth\Api\Data\DuoDataExtensionInterface|null + */ + public function getExtensionAttributes(): ?DuoDataExtensionInterface + { + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object + * + * @param \Magento\TwoFactorAuth\Api\Data\DuoDataExtensionInterface $extensionAttributes + * @return void + */ + public function setExtensionAttributes(DuoDataExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php new file mode 100644 index 00000000..ceab0978 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php @@ -0,0 +1,162 @@ +userAuthenticator = $userAuthenticator; + $this->alert = $alert; + $this->duo = $duo; + $this->tokenFactory = $tokenFactory; + $this->dataFactory = $dataFactory; + $this->dataObjectFactory = $dataObjectFactory; + $this->tfa = $tfa; + } + + /** + * @inheritDoc + */ + public function getAuthenticateData(string $username, string $password): DuoDataInterface + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $userId = (int)$user->getId(); + + if (!$this->tfa->getProviderIsAllowed($userId, DuoSecurity::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(DuoSecurity::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is not configured.')); + } + + return $this->dataFactory->create( + [ + 'data' => [ + DuoDataInterface::API_HOSTNAME => $this->duo->getApiHostname(), + DuoDataInterface::SIGNATURE => $this->duo->getRequestSignature($user) + ] + ] + ); + } + + /** + * @inheritDoc + */ + public function verify(string $username, string $password, string $signatureResponse): string + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $userId = (int)$user->getId(); + + if (!$this->tfa->getProviderIsAllowed($userId, DuoSecurity::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(DuoSecurity::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is not configured.')); + } + + $this->assertResponseIsValid($user, $signatureResponse); + + return $this->tokenFactory->create() + ->createAdminToken($userId) + ->getToken(); + } + + /** + * Assert that the given signature is valid for the user + * + * @param UserInterface $user + * @param string $signatureResponse + * @throws WebApiException + */ + public function assertResponseIsValid(UserInterface $user, string $signatureResponse): void + { + $data = $this->dataObjectFactory->create( + [ + 'data' => [ + 'sig_response' => $signatureResponse + ] + ] + ); + if (!$this->duo->verify($user, $data)) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'DuoSecurity invalid auth', + AlertInterface::LEVEL_WARNING, + $user->getUserName() + ); + + throw new WebApiException(__('Invalid response')); + } + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php new file mode 100644 index 00000000..9ebc75c9 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php @@ -0,0 +1,110 @@ +userAuthenticator = $userAuthenticator; + $this->duo = $duo; + $this->tokenFactory = $tokenFactory; + $this->dataFactory = $dataFactory; + $this->tfa = $tfa; + $this->authenticate = $authenticate; + } + + /** + * @inheritDoc + */ + public function getConfigurationData(int $userId, string $tfaToken): DuoDataInterface + { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, DuoSecurity::CODE); + + return $this->dataFactory->create( + [ + 'data' => [ + DuoDataInterface::API_HOSTNAME => $this->duo->getApiHostname(), + DuoDataInterface::SIGNATURE => $this->duo->getRequestSignature($user) + ] + ] + ); + } + + /** + * @inheritDoc + */ + public function activate(int $userId, string $tfaToken, string $signatureResponse): string + { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, DuoSecurity::CODE); + + $this->authenticate->assertResponseIsValid($user, $signatureResponse); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($userId); + + return $this->tokenFactory->create() + ->createAdminToken($userId) + ->getToken(); + } +} diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php new file mode 100644 index 00000000..780a8ddf --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php @@ -0,0 +1,293 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + $this->duo = $this->createMock(DuoSecurity::class); + $this->model = $objectManager->create( + Authenticate::class, + [ + 'duo' => $this->duo, + ] + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthenticationException + */ + public function testGetAuthenticateDataInvalidCredentials() + { + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->getAuthenticateData( + 'adminUser', + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not configured. + */ + public function testGetAuthenticateDataNotConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->resetConfiguration($userId); + + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->getAuthenticateData( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testGetAuthenticateDataUnavailableProvider() + { + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->getAuthenticateData( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + } + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthenticationException + */ + public function testVerifyInvalidCredentials() + { + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->verify( + 'adminUser', + 'abc', + 'signature' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not configured. + */ + public function testVerifyNotConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->resetConfiguration($userId); + + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'signature' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testVerifyUnavailableProvider() + { + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'signature' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testGetAuthenticateDataValidRequest() + { + $userId = $this->getUserId(); + + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($userId); + + $this->duo + ->method('getApiHostname') + ->willReturn('abc'); + $this->duo + ->method('getRequestSignature') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ) + ->willReturn('cba'); + + $result = $this->model->getAuthenticateData( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + + self::assertInstanceOf(DuoDataInterface::class, $result); + self::assertSame('abc', $result->getApiHostname()); + self::assertSame('cba', $result->getSignature()); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testVerifyValidRequest() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($userId); + + $signature = 'a signature'; + $this->duo->method('verify') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + $this->callback(function ($value) use ($signature) { + return $value->getData('sig_response') === $signature; + }) + ) + ->willReturn(true); + + $token = $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + $signature + ); + + self::assertRegExp('/^[a-z0-9]{32}$/', $token); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Invalid response + */ + public function testVerifyInvalidRequest() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($userId); + + $signature = 'a signature'; + $this->duo->method('verify') + ->willReturn(false); + + $token = $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + $signature + ); + + self::assertEmpty($token); + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('adminUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php new file mode 100644 index 00000000..340c9922 --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php @@ -0,0 +1,293 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + $this->duo = $this->createMock(DuoSecurity::class); + $this->authenticate = $this->createMock(Authenticate::class); + $this->model = $objectManager->create( + Configure::class, + [ + 'duo' => $this->duo, + 'authenticate' => $this->authenticate + ] + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthorizationException + * @expectedExceptionMessage Invalid tfa token + */ + public function testGetConfigurationDataInvalidTfat() + { + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->getConfigurationData( + $this->getUserId(), + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is already configured. + */ + public function testGetConfigurationDataAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($userId); + + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->getConfigurationData( + $userId, + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testGetConfigurationDataUnavailableProvider() + { + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->getRegistrationData( + $this->getUserId(), + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthorizationException + * @expectedExceptionMessage Invalid tfa token + */ + public function testActivateInvalidTfat() + { + $userId = $this->getUserId(); + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->activate( + $userId, + 'abc', + 'something' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is already configured. + */ + public function testActivateAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($userId); + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->activate( + $userId, + 'abc', + 'something' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testActivateUnavailableProvider() + { + $userId = $this->getUserId(); + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->activate( + $userId, + 'abc', + 'something' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testGetConfigurationDataValidRequest() + { + $userId = $this->getUserId(); + + $this->duo + ->method('getApiHostname') + ->willReturn('abc'); + $this->duo + ->method('getRequestSignature') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ) + ->willReturn('cba'); + + $result = $this->model->getConfigurationData( + $userId, + $this->tokenManager->issueFor($userId) + ); + + self::assertInstanceOf(DuoDataInterface::class, $result); + self::assertSame('abc', $result->getApiHostname()); + self::assertSame('cba', $result->getSignature()); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testActivateValidRequest() + { + $userId = $this->getUserId(); + $tfat = $this->tokenManager->issueFor($userId); + + $signature = 'a signature'; + $this->authenticate->method('assertResponseIsValid') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + $signature + ); + + $token = $this->model->activate($userId, $tfat, $signature); + + self::assertRegExp('/^[a-z0-9]{32}$/', $token); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Something + */ + public function testActivateInvalidDataThrowsException() + { + $userId = $this->getUserId(); + $tfat = $this->tokenManager->issueFor($userId); + + $signature = 'a signature'; + $this->authenticate->method('assertResponseIsValid') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + $signature + ) + ->willThrowException(new \InvalidArgumentException('Something')); + + $result = $this->model->activate($userId, $tfat, $signature); + + self::assertEmpty($result); + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('adminUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php index 495d674f..2065c291 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php @@ -254,11 +254,13 @@ public function testVerifyThrowsExceptionRequest() ->method('verify') ->willThrowException(new \InvalidArgumentException('Something')); - $this->model->verify( + $result = $this->model->verify( 'adminUser', Bootstrap::ADMIN_PASSWORD, json_encode(['foo' => 'bar']) ); + + self::assertEmpty($result); } private function getUserId(): int diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php index a73af69a..5b17d1ef 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php @@ -257,7 +257,9 @@ public function testActivateInvalidKeyDataThrowsException() ->method('registerDevice') ->willThrowException(new \InvalidArgumentException('Something')); - $this->model->activate($userId, $tfat, json_encode(['foo' => 'bar'])); + $result = $this->model->activate($userId, $tfat, json_encode(['foo' => 'bar'])); + + self::assertEmpty($result); } private function getUserId(): int diff --git a/TwoFactorAuth/etc/di.xml b/TwoFactorAuth/etc/di.xml index 87ca199a..b419a3af 100644 --- a/TwoFactorAuth/etc/di.xml +++ b/TwoFactorAuth/etc/di.xml @@ -37,6 +37,9 @@ + + + diff --git a/TwoFactorAuth/etc/webapi.xml b/TwoFactorAuth/etc/webapi.xml index 1fe1386f..effe4ec7 100644 --- a/TwoFactorAuth/etc/webapi.xml +++ b/TwoFactorAuth/etc/webapi.xml @@ -161,4 +161,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + From afb27c779e4bfa0d3457a6467ef152b1ee40ccf5 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Tue, 7 Apr 2020 15:22:12 -0500 Subject: [PATCH 10/58] MC-30536: Enable 2FA for web API - Fixed duo test --- .../Model/Provider/Engine/DuoSecurity/ConfigureTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php index 340c9922..dadbb5dc 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php @@ -124,7 +124,7 @@ public function testGetConfigurationDataUnavailableProvider() $this->duo ->expects($this->never()) ->method('getRequestSignature'); - $this->model->getRegistrationData( + $this->model->getConfigurationData( $this->getUserId(), 'abc' ); From c64c40ceb80949092ec5b1e213eab02f67299e84 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 27 Mar 2020 16:10:56 -0500 Subject: [PATCH 11/58] MC-30536: Enable 2FA for web API --- .../Api/AdminTokenServiceInterface.php | 29 ++ .../Api/AuthyAuthenticateInterface.php | 56 +++ TwoFactorAuth/Api/AuthyConfigureInterface.php | 42 +++ .../Api/Data/AdminTokenResponseInterface.php | 95 ++++++ .../Api/Data/AuthyDeviceInterface.php | 105 ++++++ ...thyRegistrationPromptResponseInterface.php | 76 +++++ TwoFactorAuth/Api/Data/DuoDataInterface.php | 75 +++++ .../Api/Data/GoogleConfigureInterface.php | 75 +++++ .../Api/Data/U2fWebAuthnRequestInterface.php | 53 +++ .../Api/DuoAuthenticateInterface.php | 39 +++ TwoFactorAuth/Api/DuoConfigureInterface.php | 39 +++ .../Api/GoogleAuthenticateInterface.php | 25 ++ .../Api/GoogleConfigureInterface.php | 37 ++ TwoFactorAuth/Api/ProviderInterface.php | 14 + TwoFactorAuth/Api/TfaInterface.php | 5 + TwoFactorAuth/Api/TfatActionsInterface.php | 33 ++ .../Api/U2fKeyAuthenticateInterface.php | 36 ++ .../Api/U2fKeyConfigureInterface.php | 36 ++ .../Model/AdminAccessTokenService.php | 133 ++++++++ .../Model/Data/AdminTokenResponse.php | 82 +++++ .../Data/Provider/Engine/Authy/Device.php | 90 +++++ .../Engine/Authy/RegistrationResponse.php | 74 ++++ .../Data/Provider/Engine/DuoSecurity/Data.php | 74 ++++ .../Engine/Google/AuthenticateData.php | 57 ++++ .../Engine/Google/ConfigurationData.php | 83 +++++ .../Engine/U2fkey/WebAuthnRequest.php | 51 +++ TwoFactorAuth/Model/EmailUserNotifier.php | 48 ++- .../Provider/Engine/Authy/Authenticate.php | 193 +++++++++++ .../Model/Provider/Engine/Authy/Configure.php | 153 +++++++++ .../Engine/DuoSecurity/Authenticate.php | 162 +++++++++ .../Provider/Engine/DuoSecurity/Configure.php | 110 ++++++ .../Model/Provider/Engine/Google.php | 4 +- .../Provider/Engine/Google/Authenticate.php | 117 +++++++ .../Provider/Engine/Google/Configure.php | 142 ++++++++ .../Model/Provider/Engine/U2fKey.php | 5 + .../Provider/Engine/U2fKey/Authenticate.php | 188 +++++++++++ .../Provider/Engine/U2fKey/Configure.php | 164 +++++++++ .../Model/Provider/Engine/U2fKey/WebAuthn.php | 33 +- TwoFactorAuth/Model/TfatActions.php | 86 +++++ TwoFactorAuth/Model/UserAuthenticator.php | 148 ++++++++ .../Model/UserConfig/SignedTokenManager.php | 2 +- .../Test/Api/AdminIntegrationTokenTest.php | 182 ++++++++++ TwoFactorAuth/Test/Api/GoogleActivateTest.php | 181 ++++++++++ .../Test/Api/GoogleAuthenticateTest.php | 228 +++++++++++++ .../Test/Api/GoogleConfigureTest.php | 148 ++++++++ .../Engine/Authy/AuthenticateTest.php | 150 +++++++++ .../Provider/Engine/Authy/ConfigureTest.php | 318 ++++++++++++++++++ .../Engine/DuoSecurity/AuthenticateTest.php | 293 ++++++++++++++++ .../Engine/DuoSecurity/ConfigureTest.php | 293 ++++++++++++++++ .../Engine/U2fKey/AuthenticateTest.php | 273 +++++++++++++++ .../Provider/Engine/U2fKey/ConfigureTest.php | 272 +++++++++++++++ TwoFactorAuth/etc/adminhtml/system.xml | 14 + TwoFactorAuth/etc/di.xml | 16 + TwoFactorAuth/etc/webapi.xml | 146 +++++++- 54 files changed, 5560 insertions(+), 23 deletions(-) create mode 100644 TwoFactorAuth/Api/AdminTokenServiceInterface.php create mode 100644 TwoFactorAuth/Api/AuthyAuthenticateInterface.php create mode 100644 TwoFactorAuth/Api/AuthyConfigureInterface.php create mode 100644 TwoFactorAuth/Api/Data/AdminTokenResponseInterface.php create mode 100644 TwoFactorAuth/Api/Data/AuthyDeviceInterface.php create mode 100644 TwoFactorAuth/Api/Data/AuthyRegistrationPromptResponseInterface.php create mode 100644 TwoFactorAuth/Api/Data/DuoDataInterface.php create mode 100644 TwoFactorAuth/Api/Data/GoogleConfigureInterface.php create mode 100644 TwoFactorAuth/Api/Data/U2fWebAuthnRequestInterface.php create mode 100644 TwoFactorAuth/Api/DuoAuthenticateInterface.php create mode 100644 TwoFactorAuth/Api/DuoConfigureInterface.php create mode 100644 TwoFactorAuth/Api/GoogleAuthenticateInterface.php create mode 100644 TwoFactorAuth/Api/GoogleConfigureInterface.php create mode 100644 TwoFactorAuth/Api/TfatActionsInterface.php create mode 100644 TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php create mode 100644 TwoFactorAuth/Api/U2fKeyConfigureInterface.php create mode 100644 TwoFactorAuth/Model/AdminAccessTokenService.php create mode 100644 TwoFactorAuth/Model/Data/AdminTokenResponse.php create mode 100644 TwoFactorAuth/Model/Data/Provider/Engine/Authy/Device.php create mode 100644 TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php create mode 100644 TwoFactorAuth/Model/Data/Provider/Engine/DuoSecurity/Data.php create mode 100644 TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php create mode 100644 TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php create mode 100644 TwoFactorAuth/Model/Data/Provider/Engine/U2fkey/WebAuthnRequest.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/Google/Configure.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php create mode 100644 TwoFactorAuth/Model/TfatActions.php create mode 100644 TwoFactorAuth/Model/UserAuthenticator.php create mode 100644 TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php create mode 100644 TwoFactorAuth/Test/Api/GoogleActivateTest.php create mode 100644 TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php create mode 100644 TwoFactorAuth/Test/Api/GoogleConfigureTest.php create mode 100644 TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php create mode 100644 TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php create mode 100644 TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php create mode 100644 TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php create mode 100644 TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php create mode 100644 TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php diff --git a/TwoFactorAuth/Api/AdminTokenServiceInterface.php b/TwoFactorAuth/Api/AdminTokenServiceInterface.php new file mode 100644 index 00000000..7738408f --- /dev/null +++ b/TwoFactorAuth/Api/AdminTokenServiceInterface.php @@ -0,0 +1,29 @@ +tfa = $tfa; + $this->configRequestManager = $configRequestManager; + $this->auth = $auth; + $this->userAuthenticator = $userAuthenticator; + $this->responseFactory = $responseFactory; + } + + /** + * Create access token for admin given the admin credentials. + * + * @param string $username + * @param string $password + * @return \Magento\TwoFactorAuth\Api\Data\AdminTokenResponseInterface + */ + public function createAdminAccessToken(string $username, string $password): AdminTokenResponseInterface + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + + $userId = (int)$user->getId(); + + if (!$this->configRequestManager->isConfigurationRequiredFor($userId)) { + return $this->responseFactory->create( + [ + 'data' => [ + AdminTokenResponseInterface::USER_ID => $userId, + AdminTokenResponseInterface::MESSAGE => + (string)__('Please use the 2fa provider-specific endpoints to obtain a token.'), + AdminTokenResponseInterface::ACTIVE_PROVIDERS => array_filter( + $this->tfa->getUserProviders($userId), + function ($provider) use ($userId) { + return $provider->isActive($userId) ? $provider : null; + } + ), + ] + ] + ); + } elseif (empty($this->tfa->getUserProviders($userId))) { + // It is expected that available 2fa providers are selected via db or admin ui + throw new WebapiException( + __('Please ask an administrator with sufficient access to configure 2FA first') + ); + } + + try { + $this->configRequestManager->sendConfigRequestTo($user); + } catch (AuthorizationException|NotificationExceptionInterface $exception) { + throw new WebapiException( + __('Failed to send the message. Please contact the administrator') + ); + } + + return $this->responseFactory->create( + [ + 'data' => [ + AdminTokenResponseInterface::USER_ID => $userId, + AdminTokenResponseInterface::MESSAGE => + (string)__('You are required to configure personal Two-Factor Authorization in order to login. ' + . 'Please check your email.'), + AdminTokenResponseInterface::ACTIVE_PROVIDERS => array_filter( + $this->tfa->getUserProviders($userId), + function ($provider) use ($userId) { + return $provider->isActive($userId) ? $provider : null; + } + ) + ] + ] + ); + } +} diff --git a/TwoFactorAuth/Model/Data/AdminTokenResponse.php b/TwoFactorAuth/Model/Data/AdminTokenResponse.php new file mode 100644 index 00000000..956ca842 --- /dev/null +++ b/TwoFactorAuth/Model/Data/AdminTokenResponse.php @@ -0,0 +1,82 @@ +_get(self::USER_ID); + } + + /** + * @inheritDoc + */ + public function setUserId(int $value): void + { + $this->setData(self::USER_ID, $value); + } + + /** + * @inheritDoc + */ + public function getMessage(): string + { + return (string)$this->_get(self::MESSAGE); + } + + /** + * @inheritDoc + */ + public function setMessage(string $value): void + { + $this->setData(self::MESSAGE, $value); + } + + /** + * @inheritDoc + */ + public function getActiveProviders(): array + { + return $this->_get(self::ACTIVE_PROVIDERS); + } + + /** + * @inheritDoc + */ + public function setActiveProviders(array $value): void + { + $this->setData(self::ACTIVE_PROVIDERS, $value); + } + + /** + * @inheritdoc + */ + public function getExtensionAttributes(): ?AdminTokenResponseExtensionInterface + { + return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * @inheritDoc + */ + public function setExtensionAttributes(AdminTokenResponseExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/Authy/Device.php b/TwoFactorAuth/Model/Data/Provider/Engine/Authy/Device.php new file mode 100644 index 00000000..bae70749 --- /dev/null +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Authy/Device.php @@ -0,0 +1,90 @@ +_getData(self::COUNTRY); + } + + /** + * @inheritDoc + */ + public function setCountry(string $value): void + { + $this->setData(self::COUNTRY, $value); + } + + /** + * @inheritDoc + */ + public function getPhoneNumber(): string + { + return $this->_getData(self::PHONE); + } + + /** + * @inheritDoc + */ + public function setPhoneNumber(string $value): void + { + $this->setData(self::PHONE, $value); + } + + /** + * @inheritDoc + */ + public function getMethod(): string + { + return $this->_getData(self::METHOD); + } + + /** + * @inheritDoc + */ + public function setMethod(string $value): void + { + $this->setData(self::METHOD, $value); + } + + /** + * Retrieve existing extension attributes object or create a new one + * + * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation + * + * @return \Magento\TwoFactorAuth\Api\Data\AuthyDeviceExtensionInterface|null + */ + public function getExtensionAttributes(): ?AuthyDeviceExtensionInterface + { + return $this->_getData(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object + * + * @param \Magento\TwoFactorAuth\Api\Data\AuthyDeviceExtensionInterface $extensionAttributes + * @return void + */ + public function setExtensionAttributes(AuthyDeviceExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php b/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php new file mode 100644 index 00000000..e16ec542 --- /dev/null +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php @@ -0,0 +1,74 @@ +_get(self::MESSAGE); + } + + /** + * @inheritDoc + */ + public function setMessage(string $value): void + { + $this->setData(self::MESSAGE, $value); + } + + /** + * @inheritDoc + */ + public function getExpirationSeconds(): int + { + return (int)$this->_get(self::EXPIRATION_SECONDS); + } + + /** + * @inheritDoc + */ + public function setExpirationSeconds(int $value): void + { + $this->setData(self::EXPIRATION_SECONDS, $value); + } + + /** + * Retrieve existing extension attributes object or create a new one + * + * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation + * + * @return \Magento\TwoFactorAuth\Api\Data\AuthyRegistrationPromptResponseExtensionInterface|null + */ + public function getExtensionAttributes(): ?AuthyRegistrationPromptResponseExtensionInterface + { + return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object + * + * @param \Magento\TwoFactorAuth\Api\Data\AuthyRegistrationPromptResponseExtensionInterface $extensionAttributes + */ + public function setExtensionAttributes( + AuthyRegistrationPromptResponseExtensionInterface $extensionAttributes + ): void { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/DuoSecurity/Data.php b/TwoFactorAuth/Model/Data/Provider/Engine/DuoSecurity/Data.php new file mode 100644 index 00000000..5834429d --- /dev/null +++ b/TwoFactorAuth/Model/Data/Provider/Engine/DuoSecurity/Data.php @@ -0,0 +1,74 @@ +_getData(self::SIGNATURE); + } + + /** + * @inheritDoc + */ + public function setSignature(string $value): void + { + $this->setData(self::SIGNATURE, $value); + } + + /** + * @inheritDoc + */ + public function getApiHostname(): string + { + return (string)$this->_getData(self::API_HOSTNAME); + } + + /** + * @inheritDoc + */ + public function setApiHostname(string $value): void + { + $this->setData(self::API_HOSTNAME, $value); + } + + /** + * Retrieve existing extension attributes object or create a new one + * + * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation + * + * @return \Magento\TwoFactorAuth\Api\Data\DuoDataExtensionInterface|null + */ + public function getExtensionAttributes(): ?DuoDataExtensionInterface + { + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object + * + * @param \Magento\TwoFactorAuth\Api\Data\DuoDataExtensionInterface $extensionAttributes + * @return void + */ + public function setExtensionAttributes(DuoDataExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php b/TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php new file mode 100644 index 00000000..5e098f00 --- /dev/null +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php @@ -0,0 +1,57 @@ +_get(self::OTP); + } + + /** + * @inheritDoc + */ + public function setOtp(string $value): void + { + $this->setData(self::OTP, $value); + } + + /** + * Retrieve existing extension attributes object or create a new one + * + * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation + * + * @return \Magento\TwoFactorAuth\Api\Data\GoogleAuthenticateExtensionInterface|null + */ + public function getExtensionAttributes(): ?GoogleAuthenticateExtensionInterface + { + return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object + * + * @param \Magento\TwoFactorAuth\Api\Data\GoogleAuthenticateExtensionInterface $extensionAttributes + */ + public function setExtensionAttributes(GoogleAuthenticateExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php b/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php new file mode 100644 index 00000000..d498ae1d --- /dev/null +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php @@ -0,0 +1,83 @@ +_get(self::QR_CODE_URL); + } + + /** + * Set value for qr code url + * + * @param string $value + * @return void + */ + public function setQrCodeUrl(string $value): void + { + $this->setData(self::QR_CODE_URL, $value); + } + + /** + * Get value for secret code + * + * @return string + */ + public function getSecretCode(): string + { + return (string)$this->_get(self::SECRET_CODE); + } + + /** + * Set value for secret code + * + * @param string $value + * @return void + */ + public function setSecretCode(string $value): void + { + $this->setData(self::SECRET_CODE, $value); + } + + /** + * Retrieve existing extension attributes object or create a new one + * + * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation + * + * @return \Magento\TwoFactorAuth\Api\Data\GoogleConfigureExtensionInterface|null + */ + public function getExtensionAttributes(): ?GoogleConfigureExtensionInterface + { + return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * Set an extension attributes object + * + * @param \Magento\TwoFactorAuth\Api\Data\GoogleConfigureExtensionInterface $extensionAttributes + */ + public function setExtensionAttributes(GoogleConfigureExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/U2fkey/WebAuthnRequest.php b/TwoFactorAuth/Model/Data/Provider/Engine/U2fkey/WebAuthnRequest.php new file mode 100644 index 00000000..e1a82fbd --- /dev/null +++ b/TwoFactorAuth/Model/Data/Provider/Engine/U2fkey/WebAuthnRequest.php @@ -0,0 +1,51 @@ +_getData(self::CREDENTIAL_REQUEST_OPTIONS_JSON); + } + + /** + * @inheritDoc + */ + public function setCredentialRequestOptionsJson(string $value): void + { + $this->setData(self::CREDENTIAL_REQUEST_OPTIONS_JSON, $value); + } + + /** + * @inheritDoc + */ + public function getExtensionAttributes(): ?U2FWebAuthnRequestExtensionInterface + { + return $this->_getData(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * @inheritDoc + */ + public function setExtensionAttributes(U2FWebAuthnRequestExtensionInterface $extensionAttributes): void + { + $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/TwoFactorAuth/Model/EmailUserNotifier.php b/TwoFactorAuth/Model/EmailUserNotifier.php index f1cc1801..e87b7181 100644 --- a/TwoFactorAuth/Model/EmailUserNotifier.php +++ b/TwoFactorAuth/Model/EmailUserNotifier.php @@ -8,9 +8,12 @@ namespace Magento\TwoFactorAuth\Model; +use Magento\Framework\App\Area; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\State; use Magento\Framework\UrlInterface; use Magento\Store\Model\StoreManagerInterface; +use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\User\Model\User; use Magento\TwoFactorAuth\Api\Exception\NotificationExceptionInterface; use Magento\TwoFactorAuth\Api\UserNotifierInterface; @@ -48,25 +51,33 @@ class EmailUserNotifier implements UserNotifierInterface */ private $url; + /** + * @var State + */ + private $appState; + /** * @param ScopeConfigInterface $scopeConfig * @param TransportBuilder $transportBuilder * @param StoreManagerInterface $storeManager * @param LoggerInterface $logger * @param UrlInterface $url + * @param State $appState */ public function __construct( ScopeConfigInterface $scopeConfig, TransportBuilder $transportBuilder, StoreManagerInterface $storeManager, LoggerInterface $logger, - UrlInterface $url + UrlInterface $url, + State $appState ) { $this->scopeConfig = $scopeConfig; $this->transportBuilder = $transportBuilder; $this->storeManager = $storeManager; $this->logger = $logger; $this->url = $url; + $this->appState = $appState; } /** @@ -75,12 +86,25 @@ public function __construct( * @param User $user * @param string $token * @param string $emailTemplateId + * @param bool $useWebApiUrl * @return void - * @throws NotificationExceptionInterface */ - private function sendConfigRequired(User $user, string $token, string $emailTemplateId): void - { + private function sendConfigRequired( + User $user, + string $token, + string $emailTemplateId, + bool $useWebApiUrl = false + ): void { try { + $userUrl = $this->scopeConfig->getValue(TfaInterface::XML_PATH_WEBAPI_CONFIG_EMAIL_URL); + if ($useWebApiUrl && $userUrl) { + $url = $userUrl . + (parse_url($userUrl, PHP_URL_QUERY) ? '&' : '?') . + http_build_query(['tfat' => $token, 'user_id' => $user->getId()]); + } else { + $url = $this->url->getUrl('tfa/tfa/index', ['tfat' => $token]); + } + $transport = $this->transportBuilder ->setTemplateIdentifier($emailTemplateId) ->setTemplateOptions([ @@ -92,7 +116,7 @@ private function sendConfigRequired(User $user, string $token, string $emailTemp 'username' => $user->getFirstName() . ' ' . $user->getLastName(), 'token' => $token, 'store_name' => $this->storeManager->getStore()->getFrontendName(), - 'url' => $this->url->getUrl('tfa/tfa/index', ['tfat' => $token]) + 'url' => $url ] ) ->setFromByScope( @@ -112,7 +136,7 @@ private function sendConfigRequired(User $user, string $token, string $emailTemp */ public function sendUserConfigRequestMessage(User $user, string $token): void { - $this->sendConfigRequired($user, $token, 'tfa_admin_user_config_required'); + $this->sendConfigRequired($user, $token, 'tfa_admin_user_config_required', $this->isWebapi()); } /** @@ -120,6 +144,16 @@ public function sendUserConfigRequestMessage(User $user, string $token): void */ public function sendAppConfigRequestMessage(User $user, string $token): void { - $this->sendConfigRequired($user, $token, 'tfa_admin_app_config_required'); + $this->sendConfigRequired($user, $token, 'tfa_admin_app_config_required', false); + } + + /** + * Determine if the environment is webapi or not + * + * @return bool + */ + private function isWebapi(): bool + { + return in_array($this->appState->getAreaCode(), [Area::AREA_WEBAPI_REST, Area::AREA_WEBAPI_SOAP]); } } diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php new file mode 100644 index 00000000..36f6b436 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php @@ -0,0 +1,193 @@ +userAuthenticator = $userAuthenticator; + $this->authy = $authy; + $this->alert = $alert; + $this->dataObjectFactory = $dataObjectFactory; + $this->tokenFactory = $tokenFactory; + $this->authyToken = $authyToken; + $this->tfa = $tfa; + $this->oneTouch = $oneTouch; + } + + /** + * @inheritDoc + */ + public function authenticateWithToken(string $username, string $password, string $otp): string + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + + if (!$this->tfa->getProviderIsAllowed((int)$user->getId(), Authy::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(Authy::CODE)->isActive((int)$user->getId())) { + throw new WebApiException(__('Provider is not configured.')); + } + + try { + $this->authy->verify($user, $this->dataObjectFactory->create([ + 'data' => [ + 'tfa_code' => $otp + ], + ])); + + return $this->tokenFactory->create() + ->createAdminToken((int)$user->getId()) + ->getToken(); + } catch (\Exception $e) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'Authy error', + AlertInterface::LEVEL_ERROR, + $user->getUserName(), + $e->getMessage() + ); + throw $e; + } + } + + /** + * @inheritDoc + */ + public function sendToken(string $username, string $password, string $via): bool + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + + if (!$this->tfa->getProviderIsAllowed((int)$user->getId(), Authy::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(Authy::CODE)->isActive((int)$user->getId())) { + throw new WebApiException(__('Provider is not configured.')); + } + + if ($via === 'onetouch') { + $this->oneTouch->request($user); + } else { + $this->authyToken->request($user, $via); + } + + return true; + } + + /** + * @inheritDoc + */ + public function authenticateWithOnetouch(string $username, string $password): string + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + + if (!$this->tfa->getProviderIsAllowed((int)$user->getId(), Authy::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(Authy::CODE)->isActive((int)$user->getId())) { + throw new WebApiException(__('Provider is not configured.')); + } + + try { + $res = $this->oneTouch->verify($user); + if ($res === 'approved') { + return $this->tokenFactory->create() + ->createAdminToken((int)$user->getId()) + ->getToken(); + } else { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'Authy onetouch auth denied', + AlertInterface::LEVEL_WARNING, + $user->getUserName() + ); + + throw new AuthorizationException(__('Onetouch prompt was denied or timed out.')); + } + } catch (\Exception $e) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'Authy onetouch error', + AlertInterface::LEVEL_ERROR, + $user->getUserName(), + $e->getMessage() + ); + + throw $e; + } + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php new file mode 100644 index 00000000..a7b97d45 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php @@ -0,0 +1,153 @@ +alert = $alert; + $this->verification = $verification; + $this->responseFactory = $responseFactory; + $this->tokenFactory = $tokenFactory; + $this->authy = $authy; + $this->userAuthenticator = $userAuthenticator; + } + + /** + * @inheritDoc + */ + public function sendDeviceRegistrationPrompt( + int $userId, + string $tfaToken, + AuthyDeviceInterface $deviceData + ): ResponseInterface { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, Authy::CODE); + + $response = []; + $this->verification->request( + $user, + $deviceData->getCountry(), + $deviceData->getPhoneNumber(), + $deviceData->getMethod(), + $response + ); + + $this->alert->event( + 'Magento_TwoFactorAuth', + 'New authy verification request via ' . $deviceData->getMethod(), + AlertInterface::LEVEL_INFO, + $user->getUserName() + ); + + return $this->responseFactory->create( + [ + 'data' => [ + ResponseInterface::MESSAGE => $response['message'], + ResponseInterface::EXPIRATION_SECONDS => (int)$response['seconds_to_expire'], + ] + ] + ); + } + + /** + * Activate the provider and get an admin token + * + * @param int $userId + * @param string $tfaToken + * @param string $otp + * @return string + */ + public function activate(int $userId, string $tfaToken, string $otp): string + { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, Authy::CODE); + + try { + $this->verification->verify($user, $otp); + $this->authy->enroll($user); + + $this->alert->event( + 'Magento_TwoFactorAuth', + 'Authy identity verified', + AlertInterface::LEVEL_INFO, + $user->getUserName() + ); + + return $this->tokenFactory->create() + ->createAdminToken($userId) + ->getToken(); + } catch (\Throwable $e) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'Authy identity verification failure', + AlertInterface::LEVEL_ERROR, + $user->getUserName(), + $e->getMessage() + ); + + throw $e; + } + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php new file mode 100644 index 00000000..ceab0978 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php @@ -0,0 +1,162 @@ +userAuthenticator = $userAuthenticator; + $this->alert = $alert; + $this->duo = $duo; + $this->tokenFactory = $tokenFactory; + $this->dataFactory = $dataFactory; + $this->dataObjectFactory = $dataObjectFactory; + $this->tfa = $tfa; + } + + /** + * @inheritDoc + */ + public function getAuthenticateData(string $username, string $password): DuoDataInterface + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $userId = (int)$user->getId(); + + if (!$this->tfa->getProviderIsAllowed($userId, DuoSecurity::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(DuoSecurity::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is not configured.')); + } + + return $this->dataFactory->create( + [ + 'data' => [ + DuoDataInterface::API_HOSTNAME => $this->duo->getApiHostname(), + DuoDataInterface::SIGNATURE => $this->duo->getRequestSignature($user) + ] + ] + ); + } + + /** + * @inheritDoc + */ + public function verify(string $username, string $password, string $signatureResponse): string + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $userId = (int)$user->getId(); + + if (!$this->tfa->getProviderIsAllowed($userId, DuoSecurity::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(DuoSecurity::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is not configured.')); + } + + $this->assertResponseIsValid($user, $signatureResponse); + + return $this->tokenFactory->create() + ->createAdminToken($userId) + ->getToken(); + } + + /** + * Assert that the given signature is valid for the user + * + * @param UserInterface $user + * @param string $signatureResponse + * @throws WebApiException + */ + public function assertResponseIsValid(UserInterface $user, string $signatureResponse): void + { + $data = $this->dataObjectFactory->create( + [ + 'data' => [ + 'sig_response' => $signatureResponse + ] + ] + ); + if (!$this->duo->verify($user, $data)) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'DuoSecurity invalid auth', + AlertInterface::LEVEL_WARNING, + $user->getUserName() + ); + + throw new WebApiException(__('Invalid response')); + } + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php new file mode 100644 index 00000000..9ebc75c9 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php @@ -0,0 +1,110 @@ +userAuthenticator = $userAuthenticator; + $this->duo = $duo; + $this->tokenFactory = $tokenFactory; + $this->dataFactory = $dataFactory; + $this->tfa = $tfa; + $this->authenticate = $authenticate; + } + + /** + * @inheritDoc + */ + public function getConfigurationData(int $userId, string $tfaToken): DuoDataInterface + { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, DuoSecurity::CODE); + + return $this->dataFactory->create( + [ + 'data' => [ + DuoDataInterface::API_HOSTNAME => $this->duo->getApiHostname(), + DuoDataInterface::SIGNATURE => $this->duo->getRequestSignature($user) + ] + ] + ); + } + + /** + * @inheritDoc + */ + public function activate(int $userId, string $tfaToken, string $signatureResponse): string + { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, DuoSecurity::CODE); + + $this->authenticate->assertResponseIsValid($user, $signatureResponse); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($userId); + + return $this->tokenFactory->create() + ->createAdminToken($userId) + ->getToken(); + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/Google.php b/TwoFactorAuth/Model/Provider/Engine/Google.php index cfa85474..aa3a7819 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google.php @@ -142,9 +142,9 @@ public function verify(UserInterface $user, DataObject $request): bool } $totp = $this->getTotp($user); - $totp->now(); + $config = $this->configManager->getProviderConfig((int)$user->getId(), static::CODE); - return $totp->verify($token); + return $totp->verify($token, null, $config['window'] ?? null); } /** diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php new file mode 100644 index 00000000..2298d3ec --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php @@ -0,0 +1,117 @@ +google = $google; + $this->tfa = $tfa; + $this->dataObjectFactory = $dataObjectFactory; + $this->alert = $alert; + $this->tokenFactory = $tokenFactory; + $this->userAuthenticator = $userAuthenticator; + } + + /** + * Get an admin token by authenticating using google + * + * @param string $username + * @param string $password + * @param string $otp + * @return string + * @throws AuthorizationException + * @throws WebApiException + */ + public function getToken(string $username, string $password, string $otp): string + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $userId = (int)$user->getId(); + + if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(Google::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is not configured.')); + } elseif ($this->google->verify($user, $this->dataObjectFactory->create([ + 'data' => [ + 'tfa_code' => $otp + ], + ])) + ) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'New Google Authenticator code issued', + AlertInterface::LEVEL_INFO, + $user->getUserName() + ); + + return $this->tokenFactory->create()->createAdminToken($userId)->getToken(); + } else { + throw new AuthorizationException(__('Invalid code.')); + } + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php new file mode 100644 index 00000000..58f6d85f --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php @@ -0,0 +1,142 @@ +configurationDataFactory = $configurationDataFactory; + $this->google = $google; + $this->tfa = $tfa; + $this->dataObjectFactory = $dataObjectFactory; + $this->alert = $alert; + $this->tokenFactory = $tokenFactory; + $this->userAuthenticator = $userAuthenticator; + } + + /** + * @inheritDoc + */ + public function getConfigurationData(int $userId, string $tfaToken): GoogleConfigurationData + { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, Google::CODE); + + return $this->configurationDataFactory->create( + [ + 'data' => [ + GoogleConfigurationData::QR_CODE_URL => + 'data:image/png;base64,' . base64_encode($this->google->getQrCodeAsPng($user)), + GoogleConfigurationData::SECRET_CODE => $this->google->getSecretCode($user) + ] + ] + ); + } + + /** + * Activate the provider + * + * @param int $userId + * @param string $tfaToken + * @param string $otp + * @return string + * @throws AuthorizationException + * @throws WebApiException + */ + public function activate(int $userId, string $tfaToken, string $otp): string + { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, Google::CODE); + + if ($this->google->verify($user, $this->dataObjectFactory->create([ + 'data' => [ + 'tfa_code' => $otp + ], + ])) + ) { + $this->tfa->getProvider(Google::CODE)->activate((int)$user->getId()); + + $this->alert->event( + 'Magento_TwoFactorAuth', + 'New Google Authenticator code issued', + AlertInterface::LEVEL_INFO, + $user->getUserName() + ); + + return $this->tokenFactory->create()->createAdminToken($userId)->getToken(); + } else { + throw new AuthorizationException(__('Invalid code.')); + } + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey.php index a6725caf..a4a6b313 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey.php @@ -21,6 +21,11 @@ */ class U2fKey implements EngineInterface { + /** + * The config path for the domain to use when issuing challenged from the web api + */ + const XML_PATH_WEBAPI_DOMAIN = 'twofactorauth/u2fkey/webapi_challenge_domain'; + /** * Engine code * diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php new file mode 100644 index 00000000..c23d29b1 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php @@ -0,0 +1,188 @@ +tfa = $tfa; + $this->u2fKey = $u2fKey; + $this->alert = $alert; + $this->tokenFactory = $tokenFactory; + $this->dataObjectFactory = $dataObjectFactory; + $this->userAuthenticator = $userAuthenticator; + $this->authnRequestInterfaceFactory = $authnRequestInterfaceFactory; + $this->json = $json; + $this->configManager = $configManager; + } + + /** + * @inheritDoc + */ + public function getAuthenticationData(string $username, string $password): U2FWebAuthnRequestInterface + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $userId = (int)$user->getId(); + + if (!$this->tfa->getProviderIsAllowed($userId, U2fKey::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(U2fKey::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is not configured.')); + } + + $data = $this->u2fKey->getAuthenticateData($user); + $this->configManager->addProviderConfig( + $userId, + U2fKey::CODE, + [self::AUTHENTICATION_CHALLENGE_KEY => $data['credentialRequestOptions']['challenge']] + ); + + $json = $this->json->serialize($data); + + return $this->authnRequestInterfaceFactory->create( + [ + 'data' => [ + U2FWebAuthnRequestInterface::CREDENTIAL_REQUEST_OPTIONS_JSON => $json + ] + ] + ); + } + + /** + * @inheritDoc + */ + public function verify(string $username, string $password, string $publicKeyCredentialJson): string + { + $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $userId = (int)$user->getId(); + + $config = $this->configManager->getProviderConfig($userId, U2fKey::CODE); + if (!$this->tfa->getProviderIsAllowed($userId, U2fKey::CODE)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode(U2fKey::CODE)->isActive($userId)) { + throw new WebApiException(__('Provider is not configured.')); + } elseif (empty($config[self::AUTHENTICATION_CHALLENGE_KEY])) { + throw new WebApiException(__('U2f authentication prompt not sent.')); + } + + try { + $this->u2fKey->verify($user, $this->dataObjectFactory->create( + [ + 'data' => [ + 'publicKeyCredential' => $this->json->unserialize($publicKeyCredentialJson), + 'originalChallenge' => $config[self::AUTHENTICATION_CHALLENGE_KEY] + ] + ] + )); + } catch (\Exception $e) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'U2F error', + AlertInterface::LEVEL_ERROR, + $user->getUserName(), + $e->getMessage() + ); + throw $e; + } + + $this->configManager->addProviderConfig( + $userId, + U2fKey::CODE, + [self::AUTHENTICATION_CHALLENGE_KEY => null] + ); + + return $this->tokenFactory->create() + ->createAdminToken($userId) + ->getToken(); + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php new file mode 100644 index 00000000..76d9bac0 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php @@ -0,0 +1,164 @@ +u2fKey = $u2fKey; + $this->userAuthenticator = $userAuthenticator; + $this->configManager = $configManager; + $this->authnInterfaceFactory = $authnInterfaceFactory; + $this->alert = $alert; + $this->tokenFactory = $tokenFactory; + $this->json = $json; + } + + /** + * @inheritDoc + */ + public function getRegistrationData(int $userId, string $tfaToken): U2FWebAuthnRequestInterface + { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, U2fKey::CODE); + + $data = $this->u2fKey->getRegisterData($user); + + $this->configManager->addProviderConfig( + $userId, + U2fKey::CODE, + [self::REGISTER_CHALLENGE_KEY => $data['publicKey']['challenge']] + ); + + return $this->authnInterfaceFactory->create( + [ + 'data' => [ + U2FWebAuthnRequestInterface::CREDENTIAL_REQUEST_OPTIONS_JSON => $this->json->serialize($data) + ] + ] + ); + } + + /** + * @inheritDoc + */ + public function activate(int $userId, string $tfaToken, string $publicKeyCredentialJson): string + { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, U2fKey::CODE); + + $config = $this->configManager->getProviderConfig($userId, U2fKey::CODE); + + if (empty($config[self::REGISTER_CHALLENGE_KEY])) { + throw new WebApiException(__('U2f key registration was not started.')); + } + + try { + $this->u2fKey->registerDevice( + $user, + [ + 'publicKeyCredential' => $this->json->unserialize($publicKeyCredentialJson), + 'challenge' => $config[self::REGISTER_CHALLENGE_KEY] + ] + ); + $this->alert->event( + 'Magento_TwoFactorAuth', + 'U2F New device registered', + AlertInterface::LEVEL_INFO, + $user->getUserName() + ); + } catch (\Exception $e) { + $this->alert->event( + 'Magento_TwoFactorAuth', + 'U2F error while adding device', + AlertInterface::LEVEL_ERROR, + $user->getUserName(), + $e->getMessage() + ); + throw $e; + } + + $this->configManager->addProviderConfig( + $userId, + U2fKey::CODE, + [self::REGISTER_CHALLENGE_KEY => null] + ); + + return $this->tokenFactory->create() + ->createAdminToken($userId) + ->getToken(); + } + +} diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php index 53d0c7a7..afab25e7 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php @@ -9,10 +9,14 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; use CBOR\CBOREncoder; +use Magento\Framework\App\Area; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\State; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Validation\ValidationException; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; +use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; use Magento\User\Api\Data\UserInterface; /** @@ -32,13 +36,29 @@ class WebAuthn */ private $storeManager; + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var State + */ + private $appState; + /** * @param StoreManagerInterface $storeManager + * @param ScopeConfigInterface $scopeConfig + * @param State $appState */ public function __construct( - StoreManagerInterface $storeManager + StoreManagerInterface $storeManager, + ScopeConfigInterface $scopeConfig, + State $appState ) { $this->storeManager = $storeManager; + $this->scopeConfig = $scopeConfig; + $this->appState = $appState; } /** @@ -74,7 +94,7 @@ public function assertCredentialDataIsValid( // Steps 7-9 if (rtrim(strtr(base64_encode($this->convertArrayToBytes($originalChallenge)), '+/', '-_'), '=') !== $credentialData['response']['clientData']['challenge'] - || 'https://' . $domain !== $credentialData['response']['clientData']['origin'] + || $domain !== parse_url($credentialData['response']['clientData']['origin'], \PHP_URL_HOST) || $credentialData['response']['clientData']['type'] !== 'webauthn.get' ) { throw new LocalizedException(__('Invalid U2F key.')); @@ -221,7 +241,7 @@ public function getPublicKeyFromRegistrationData(array $data): array if (rtrim(strtr(base64_encode($this->convertArrayToBytes($data['challenge'])), '+/', '-_'), '=') !== $credentialData['response']['clientData']['challenge'] - || 'https://' . $domain !== $credentialData['response']['clientData']['origin'] + || $domain !== parse_url($credentialData['response']['clientData']['origin'], \PHP_URL_HOST) || $credentialData['response']['clientData']['type'] !== 'webauthn.create' ) { throw new LocalizedException(__('Invalid U2F key.')); @@ -322,6 +342,13 @@ private function convertArrayToBytes(array $bytes): string */ private function getDomainName(): string { + $configValue = $this->scopeConfig->getValue(U2fKey::XML_PATH_WEBAPI_DOMAIN); + if ($configValue && + in_array($this->appState->getAreaCode(), [Area::AREA_WEBAPI_REST, Area::AREA_WEBAPI_SOAP]) + ) { + return $configValue; + } + $store = $this->storeManager->getStore(Store::ADMIN_CODE); $baseUrl = $store->getBaseUrl(); if (!preg_match('/^(https?:\/\/(?P.+?))\//', $baseUrl, $matches)) { diff --git a/TwoFactorAuth/Model/TfatActions.php b/TwoFactorAuth/Model/TfatActions.php new file mode 100644 index 00000000..cfef9cbc --- /dev/null +++ b/TwoFactorAuth/Model/TfatActions.php @@ -0,0 +1,86 @@ +tokenManager = $tokenManager; + $this->tfa = $tfa; + } + + /** + * Get list of providers available for the user + * + * @param int $userId + * @param string $tfaToken + * @return \Magento\TwoFactorAuth\Api\ProviderInterface[] + * @throws AuthorizationException + */ + public function getUserProviders(int $userId, string $tfaToken): array + { + $this->validateTfat($userId, $tfaToken); + + return $this->tfa->getUserProviders($userId); + } + + /** + * Get list of providers requiring activation + * + * @param int $userId + * @param string $tfaToken + * @return \Magento\TwoFactorAuth\Api\ProviderInterface[] + * @throws AuthorizationException + */ + public function getProvidersToActivate(int $userId, string $tfaToken): array + { + $this->validateTfat($userId, $tfaToken); + + return $this->tfa->getProvidersToActivate($userId); + } + + /** + * Validate the given 2fa token + * + * @param int $userId + * @param string $tfat + * @throws AuthorizationException + */ + private function validateTfat(int $userId, string $tfat): void + { + if (!$this->tokenManager->isValidFor($userId, $tfat)) { + throw new AuthorizationException(__('Invalid token.')); + } + } +} diff --git a/TwoFactorAuth/Model/UserAuthenticator.php b/TwoFactorAuth/Model/UserAuthenticator.php new file mode 100644 index 00000000..08ca2cd0 --- /dev/null +++ b/TwoFactorAuth/Model/UserAuthenticator.php @@ -0,0 +1,148 @@ +credentialsValidator = $credentialsValidator; + $this->userFactory = $userFactory; + $this->requestThrottler = $requestThrottler; + $this->userResource = $userResource; + $this->tfa = $tfa; + $this->dataObjectFactory = $dataObjectFactory; + $this->tokenManager = $tokenManager; + } + + /** + * Get a user with credentials while enforcing throttling + * + * @param string $username + * @param string $password + * @return User + */ + public function authenticateWithCredentials(string $username, string $password): User + { + $this->credentialsValidator->validate($username, $password); + $this->requestThrottler->throttle($username, RequestThrottler::USER_TYPE_ADMIN); + + $user = $this->userFactory->create(); + $user->login($username, $password); + + if (!$user->getId()) { + $this->requestThrottler->logAuthenticationFailure($username, RequestThrottler::USER_TYPE_ADMIN); + + throw new AuthenticationException( + __( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + ) + ); + } + + $this->requestThrottler->resetAuthenticationFailuresCount($username, RequestThrottler::USER_TYPE_ADMIN); + + return $user; + } + + /** + * Obtain a user with an id and a tfa token + * + * @param int $userId + * @param string $tfaToken + * @param string $providerCode + * @return User + * @throws AuthorizationException + * @throws WebApiException + */ + public function authenticateWithTokenAndProvider(int $userId, string $tfaToken, string $providerCode): User + { + if (!$this->tfa->getProviderIsAllowed($userId, $providerCode)) { + throw new WebApiException(__('Provider is not allowed.')); + } elseif ($this->tfa->getProviderByCode($providerCode)->isActive($userId)) { + throw new WebApiException(__('Provider is already configured.')); + } elseif (!$this->tokenManager->isValidFor($userId, $tfaToken)) { + throw new AuthorizationException( + __('Invalid tfa token') + ); + } + + $user = $this->userFactory->create(); + $this->userResource->load($user, $userId); + + return $user; + } +} diff --git a/TwoFactorAuth/Model/UserConfig/SignedTokenManager.php b/TwoFactorAuth/Model/UserConfig/SignedTokenManager.php index d0d3e93c..0bda1904 100644 --- a/TwoFactorAuth/Model/UserConfig/SignedTokenManager.php +++ b/TwoFactorAuth/Model/UserConfig/SignedTokenManager.php @@ -64,8 +64,8 @@ public function issueFor(int $userId): string public function isValidFor(int $userId, string $token): bool { $isValid = false; - [$encodedData, $signatureProvided] = explode('.', base64_decode($token)); try { + [$encodedData, $signatureProvided] = explode('.', base64_decode($token)); $data = $this->json->unserialize($encodedData); if (array_key_exists('user_id', $data) && array_key_exists('tfa_configuration', $data) diff --git a/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php new file mode 100644 index 00000000..5f87f31e --- /dev/null +++ b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php @@ -0,0 +1,182 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->userConfig = $objectManager->get(UserConfigManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + $this->config = $objectManager->get(Config::class); + } + + /** + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testDefaultBehaviorForInvalidCredentials() + { + $serviceInfo = $this->buildServiceInfo(); + + try { + $this->_webApiCall( + $serviceInfo, + ['username' => 'customRoleUser', 'password' => 'bad'] + ); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.', + $response['message'] + ); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testUserWithConfigured2fa() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(Google::CODE)->activate($userId); + $serviceInfo = $this->buildServiceInfo(); + + $response = $this->_webApiCall( + $serviceInfo, + ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] + ); + self::assertSame($userId, (int)$response[AdminTokenResponseInterface::USER_ID]); + self::assertSame( + 'Please use the 2fa provider-specific endpoints to obtain a token.', + $response[AdminTokenResponseInterface::MESSAGE] + ); + self::assertCount(1, $response[AdminTokenResponseInterface::ACTIVE_PROVIDERS]); + self::assertSame('google', $response[AdminTokenResponseInterface::ACTIVE_PROVIDERS][0]['code']); + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google,duo_security + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testUserWithAvailableUnconfigured2fa() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(Google::CODE)->activate($userId); + $serviceInfo = $this->buildServiceInfo(); + + $response = $this->_webApiCall( + $serviceInfo, + ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] + ); + self::assertSame($userId, (int)$response[AdminTokenResponseInterface::USER_ID]); + self::assertSame( + 'You are required to configure personal Two-Factor Authorization in order to login. ' + . 'Please check your email.', + $response[AdminTokenResponseInterface::MESSAGE] + ); + self::assertCount(1, $response[AdminTokenResponseInterface::ACTIVE_PROVIDERS]); + self::assertSame('google', $response[AdminTokenResponseInterface::ACTIVE_PROVIDERS][0]['code']); + } + + /** + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testNoAvailable2faProviders() + { + $this->config->setDataByPath('twofactorauth/general/force_providers', ''); + $this->config->save(); + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(Google::CODE)->activate($userId); + $serviceInfo = $this->buildServiceInfo(); + + try { + $this->_webApiCall( + $serviceInfo, + ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] + ); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame( + 'Please ask an administrator with sufficient access to configure 2FA first', + $response['message'] + ); + } + } + + /** + * @return array + */ + private function buildServiceInfo(): array + { + return [ + 'rest' => [ + // Ensure the default auth is invalidated + 'token' => 'invalid', + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => Request::HTTP_METHOD_POST + ], + 'soap' => [ + // Ensure the default auth is invalidated + 'token' => 'invalid', + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . self::OPERATION + ] + ]; + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Api/GoogleActivateTest.php b/TwoFactorAuth/Test/Api/GoogleActivateTest.php new file mode 100644 index 00000000..b3c65793 --- /dev/null +++ b/TwoFactorAuth/Test/Api/GoogleActivateTest.php @@ -0,0 +1,181 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + $this->userFactory = $objectManager->get(UserFactory::class); + $this->google = $objectManager->get(Google::class); + $this->userConfig = $objectManager->get(UserConfigManagerInterface::class); + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testInvalidTfat() + { + $serviceInfo = $this->buildServiceInfo($this->getUserId()); + + try { + $this->_webApiCall($serviceInfo, ['tfaToken' => 'abc', 'otp' => 'invalid']); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Invalid tfa token', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers duo_security + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testUnavailableProvider() + { + $userId = $this->getUserId(); + $token = $this->tokenManager->issueFor($userId); + $serviceInfo = $this->buildServiceInfo($userId); + + try { + $this->_webApiCall($serviceInfo, ['tfaToken' => $token, 'otp' => 'invalid']); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Provider is not allowed.', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testAlreadyActivatedProvider() + { + $userId = $this->getUserId(); + $token = $this->tokenManager->issueFor($userId); + $serviceInfo = $this->buildServiceInfo($userId); + $otp = $this->getUserOtp(); + $this->tfa->getProviderByCode(Google::CODE) + ->activate($userId); + + try { + $this->_webApiCall($serviceInfo, ['tfaToken' => $token, 'otp' => $otp]); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Provider is already configured.', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testActivate() + { + $userId = $this->getUserId(); + $token = $this->tokenManager->issueFor($userId); + $otp = $this->getUserOtp(); + $serviceInfo = $this->buildServiceInfo($userId); + + $response = $this->_webApiCall( + $serviceInfo, + [ + 'tfaToken' => $token, + 'otp' => $otp + ] + ); + self::assertNotEmpty($response); + self::assertRegExp('/^[a-z0-9]{32}$/', $response); + } + + private function getUserOtp(): string + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + $totp = new TOTP($user->getEmail(), $this->google->getSecretCode($user)); + + // Enable longer window of valid tokens to prevent test race condition + $this->userConfig->addProviderConfig((int)$user->getId(), Google::CODE, ['window' => 120]); + + return $totp->now(); + } + + /** + * @param int $userId + * @return array + */ + private function buildServiceInfo(int $userId): array + { + return [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '/' . $userId, + 'httpMethod' => Request::HTTP_METHOD_POST + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . self::OPERATION + ] + ]; + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php new file mode 100644 index 00000000..6b20da8e --- /dev/null +++ b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php @@ -0,0 +1,228 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->google = $objectManager->get(Google::class); + $this->userConfig = $objectManager->get(UserConfigManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + } + + /** + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testInvalidCredentials() + { + $serviceInfo = $this->buildServiceInfo(); + + try { + $this->_webApiCall( + $serviceInfo, + [ + 'username' => 'customRoleUser', + 'password' => 'bad', + 'otp' => 'foo' + ] + ); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.', + $response['message'] + ); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers duo_security + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testUnavailableProvider() + { + $serviceInfo = $this->buildServiceInfo(); + + try { + $this->_webApiCall( + $serviceInfo, + [ + 'username' => 'customRoleUser', + 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, + 'otp' => 'foo' + ] + ); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Provider is not allowed.', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testInvalidToken() + { + $userId = $this->getUserId(); + $serviceInfo = $this->buildServiceInfo(); + $this->tfa->getProviderByCode(Google::CODE) + ->activate($userId); + + try { + $this->_webApiCall( + $serviceInfo, + [ + 'username' => 'customRoleUser', + 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, + 'otp' => 'bad' + ] + ); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Invalid code.', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testNotConfiguredProvider() + { + $userId = $this->getUserId(); + $serviceInfo = $this->buildServiceInfo(); + $this->tfa->getProviderByCode(Google::CODE) + ->resetConfiguration($userId); + + try { + $this->_webApiCall( + $serviceInfo, + [ + 'username' => 'customRoleUser', + 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, + 'otp' => 'foo' + ] + ); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Provider is not configured.', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testValidToken() + { + $userId = $this->getUserId(); + $otp = $this->getUserOtp(); + $serviceInfo = $this->buildServiceInfo(); + $this->tfa->getProviderByCode(Google::CODE) + ->activate($userId); + + $response = $this->_webApiCall( + $serviceInfo, + [ + 'username' => 'customRoleUser', + 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, + 'otp' => $otp + ] + ); + self::assertNotEmpty($response); + self::assertRegExp('/^[a-z0-9]{32}$/', $response); + } + + /** + * @return array + */ + private function buildServiceInfo(): array + { + return [ + 'rest' => [ + // Ensure the default auth is invalidated + 'token' => 'invalid', + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => Request::HTTP_METHOD_POST + ], + 'soap' => [ + // Ensure the default auth is invalidated + 'token' => 'invalid', + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . self::OPERATION + ] + ]; + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + + return (int)$user->getId(); + } + + private function getUserOtp(): string + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + $totp = new TOTP($user->getEmail(), $this->google->getSecretCode($user)); + + // Enable longer window of valid tokens to prevent test race condition + $this->userConfig->addProviderConfig((int)$user->getId(), Google::CODE, ['window' => 120]); + + return $totp->now(); + } +} diff --git a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php new file mode 100644 index 00000000..5eabd99f --- /dev/null +++ b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php @@ -0,0 +1,148 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testInvalidTfat() + { + $serviceInfo = $this->buildServiceInfo($this->getUserId()); + + try { + $this->_webApiCall($serviceInfo, ['tfaToken' => 'abc']); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Invalid tfa token', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers duo_security + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testUnavailableProvider() + { + $userId = $this->getUserId(); + $token = $this->tokenManager->issueFor($userId); + $serviceInfo = $this->buildServiceInfo($userId); + + try { + $this->_webApiCall($serviceInfo, ['tfaToken' => $token]); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Provider is not allowed.', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $token = $this->tokenManager->issueFor($userId); + $serviceInfo = $this->buildServiceInfo($userId); + $this->tfa->getProviderByCode(Google::CODE) + ->activate($userId); + + try { + $this->_webApiCall($serviceInfo, ['tfaToken' => $token]); + self::fail('Endpoint should have thrown an exception'); + } catch (\Throwable $exception) { + $response = json_decode($exception->getMessage(), true); + self::assertEmpty(json_last_error()); + self::assertSame('Provider is already configured.', $response['message']); + } + } + + /** + * @magentoConfigFixture twofactorauth/general/force_providers google + * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + */ + public function testValidRequest() + { + $userId = $this->getUserId(); + $token = $this->tokenManager->issueFor($userId); + $serviceInfo = $this->buildServiceInfo($userId); + + $response = $this->_webApiCall($serviceInfo, ['tfaToken' => $token]); + self::assertNotEmpty($response['qr_code_url']); + self::assertStringStartsWith('data:image/png', $response['qr_code_url']); + self::assertNotEmpty($response['secret_code']); + } + + /** + * @param int $userId + * @return array + */ + private function buildServiceInfo(int $userId): array + { + return [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '/' . $userId, + 'httpMethod' => Request::HTTP_METHOD_POST + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . self::OPERATION + ] + ]; + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('customRoleUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php new file mode 100644 index 00000000..1e023594 --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php @@ -0,0 +1,150 @@ +tfa = $objectManager->get(TfaInterface::class); + $this->authy = $this->createMock(Authy::class); + $this->userFactory = $objectManager->get(UserFactory::class); + $this->model = $objectManager->create( + Authenticate::class, + [ + 'authy' => $this->authy + ] + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthenticationException + */ + public function testAuthenticateInvalidCredentials() + { + $this->authy + ->expects($this->never()) + ->method('verify'); + $this->model->authenticateWithToken( + 'adminUser', + 'bad', + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not configured. + */ + public function testAuthenticateNotConfiguredProvider() + { + $this->authy + ->expects($this->never()) + ->method('verify'); + $this->model->authenticateWithToken( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testAuthenticateUnavailableProvider() + { + $this->authy + ->expects($this->never()) + ->method('verify'); + $this->model->authenticateWithToken( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testAuthenticateValidRequest() + { + $this->tfa->getProviderByCode(Authy::CODE) + ->activate($this->getUserId()); + $userId = $this->getUserId(); + $this->authy + ->expects($this->once()) + ->method('verify') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + $this->callback(function ($value) { + return $value->getData('tfa_code') === 'abc'; + }) + ); + $result = $this->model->authenticateWithToken( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'abc' + ); + + self::assertRegExp('/^[a-z0-9]{32}$/', $result); + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('adminUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php new file mode 100644 index 00000000..ece9d9cd --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php @@ -0,0 +1,318 @@ +verification = $this->createMock(Verification::class); + $this->userFactory = $objectManager->get(UserFactory::class); + $this->deviceDataFactory = $objectManager->get(AuthyDeviceInterfaceFactory::class); + $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + $this->authy = $this->createMock(Authy::class); + $this->model = $objectManager->create( + Configure::class, + [ + 'verification' => $this->verification, + 'authy' => $this->authy + ] + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthorizationException + * @expectedExceptionMessage Invalid tfa token + */ + public function testConfigureInvalidTfat() + { + $userId = $this->getUserId(); + $this->verification + ->expects($this->never()) + ->method('request'); + $this->model->sendDeviceRegistrationPrompt( + $userId, + 'abc', + $this->deviceDataFactory->create( + [ + 'data' => [ + AuthyDeviceInterface::COUNTRY => '1', + AuthyDeviceInterface::PHONE => '555-555-5555', + AuthyDeviceInterface::METHOD => AuthyDeviceInterface::METHOD_SMS, + ] + ] + ) + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is already configured. + */ + public function testConfigureAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(Authy::CODE) + ->activate($userId); + $this->verification + ->expects($this->never()) + ->method('request'); + $this->model->sendDeviceRegistrationPrompt( + $userId, + 'abc', + $this->deviceDataFactory->create( + [ + 'data' => [ + AuthyDeviceInterface::COUNTRY => '1', + AuthyDeviceInterface::PHONE => '555-555-5555', + AuthyDeviceInterface::METHOD => AuthyDeviceInterface::METHOD_SMS, + ] + ] + ) + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testConfigureUnavailableProvider() + { + $userId = $this->getUserId(); + $this->verification + ->expects($this->never()) + ->method('request'); + $this->model->sendDeviceRegistrationPrompt( + $userId, + 'abc', + $this->deviceDataFactory->create( + [ + 'data' => [ + AuthyDeviceInterface::COUNTRY => '1', + AuthyDeviceInterface::PHONE => '555-555-5555', + AuthyDeviceInterface::METHOD => AuthyDeviceInterface::METHOD_SMS, + ] + ] + ) + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testConfigureValidRequest() + { + $userId = $this->getUserId(); + + $this->verification + ->method('request') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + '4', + '555-555-5555', + AuthyDeviceInterface::METHOD_SMS, + $this->anything() + ) + ->willReturnCallback( + function ($userId, $country, $phone, $method, &$response) { + // These keys come from authy api not our model + $response['message'] = 'foo'; + $response['seconds_to_expire'] = 123; + } + ); + + $result = $this->model->sendDeviceRegistrationPrompt( + $userId, + $this->tokenManager->issueFor($userId), + $this->deviceDataFactory->create( + [ + 'data' => [ + AuthyDeviceInterface::COUNTRY => '4', + AuthyDeviceInterface::PHONE => '555-555-5555', + AuthyDeviceInterface::METHOD => AuthyDeviceInterface::METHOD_SMS, + ] + ] + ) + ); + + self::assertSame('foo', $result->getMessage()); + self::assertSame(123, $result->getExpirationSeconds()); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthorizationException + * @expectedExceptionMessage Invalid tfa token + */ + public function testActivateInvalidTfat() + { + $userId = $this->getUserId(); + $this->verification + ->expects($this->never()) + ->method('request'); + $this->authy + ->expects($this->never()) + ->method('enroll'); + $this->model->activate( + $userId, + 'abc', + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is already configured. + */ + public function testActivateAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(Authy::CODE) + ->activate($userId); + $this->authy + ->expects($this->never()) + ->method('enroll'); + $this->verification + ->expects($this->never()) + ->method('request'); + $this->model->activate( + $userId, + 'abc', + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testActivateUnavailableProvider() + { + $userId = $this->getUserId(); + $this->authy + ->expects($this->never()) + ->method('enroll'); + $this->verification + ->expects($this->never()) + ->method('request'); + $this->model->activate( + $userId, + 'abc', + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testActivateValidRequest() + { + $userId = $this->getUserId(); + $this->verification + ->method('verify') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + 'cba' + ); + $this->authy + ->expects($this->once()) + ->method('enroll') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ); + $this->model->activate( + $userId, + $this->tokenManager->issueFor($userId), + 'cba' + ); + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('adminUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php new file mode 100644 index 00000000..780a8ddf --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php @@ -0,0 +1,293 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + $this->duo = $this->createMock(DuoSecurity::class); + $this->model = $objectManager->create( + Authenticate::class, + [ + 'duo' => $this->duo, + ] + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthenticationException + */ + public function testGetAuthenticateDataInvalidCredentials() + { + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->getAuthenticateData( + 'adminUser', + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not configured. + */ + public function testGetAuthenticateDataNotConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->resetConfiguration($userId); + + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->getAuthenticateData( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testGetAuthenticateDataUnavailableProvider() + { + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->getAuthenticateData( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + } + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthenticationException + */ + public function testVerifyInvalidCredentials() + { + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->verify( + 'adminUser', + 'abc', + 'signature' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not configured. + */ + public function testVerifyNotConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->resetConfiguration($userId); + + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'signature' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testVerifyUnavailableProvider() + { + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'signature' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testGetAuthenticateDataValidRequest() + { + $userId = $this->getUserId(); + + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($userId); + + $this->duo + ->method('getApiHostname') + ->willReturn('abc'); + $this->duo + ->method('getRequestSignature') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ) + ->willReturn('cba'); + + $result = $this->model->getAuthenticateData( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + + self::assertInstanceOf(DuoDataInterface::class, $result); + self::assertSame('abc', $result->getApiHostname()); + self::assertSame('cba', $result->getSignature()); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testVerifyValidRequest() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($userId); + + $signature = 'a signature'; + $this->duo->method('verify') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + $this->callback(function ($value) use ($signature) { + return $value->getData('sig_response') === $signature; + }) + ) + ->willReturn(true); + + $token = $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + $signature + ); + + self::assertRegExp('/^[a-z0-9]{32}$/', $token); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Invalid response + */ + public function testVerifyInvalidRequest() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($userId); + + $signature = 'a signature'; + $this->duo->method('verify') + ->willReturn(false); + + $token = $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + $signature + ); + + self::assertEmpty($token); + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('adminUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php new file mode 100644 index 00000000..dadbb5dc --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php @@ -0,0 +1,293 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + $this->duo = $this->createMock(DuoSecurity::class); + $this->authenticate = $this->createMock(Authenticate::class); + $this->model = $objectManager->create( + Configure::class, + [ + 'duo' => $this->duo, + 'authenticate' => $this->authenticate + ] + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthorizationException + * @expectedExceptionMessage Invalid tfa token + */ + public function testGetConfigurationDataInvalidTfat() + { + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->getConfigurationData( + $this->getUserId(), + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is already configured. + */ + public function testGetConfigurationDataAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($userId); + + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->getConfigurationData( + $userId, + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testGetConfigurationDataUnavailableProvider() + { + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->getConfigurationData( + $this->getUserId(), + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthorizationException + * @expectedExceptionMessage Invalid tfa token + */ + public function testActivateInvalidTfat() + { + $userId = $this->getUserId(); + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->activate( + $userId, + 'abc', + 'something' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is already configured. + */ + public function testActivateAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($userId); + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->activate( + $userId, + 'abc', + 'something' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testActivateUnavailableProvider() + { + $userId = $this->getUserId(); + $this->duo + ->expects($this->never()) + ->method('getRequestSignature'); + $this->model->activate( + $userId, + 'abc', + 'something' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testGetConfigurationDataValidRequest() + { + $userId = $this->getUserId(); + + $this->duo + ->method('getApiHostname') + ->willReturn('abc'); + $this->duo + ->method('getRequestSignature') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ) + ->willReturn('cba'); + + $result = $this->model->getConfigurationData( + $userId, + $this->tokenManager->issueFor($userId) + ); + + self::assertInstanceOf(DuoDataInterface::class, $result); + self::assertSame('abc', $result->getApiHostname()); + self::assertSame('cba', $result->getSignature()); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testActivateValidRequest() + { + $userId = $this->getUserId(); + $tfat = $this->tokenManager->issueFor($userId); + + $signature = 'a signature'; + $this->authenticate->method('assertResponseIsValid') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + $signature + ); + + $token = $this->model->activate($userId, $tfat, $signature); + + self::assertRegExp('/^[a-z0-9]{32}$/', $token); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoConfigFixture default/twofactorauth/duo/integration_key abc123 + * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 + * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Something + */ + public function testActivateInvalidDataThrowsException() + { + $userId = $this->getUserId(); + $tfat = $this->tokenManager->issueFor($userId); + + $signature = 'a signature'; + $this->authenticate->method('assertResponseIsValid') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + $signature + ) + ->willThrowException(new \InvalidArgumentException('Something')); + + $result = $this->model->activate($userId, $tfat, $signature); + + self::assertEmpty($result); + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('adminUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php new file mode 100644 index 00000000..2065c291 --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php @@ -0,0 +1,273 @@ +tfa = $objectManager->get(TfaInterface::class); + $this->u2fkey = $this->createMock(U2fKey::class); + $this->userFactory = $objectManager->get(UserFactory::class); + $this->model = $objectManager->create( + Authenticate::class, + [ + 'u2fKey' => $this->u2fkey + ] + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthenticationException + */ + public function testGetDataInvalidCredentials() + { + $this->u2fkey + ->expects($this->never()) + ->method('getAuthenticateData'); + $this->model->getAuthenticationData( + 'adminUser', + 'bad' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not configured. + */ + public function testGetDataNotConfiguredProvider() + { + $this->u2fkey + ->expects($this->never()) + ->method('getAuthenticateData'); + $this->model->getAuthenticationData( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testGetDataUnavailableProvider() + { + $this->u2fkey + ->expects($this->never()) + ->method('getAuthenticateData'); + $this->model->getAuthenticationData( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthenticationException + */ + public function testVerifyInvalidCredentials() + { + $this->u2fkey + ->expects($this->never()) + ->method('verify'); + $this->model->verify( + 'adminUser', + 'bad', + 'I identify as JSON' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not configured. + */ + public function testVerifyNotConfiguredProvider() + { + $this->u2fkey + ->expects($this->never()) + ->method('verify'); + $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'I identify as JSON' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testVerifyUnavailableProvider() + { + $this->u2fkey + ->expects($this->never()) + ->method('verify'); + $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'I identify as JSON' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testGetDataValidRequest() + { + $this->tfa->getProviderByCode(U2fKey::CODE) + ->activate($this->getUserId()); + $userId = $this->getUserId(); + + $data = ['credentialRequestOptions' => ['challenge' => [1, 2, 3]]]; + $this->u2fkey + ->expects($this->once()) + ->method('getAuthenticateData') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ) + ->willReturn($data); + + $result = $this->model->getAuthenticationData( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + + self::assertInstanceOf(U2FWebAuthnRequestInterface::class, $result); + self::assertSame(json_encode($data), $result->getCredentialRequestOptionsJson()); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testVerifyValidRequest() + { + $this->tfa->getProviderByCode(U2fKey::CODE) + ->activate($this->getUserId()); + $userId = $this->getUserId(); + + $this->u2fkey + ->expects($this->once()) + ->method('getAuthenticateData') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ) + ->willReturn(['credentialRequestOptions' => ['challenge' => [3, 2, 1]]]); + $this->model->getAuthenticationData('adminUser', Bootstrap::ADMIN_PASSWORD); + + $verifyData = ['foo' => 'bar']; + $this->u2fkey + ->expects($this->once()) + ->method('verify') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + $this->callback(function ($data) use ($verifyData) { + return $data->getData('publicKeyCredential') === $verifyData + // Assert the previously issued challenge is used for verification + && $data->getData('originalChallenge') === [3, 2, 1]; + }) + ); + + $token = $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + json_encode($verifyData) + ); + self::assertRegExp('/^[a-z0-9]{32}$/', $token); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Something + */ + public function testVerifyThrowsExceptionRequest() + { + $this->tfa->getProviderByCode(U2fKey::CODE) + ->activate($this->getUserId()); + $userId = $this->getUserId(); + + $this->u2fkey + ->method('getAuthenticateData') + ->willReturn(['credentialRequestOptions' => ['challenge' => [4, 5, 6]]]); + $this->model->getAuthenticationData('adminUser', Bootstrap::ADMIN_PASSWORD); + + $this->u2fkey + ->method('verify') + ->willThrowException(new \InvalidArgumentException('Something')); + + $result = $this->model->verify( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + json_encode(['foo' => 'bar']) + ); + + self::assertEmpty($result); + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('adminUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php new file mode 100644 index 00000000..5b17d1ef --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php @@ -0,0 +1,272 @@ +userFactory = $objectManager->get(UserFactory::class); + $this->tokenManager = $objectManager->get(UserConfigTokenManagerInterface::class); + $this->tfa = $objectManager->get(TfaInterface::class); + $this->u2fkey = $this->createMock(U2fKey::class); + $this->model = $objectManager->create( + Configure::class, + [ + 'u2fKey' => $this->u2fkey + ] + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthorizationException + * @expectedExceptionMessage Invalid tfa token + */ + public function testGetRegistrationDataInvalidTfat() + { + $userId = $this->getUserId(); + $this->u2fkey + ->expects($this->never()) + ->method('getRegisterData'); + $this->model->getRegistrationData( + $userId, + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is already configured. + */ + public function testGetRegistrationDataAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(U2fKey::CODE) + ->activate($userId); + $this->u2fkey + ->expects($this->never()) + ->method('getRegisterData'); + $this->model->getRegistrationData( + $userId, + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testGetRegistrationDataUnavailableProvider() + { + $userId = $this->getUserId(); + $this->u2fkey + ->expects($this->never()) + ->method('getRegisterData'); + $this->model->getRegistrationData( + $userId, + 'abc' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthorizationException + * @expectedExceptionMessage Invalid tfa token + */ + public function testActivateInvalidTfat() + { + $userId = $this->getUserId(); + $this->u2fkey + ->expects($this->never()) + ->method('registerDevice'); + $this->model->activate( + $userId, + 'abc', + 'I identify as JSON' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is already configured. + */ + public function testActivateAlreadyConfiguredProvider() + { + $userId = $this->getUserId(); + $this->tfa->getProviderByCode(U2fKey::CODE) + ->activate($userId); + $this->u2fkey + ->expects($this->never()) + ->method('registerDevice'); + $this->model->activate( + $userId, + 'abc', + 'I identify as JSON' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Webapi\Exception + * @expectedExceptionMessage Provider is not allowed. + */ + public function testActivateUnavailableProvider() + { + $userId = $this->getUserId(); + $this->u2fkey + ->expects($this->never()) + ->method('registerDevice'); + $this->model->activate( + $userId, + 'abc', + 'I identify as JSON' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testGetRegistrationDataValidRequest() + { + $userId = $this->getUserId(); + $data = ['publicKey' => ['challenge' => [1, 2, 3]]]; + + $this->u2fkey + ->method('getRegisterData') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ) + ->willReturn($data); + + $result = $this->model->getRegistrationData( + $userId, + $this->tokenManager->issueFor($userId) + ); + + self::assertInstanceOf(U2FWebAuthnRequestInterface::class, $result); + self::assertSame(json_encode($data), $result->getCredentialRequestOptionsJson()); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testActivateValidRequest() + { + $userId = $this->getUserId(); + $tfat = $this->tokenManager->issueFor($userId); + + $this->u2fkey + ->method('getRegisterData') + ->willReturn(['publicKey' => ['challenge' => [3, 2, 1]]]); + $this->model->getRegistrationData($userId, $tfat); + + $activateData = ['foo' => 'bar']; + $this->u2fkey + ->method('registerDevice') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + [ + 'publicKeyCredential' => $activateData, + // Asserts the previously issued challenge was used for verification + 'challenge' => [3, 2, 1] + ] + ); + + $token = $this->model->activate($userId, $tfat, json_encode($activateData)); + + self::assertRegExp('/^[a-z0-9]{32}$/', $token); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Something + */ + public function testActivateInvalidKeyDataThrowsException() + { + $userId = $this->getUserId(); + $tfat = $this->tokenManager->issueFor($userId); + + $this->u2fkey + ->method('getRegisterData') + ->willReturn(['publicKey' => ['challenge' => [3, 2, 1]]]); + $this->model->getRegistrationData($userId, $tfat); + + $this->u2fkey + ->method('registerDevice') + ->willThrowException(new \InvalidArgumentException('Something')); + + $result = $this->model->activate($userId, $tfat, json_encode(['foo' => 'bar'])); + + self::assertEmpty($result); + } + + private function getUserId(): int + { + $user = $this->userFactory->create(); + $user->loadByUsername('adminUser'); + + return (int)$user->getId(); + } +} diff --git a/TwoFactorAuth/etc/adminhtml/system.xml b/TwoFactorAuth/etc/adminhtml/system.xml index 94180a24..b5878177 100644 --- a/TwoFactorAuth/etc/adminhtml/system.xml +++ b/TwoFactorAuth/etc/adminhtml/system.xml @@ -30,6 +30,11 @@ Two-factor authorization providers for admin users to use during login Magento\TwoFactorAuth\Model\Config\Backend\ForceProviders + + + This can be used to override the default email configuration link that is sent when using the Magento Web API's to authenticate. + OneTouch Message + + + + + This domain will be used when issuing and processing WebAuthn challenges via WebApi. The store domain will be used by default. + + diff --git a/TwoFactorAuth/etc/di.xml b/TwoFactorAuth/etc/di.xml index 3f80d9fa..b419a3af 100644 --- a/TwoFactorAuth/etc/di.xml +++ b/TwoFactorAuth/etc/di.xml @@ -23,7 +23,23 @@ + + + + + + + + + + + + + + + + diff --git a/TwoFactorAuth/etc/webapi.xml b/TwoFactorAuth/etc/webapi.xml index 917171ac..effe4ec7 100644 --- a/TwoFactorAuth/etc/webapi.xml +++ b/TwoFactorAuth/etc/webapi.xml @@ -8,59 +8,185 @@ + + + + + + + - + - + - + - + - + - + - + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0c43f6da785d95db8395607c8bcd971a3dd10831 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Tue, 7 Apr 2020 16:56:46 -0500 Subject: [PATCH 12/58] MC-30536: Enable 2FA for web API - upgrade qr-code library to 3.7 --- TwoFactorAuth/Model/Provider/Engine/Google.php | 4 +++- TwoFactorAuth/composer.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/TwoFactorAuth/Model/Provider/Engine/Google.php b/TwoFactorAuth/Model/Provider/Engine/Google.php index aa3a7819..2713f2f1 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google.php @@ -7,6 +7,7 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine; +use Endroid\QrCode\ErrorCorrectionLevel; use Endroid\QrCode\Exception\ValidationException; use Endroid\QrCode\QrCode; use Endroid\QrCode\Writer\PngWriter; @@ -159,7 +160,8 @@ public function getQrCodeAsPng(UserInterface $user): string // @codingStandardsIgnoreStart $qrCode = new QrCode($this->getProvisioningUrl($user)); $qrCode->setSize(400); - $qrCode->setErrorCorrectionLevel('high'); + $qrCode->setMargin(0); + $qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH()); $qrCode->setForegroundColor(['r' => 0, 'g' => 0, 'b' => 0, 'a' => 0]); $qrCode->setBackgroundColor(['r' => 255, 'g' => 255, 'b' => 255, 'a' => 0]); $qrCode->setLabelFontSize(16); diff --git a/TwoFactorAuth/composer.json b/TwoFactorAuth/composer.json index 366dcff4..b6210711 100644 --- a/TwoFactorAuth/composer.json +++ b/TwoFactorAuth/composer.json @@ -12,7 +12,7 @@ "magento/module-user": "*", "christian-riesen/base32": "^1.3", "spomky-labs/otphp": "~8.3", - "endroid/qr-code": "^2.5", + "endroid/qr-code": "^3.7", "donatj/phpuseragentparser": "~0.7", "2tvenom/cborencode": "^1.0", "phpseclib/phpseclib": "~2.0" From 871e025bf8859e6946c77bc378fdbece9c522e7c Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 9 Apr 2020 11:53:43 -0500 Subject: [PATCH 13/58] MC-30536: Enable 2FA for web API - Refactored to not need userId in endpoints - Refactored token generation and user authentication --- .../Api/AdminTokenServiceInterface.php | 9 +- TwoFactorAuth/Api/AuthyConfigureInterface.php | 7 +- .../Api/Data/AdminTokenResponseInterface.php | 95 --------------- TwoFactorAuth/Api/DuoConfigureInterface.php | 7 +- .../Api/GoogleConfigureInterface.php | 7 +- TwoFactorAuth/Api/TfaInterface.php | 4 +- TwoFactorAuth/Api/TfatActionsInterface.php | 6 +- .../Api/U2fKeyConfigureInterface.php | 8 +- .../Model/AdminAccessTokenService.php | 109 +++++++++--------- .../Model/Data/AdminTokenResponse.php | 82 ------------- TwoFactorAuth/Model/EmailUserNotifier.php | 7 +- .../Provider/Engine/Authy/Authenticate.php | 66 +++++++---- .../Model/Provider/Engine/Authy/Configure.php | 32 +---- .../Engine/DuoSecurity/Authenticate.php | 59 +++++++--- .../Provider/Engine/DuoSecurity/Configure.php | 22 +--- .../Provider/Engine/Google/Authenticate.php | 47 ++++---- .../Provider/Engine/Google/Configure.php | 29 +---- .../Provider/Engine/U2fKey/Authenticate.php | 70 +++++++---- .../Provider/Engine/U2fKey/Configure.php | 24 ++-- TwoFactorAuth/Model/TfatActions.php | 40 ++++--- TwoFactorAuth/Model/UserAuthenticator.php | 76 +++--------- TwoFactorAuth/Plugin/DeleteCookieOnLogout.php | 68 +++++++++++ .../Test/Api/AdminIntegrationTokenTest.php | 53 +++++---- TwoFactorAuth/Test/Api/GoogleActivateTest.php | 15 ++- .../Test/Api/GoogleConfigureTest.php | 13 +-- .../Engine/Authy/AuthenticateTest.php | 2 + .../Provider/Engine/Authy/ConfigureTest.php | 21 ++-- .../Engine/DuoSecurity/AuthenticateTest.php | 4 + .../Engine/DuoSecurity/ConfigureTest.php | 22 ++-- .../Engine/U2fKey/AuthenticateTest.php | 7 ++ .../Provider/Engine/U2fKey/ConfigureTest.php | 25 ++-- TwoFactorAuth/etc/adminhtml/di.xml | 8 ++ TwoFactorAuth/etc/adminhtml/system.xml | 4 +- TwoFactorAuth/etc/di.xml | 1 - TwoFactorAuth/etc/module.xml | 1 + TwoFactorAuth/etc/webapi.xml | 20 ++-- .../view/adminhtml/layout/tfa_authy_auth.xml | 1 + .../adminhtml/layout/tfa_authy_configure.xml | 1 + .../view/adminhtml/layout/tfa_duo_auth.xml | 1 + .../view/adminhtml/layout/tfa_google_auth.xml | 1 + .../adminhtml/layout/tfa_google_configure.xml | 1 + .../view/adminhtml/layout/tfa_screen.xml | 28 +++++ .../adminhtml/layout/tfa_tfa_accessdenied.xml | 1 + .../adminhtml/layout/tfa_tfa_configure.xml | 1 + .../layout/tfa_tfa_requestconfig.xml | 2 + .../view/adminhtml/layout/tfa_u2f_auth.xml | 1 + .../adminhtml/layout/tfa_u2f_configure.xml | 1 + .../adminhtml/templates/tfa/logout_link.phtml | 10 ++ .../view/adminhtml/web/css/tfa-screen.css | 10 ++ 49 files changed, 526 insertions(+), 603 deletions(-) delete mode 100644 TwoFactorAuth/Api/Data/AdminTokenResponseInterface.php delete mode 100644 TwoFactorAuth/Model/Data/AdminTokenResponse.php create mode 100644 TwoFactorAuth/Plugin/DeleteCookieOnLogout.php create mode 100644 TwoFactorAuth/view/adminhtml/layout/tfa_screen.xml create mode 100644 TwoFactorAuth/view/adminhtml/templates/tfa/logout_link.phtml create mode 100644 TwoFactorAuth/view/adminhtml/web/css/tfa-screen.css diff --git a/TwoFactorAuth/Api/AdminTokenServiceInterface.php b/TwoFactorAuth/Api/AdminTokenServiceInterface.php index 7738408f..79a4ee49 100644 --- a/TwoFactorAuth/Api/AdminTokenServiceInterface.php +++ b/TwoFactorAuth/Api/AdminTokenServiceInterface.php @@ -8,7 +8,7 @@ namespace Magento\TwoFactorAuth\Api; -use Magento\TwoFactorAuth\Api\Data\AdminTokenResponseInterface; +use Magento\Framework\Webapi\Exception as WebApiException; /** * Obtain basic information about the user required to setup or use 2fa @@ -20,10 +20,11 @@ interface AdminTokenServiceInterface * * @param string $username * @param string $password - * @throws \Magento\Framework\Exception\InputException For invalid input + * @return void + * @throws WebApiException + * @throws \Magento\Framework\Exception\InputException * @throws \Magento\Framework\Exception\AuthenticationException * @throws \Magento\Framework\Exception\LocalizedException - * @return AdminTokenResponseInterface */ - public function createAdminAccessToken(string $username, string $password): AdminTokenResponseInterface; + public function createAdminAccessToken(string $username, string $password): void; } diff --git a/TwoFactorAuth/Api/AuthyConfigureInterface.php b/TwoFactorAuth/Api/AuthyConfigureInterface.php index 1e51ddf9..8f315dcf 100644 --- a/TwoFactorAuth/Api/AuthyConfigureInterface.php +++ b/TwoFactorAuth/Api/AuthyConfigureInterface.php @@ -19,13 +19,11 @@ interface AuthyConfigureInterface /** * Get the information required to configure google * - * @param int $userId * @param string $tfaToken * @param AuthyDeviceInterface $deviceData * @return \Magento\TwoFactorAuth\Api\Data\AuthyRegistrationPromptResponseInterface */ public function sendDeviceRegistrationPrompt( - int $userId, string $tfaToken, AuthyDeviceInterface $deviceData ): ResponseInterface; @@ -33,10 +31,9 @@ public function sendDeviceRegistrationPrompt( /** * Activate the provider and get an admin token * - * @param int $userId * @param string $tfaToken * @param string $otp - * @return string + * @return bool */ - public function activate(int $userId, string $tfaToken, string $otp): string; + public function activate(string $tfaToken, string $otp): bool; } diff --git a/TwoFactorAuth/Api/Data/AdminTokenResponseInterface.php b/TwoFactorAuth/Api/Data/AdminTokenResponseInterface.php deleted file mode 100644 index 22a09b26..00000000 --- a/TwoFactorAuth/Api/Data/AdminTokenResponseInterface.php +++ /dev/null @@ -1,95 +0,0 @@ -tfa = $tfa; $this->configRequestManager = $configRequestManager; - $this->auth = $auth; - $this->userAuthenticator = $userAuthenticator; - $this->responseFactory = $responseFactory; + $this->userFactory = $userFactory; + $this->adminTokenService = $adminTokenService; } /** - * Create access token for admin given the admin credentials. + * Prevent the admin token from being created with this api * * @param string $username * @param string $password - * @return \Magento\TwoFactorAuth\Api\Data\AdminTokenResponseInterface + * @return void + * @throws AuthenticationException + * @throws WebapiException + * @throws InputException + * @throws LocalizedException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function createAdminAccessToken(string $username, string $password): AdminTokenResponseInterface + public function createAdminAccessToken(string $username, string $password): void { - $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); - + // No exception means valid input. Ignore the created token. + $this->adminTokenService->createAdminAccessToken($username, $password); + $user = $this->userFactory->create(); + $user->loadByUsername($username); $userId = (int)$user->getId(); + if ($userId === 0) { + throw new AuthenticationException(__( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + )); + } + + $providerCodes = []; + $activeProviderCodes = []; + foreach ($this->tfa->getUserProviders($userId) as $provider) { + $providerCodes[] = $provider->getCode(); + if ($provider->isActive($userId)) { + $activeProviderCodes[] = $provider->getCode(); + } + } if (!$this->configRequestManager->isConfigurationRequiredFor($userId)) { - return $this->responseFactory->create( + throw new WebapiException( + __('Please use the 2fa provider-specific endpoints to obtain a token.'), + 0, + WebapiException::HTTP_UNAUTHORIZED, [ - 'data' => [ - AdminTokenResponseInterface::USER_ID => $userId, - AdminTokenResponseInterface::MESSAGE => - (string)__('Please use the 2fa provider-specific endpoints to obtain a token.'), - AdminTokenResponseInterface::ACTIVE_PROVIDERS => array_filter( - $this->tfa->getUserProviders($userId), - function ($provider) use ($userId) { - return $provider->isActive($userId) ? $provider : null; - } - ), - ] + 'active_providers' => $activeProviderCodes ] ); } elseif (empty($this->tfa->getUserProviders($userId))) { @@ -113,20 +122,14 @@ function ($provider) use ($userId) { ); } - return $this->responseFactory->create( + throw new WebapiException( + __('You are required to configure personal Two-Factor Authorization in order to login. ' + . 'Please check your email.'), + 0, + WebapiException::HTTP_UNAUTHORIZED, [ - 'data' => [ - AdminTokenResponseInterface::USER_ID => $userId, - AdminTokenResponseInterface::MESSAGE => - (string)__('You are required to configure personal Two-Factor Authorization in order to login. ' - . 'Please check your email.'), - AdminTokenResponseInterface::ACTIVE_PROVIDERS => array_filter( - $this->tfa->getUserProviders($userId), - function ($provider) use ($userId) { - return $provider->isActive($userId) ? $provider : null; - } - ) - ] + 'providers' => $providerCodes, + 'active_providers' => $activeProviderCodes ] ); } diff --git a/TwoFactorAuth/Model/Data/AdminTokenResponse.php b/TwoFactorAuth/Model/Data/AdminTokenResponse.php deleted file mode 100644 index 956ca842..00000000 --- a/TwoFactorAuth/Model/Data/AdminTokenResponse.php +++ /dev/null @@ -1,82 +0,0 @@ -_get(self::USER_ID); - } - - /** - * @inheritDoc - */ - public function setUserId(int $value): void - { - $this->setData(self::USER_ID, $value); - } - - /** - * @inheritDoc - */ - public function getMessage(): string - { - return (string)$this->_get(self::MESSAGE); - } - - /** - * @inheritDoc - */ - public function setMessage(string $value): void - { - $this->setData(self::MESSAGE, $value); - } - - /** - * @inheritDoc - */ - public function getActiveProviders(): array - { - return $this->_get(self::ACTIVE_PROVIDERS); - } - - /** - * @inheritDoc - */ - public function setActiveProviders(array $value): void - { - $this->setData(self::ACTIVE_PROVIDERS, $value); - } - - /** - * @inheritdoc - */ - public function getExtensionAttributes(): ?AdminTokenResponseExtensionInterface - { - return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); - } - - /** - * @inheritDoc - */ - public function setExtensionAttributes(AdminTokenResponseExtensionInterface $extensionAttributes): void - { - $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); - } -} diff --git a/TwoFactorAuth/Model/EmailUserNotifier.php b/TwoFactorAuth/Model/EmailUserNotifier.php index e87b7181..9d399394 100644 --- a/TwoFactorAuth/Model/EmailUserNotifier.php +++ b/TwoFactorAuth/Model/EmailUserNotifier.php @@ -15,7 +15,6 @@ use Magento\Store\Model\StoreManagerInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\User\Model\User; -use Magento\TwoFactorAuth\Api\Exception\NotificationExceptionInterface; use Magento\TwoFactorAuth\Api\UserNotifierInterface; use Magento\Framework\Mail\Template\TransportBuilder; use Magento\TwoFactorAuth\Model\Exception\NotificationException; @@ -96,11 +95,9 @@ private function sendConfigRequired( bool $useWebApiUrl = false ): void { try { - $userUrl = $this->scopeConfig->getValue(TfaInterface::XML_PATH_WEBAPI_CONFIG_EMAIL_URL); + $userUrl = $this->scopeConfig->getValue(TfaInterface::XML_PATH_WEBAPI_NOTIFICATION_URL); if ($useWebApiUrl && $userUrl) { - $url = $userUrl . - (parse_url($userUrl, PHP_URL_QUERY) ? '&' : '?') . - http_build_query(['tfat' => $token, 'user_id' => $user->getId()]); + $url = str_replace(':tfat', $token, $userUrl); } else { $url = $this->url->getUrl('tfa/tfa/index', ['tfat' => $token]); } diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php index 36f6b436..357aba35 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php @@ -9,14 +9,15 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\Authy; use Magento\Framework\DataObjectFactory; -use Magento\Framework\Exception\AuthorizationException; +use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Webapi\Exception as WebApiException; +use Magento\Integration\Api\AdminTokenServiceInterface; use Magento\TwoFactorAuth\Api\AuthyAuthenticateInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Authy; -use Magento\TwoFactorAuth\Model\UserAuthenticator; -use Magento\Integration\Model\Oauth\TokenFactory as TokenModelFactory; +use Magento\User\Api\Data\UserInterface; +use Magento\User\Model\UserFactory; /** * Authenticate a user with authy @@ -24,9 +25,9 @@ class Authenticate implements AuthyAuthenticateInterface { /** - * @var UserAuthenticator + * @var UserFactory */ - private $userAuthenticator; + private $userFactory; /** * @var Authy @@ -42,10 +43,11 @@ class Authenticate implements AuthyAuthenticateInterface * @var DataObjectFactory */ private $dataObjectFactory; + /** - * @var TokenModelFactory + * @var AdminTokenServiceInterface */ - private $tokenFactory; + private $adminTokenService; /** * @var Token @@ -63,30 +65,30 @@ class Authenticate implements AuthyAuthenticateInterface private $oneTouch; /** - * @param UserAuthenticator $userAuthenticator + * @param UserFactory $userFactory * @param Authy $authy * @param AlertInterface $alert * @param DataObjectFactory $dataObjectFactory - * @param TokenModelFactory $tokenFactory + * @param AdminTokenServiceInterface $adminTokenService * @param Token $authyToken * @param TfaInterface $tfa * @param OneTouch $oneTouch */ public function __construct( - UserAuthenticator $userAuthenticator, + UserFactory $userFactory, Authy $authy, AlertInterface $alert, DataObjectFactory $dataObjectFactory, - TokenModelFactory $tokenFactory, + AdminTokenServiceInterface $adminTokenService, Token $authyToken, TfaInterface $tfa, OneTouch $oneTouch ) { - $this->userAuthenticator = $userAuthenticator; + $this->userFactory = $userFactory; $this->authy = $authy; $this->alert = $alert; $this->dataObjectFactory = $dataObjectFactory; - $this->tokenFactory = $tokenFactory; + $this->adminTokenService = $adminTokenService; $this->authyToken = $authyToken; $this->tfa = $tfa; $this->oneTouch = $oneTouch; @@ -97,7 +99,7 @@ public function __construct( */ public function authenticateWithToken(string $username, string $password, string $otp): string { - $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $user = $this->getUser($username); if (!$this->tfa->getProviderIsAllowed((int)$user->getId(), Authy::CODE)) { throw new WebApiException(__('Provider is not allowed.')); @@ -105,6 +107,8 @@ public function authenticateWithToken(string $username, string $password, string throw new WebApiException(__('Provider is not configured.')); } + $token = $this->adminTokenService->createAdminAccessToken($username, $password); + try { $this->authy->verify($user, $this->dataObjectFactory->create([ 'data' => [ @@ -112,9 +116,7 @@ public function authenticateWithToken(string $username, string $password, string ], ])); - return $this->tokenFactory->create() - ->createAdminToken((int)$user->getId()) - ->getToken(); + return $token; } catch (\Exception $e) { $this->alert->event( 'Magento_TwoFactorAuth', @@ -132,7 +134,7 @@ public function authenticateWithToken(string $username, string $password, string */ public function sendToken(string $username, string $password, string $via): bool { - $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $user = $this->getUser($username); if (!$this->tfa->getProviderIsAllowed((int)$user->getId(), Authy::CODE)) { throw new WebApiException(__('Provider is not allowed.')); @@ -154,7 +156,7 @@ public function sendToken(string $username, string $password, string $via): bool */ public function authenticateWithOnetouch(string $username, string $password): string { - $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $user = $this->getUser($username); if (!$this->tfa->getProviderIsAllowed((int)$user->getId(), Authy::CODE)) { throw new WebApiException(__('Provider is not allowed.')); @@ -165,7 +167,7 @@ public function authenticateWithOnetouch(string $username, string $password): st try { $res = $this->oneTouch->verify($user); if ($res === 'approved') { - return $this->tokenFactory->create() + return $this->adminTokenService->create() ->createAdminToken((int)$user->getId()) ->getToken(); } else { @@ -176,7 +178,7 @@ public function authenticateWithOnetouch(string $username, string $password): st $user->getUserName() ); - throw new AuthorizationException(__('Onetouch prompt was denied or timed out.')); + throw new WebApiException(__('Onetouch prompt was denied or timed out.')); } } catch (\Exception $e) { $this->alert->event( @@ -190,4 +192,26 @@ public function authenticateWithOnetouch(string $username, string $password): st throw $e; } } + + /** + * Retrieve a user using the username + * + * @param string $username + * @return UserInterface + * @throws AuthenticationException + */ + private function getUser(string $username): UserInterface + { + $user = $this->userFactory->create(); + $user->loadByUsername($username); + $userId = (int)$user->getId(); + if ($userId === 0) { + throw new AuthenticationException(__( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + )); + } + + return $user; + } } diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php index a7b97d45..1665f8f5 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php @@ -8,7 +8,6 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\Authy; -use Magento\Integration\Model\Oauth\TokenFactory as TokenModelFactory; use Magento\TwoFactorAuth\Api\AuthyConfigureInterface; use Magento\TwoFactorAuth\Api\Data\AuthyDeviceInterface; use Magento\TwoFactorAuth\Model\AlertInterface; @@ -37,11 +36,6 @@ class Configure implements AuthyConfigureInterface */ private $responseFactory; - /** - * @var TokenModelFactory - */ - private $tokenFactory; - /** * @var Authy */ @@ -56,7 +50,6 @@ class Configure implements AuthyConfigureInterface * @param AlertInterface $alert * @param Verification $verification * @param ResponseFactory $responseFactory - * @param TokenModelFactory $tokenFactory * @param Authy $authy * @param UserAuthenticator $userAuthenticator */ @@ -64,14 +57,12 @@ public function __construct( AlertInterface $alert, Verification $verification, ResponseFactory $responseFactory, - TokenModelFactory $tokenFactory, Authy $authy, UserAuthenticator $userAuthenticator ) { $this->alert = $alert; $this->verification = $verification; $this->responseFactory = $responseFactory; - $this->tokenFactory = $tokenFactory; $this->authy = $authy; $this->userAuthenticator = $userAuthenticator; } @@ -79,12 +70,8 @@ public function __construct( /** * @inheritDoc */ - public function sendDeviceRegistrationPrompt( - int $userId, - string $tfaToken, - AuthyDeviceInterface $deviceData - ): ResponseInterface { - $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, Authy::CODE); + public function sendDeviceRegistrationPrompt(string $tfaToken, AuthyDeviceInterface $deviceData): ResponseInterface { + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, Authy::CODE); $response = []; $this->verification->request( @@ -113,16 +100,11 @@ public function sendDeviceRegistrationPrompt( } /** - * Activate the provider and get an admin token - * - * @param int $userId - * @param string $tfaToken - * @param string $otp - * @return string + * @inheritDoc */ - public function activate(int $userId, string $tfaToken, string $otp): string + public function activate(string $tfaToken, string $otp): bool { - $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, Authy::CODE); + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, Authy::CODE); try { $this->verification->verify($user, $otp); @@ -135,9 +117,7 @@ public function activate(int $userId, string $tfaToken, string $otp): string $user->getUserName() ); - return $this->tokenFactory->create() - ->createAdminToken($userId) - ->getToken(); + return true; } catch (\Throwable $e) { $this->alert->event( 'Magento_TwoFactorAuth', diff --git a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php index ceab0978..6d5149d3 100644 --- a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php @@ -9,16 +9,17 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity; use Magento\Framework\DataObjectFactory; +use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Webapi\Exception as WebApiException; -use Magento\Integration\Model\Oauth\TokenFactory; +use Magento\Integration\Api\AdminTokenServiceInterface; use Magento\TwoFactorAuth\Api\Data\DuoDataInterface; use Magento\TwoFactorAuth\Api\Data\DuoDataInterfaceFactory; use Magento\TwoFactorAuth\Api\DuoAuthenticateInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity; -use Magento\TwoFactorAuth\Model\UserAuthenticator; use Magento\User\Api\Data\UserInterface; +use Magento\User\Model\UserFactory; /** * Authenticate with duo @@ -26,9 +27,9 @@ class Authenticate implements DuoAuthenticateInterface { /** - * @var UserAuthenticator + * @var UserFactory */ - private $userAuthenticator; + private $userFactory; /** * @var AlertInterface @@ -41,9 +42,9 @@ class Authenticate implements DuoAuthenticateInterface private $duo; /** - * @var TokenFactory + * @var AdminTokenServiceInterface */ - private $tokenFactory; + private $adminTokenService; /** * @var DuoDataInterfaceFactory @@ -61,27 +62,27 @@ class Authenticate implements DuoAuthenticateInterface private $tfa; /** - * @param UserAuthenticator $userAuthenticator + * @param UserFactory $userFactory * @param AlertInterface $alert * @param DuoSecurity $duo - * @param TokenFactory $tokenFactory + * @param AdminTokenServiceInterface $adminTokenService * @param DuoDataInterfaceFactory $dataFactory * @param DataObjectFactory $dataObjectFactory * @param TfaInterface $tfa */ public function __construct( - UserAuthenticator $userAuthenticator, + UserFactory $userFactory, AlertInterface $alert, DuoSecurity $duo, - TokenFactory $tokenFactory, + AdminTokenServiceInterface $adminTokenService, DuoDataInterfaceFactory $dataFactory, DataObjectFactory $dataObjectFactory, TfaInterface $tfa ) { - $this->userAuthenticator = $userAuthenticator; + $this->userFactory = $userFactory; $this->alert = $alert; $this->duo = $duo; - $this->tokenFactory = $tokenFactory; + $this->adminTokenService = $adminTokenService; $this->dataFactory = $dataFactory; $this->dataObjectFactory = $dataObjectFactory; $this->tfa = $tfa; @@ -92,7 +93,7 @@ public function __construct( */ public function getAuthenticateData(string $username, string $password): DuoDataInterface { - $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $user = $this->getUser($username); $userId = (int)$user->getId(); if (!$this->tfa->getProviderIsAllowed($userId, DuoSecurity::CODE)) { @@ -101,6 +102,8 @@ public function getAuthenticateData(string $username, string $password): DuoData throw new WebApiException(__('Provider is not configured.')); } + $this->adminTokenService->createAdminAccessToken($username, $password); + return $this->dataFactory->create( [ 'data' => [ @@ -116,7 +119,7 @@ public function getAuthenticateData(string $username, string $password): DuoData */ public function verify(string $username, string $password, string $signatureResponse): string { - $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $user = $this->getUser($username); $userId = (int)$user->getId(); if (!$this->tfa->getProviderIsAllowed($userId, DuoSecurity::CODE)) { @@ -125,11 +128,11 @@ public function verify(string $username, string $password, string $signatureResp throw new WebApiException(__('Provider is not configured.')); } + $token = $this->adminTokenService->createAdminAccessToken($username, $password); + $this->assertResponseIsValid($user, $signatureResponse); - return $this->tokenFactory->create() - ->createAdminToken($userId) - ->getToken(); + return $token; } /** @@ -159,4 +162,26 @@ public function assertResponseIsValid(UserInterface $user, string $signatureResp throw new WebApiException(__('Invalid response')); } } + + /** + * Retrieve a user using the username + * + * @param string $username + * @return UserInterface + * @throws AuthenticationException + */ + private function getUser(string $username): UserInterface + { + $user = $this->userFactory->create(); + $user->loadByUsername($username); + $userId = (int)$user->getId(); + if ($userId === 0) { + throw new AuthenticationException(__( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + )); + } + + return $user; + } } diff --git a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php index 9ebc75c9..89005aa7 100644 --- a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php @@ -8,7 +8,6 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity; -use Magento\Integration\Model\Oauth\TokenFactory; use Magento\TwoFactorAuth\Api\Data\DuoDataInterface; use Magento\TwoFactorAuth\Api\Data\DuoDataInterfaceFactory; use Magento\TwoFactorAuth\Api\DuoConfigureInterface; @@ -31,11 +30,6 @@ class Configure implements DuoConfigureInterface */ private $duo; - /** - * @var TokenFactory - */ - private $tokenFactory; - /** * @var DuoDataInterfaceFactory */ @@ -54,7 +48,6 @@ class Configure implements DuoConfigureInterface /** * @param UserAuthenticator $userAuthenticator * @param DuoSecurity $duo - * @param TokenFactory $tokenFactory * @param DuoDataInterfaceFactory $dataFactory * @param TfaInterface $tfa * @param Authenticate $authenticate @@ -62,14 +55,12 @@ class Configure implements DuoConfigureInterface public function __construct( UserAuthenticator $userAuthenticator, DuoSecurity $duo, - TokenFactory $tokenFactory, DuoDataInterfaceFactory $dataFactory, TfaInterface $tfa, Authenticate $authenticate ) { $this->userAuthenticator = $userAuthenticator; $this->duo = $duo; - $this->tokenFactory = $tokenFactory; $this->dataFactory = $dataFactory; $this->tfa = $tfa; $this->authenticate = $authenticate; @@ -78,9 +69,9 @@ public function __construct( /** * @inheritDoc */ - public function getConfigurationData(int $userId, string $tfaToken): DuoDataInterface + public function getConfigurationData(string $tfaToken): DuoDataInterface { - $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, DuoSecurity::CODE); + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, DuoSecurity::CODE); return $this->dataFactory->create( [ @@ -95,16 +86,15 @@ public function getConfigurationData(int $userId, string $tfaToken): DuoDataInte /** * @inheritDoc */ - public function activate(int $userId, string $tfaToken, string $signatureResponse): string + public function activate(string $tfaToken, string $signatureResponse): bool { - $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, DuoSecurity::CODE); + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, DuoSecurity::CODE); + $userId = (int)$user->getId(); $this->authenticate->assertResponseIsValid($user, $signatureResponse); $this->tfa->getProviderByCode(DuoSecurity::CODE) ->activate($userId); - return $this->tokenFactory->create() - ->createAdminToken($userId) - ->getToken(); + return true; } } diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php index 2298d3ec..5e4b1f15 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php @@ -9,14 +9,15 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\Framework\DataObjectFactory; +use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Webapi\Exception as WebApiException; +use Magento\Integration\Api\AdminTokenServiceInterface; use Magento\TwoFactorAuth\Api\GoogleAuthenticateInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; -use Magento\TwoFactorAuth\Model\UserAuthenticator; -use Magento\Integration\Model\Oauth\TokenFactory as TokenModelFactory; +use Magento\User\Model\UserFactory; /** * Authenticate with google provider @@ -44,53 +45,55 @@ class Authenticate implements GoogleAuthenticateInterface private $alert; /** - * @var TokenModelFactory + * @var AdminTokenServiceInterface */ - private $tokenFactory; + private $adminTokenService; /** - * @var UserAuthenticator + * @var UserFactory */ - private $userAuthenticator; + private $userFactory; /** * @param Google $google * @param TfaInterface $tfa * @param DataObjectFactory $dataObjectFactory * @param AlertInterface $alert - * @param TokenModelFactory $tokenFactory - * @param UserAuthenticator $userAuthenticator + * @param AdminTokenServiceInterface $adminTokenService + * @param UserFactory $userFactory */ public function __construct( Google $google, TfaInterface $tfa, DataObjectFactory $dataObjectFactory, AlertInterface $alert, - TokenModelFactory $tokenFactory, - UserAuthenticator $userAuthenticator + AdminTokenServiceInterface $adminTokenService, + UserFactory $userFactory ) { $this->google = $google; $this->tfa = $tfa; $this->dataObjectFactory = $dataObjectFactory; $this->alert = $alert; - $this->tokenFactory = $tokenFactory; - $this->userAuthenticator = $userAuthenticator; + $this->adminTokenService = $adminTokenService; + $this->userFactory = $userFactory; } /** - * Get an admin token by authenticating using google - * - * @param string $username - * @param string $password - * @param string $otp - * @return string - * @throws AuthorizationException - * @throws WebApiException + * @inheritDoc */ public function getToken(string $username, string $password, string $otp): string { - $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $user = $this->userFactory->create(); + $user->loadByUsername($username); $userId = (int)$user->getId(); + if ($userId === 0) { + throw new AuthenticationException(__( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + )); + } + + $token = $this->adminTokenService->createAdminAccessToken($username, $password); if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { throw new WebApiException(__('Provider is not allowed.')); @@ -109,7 +112,7 @@ public function getToken(string $username, string $password, string $otp): strin $user->getUserName() ); - return $this->tokenFactory->create()->createAdminToken($userId)->getToken(); + return $token; } else { throw new AuthorizationException(__('Invalid code.')); } diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php index 58f6d85f..552c8995 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php @@ -10,8 +10,6 @@ use Magento\Framework\DataObjectFactory; use Magento\Framework\Exception\AuthorizationException; -use Magento\Framework\Webapi\Exception as WebApiException; -use Magento\Integration\Model\Oauth\TokenFactory as TokenModelFactory; use Magento\TwoFactorAuth\Api\Data\GoogleConfigureInterface as GoogleConfigurationData; use Magento\TwoFactorAuth\Api\GoogleConfigureInterface; use Magento\TwoFactorAuth\Api\TfaInterface; @@ -50,11 +48,6 @@ class Configure implements GoogleConfigureInterface */ private $alert; - /** - * @var TokenModelFactory - */ - private $tokenFactory; - /** * @var UserAuthenticator */ @@ -66,7 +59,6 @@ class Configure implements GoogleConfigureInterface * @param TfaInterface $tfa * @param DataObjectFactory $dataObjectFactory * @param AlertInterface $alert - * @param TokenModelFactory $tokenFactory * @param UserAuthenticator $userAuthenticator */ public function __construct( @@ -75,7 +67,6 @@ public function __construct( TfaInterface $tfa, DataObjectFactory $dataObjectFactory, AlertInterface $alert, - TokenModelFactory $tokenFactory, UserAuthenticator $userAuthenticator ) { $this->configurationDataFactory = $configurationDataFactory; @@ -83,16 +74,15 @@ public function __construct( $this->tfa = $tfa; $this->dataObjectFactory = $dataObjectFactory; $this->alert = $alert; - $this->tokenFactory = $tokenFactory; $this->userAuthenticator = $userAuthenticator; } /** * @inheritDoc */ - public function getConfigurationData(int $userId, string $tfaToken): GoogleConfigurationData + public function getConfigurationData(string $tfaToken): GoogleConfigurationData { - $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, Google::CODE); + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, Google::CODE); return $this->configurationDataFactory->create( [ @@ -106,18 +96,11 @@ public function getConfigurationData(int $userId, string $tfaToken): GoogleConfi } /** - * Activate the provider - * - * @param int $userId - * @param string $tfaToken - * @param string $otp - * @return string - * @throws AuthorizationException - * @throws WebApiException + * @inheritDoc */ - public function activate(int $userId, string $tfaToken, string $otp): string + public function activate(string $tfaToken, string $otp): bool { - $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, Google::CODE); + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, Google::CODE); if ($this->google->verify($user, $this->dataObjectFactory->create([ 'data' => [ @@ -134,7 +117,7 @@ public function activate(int $userId, string $tfaToken, string $otp): string $user->getUserName() ); - return $this->tokenFactory->create()->createAdminToken($userId)->getToken(); + return true; } else { throw new AuthorizationException(__('Invalid code.')); } diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php index c23d29b1..7914b788 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php @@ -9,9 +9,10 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; use Magento\Framework\DataObjectFactory; +use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\Webapi\Exception as WebApiException; -use Magento\Integration\Model\Oauth\TokenFactory; +use Magento\Integration\Api\AdminTokenServiceInterface; use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface; use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterfaceFactory; use Magento\TwoFactorAuth\Api\TfaInterface; @@ -19,7 +20,8 @@ use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; -use Magento\TwoFactorAuth\Model\UserAuthenticator; +use Magento\User\Api\Data\UserInterface; +use Magento\User\Model\UserFactory; /** * Authenticate with the u2f provider and get an admin token @@ -43,20 +45,15 @@ class Authenticate implements U2fKeyAuthenticateInterface */ private $alert; - /** - * @var TokenFactory - */ - private $tokenFactory; - /** * @var DataObjectFactory */ private $dataObjectFactory; /** - * @var UserAuthenticator + * @var UserFactory */ - private $userAuthenticator; + private $userFactory; /** * @var U2FWebAuthnRequestInterfaceFactory @@ -73,37 +70,42 @@ class Authenticate implements U2fKeyAuthenticateInterface */ private $configManager; + /** + * @var AdminTokenServiceInterface + */ + private $adminTokenService; + /** * @param TfaInterface $tfa * @param U2fKey $u2fKey * @param AlertInterface $alert - * @param TokenFactory $tokenFactory * @param DataObjectFactory $dataObjectFactory - * @param UserAuthenticator $userAuthenticator + * @param UserFactory $userFactory * @param U2FWebAuthnRequestInterfaceFactory $authnRequestInterfaceFactory * @param Json $json * @param UserConfigManagerInterface $configManager + * @param AdminTokenServiceInterface $adminTokenService */ public function __construct( TfaInterface $tfa, U2fKey $u2fKey, AlertInterface $alert, - TokenFactory $tokenFactory, DataObjectFactory $dataObjectFactory, - UserAuthenticator $userAuthenticator, + UserFactory $userFactory, U2FWebAuthnRequestInterfaceFactory $authnRequestInterfaceFactory, Json $json, - UserConfigManagerInterface $configManager + UserConfigManagerInterface $configManager, + AdminTokenServiceInterface $adminTokenService ) { $this->tfa = $tfa; $this->u2fKey = $u2fKey; $this->alert = $alert; - $this->tokenFactory = $tokenFactory; $this->dataObjectFactory = $dataObjectFactory; - $this->userAuthenticator = $userAuthenticator; + $this->userFactory = $userFactory; $this->authnRequestInterfaceFactory = $authnRequestInterfaceFactory; $this->json = $json; $this->configManager = $configManager; + $this->adminTokenService = $adminTokenService; } /** @@ -111,7 +113,7 @@ public function __construct( */ public function getAuthenticationData(string $username, string $password): U2FWebAuthnRequestInterface { - $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $user = $this->getUser($username); $userId = (int)$user->getId(); if (!$this->tfa->getProviderIsAllowed($userId, U2fKey::CODE)) { @@ -120,6 +122,9 @@ public function getAuthenticationData(string $username, string $password): U2FWe throw new WebApiException(__('Provider is not configured.')); } + // No exception means valid + $this->adminTokenService->createAdminAccessToken($username, $password); + $data = $this->u2fKey->getAuthenticateData($user); $this->configManager->addProviderConfig( $userId, @@ -143,7 +148,7 @@ public function getAuthenticationData(string $username, string $password): U2FWe */ public function verify(string $username, string $password, string $publicKeyCredentialJson): string { - $user = $this->userAuthenticator->authenticateWithCredentials($username, $password); + $user = $this->getUser($username); $userId = (int)$user->getId(); $config = $this->configManager->getProviderConfig($userId, U2fKey::CODE); @@ -155,6 +160,9 @@ public function verify(string $username, string $password, string $publicKeyCred throw new WebApiException(__('U2f authentication prompt not sent.')); } + // Validates/throttles credentials + $token = $this->adminTokenService->createAdminAccessToken($username, $password); + try { $this->u2fKey->verify($user, $this->dataObjectFactory->create( [ @@ -181,8 +189,28 @@ public function verify(string $username, string $password, string $publicKeyCred [self::AUTHENTICATION_CHALLENGE_KEY => null] ); - return $this->tokenFactory->create() - ->createAdminToken($userId) - ->getToken(); + return $token; + } + + /** + * Retrieve a user using the username + * + * @param string $username + * @return UserInterface + * @throws AuthenticationException + */ + private function getUser(string $username): UserInterface + { + $user = $this->userFactory->create(); + $user->loadByUsername($username); + $userId = (int)$user->getId(); + if ($userId === 0) { + throw new AuthenticationException(__( + 'The account sign-in was incorrect or your account is disabled temporarily. ' + . 'Please wait and try again later.' + )); + } + + return $user; } } diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php index 76d9bac0..657c02df 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php @@ -10,7 +10,6 @@ use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\Webapi\Exception as WebApiException; -use Magento\Integration\Model\Oauth\TokenFactory; use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface; use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterfaceFactory; use Magento\TwoFactorAuth\Api\U2fKeyConfigureInterface; @@ -51,11 +50,6 @@ class Configure implements U2fKeyConfigureInterface */ private $alert; - /** - * @var TokenFactory - */ - private $tokenFactory; - /** * @var Json */ @@ -67,7 +61,6 @@ class Configure implements U2fKeyConfigureInterface * @param UserConfigManagerInterface $configManager * @param U2FWebAuthnRequestInterfaceFactory $authnInterfaceFactory * @param AlertInterface $alert - * @param TokenFactory $tokenFactory * @param Json $json */ public function __construct( @@ -76,7 +69,6 @@ public function __construct( UserConfigManagerInterface $configManager, U2FWebAuthnRequestInterfaceFactory $authnInterfaceFactory, AlertInterface $alert, - TokenFactory $tokenFactory, Json $json ) { $this->u2fKey = $u2fKey; @@ -84,16 +76,16 @@ public function __construct( $this->configManager = $configManager; $this->authnInterfaceFactory = $authnInterfaceFactory; $this->alert = $alert; - $this->tokenFactory = $tokenFactory; $this->json = $json; } /** * @inheritDoc */ - public function getRegistrationData(int $userId, string $tfaToken): U2FWebAuthnRequestInterface + public function getRegistrationData(string $tfaToken): U2FWebAuthnRequestInterface { - $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, U2fKey::CODE); + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, U2fKey::CODE); + $userId = (int)$user->getId(); $data = $this->u2fKey->getRegisterData($user); @@ -115,9 +107,10 @@ public function getRegistrationData(int $userId, string $tfaToken): U2FWebAuthnR /** * @inheritDoc */ - public function activate(int $userId, string $tfaToken, string $publicKeyCredentialJson): string + public function activate(string $tfaToken, string $publicKeyCredentialJson): bool { - $user = $this->userAuthenticator->authenticateWithTokenAndProvider($userId, $tfaToken, U2fKey::CODE); + $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, U2fKey::CODE); + $userId = (int)$user->getId(); $config = $this->configManager->getProviderConfig($userId, U2fKey::CODE); @@ -156,9 +149,6 @@ public function activate(int $userId, string $tfaToken, string $publicKeyCredent [self::REGISTER_CHALLENGE_KEY => null] ); - return $this->tokenFactory->create() - ->createAdminToken($userId) - ->getToken(); + return true; } - } diff --git a/TwoFactorAuth/Model/TfatActions.php b/TwoFactorAuth/Model/TfatActions.php index cfef9cbc..f95ddda0 100644 --- a/TwoFactorAuth/Model/TfatActions.php +++ b/TwoFactorAuth/Model/TfatActions.php @@ -9,6 +9,7 @@ namespace Magento\TwoFactorAuth\Model; use Magento\Framework\Exception\AuthorizationException; +use Magento\Framework\Serialize\Serializer\Json; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\TfatActionsInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; @@ -28,44 +29,42 @@ class TfatActions implements TfatActionsInterface */ private $tfa; + /** + * @var Json + */ + private $json; + /** * @param UserConfigTokenManagerInterface $tokenManager * @param TfaInterface $tfa + * @param Json $json */ public function __construct( UserConfigTokenManagerInterface $tokenManager, - TfaInterface $tfa + TfaInterface $tfa, + Json $json ) { $this->tokenManager = $tokenManager; $this->tfa = $tfa; + $this->json = $json; } /** - * Get list of providers available for the user - * - * @param int $userId - * @param string $tfaToken - * @return \Magento\TwoFactorAuth\Api\ProviderInterface[] - * @throws AuthorizationException + * @inheritDoc */ - public function getUserProviders(int $userId, string $tfaToken): array + public function getUserProviders(string $tfaToken): array { - $this->validateTfat($userId, $tfaToken); + $userId = $this->validateTfat($tfaToken); return $this->tfa->getUserProviders($userId); } /** - * Get list of providers requiring activation - * - * @param int $userId - * @param string $tfaToken - * @return \Magento\TwoFactorAuth\Api\ProviderInterface[] - * @throws AuthorizationException + * @inheritDoc */ - public function getProvidersToActivate(int $userId, string $tfaToken): array + public function getProvidersToActivate(string $tfaToken): array { - $this->validateTfat($userId, $tfaToken); + $userId = $this->validateTfat($tfaToken); return $this->tfa->getProvidersToActivate($userId); } @@ -73,14 +72,17 @@ public function getProvidersToActivate(int $userId, string $tfaToken): array /** * Validate the given 2fa token * - * @param int $userId * @param string $tfat + * @return int * @throws AuthorizationException */ - private function validateTfat(int $userId, string $tfat): void + private function validateTfat(string $tfat): int { + ['user_id' => $userId] = $this->json->unserialize(explode('.', base64_decode($tfat))[0]); if (!$this->tokenManager->isValidFor($userId, $tfat)) { throw new AuthorizationException(__('Invalid token.')); } + + return $userId; } } diff --git a/TwoFactorAuth/Model/UserAuthenticator.php b/TwoFactorAuth/Model/UserAuthenticator.php index 08ca2cd0..8cd270da 100644 --- a/TwoFactorAuth/Model/UserAuthenticator.php +++ b/TwoFactorAuth/Model/UserAuthenticator.php @@ -8,12 +8,10 @@ namespace Magento\TwoFactorAuth\Model; -use Magento\Framework\DataObjectFactory; use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Exception\AuthorizationException; +use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\Webapi\Exception as WebApiException; -use Magento\Integration\Model\CredentialsValidator; -use Magento\Integration\Model\Oauth\Token\RequestThrottler; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\User\Model\ResourceModel\User as UserResource; @@ -25,21 +23,11 @@ */ class UserAuthenticator { - /** - * @var CredentialsValidator - */ - private $credentialsValidator; - /** * @var UserFactory */ private $userFactory; - /** - * @var RequestThrottler - */ - private $requestThrottler; - /** * @var UserResource */ @@ -51,85 +39,55 @@ class UserAuthenticator private $tfa; /** - * @var DataObjectFactory + * @var UserConfigTokenManagerInterface */ - private $dataObjectFactory; + private $tokenManager; /** - * @var UserConfigTokenManagerInterface + * @var Json */ - private $tokenManager; + private $json; /** - * @param CredentialsValidator $credentialsValidator * @param UserFactory $userFactory - * @param RequestThrottler $requestThrottler * @param UserResource $userResource * @param UserConfigTokenManagerInterface $tokenManager * @param TfaInterface $tfa - * @param DataObjectFactory $dataObjectFactory + * @param Json $json */ public function __construct( - CredentialsValidator $credentialsValidator, UserFactory $userFactory, - RequestThrottler $requestThrottler, UserResource $userResource, UserConfigTokenManagerInterface $tokenManager, TfaInterface $tfa, - DataObjectFactory $dataObjectFactory + Json $json ) { - $this->credentialsValidator = $credentialsValidator; $this->userFactory = $userFactory; - $this->requestThrottler = $requestThrottler; $this->userResource = $userResource; $this->tfa = $tfa; - $this->dataObjectFactory = $dataObjectFactory; $this->tokenManager = $tokenManager; - } - - /** - * Get a user with credentials while enforcing throttling - * - * @param string $username - * @param string $password - * @return User - */ - public function authenticateWithCredentials(string $username, string $password): User - { - $this->credentialsValidator->validate($username, $password); - $this->requestThrottler->throttle($username, RequestThrottler::USER_TYPE_ADMIN); - - $user = $this->userFactory->create(); - $user->login($username, $password); - - if (!$user->getId()) { - $this->requestThrottler->logAuthenticationFailure($username, RequestThrottler::USER_TYPE_ADMIN); - - throw new AuthenticationException( - __( - 'The account sign-in was incorrect or your account is disabled temporarily. ' - . 'Please wait and try again later.' - ) - ); - } - - $this->requestThrottler->resetAuthenticationFailuresCount($username, RequestThrottler::USER_TYPE_ADMIN); - - return $user; + $this->json = $json; } /** * Obtain a user with an id and a tfa token * - * @param int $userId * @param string $tfaToken * @param string $providerCode * @return User * @throws AuthorizationException * @throws WebApiException */ - public function authenticateWithTokenAndProvider(int $userId, string $tfaToken, string $providerCode): User + public function authenticateWithTokenAndProvider(string $tfaToken, string $providerCode): User { + try { + ['user_id' => $userId] = $this->json->unserialize(explode('.', base64_decode($tfaToken))[0]); + } catch (\Exception $e) { + throw new AuthorizationException( + __('Invalid tfa token') + ); + } + if (!$this->tfa->getProviderIsAllowed($userId, $providerCode)) { throw new WebApiException(__('Provider is not allowed.')); } elseif ($this->tfa->getProviderByCode($providerCode)->isActive($userId)) { diff --git a/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php b/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php new file mode 100644 index 00000000..a37953a5 --- /dev/null +++ b/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php @@ -0,0 +1,68 @@ +cookies = $cookies; + $this->cookieMetadataFactory = $cookieMetadataFactory; + $this->session = $session; + $this->sessionManager = $sessionManager; + } + + /** + * Delete the tfat cookie + */ + public function beforeLogout() + { + $metadata = $this->cookieMetadataFactory->createSensitiveCookieMetadata() + ->setPath($this->sessionManager->getCookiePath()); + $this->cookies->deleteCookie('tfa-ct', $metadata); + } +} diff --git a/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php index 5f87f31e..d28f9e9d 100644 --- a/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php +++ b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php @@ -12,7 +12,6 @@ use Magento\TestFramework\Bootstrap as TestBootstrap; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; -use Magento\TwoFactorAuth\Api\Data\AdminTokenResponseInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\User\Model\UserFactory; @@ -86,17 +85,20 @@ public function testUserWithConfigured2fa() $this->tfa->getProviderByCode(Google::CODE)->activate($userId); $serviceInfo = $this->buildServiceInfo(); - $response = $this->_webApiCall( - $serviceInfo, - ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] - ); - self::assertSame($userId, (int)$response[AdminTokenResponseInterface::USER_ID]); - self::assertSame( - 'Please use the 2fa provider-specific endpoints to obtain a token.', - $response[AdminTokenResponseInterface::MESSAGE] - ); - self::assertCount(1, $response[AdminTokenResponseInterface::ACTIVE_PROVIDERS]); - self::assertSame('google', $response[AdminTokenResponseInterface::ACTIVE_PROVIDERS][0]['code']); + try { + $this->_webApiCall( + $serviceInfo, + ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] + ); + } catch (\Exception $e) { + $response = json_decode($e->getMessage(), true); + self::assertSame( + 'Please use the 2fa provider-specific endpoints to obtain a token.', + $response['message'] + ); + self::assertCount(1, $response['parameters']['active_providers']); + self::assertSame('google', $response['parameters']['active_providers'][0]); + } } /** @@ -109,18 +111,21 @@ public function testUserWithAvailableUnconfigured2fa() $this->tfa->getProviderByCode(Google::CODE)->activate($userId); $serviceInfo = $this->buildServiceInfo(); - $response = $this->_webApiCall( - $serviceInfo, - ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] - ); - self::assertSame($userId, (int)$response[AdminTokenResponseInterface::USER_ID]); - self::assertSame( - 'You are required to configure personal Two-Factor Authorization in order to login. ' - . 'Please check your email.', - $response[AdminTokenResponseInterface::MESSAGE] - ); - self::assertCount(1, $response[AdminTokenResponseInterface::ACTIVE_PROVIDERS]); - self::assertSame('google', $response[AdminTokenResponseInterface::ACTIVE_PROVIDERS][0]['code']); + try { + $this->_webApiCall( + $serviceInfo, + ['username' => 'customRoleUser', 'password' => TestBootstrap::ADMIN_PASSWORD] + ); + } catch (\Exception $e) { + $response = json_decode($e->getMessage(), true); + self::assertSame( + 'You are required to configure personal Two-Factor Authorization in order to login. ' + . 'Please check your email.', + $response['message'] + ); + self::assertCount(1, $response['parameters']['active_providers']); + self::assertSame('google', $response['parameters']['active_providers'][0]); + } } /** diff --git a/TwoFactorAuth/Test/Api/GoogleActivateTest.php b/TwoFactorAuth/Test/Api/GoogleActivateTest.php index b3c65793..eaa120ad 100644 --- a/TwoFactorAuth/Test/Api/GoogleActivateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleActivateTest.php @@ -63,7 +63,7 @@ protected function setUp() */ public function testInvalidTfat() { - $serviceInfo = $this->buildServiceInfo($this->getUserId()); + $serviceInfo = $this->buildServiceInfo(); try { $this->_webApiCall($serviceInfo, ['tfaToken' => 'abc', 'otp' => 'invalid']); @@ -83,7 +83,7 @@ public function testUnavailableProvider() { $userId = $this->getUserId(); $token = $this->tokenManager->issueFor($userId); - $serviceInfo = $this->buildServiceInfo($userId); + $serviceInfo = $this->buildServiceInfo(); try { $this->_webApiCall($serviceInfo, ['tfaToken' => $token, 'otp' => 'invalid']); @@ -103,7 +103,7 @@ public function testAlreadyActivatedProvider() { $userId = $this->getUserId(); $token = $this->tokenManager->issueFor($userId); - $serviceInfo = $this->buildServiceInfo($userId); + $serviceInfo = $this->buildServiceInfo(); $otp = $this->getUserOtp(); $this->tfa->getProviderByCode(Google::CODE) ->activate($userId); @@ -127,7 +127,7 @@ public function testActivate() $userId = $this->getUserId(); $token = $this->tokenManager->issueFor($userId); $otp = $this->getUserOtp(); - $serviceInfo = $this->buildServiceInfo($userId); + $serviceInfo = $this->buildServiceInfo(); $response = $this->_webApiCall( $serviceInfo, @@ -137,7 +137,7 @@ public function testActivate() ] ); self::assertNotEmpty($response); - self::assertRegExp('/^[a-z0-9]{32}$/', $response); + self::assertTrue($response); } private function getUserOtp(): string @@ -153,14 +153,13 @@ private function getUserOtp(): string } /** - * @param int $userId * @return array */ - private function buildServiceInfo(int $userId): array + private function buildServiceInfo(): array { return [ 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . '/' . $userId, + 'resourcePath' => self::RESOURCE_PATH, 'httpMethod' => Request::HTTP_METHOD_POST ], 'soap' => [ diff --git a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php index 5eabd99f..fdc67469 100644 --- a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php +++ b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php @@ -49,7 +49,7 @@ protected function setUp() */ public function testInvalidTfat() { - $serviceInfo = $this->buildServiceInfo($this->getUserId()); + $serviceInfo = $this->buildServiceInfo(); try { $this->_webApiCall($serviceInfo, ['tfaToken' => 'abc']); @@ -69,7 +69,7 @@ public function testUnavailableProvider() { $userId = $this->getUserId(); $token = $this->tokenManager->issueFor($userId); - $serviceInfo = $this->buildServiceInfo($userId); + $serviceInfo = $this->buildServiceInfo(); try { $this->_webApiCall($serviceInfo, ['tfaToken' => $token]); @@ -89,7 +89,7 @@ public function testAlreadyConfiguredProvider() { $userId = $this->getUserId(); $token = $this->tokenManager->issueFor($userId); - $serviceInfo = $this->buildServiceInfo($userId); + $serviceInfo = $this->buildServiceInfo(); $this->tfa->getProviderByCode(Google::CODE) ->activate($userId); @@ -111,7 +111,7 @@ public function testValidRequest() { $userId = $this->getUserId(); $token = $this->tokenManager->issueFor($userId); - $serviceInfo = $this->buildServiceInfo($userId); + $serviceInfo = $this->buildServiceInfo(); $response = $this->_webApiCall($serviceInfo, ['tfaToken' => $token]); self::assertNotEmpty($response['qr_code_url']); @@ -120,14 +120,13 @@ public function testValidRequest() } /** - * @param int $userId * @return array */ - private function buildServiceInfo(int $userId): array + private function buildServiceInfo(): array { return [ 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . '/' . $userId, + 'resourcePath' => self::RESOURCE_PATH, 'httpMethod' => Request::HTTP_METHOD_POST ], 'soap' => [ diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php index 1e023594..e4660035 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php @@ -63,6 +63,8 @@ protected function setUp() */ public function testAuthenticateInvalidCredentials() { + $this->tfa->getProviderByCode(Authy::CODE) + ->activate($this->getUserId()); $this->authy ->expects($this->never()) ->method('verify'); diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php index ece9d9cd..05929238 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php @@ -85,12 +85,10 @@ protected function setUp() */ public function testConfigureInvalidTfat() { - $userId = $this->getUserId(); $this->verification ->expects($this->never()) ->method('request'); $this->model->sendDeviceRegistrationPrompt( - $userId, 'abc', $this->deviceDataFactory->create( [ @@ -120,8 +118,7 @@ public function testConfigureAlreadyConfiguredProvider() ->expects($this->never()) ->method('request'); $this->model->sendDeviceRegistrationPrompt( - $userId, - 'abc', + $this->tokenManager->issueFor($userId), $this->deviceDataFactory->create( [ 'data' => [ @@ -147,8 +144,7 @@ public function testConfigureUnavailableProvider() ->expects($this->never()) ->method('request'); $this->model->sendDeviceRegistrationPrompt( - $userId, - 'abc', + $this->tokenManager->issueFor($userId), $this->deviceDataFactory->create( [ 'data' => [ @@ -190,7 +186,6 @@ function ($userId, $country, $phone, $method, &$response) { ); $result = $this->model->sendDeviceRegistrationPrompt( - $userId, $this->tokenManager->issueFor($userId), $this->deviceDataFactory->create( [ @@ -224,7 +219,6 @@ public function testActivateInvalidTfat() ->expects($this->never()) ->method('enroll'); $this->model->activate( - $userId, 'abc', 'abc' ); @@ -249,8 +243,7 @@ public function testActivateAlreadyConfiguredProvider() ->expects($this->never()) ->method('request'); $this->model->activate( - $userId, - 'abc', + $this->tokenManager->issueFor($userId), 'abc' ); } @@ -271,8 +264,7 @@ public function testActivateUnavailableProvider() ->expects($this->never()) ->method('request'); $this->model->activate( - $userId, - 'abc', + $this->tokenManager->issueFor($userId), 'abc' ); } @@ -301,11 +293,12 @@ public function testActivateValidRequest() return (int)$value->getId() === $userId; }) ); - $this->model->activate( - $userId, + $result = $this->model->activate( $this->tokenManager->issueFor($userId), 'cba' ); + + self::assertTrue($result); } private function getUserId(): int diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php index 780a8ddf..0d9db8fb 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php @@ -73,6 +73,8 @@ protected function setUp() */ public function testGetAuthenticateDataInvalidCredentials() { + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($this->getUserId()); $this->duo ->expects($this->never()) ->method('getRequestSignature'); @@ -132,6 +134,8 @@ public function testGetAuthenticateDataUnavailableProvider() */ public function testVerifyInvalidCredentials() { + $this->tfa->getProviderByCode(DuoSecurity::CODE) + ->activate($this->getUserId()); $this->duo ->expects($this->never()) ->method('getRequestSignature'); diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php index dadbb5dc..f8662244 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php @@ -84,7 +84,6 @@ public function testGetConfigurationDataInvalidTfat() ->expects($this->never()) ->method('getRequestSignature'); $this->model->getConfigurationData( - $this->getUserId(), 'abc' ); } @@ -108,8 +107,7 @@ public function testGetConfigurationDataAlreadyConfiguredProvider() ->expects($this->never()) ->method('getRequestSignature'); $this->model->getConfigurationData( - $userId, - 'abc' + $this->tokenManager->issueFor($userId) ); } @@ -125,8 +123,7 @@ public function testGetConfigurationDataUnavailableProvider() ->expects($this->never()) ->method('getRequestSignature'); $this->model->getConfigurationData( - $this->getUserId(), - 'abc' + $this->tokenManager->issueFor($this->getUserId()) ); } @@ -141,12 +138,10 @@ public function testGetConfigurationDataUnavailableProvider() */ public function testActivateInvalidTfat() { - $userId = $this->getUserId(); $this->duo ->expects($this->never()) ->method('getRequestSignature'); $this->model->activate( - $userId, 'abc', 'something' ); @@ -170,8 +165,7 @@ public function testActivateAlreadyConfiguredProvider() ->expects($this->never()) ->method('getRequestSignature'); $this->model->activate( - $userId, - 'abc', + $this->tokenManager->issueFor($userId), 'something' ); } @@ -189,8 +183,7 @@ public function testActivateUnavailableProvider() ->expects($this->never()) ->method('getRequestSignature'); $this->model->activate( - $userId, - 'abc', + $this->tokenManager->issueFor($userId), 'something' ); } @@ -219,7 +212,6 @@ public function testGetConfigurationDataValidRequest() ->willReturn('cba'); $result = $this->model->getConfigurationData( - $userId, $this->tokenManager->issueFor($userId) ); @@ -249,9 +241,9 @@ public function testActivateValidRequest() $signature ); - $token = $this->model->activate($userId, $tfat, $signature); + $result = $this->model->activate($tfat, $signature); - self::assertRegExp('/^[a-z0-9]{32}$/', $token); + self::assertTrue($result); } /** @@ -278,7 +270,7 @@ public function testActivateInvalidDataThrowsException() ) ->willThrowException(new \InvalidArgumentException('Something')); - $result = $this->model->activate($userId, $tfat, $signature); + $result = $this->model->activate($tfat, $signature); self::assertEmpty($result); } diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php index 2065c291..a9df30fe 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php @@ -63,6 +63,8 @@ protected function setUp() */ public function testGetDataInvalidCredentials() { + $this->tfa->getProviderByCode(U2fKey::CODE) + ->activate($this->getUserId()); $this->u2fkey ->expects($this->never()) ->method('getAuthenticateData'); @@ -113,9 +115,14 @@ public function testGetDataUnavailableProvider() */ public function testVerifyInvalidCredentials() { + $this->tfa->getProviderByCode(U2fKey::CODE) + ->activate($this->getUserId()); $this->u2fkey ->expects($this->never()) ->method('verify'); + $this->u2fkey->method('getAuthenticateData') + ->willReturn(['credentialRequestOptions' => ['challenge' => [1, 2, 3]]]); + $this->model->getAuthenticationData('adminUser', Bootstrap::ADMIN_PASSWORD); $this->model->verify( 'adminUser', 'bad', diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php index 5b17d1ef..0dff8b7f 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php @@ -75,7 +75,6 @@ public function testGetRegistrationDataInvalidTfat() ->expects($this->never()) ->method('getRegisterData'); $this->model->getRegistrationData( - $userId, 'abc' ); } @@ -95,8 +94,7 @@ public function testGetRegistrationDataAlreadyConfiguredProvider() ->expects($this->never()) ->method('getRegisterData'); $this->model->getRegistrationData( - $userId, - 'abc' + $this->tokenManager->issueFor($userId) ); } @@ -113,8 +111,7 @@ public function testGetRegistrationDataUnavailableProvider() ->expects($this->never()) ->method('getRegisterData'); $this->model->getRegistrationData( - $userId, - 'abc' + $this->tokenManager->issueFor($userId) ); } @@ -131,7 +128,6 @@ public function testActivateInvalidTfat() ->expects($this->never()) ->method('registerDevice'); $this->model->activate( - $userId, 'abc', 'I identify as JSON' ); @@ -152,8 +148,7 @@ public function testActivateAlreadyConfiguredProvider() ->expects($this->never()) ->method('registerDevice'); $this->model->activate( - $userId, - 'abc', + $this->tokenManager->issueFor($userId), 'I identify as JSON' ); } @@ -171,8 +166,7 @@ public function testActivateUnavailableProvider() ->expects($this->never()) ->method('registerDevice'); $this->model->activate( - $userId, - 'abc', + $this->tokenManager->issueFor($userId), 'I identify as JSON' ); } @@ -196,7 +190,6 @@ public function testGetRegistrationDataValidRequest() ->willReturn($data); $result = $this->model->getRegistrationData( - $userId, $this->tokenManager->issueFor($userId) ); @@ -216,7 +209,7 @@ public function testActivateValidRequest() $this->u2fkey ->method('getRegisterData') ->willReturn(['publicKey' => ['challenge' => [3, 2, 1]]]); - $this->model->getRegistrationData($userId, $tfat); + $this->model->getRegistrationData($tfat); $activateData = ['foo' => 'bar']; $this->u2fkey @@ -232,9 +225,9 @@ public function testActivateValidRequest() ] ); - $token = $this->model->activate($userId, $tfat, json_encode($activateData)); + $result = $this->model->activate($tfat, json_encode($activateData)); - self::assertRegExp('/^[a-z0-9]{32}$/', $token); + self::assertTrue($result); } /** @@ -251,13 +244,13 @@ public function testActivateInvalidKeyDataThrowsException() $this->u2fkey ->method('getRegisterData') ->willReturn(['publicKey' => ['challenge' => [3, 2, 1]]]); - $this->model->getRegistrationData($userId, $tfat); + $this->model->getRegistrationData($tfat); $this->u2fkey ->method('registerDevice') ->willThrowException(new \InvalidArgumentException('Something')); - $result = $this->model->activate($userId, $tfat, json_encode(['foo' => 'bar'])); + $result = $this->model->activate($tfat, json_encode(['foo' => 'bar'])); self::assertEmpty($result); } diff --git a/TwoFactorAuth/etc/adminhtml/di.xml b/TwoFactorAuth/etc/adminhtml/di.xml index 2c57e43f..598d7b0d 100644 --- a/TwoFactorAuth/etc/adminhtml/di.xml +++ b/TwoFactorAuth/etc/adminhtml/di.xml @@ -17,4 +17,12 @@ type="Magento\TwoFactorAuth\Plugin\AvoidRecursionOnPasswordChange"/> + + + + + + + + diff --git a/TwoFactorAuth/etc/adminhtml/system.xml b/TwoFactorAuth/etc/adminhtml/system.xml index b5878177..51732aca 100644 --- a/TwoFactorAuth/etc/adminhtml/system.xml +++ b/TwoFactorAuth/etc/adminhtml/system.xml @@ -30,10 +30,10 @@ Two-factor authorization providers for admin users to use during login Magento\TwoFactorAuth\Model\Config\Backend\ForceProviders - - This can be used to override the default email configuration link that is sent when using the Magento Web API's to authenticate. + This can be used to override the default email configuration link that is sent when using the Magento Web API's to authenticate. Use the placeholder :tfat to indicate where the token should be injected diff --git a/TwoFactorAuth/etc/di.xml b/TwoFactorAuth/etc/di.xml index b419a3af..77b5c14b 100644 --- a/TwoFactorAuth/etc/di.xml +++ b/TwoFactorAuth/etc/di.xml @@ -29,7 +29,6 @@ - diff --git a/TwoFactorAuth/etc/module.xml b/TwoFactorAuth/etc/module.xml index 5cfe6c60..9f24769d 100644 --- a/TwoFactorAuth/etc/module.xml +++ b/TwoFactorAuth/etc/module.xml @@ -10,6 +10,7 @@ + diff --git a/TwoFactorAuth/etc/webapi.xml b/TwoFactorAuth/etc/webapi.xml index effe4ec7..4c5c081b 100644 --- a/TwoFactorAuth/etc/webapi.xml +++ b/TwoFactorAuth/etc/webapi.xml @@ -64,21 +64,21 @@ - + - + - + @@ -92,21 +92,21 @@ - + - + - + @@ -134,14 +134,14 @@ - + - + @@ -162,14 +162,14 @@ - + - + diff --git a/TwoFactorAuth/view/adminhtml/layout/tfa_authy_auth.xml b/TwoFactorAuth/view/adminhtml/layout/tfa_authy_auth.xml index 54b37fa1..2d8327df 100644 --- a/TwoFactorAuth/view/adminhtml/layout/tfa_authy_auth.xml +++ b/TwoFactorAuth/view/adminhtml/layout/tfa_authy_auth.xml @@ -8,6 +8,7 @@ + diff --git a/TwoFactorAuth/view/adminhtml/layout/tfa_authy_configure.xml b/TwoFactorAuth/view/adminhtml/layout/tfa_authy_configure.xml index 99301065..1a79da42 100644 --- a/TwoFactorAuth/view/adminhtml/layout/tfa_authy_configure.xml +++ b/TwoFactorAuth/view/adminhtml/layout/tfa_authy_configure.xml @@ -8,6 +8,7 @@ + diff --git a/TwoFactorAuth/view/adminhtml/layout/tfa_duo_auth.xml b/TwoFactorAuth/view/adminhtml/layout/tfa_duo_auth.xml index 067c3497..273a7cdf 100644 --- a/TwoFactorAuth/view/adminhtml/layout/tfa_duo_auth.xml +++ b/TwoFactorAuth/view/adminhtml/layout/tfa_duo_auth.xml @@ -8,6 +8,7 @@ + diff --git a/TwoFactorAuth/view/adminhtml/layout/tfa_google_auth.xml b/TwoFactorAuth/view/adminhtml/layout/tfa_google_auth.xml index c361ea70..8288e39e 100644 --- a/TwoFactorAuth/view/adminhtml/layout/tfa_google_auth.xml +++ b/TwoFactorAuth/view/adminhtml/layout/tfa_google_auth.xml @@ -8,6 +8,7 @@ + diff --git a/TwoFactorAuth/view/adminhtml/layout/tfa_google_configure.xml b/TwoFactorAuth/view/adminhtml/layout/tfa_google_configure.xml index cfc8f5a3..a571f628 100644 --- a/TwoFactorAuth/view/adminhtml/layout/tfa_google_configure.xml +++ b/TwoFactorAuth/view/adminhtml/layout/tfa_google_configure.xml @@ -8,6 +8,7 @@ + diff --git a/TwoFactorAuth/view/adminhtml/layout/tfa_screen.xml b/TwoFactorAuth/view/adminhtml/layout/tfa_screen.xml new file mode 100644 index 00000000..5d840c92 --- /dev/null +++ b/TwoFactorAuth/view/adminhtml/layout/tfa_screen.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + Logout + adminhtml/auth/logout + tfa-logout-link + + + + + diff --git a/TwoFactorAuth/view/adminhtml/layout/tfa_tfa_accessdenied.xml b/TwoFactorAuth/view/adminhtml/layout/tfa_tfa_accessdenied.xml index 1111bafc..956b8550 100644 --- a/TwoFactorAuth/view/adminhtml/layout/tfa_tfa_accessdenied.xml +++ b/TwoFactorAuth/view/adminhtml/layout/tfa_tfa_accessdenied.xml @@ -8,6 +8,7 @@ + diff --git a/TwoFactorAuth/view/adminhtml/layout/tfa_tfa_configure.xml b/TwoFactorAuth/view/adminhtml/layout/tfa_tfa_configure.xml index efe6d105..bb56d4a7 100644 --- a/TwoFactorAuth/view/adminhtml/layout/tfa_tfa_configure.xml +++ b/TwoFactorAuth/view/adminhtml/layout/tfa_tfa_configure.xml @@ -9,6 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + diff --git a/TwoFactorAuth/view/adminhtml/layout/tfa_tfa_requestconfig.xml b/TwoFactorAuth/view/adminhtml/layout/tfa_tfa_requestconfig.xml index d8517a6c..60c099f9 100644 --- a/TwoFactorAuth/view/adminhtml/layout/tfa_tfa_requestconfig.xml +++ b/TwoFactorAuth/view/adminhtml/layout/tfa_tfa_requestconfig.xml @@ -8,6 +8,8 @@ + + diff --git a/TwoFactorAuth/view/adminhtml/layout/tfa_u2f_auth.xml b/TwoFactorAuth/view/adminhtml/layout/tfa_u2f_auth.xml index 375c9f1e..5aa91757 100644 --- a/TwoFactorAuth/view/adminhtml/layout/tfa_u2f_auth.xml +++ b/TwoFactorAuth/view/adminhtml/layout/tfa_u2f_auth.xml @@ -8,6 +8,7 @@ + diff --git a/TwoFactorAuth/view/adminhtml/layout/tfa_u2f_configure.xml b/TwoFactorAuth/view/adminhtml/layout/tfa_u2f_configure.xml index f20ad606..8a7d202d 100644 --- a/TwoFactorAuth/view/adminhtml/layout/tfa_u2f_configure.xml +++ b/TwoFactorAuth/view/adminhtml/layout/tfa_u2f_configure.xml @@ -8,6 +8,7 @@ + diff --git a/TwoFactorAuth/view/adminhtml/templates/tfa/logout_link.phtml b/TwoFactorAuth/view/adminhtml/templates/tfa/logout_link.phtml new file mode 100644 index 00000000..6f4fcdff --- /dev/null +++ b/TwoFactorAuth/view/adminhtml/templates/tfa/logout_link.phtml @@ -0,0 +1,10 @@ + +getLinkAttributes()?>>escapeHtml($block->getLabel())?> diff --git a/TwoFactorAuth/view/adminhtml/web/css/tfa-screen.css b/TwoFactorAuth/view/adminhtml/web/css/tfa-screen.css new file mode 100644 index 00000000..c7a4aaff --- /dev/null +++ b/TwoFactorAuth/view/adminhtml/web/css/tfa-screen.css @@ -0,0 +1,10 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +.tfa-logout-link { + position: absolute; + top: 1em; + right: 1em; +} From f6e45ff3760be35c55e54b1451ff05a3733790d9 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 10 Apr 2020 14:18:36 -0500 Subject: [PATCH 14/58] MC-30536: Enable 2FA for web API - Refactored various parts of webapi implementation --- .../Api/U2fKeyConfigReaderInterface.php | 34 ++++++++ .../Provider/Engine/U2fKey/ConfigReader.php | 18 +++++ .../Engine/U2fKey/WebApiConfigReader.php | 46 +++++++++++ .../Engine/U2fKey/ConfigReaderTest.php | 15 ++++ .../Engine/U2fKey/WebApiConfigReaderTest.php | 77 +++++++++++++++++++ TwoFactorAuth/etc/webapi_rest/di.xml | 11 +++ TwoFactorAuth/etc/webapi_soap/di.xml | 11 +++ .../adminhtml/templates/tfa/logout_link.phtml | 10 --- 8 files changed, 212 insertions(+), 10 deletions(-) create mode 100644 TwoFactorAuth/Api/U2fKeyConfigReaderInterface.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/U2fKey/ConfigReader.php create mode 100644 TwoFactorAuth/Model/Provider/Engine/U2fKey/WebApiConfigReader.php create mode 100644 TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/ConfigReaderTest.php create mode 100644 TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/WebApiConfigReaderTest.php create mode 100644 TwoFactorAuth/etc/webapi_rest/di.xml create mode 100644 TwoFactorAuth/etc/webapi_soap/di.xml delete mode 100644 TwoFactorAuth/view/adminhtml/templates/tfa/logout_link.phtml diff --git a/TwoFactorAuth/Api/U2fKeyConfigReaderInterface.php b/TwoFactorAuth/Api/U2fKeyConfigReaderInterface.php new file mode 100644 index 00000000..bc4cb0c8 --- /dev/null +++ b/TwoFactorAuth/Api/U2fKeyConfigReaderInterface.php @@ -0,0 +1,34 @@ +storeManager = $storeManager; + } + + /** + * @inheritDoc + */ + public function getDomain(): bool + { + $store = $this->storeManager->getStore(Store::ADMIN_CODE); + $baseUrl = $store->getBaseUrl(); + if (!preg_match('/^(https?:\/\/(?P.+?))\//', $baseUrl, $matches)) { + throw new LocalizedException(__('Could not determine secure domain name.')); + } + return $matches['domain']; + } +} diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/ConfigReaderTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/ConfigReaderTest.php new file mode 100644 index 00000000..601dc9be --- /dev/null +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/ConfigReaderTest.php @@ -0,0 +1,15 @@ +storeManager = $this->createMock(StoreManagerInterface::class); + $this->reader = $objectManager->getObject( + ConfigReader::class, + [ + 'storeManager' => $this->storeManager + ] + ); + } + + public function testGetValidDomain() + { + $store = $this->createMock(Store::class); + $store->method('getBaseUrl') + ->willReturn('https://domain.com/'); + $this->storeManager + ->method('getStore') + ->willReturn($store); + $result = $this->reader->getDomain(); + self::assertSame('domain.com', $result); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Could not determine secure domain name. + * @dataProvider invalidDomainProviders + */ + public function testGetInvalidDomain($domain) + { + $store = $this->createMock(Store::class); + $store->method('getBaseUrl') + ->willReturn($domain); + $this->storeManager + ->method('getStore') + ->willReturn($store); + $this->reader->getDomain(); + } + + public function invalidDomainProviders() + { + return [ + ['foo'], + ['http://domain.com/'], + ]; + } +} diff --git a/TwoFactorAuth/etc/webapi_rest/di.xml b/TwoFactorAuth/etc/webapi_rest/di.xml new file mode 100644 index 00000000..65653904 --- /dev/null +++ b/TwoFactorAuth/etc/webapi_rest/di.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/TwoFactorAuth/etc/webapi_soap/di.xml b/TwoFactorAuth/etc/webapi_soap/di.xml new file mode 100644 index 00000000..45dde043 --- /dev/null +++ b/TwoFactorAuth/etc/webapi_soap/di.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/TwoFactorAuth/view/adminhtml/templates/tfa/logout_link.phtml b/TwoFactorAuth/view/adminhtml/templates/tfa/logout_link.phtml deleted file mode 100644 index 6f4fcdff..00000000 --- a/TwoFactorAuth/view/adminhtml/templates/tfa/logout_link.phtml +++ /dev/null @@ -1,10 +0,0 @@ - -getLinkAttributes()?>>escapeHtml($block->getLabel())?> From 987c5e351e7d414e3673d6e92c82135d5df958f6 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 10 Apr 2020 14:22:46 -0500 Subject: [PATCH 15/58] MC-30536: Enable 2FA for web API - Refactored various parts of webapi implementation --- .../Api/AdminTokenServiceInterface.php | 17 +- .../Api/AuthyAuthenticateInterface.php | 9 +- TwoFactorAuth/Api/AuthyConfigureInterface.php | 4 +- .../Api/Data/GoogleConfigureInterface.php | 12 +- .../Api/DuoAuthenticateInterface.php | 6 +- TwoFactorAuth/Api/DuoConfigureInterface.php | 4 +- .../Api/GoogleAuthenticateInterface.php | 2 +- .../Api/GoogleConfigureInterface.php | 4 +- TwoFactorAuth/Api/TfaInterface.php | 5 - .../Api/U2fKeyAuthenticateInterface.php | 6 +- .../Api/U2fKeyConfigReaderInterface.php | 22 +-- .../Api/U2fKeyConfigureInterface.php | 4 +- TwoFactorAuth/Api/UserNotifierInterface.php | 5 + .../Model/AdminAccessTokenService.php | 61 +++--- .../Engine/Authy/RegistrationResponse.php | 2 +- .../Engine/Google/ConfigurationData.php | 12 +- TwoFactorAuth/Model/EmailUserNotifier.php | 3 +- .../Provider/Engine/Authy/Authenticate.php | 53 ++--- .../Model/Provider/Engine/Authy/Configure.php | 10 +- .../Engine/DuoSecurity/Authenticate.php | 49 ++--- .../Provider/Engine/DuoSecurity/Configure.php | 4 +- .../Provider/Engine/Google/Authenticate.php | 33 +--- .../Provider/Engine/Google/Configure.php | 7 +- .../Provider/Engine/U2fKey/Authenticate.php | 42 ++-- .../Provider/Engine/U2fKey/ConfigReader.php | 32 ++- .../Provider/Engine/U2fKey/Configure.php | 8 +- .../Engine/U2fKey/WebApiConfigReader.php | 36 ++-- .../Model/Provider/Engine/U2fKey/WebAuthn.php | 60 ++---- TwoFactorAuth/Model/UserAuthenticator.php | 23 ++- TwoFactorAuth/Plugin/DeleteCookieOnLogout.php | 9 - TwoFactorAuth/Test/Api/GoogleActivateTest.php | 5 +- .../Test/Api/GoogleAuthenticateTest.php | 1 - .../Test/Api/GoogleConfigureTest.php | 7 +- .../Engine/Authy/AuthenticateTest.php | 186 +++++++++++++++++- .../Provider/Engine/Authy/ConfigureTest.php | 16 +- .../Engine/DuoSecurity/AuthenticateTest.php | 20 +- .../Engine/DuoSecurity/ConfigureTest.php | 16 +- .../Engine/U2fKey/AuthenticateTest.php | 18 +- .../Provider/Engine/U2fKey/ConfigureTest.php | 16 +- .../Engine/U2fKey/ConfigReaderTest.php | 63 +++++- .../Engine/U2fKey/WebApiConfigReaderTest.php | 73 ++++--- TwoFactorAuth/composer.json | 3 +- TwoFactorAuth/etc/adminhtml/di.xml | 4 - TwoFactorAuth/etc/di.xml | 1 + TwoFactorAuth/etc/webapi.xml | 14 +- TwoFactorAuth/etc/webapi_rest/di.xml | 2 +- .../view/adminhtml/layout/tfa_screen.xml | 4 +- .../view/adminhtml/web/css/tfa-screen.css | 3 + .../web/template/google/configure.html | 2 +- 49 files changed, 581 insertions(+), 417 deletions(-) diff --git a/TwoFactorAuth/Api/AdminTokenServiceInterface.php b/TwoFactorAuth/Api/AdminTokenServiceInterface.php index 79a4ee49..5482feba 100644 --- a/TwoFactorAuth/Api/AdminTokenServiceInterface.php +++ b/TwoFactorAuth/Api/AdminTokenServiceInterface.php @@ -8,23 +8,12 @@ namespace Magento\TwoFactorAuth\Api; -use Magento\Framework\Webapi\Exception as WebApiException; +use Magento\Integration\Api\AdminTokenServiceInterface as OriginalTokenServiceInterface; /** * Obtain basic information about the user required to setup or use 2fa */ -interface AdminTokenServiceInterface +interface AdminTokenServiceInterface extends OriginalTokenServiceInterface { - /** - * Create access token for admin given the admin credentials. - * - * @param string $username - * @param string $password - * @return void - * @throws WebApiException - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\AuthenticationException - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function createAdminAccessToken(string $username, string $password): void; + } diff --git a/TwoFactorAuth/Api/AuthyAuthenticateInterface.php b/TwoFactorAuth/Api/AuthyAuthenticateInterface.php index 235d1437..db6249d0 100644 --- a/TwoFactorAuth/Api/AuthyAuthenticateInterface.php +++ b/TwoFactorAuth/Api/AuthyAuthenticateInterface.php @@ -8,8 +8,6 @@ namespace Magento\TwoFactorAuth\Api; -use Magento\TwoFactorAuth\Api\Data\AuthyDeviceInterface; - /** * Represents the authy provider authentication */ @@ -20,16 +18,17 @@ interface AuthyAuthenticateInterface * * @param string $username * @param string $password + * @param string $otp * @return string $otp */ - public function authenticateWithToken( + public function createAdminAccessTokenWithCredentials( string $username, string $password, string $otp ): string; /** - * Send a token to a device using authy + * Send a one time password to a device using authy * * @param string $username * @param string $password @@ -49,7 +48,7 @@ public function sendToken( * @param string $password * @return string */ - public function authenticateWithOnetouch( + public function creatAdminAccessTokenWithOneTouch( string $username, string $password ): string; diff --git a/TwoFactorAuth/Api/AuthyConfigureInterface.php b/TwoFactorAuth/Api/AuthyConfigureInterface.php index 8f315dcf..caea7a00 100644 --- a/TwoFactorAuth/Api/AuthyConfigureInterface.php +++ b/TwoFactorAuth/Api/AuthyConfigureInterface.php @@ -33,7 +33,7 @@ public function sendDeviceRegistrationPrompt( * * @param string $tfaToken * @param string $otp - * @return bool + * @return void */ - public function activate(string $tfaToken, string $otp): bool; + public function activate(string $tfaToken, string $otp): void; } diff --git a/TwoFactorAuth/Api/Data/GoogleConfigureInterface.php b/TwoFactorAuth/Api/Data/GoogleConfigureInterface.php index 04deeac0..a50f04fe 100644 --- a/TwoFactorAuth/Api/Data/GoogleConfigureInterface.php +++ b/TwoFactorAuth/Api/Data/GoogleConfigureInterface.php @@ -15,9 +15,9 @@ interface GoogleConfigureInterface extends ExtensibleDataInterface { /** - * QR Code URL field name + * QR Code base 64 field name */ - public const QR_CODE_URL = 'qr_code_url'; + public const QR_CODE_BASE64 = 'qr_code_base64'; /** * Secret code field name @@ -25,19 +25,19 @@ interface GoogleConfigureInterface extends ExtensibleDataInterface public const SECRET_CODE = 'secret_code'; /** - * Get value for qr code url + * Get value for QR code base 64 * * @return string */ - public function getQrCodeUrl(): string; + public function getQrCodeBase64(): string; /** - * Set value for qr code url + * Set value for QR code base 64 * * @param string $value * @return void */ - public function setQrCodeUrl(string $value): void; + public function setQrCodeBase64(string $value): void; /** * Get value for secret code diff --git a/TwoFactorAuth/Api/DuoAuthenticateInterface.php b/TwoFactorAuth/Api/DuoAuthenticateInterface.php index 4f9a5bb9..ea3c743a 100644 --- a/TwoFactorAuth/Api/DuoAuthenticateInterface.php +++ b/TwoFactorAuth/Api/DuoAuthenticateInterface.php @@ -35,5 +35,9 @@ public function getAuthenticateData( * @param string $signatureResponse * @return string */ - public function verify(string $username, string $password, string $signatureResponse): string; + public function createAdminAccessTokenWithCredentials( + string $username, + string $password, + string $signatureResponse + ): string; } diff --git a/TwoFactorAuth/Api/DuoConfigureInterface.php b/TwoFactorAuth/Api/DuoConfigureInterface.php index c941a51e..844e2f41 100644 --- a/TwoFactorAuth/Api/DuoConfigureInterface.php +++ b/TwoFactorAuth/Api/DuoConfigureInterface.php @@ -30,7 +30,7 @@ public function getConfigurationData( * * @param string $tfaToken * @param string $signatureResponse - * @return bool + * @return void */ - public function activate(string $tfaToken, string $signatureResponse): bool; + public function activate(string $tfaToken, string $signatureResponse): void; } diff --git a/TwoFactorAuth/Api/GoogleAuthenticateInterface.php b/TwoFactorAuth/Api/GoogleAuthenticateInterface.php index c9335001..6bdd99a0 100644 --- a/TwoFactorAuth/Api/GoogleAuthenticateInterface.php +++ b/TwoFactorAuth/Api/GoogleAuthenticateInterface.php @@ -21,5 +21,5 @@ interface GoogleAuthenticateInterface * @param string $otp * @return string */ - public function getToken(string $username, string $password, string $otp): string; + public function createAdminAccessToken(string $username, string $password, string $otp): string; } diff --git a/TwoFactorAuth/Api/GoogleConfigureInterface.php b/TwoFactorAuth/Api/GoogleConfigureInterface.php index 2e0538d2..882b751e 100644 --- a/TwoFactorAuth/Api/GoogleConfigureInterface.php +++ b/TwoFactorAuth/Api/GoogleConfigureInterface.php @@ -28,7 +28,7 @@ public function getConfigurationData( * * @param string $tfaToken * @param string $otp - * @return bool + * @return void */ - public function activate(string $tfaToken, string $otp): bool; + public function activate(string $tfaToken, string $otp): void; } diff --git a/TwoFactorAuth/Api/TfaInterface.php b/TwoFactorAuth/Api/TfaInterface.php index 645141f1..7dd0fc75 100644 --- a/TwoFactorAuth/Api/TfaInterface.php +++ b/TwoFactorAuth/Api/TfaInterface.php @@ -17,11 +17,6 @@ interface TfaInterface */ public const XML_PATH_FORCED_PROVIDERS = 'twofactorauth/general/force_providers'; - /** - * URL to be used for communications sent from webapi_* area - */ - public const XML_PATH_WEBAPI_NOTIFICATION_URL = 'twofactorauth/general/webapi_notification_url'; - /** * Return true if 2FA is enabled * diff --git a/TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php b/TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php index d4423cc5..65708a4b 100644 --- a/TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php +++ b/TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php @@ -32,5 +32,9 @@ public function getAuthenticationData(string $username, string $password): U2FWe * @param string $publicKeyCredentialJson * @return string */ - public function verify(string $username, string $password, string $publicKeyCredentialJson): string; + public function createAdminAccessToken( + string $username, + string $password, + string $publicKeyCredentialJson + ): string; } diff --git a/TwoFactorAuth/Api/U2fKeyConfigReaderInterface.php b/TwoFactorAuth/Api/U2fKeyConfigReaderInterface.php index bc4cb0c8..d5f3c2d5 100644 --- a/TwoFactorAuth/Api/U2fKeyConfigReaderInterface.php +++ b/TwoFactorAuth/Api/U2fKeyConfigReaderInterface.php @@ -8,27 +8,15 @@ namespace Magento\TwoFactorAuth\Api; -use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface; - /** - * Represent configuration for u2f key provider + * Represents configuration for u2f key provider */ -interface U2fKeyConfigureInterface +interface U2fKeyConfigReaderInterface { /** - * Get the information to initiate a WebAuthn registration ceremony - * - * @param string $tfaToken - * @return U2FWebAuthnRequestInterface - */ - public function getRegistrationData(string $tfaToken): U2FWebAuthnRequestInterface; - - /** - * Activate the provider and get a token + * Get the domain to use for WebAuthn ceremonies * - * @param string $tfaToken - * @param string $publicKeyCredentialJson - * @return bool + * @return string */ - public function activate(string $tfaToken, string $publicKeyCredentialJson): bool; + public function getDomain(): string; } diff --git a/TwoFactorAuth/Api/U2fKeyConfigureInterface.php b/TwoFactorAuth/Api/U2fKeyConfigureInterface.php index bc4cb0c8..d4cb4c14 100644 --- a/TwoFactorAuth/Api/U2fKeyConfigureInterface.php +++ b/TwoFactorAuth/Api/U2fKeyConfigureInterface.php @@ -28,7 +28,7 @@ public function getRegistrationData(string $tfaToken): U2FWebAuthnRequestInterfa * * @param string $tfaToken * @param string $publicKeyCredentialJson - * @return bool + * @return void */ - public function activate(string $tfaToken, string $publicKeyCredentialJson): bool; + public function activate(string $tfaToken, string $publicKeyCredentialJson): void; } diff --git a/TwoFactorAuth/Api/UserNotifierInterface.php b/TwoFactorAuth/Api/UserNotifierInterface.php index 6ba67f45..c010badb 100644 --- a/TwoFactorAuth/Api/UserNotifierInterface.php +++ b/TwoFactorAuth/Api/UserNotifierInterface.php @@ -16,6 +16,11 @@ */ interface UserNotifierInterface { + /** + * URL to be used for communications sent from webapi_* area + */ + public const XML_PATH_WEBAPI_NOTIFICATION_URL = 'twofactorauth/general/webapi_notification_url'; + /** * Send message allowing an admin user to configure personal 2FA. * diff --git a/TwoFactorAuth/Model/AdminAccessTokenService.php b/TwoFactorAuth/Model/AdminAccessTokenService.php index bd529dcc..4a385dea 100644 --- a/TwoFactorAuth/Model/AdminAccessTokenService.php +++ b/TwoFactorAuth/Model/AdminAccessTokenService.php @@ -12,7 +12,6 @@ use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Webapi\Exception as WebapiException; use Magento\Integration\Api\AdminTokenServiceInterface; use Magento\TwoFactorAuth\Api\AdminTokenServiceInterface as AdminTokenServiceInterfaceApi; use Magento\TwoFactorAuth\Api\Exception\NotificationExceptionInterface; @@ -64,30 +63,22 @@ public function __construct( } /** - * Prevent the admin token from being created with this api + * Prevent the admin token from being created via the token service * * @param string $username * @param string $password - * @return void + * @return string * @throws AuthenticationException - * @throws WebapiException - * @throws InputException * @throws LocalizedException - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @throws InputException */ - public function createAdminAccessToken(string $username, string $password): void + public function createAdminAccessToken($username, $password): string { // No exception means valid input. Ignore the created token. $this->adminTokenService->createAdminAccessToken($username, $password); $user = $this->userFactory->create(); $user->loadByUsername($username); $userId = (int)$user->getId(); - if ($userId === 0) { - throw new AuthenticationException(__( - 'The account sign-in was incorrect or your account is disabled temporarily. ' - . 'Please wait and try again later.' - )); - } $providerCodes = []; $activeProviderCodes = []; @@ -99,17 +90,17 @@ public function createAdminAccessToken(string $username, string $password): void } if (!$this->configRequestManager->isConfigurationRequiredFor($userId)) { - throw new WebapiException( - __('Please use the 2fa provider-specific endpoints to obtain a token.'), - 0, - WebapiException::HTTP_UNAUTHORIZED, - [ - 'active_providers' => $activeProviderCodes - ] + throw new LocalizedException( + __( + 'Please use the 2fa provider-specific endpoints to obtain a token.', + [ + 'active_providers' => $activeProviderCodes + ] + ) ); } elseif (empty($this->tfa->getUserProviders($userId))) { // It is expected that available 2fa providers are selected via db or admin ui - throw new WebapiException( + throw new LocalizedException( __('Please ask an administrator with sufficient access to configure 2FA first') ); } @@ -117,20 +108,28 @@ public function createAdminAccessToken(string $username, string $password): void try { $this->configRequestManager->sendConfigRequestTo($user); } catch (AuthorizationException|NotificationExceptionInterface $exception) { - throw new WebapiException( + throw new LocalizedException( __('Failed to send the message. Please contact the administrator') ); } - throw new WebapiException( - __('You are required to configure personal Two-Factor Authorization in order to login. ' - . 'Please check your email.'), - 0, - WebapiException::HTTP_UNAUTHORIZED, - [ - 'providers' => $providerCodes, - 'active_providers' => $activeProviderCodes - ] + throw new LocalizedException( + __( + 'You are required to configure personal Two-Factor Authorization in order to login. ' + . 'Please check your email.', + [ + 'providers' => $providerCodes, + 'active_providers' => $activeProviderCodes + ] + ) ); } + + /** + * @inheritDoc + */ + public function revokeAdminAccessToken($adminId): bool + { + return $this->adminTokenService->revokeAdminAccessToken($adminId); + } } diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php b/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php index e16ec542..6d09b862 100644 --- a/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php @@ -13,7 +13,7 @@ use Magento\TwoFactorAuth\Api\Data\AuthyRegistrationPromptResponseExtensionInterface; /** - * Represents google authentication data + * Represents authy authentication data */ class RegistrationResponse extends AbstractExtensibleObject implements ResponseInterface { diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php b/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php index d498ae1d..4c709358 100644 --- a/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php @@ -18,24 +18,24 @@ class ConfigurationData extends AbstractExtensibleObject implements GoogleConfigureInterface { /** - * Get value for qr code url + * Get value for QR code base 64 * * @return string */ - public function getQrCodeUrl(): string + public function getQrCodeBase64(): string { - return (string)$this->_get(self::QR_CODE_URL); + return (string)$this->_get(self::QR_CODE_BASE64); } /** - * Set value for qr code url + * Set value for QR code base 64 * * @param string $value * @return void */ - public function setQrCodeUrl(string $value): void + public function setQrCodeBase64(string $value): void { - $this->setData(self::QR_CODE_URL, $value); + $this->setData(self::QR_CODE_BASE64, $value); } /** diff --git a/TwoFactorAuth/Model/EmailUserNotifier.php b/TwoFactorAuth/Model/EmailUserNotifier.php index 9d399394..bd7cfefd 100644 --- a/TwoFactorAuth/Model/EmailUserNotifier.php +++ b/TwoFactorAuth/Model/EmailUserNotifier.php @@ -13,7 +13,6 @@ use Magento\Framework\App\State; use Magento\Framework\UrlInterface; use Magento\Store\Model\StoreManagerInterface; -use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\User\Model\User; use Magento\TwoFactorAuth\Api\UserNotifierInterface; use Magento\Framework\Mail\Template\TransportBuilder; @@ -95,7 +94,7 @@ private function sendConfigRequired( bool $useWebApiUrl = false ): void { try { - $userUrl = $this->scopeConfig->getValue(TfaInterface::XML_PATH_WEBAPI_NOTIFICATION_URL); + $userUrl = $this->scopeConfig->getValue(UserNotifierInterface::XML_PATH_WEBAPI_NOTIFICATION_URL); if ($useWebApiUrl && $userUrl) { $url = str_replace(':tfat', $token, $userUrl); } else { diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php index 357aba35..077e23a7 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php @@ -10,12 +10,12 @@ use Magento\Framework\DataObjectFactory; use Magento\Framework\Exception\AuthenticationException; -use Magento\Framework\Webapi\Exception as WebApiException; +use Magento\Framework\Exception\LocalizedException; use Magento\Integration\Api\AdminTokenServiceInterface; use Magento\TwoFactorAuth\Api\AuthyAuthenticateInterface; -use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Authy; +use Magento\TwoFactorAuth\Model\UserAuthenticator; use Magento\User\Api\Data\UserInterface; use Magento\User\Model\UserFactory; @@ -55,9 +55,9 @@ class Authenticate implements AuthyAuthenticateInterface private $authyToken; /** - * @var TfaInterface + * @var UserAuthenticator */ - private $tfa; + private $userAuthenticator; /** * @var OneTouch @@ -71,7 +71,7 @@ class Authenticate implements AuthyAuthenticateInterface * @param DataObjectFactory $dataObjectFactory * @param AdminTokenServiceInterface $adminTokenService * @param Token $authyToken - * @param TfaInterface $tfa + * @param UserAuthenticator $userAuthenticator * @param OneTouch $oneTouch */ public function __construct( @@ -81,7 +81,7 @@ public function __construct( DataObjectFactory $dataObjectFactory, AdminTokenServiceInterface $adminTokenService, Token $authyToken, - TfaInterface $tfa, + UserAuthenticator $userAuthenticator, OneTouch $oneTouch ) { $this->userFactory = $userFactory; @@ -90,25 +90,20 @@ public function __construct( $this->dataObjectFactory = $dataObjectFactory; $this->adminTokenService = $adminTokenService; $this->authyToken = $authyToken; - $this->tfa = $tfa; + $this->userAuthenticator = $userAuthenticator; $this->oneTouch = $oneTouch; } /** * @inheritDoc */ - public function authenticateWithToken(string $username, string $password, string $otp): string + public function createAdminAccessTokenWithCredentials(string $username, string $password, string $otp): string { - $user = $this->getUser($username); - - if (!$this->tfa->getProviderIsAllowed((int)$user->getId(), Authy::CODE)) { - throw new WebApiException(__('Provider is not allowed.')); - } elseif (!$this->tfa->getProviderByCode(Authy::CODE)->isActive((int)$user->getId())) { - throw new WebApiException(__('Provider is not configured.')); - } - $token = $this->adminTokenService->createAdminAccessToken($username, $password); + $user = $this->getUser($username); + $this->userAuthenticator->assertProviderIsValidForUser((int)$user->getId(), Authy::CODE); + try { $this->authy->verify($user, $this->dataObjectFactory->create([ 'data' => [ @@ -134,13 +129,10 @@ public function authenticateWithToken(string $username, string $password, string */ public function sendToken(string $username, string $password, string $via): bool { - $user = $this->getUser($username); + $this->adminTokenService->createAdminAccessToken($username, $password); - if (!$this->tfa->getProviderIsAllowed((int)$user->getId(), Authy::CODE)) { - throw new WebApiException(__('Provider is not allowed.')); - } elseif (!$this->tfa->getProviderByCode(Authy::CODE)->isActive((int)$user->getId())) { - throw new WebApiException(__('Provider is not configured.')); - } + $user = $this->getUser($username); + $this->userAuthenticator->assertProviderIsValidForUser((int)$user->getId(), Authy::CODE); if ($via === 'onetouch') { $this->oneTouch->request($user); @@ -154,22 +146,17 @@ public function sendToken(string $username, string $password, string $via): bool /** * @inheritDoc */ - public function authenticateWithOnetouch(string $username, string $password): string + public function creatAdminAccessTokenWithOneTouch(string $username, string $password): string { - $user = $this->getUser($username); + $token = $this->adminTokenService->createAdminAccessToken($username, $password); - if (!$this->tfa->getProviderIsAllowed((int)$user->getId(), Authy::CODE)) { - throw new WebApiException(__('Provider is not allowed.')); - } elseif (!$this->tfa->getProviderByCode(Authy::CODE)->isActive((int)$user->getId())) { - throw new WebApiException(__('Provider is not configured.')); - } + $user = $this->getUser($username); + $this->userAuthenticator->assertProviderIsValidForUser((int)$user->getId(), Authy::CODE); try { $res = $this->oneTouch->verify($user); if ($res === 'approved') { - return $this->adminTokenService->create() - ->createAdminToken((int)$user->getId()) - ->getToken(); + return $token; } else { $this->alert->event( 'Magento_TwoFactorAuth', @@ -178,7 +165,7 @@ public function authenticateWithOnetouch(string $username, string $password): st $user->getUserName() ); - throw new WebApiException(__('Onetouch prompt was denied or timed out.')); + throw new LocalizedException(__('Onetouch prompt was denied or timed out.')); } } catch (\Exception $e) { $this->alert->event( diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php index 1665f8f5..df68fcf3 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Configure.php @@ -70,7 +70,11 @@ public function __construct( /** * @inheritDoc */ - public function sendDeviceRegistrationPrompt(string $tfaToken, AuthyDeviceInterface $deviceData): ResponseInterface { + public function sendDeviceRegistrationPrompt( + string $tfaToken, + AuthyDeviceInterface + $deviceData + ): ResponseInterface { $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, Authy::CODE); $response = []; @@ -102,7 +106,7 @@ public function sendDeviceRegistrationPrompt(string $tfaToken, AuthyDeviceInterf /** * @inheritDoc */ - public function activate(string $tfaToken, string $otp): bool + public function activate(string $tfaToken, string $otp): void { $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, Authy::CODE); @@ -116,8 +120,6 @@ public function activate(string $tfaToken, string $otp): bool AlertInterface::LEVEL_INFO, $user->getUserName() ); - - return true; } catch (\Throwable $e) { $this->alert->event( 'Magento_TwoFactorAuth', diff --git a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php index 6d5149d3..a12dc487 100644 --- a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Authenticate.php @@ -10,14 +10,14 @@ use Magento\Framework\DataObjectFactory; use Magento\Framework\Exception\AuthenticationException; -use Magento\Framework\Webapi\Exception as WebApiException; +use Magento\Framework\Exception\LocalizedException; use Magento\Integration\Api\AdminTokenServiceInterface; use Magento\TwoFactorAuth\Api\Data\DuoDataInterface; use Magento\TwoFactorAuth\Api\Data\DuoDataInterfaceFactory; use Magento\TwoFactorAuth\Api\DuoAuthenticateInterface; -use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity; +use Magento\TwoFactorAuth\Model\UserAuthenticator; use Magento\User\Api\Data\UserInterface; use Magento\User\Model\UserFactory; @@ -57,9 +57,9 @@ class Authenticate implements DuoAuthenticateInterface private $dataObjectFactory; /** - * @var TfaInterface + * @var UserAuthenticator */ - private $tfa; + private $userAuthenticator; /** * @param UserFactory $userFactory @@ -68,7 +68,7 @@ class Authenticate implements DuoAuthenticateInterface * @param AdminTokenServiceInterface $adminTokenService * @param DuoDataInterfaceFactory $dataFactory * @param DataObjectFactory $dataObjectFactory - * @param TfaInterface $tfa + * @param UserAuthenticator $userAuthenticator */ public function __construct( UserFactory $userFactory, @@ -77,7 +77,7 @@ public function __construct( AdminTokenServiceInterface $adminTokenService, DuoDataInterfaceFactory $dataFactory, DataObjectFactory $dataObjectFactory, - TfaInterface $tfa + UserAuthenticator $userAuthenticator ) { $this->userFactory = $userFactory; $this->alert = $alert; @@ -85,7 +85,7 @@ public function __construct( $this->adminTokenService = $adminTokenService; $this->dataFactory = $dataFactory; $this->dataObjectFactory = $dataObjectFactory; - $this->tfa = $tfa; + $this->userAuthenticator = $userAuthenticator; } /** @@ -93,17 +93,11 @@ public function __construct( */ public function getAuthenticateData(string $username, string $password): DuoDataInterface { - $user = $this->getUser($username); - $userId = (int)$user->getId(); - - if (!$this->tfa->getProviderIsAllowed($userId, DuoSecurity::CODE)) { - throw new WebApiException(__('Provider is not allowed.')); - } elseif (!$this->tfa->getProviderByCode(DuoSecurity::CODE)->isActive($userId)) { - throw new WebApiException(__('Provider is not configured.')); - } - $this->adminTokenService->createAdminAccessToken($username, $password); + $user = $this->getUser($username); + $this->userAuthenticator->assertProviderIsValidForUser((int)$user->getId(), DuoSecurity::CODE); + return $this->dataFactory->create( [ 'data' => [ @@ -117,19 +111,16 @@ public function getAuthenticateData(string $username, string $password): DuoData /** * @inheritDoc */ - public function verify(string $username, string $password, string $signatureResponse): string - { - $user = $this->getUser($username); - $userId = (int)$user->getId(); - - if (!$this->tfa->getProviderIsAllowed($userId, DuoSecurity::CODE)) { - throw new WebApiException(__('Provider is not allowed.')); - } elseif (!$this->tfa->getProviderByCode(DuoSecurity::CODE)->isActive($userId)) { - throw new WebApiException(__('Provider is not configured.')); - } - + public function createAdminAccessTokenWithCredentials( + string $username, + string $password, + string $signatureResponse + ): string { $token = $this->adminTokenService->createAdminAccessToken($username, $password); + $user = $this->getUser($username); + $this->userAuthenticator->assertProviderIsValidForUser((int)$user->getId(), DuoSecurity::CODE); + $this->assertResponseIsValid($user, $signatureResponse); return $token; @@ -140,7 +131,7 @@ public function verify(string $username, string $password, string $signatureResp * * @param UserInterface $user * @param string $signatureResponse - * @throws WebApiException + * @throws LocalizedException */ public function assertResponseIsValid(UserInterface $user, string $signatureResponse): void { @@ -159,7 +150,7 @@ public function assertResponseIsValid(UserInterface $user, string $signatureResp $user->getUserName() ); - throw new WebApiException(__('Invalid response')); + throw new LocalizedException(__('Invalid response')); } } diff --git a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php index 89005aa7..f842cb89 100644 --- a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity/Configure.php @@ -86,7 +86,7 @@ public function getConfigurationData(string $tfaToken): DuoDataInterface /** * @inheritDoc */ - public function activate(string $tfaToken, string $signatureResponse): bool + public function activate(string $tfaToken, string $signatureResponse): void { $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, DuoSecurity::CODE); $userId = (int)$user->getId(); @@ -94,7 +94,5 @@ public function activate(string $tfaToken, string $signatureResponse): bool $this->authenticate->assertResponseIsValid($user, $signatureResponse); $this->tfa->getProviderByCode(DuoSecurity::CODE) ->activate($userId); - - return true; } } diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php index 5e4b1f15..bdfdc447 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Authenticate.php @@ -9,14 +9,12 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\Framework\DataObjectFactory; -use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Exception\AuthorizationException; -use Magento\Framework\Webapi\Exception as WebApiException; use Magento\Integration\Api\AdminTokenServiceInterface; use Magento\TwoFactorAuth\Api\GoogleAuthenticateInterface; -use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; +use Magento\TwoFactorAuth\Model\UserAuthenticator; use Magento\User\Model\UserFactory; /** @@ -30,9 +28,9 @@ class Authenticate implements GoogleAuthenticateInterface private $google; /** - * @var TfaInterface + * @var UserAuthenticator */ - private $tfa; + private $userAuthenticator; /** * @var DataObjectFactory @@ -56,7 +54,7 @@ class Authenticate implements GoogleAuthenticateInterface /** * @param Google $google - * @param TfaInterface $tfa + * @param UserAuthenticator $userAuthenticator * @param DataObjectFactory $dataObjectFactory * @param AlertInterface $alert * @param AdminTokenServiceInterface $adminTokenService @@ -64,14 +62,14 @@ class Authenticate implements GoogleAuthenticateInterface */ public function __construct( Google $google, - TfaInterface $tfa, + UserAuthenticator $userAuthenticator, DataObjectFactory $dataObjectFactory, AlertInterface $alert, AdminTokenServiceInterface $adminTokenService, UserFactory $userFactory ) { $this->google = $google; - $this->tfa = $tfa; + $this->userAuthenticator = $userAuthenticator; $this->dataObjectFactory = $dataObjectFactory; $this->alert = $alert; $this->adminTokenService = $adminTokenService; @@ -81,25 +79,14 @@ public function __construct( /** * @inheritDoc */ - public function getToken(string $username, string $password, string $otp): string + public function createAdminAccessToken(string $username, string $password, string $otp): string { + $token = $this->adminTokenService->createAdminAccessToken($username, $password); $user = $this->userFactory->create(); $user->loadByUsername($username); - $userId = (int)$user->getId(); - if ($userId === 0) { - throw new AuthenticationException(__( - 'The account sign-in was incorrect or your account is disabled temporarily. ' - . 'Please wait and try again later.' - )); - } - - $token = $this->adminTokenService->createAdminAccessToken($username, $password); + $this->userAuthenticator->assertProviderIsValidForUser((int)$user->getId(), Google::CODE); - if (!$this->tfa->getProviderIsAllowed($userId, Google::CODE)) { - throw new WebApiException(__('Provider is not allowed.')); - } elseif (!$this->tfa->getProviderByCode(Google::CODE)->isActive($userId)) { - throw new WebApiException(__('Provider is not configured.')); - } elseif ($this->google->verify($user, $this->dataObjectFactory->create([ + if ($this->google->verify($user, $this->dataObjectFactory->create([ 'data' => [ 'tfa_code' => $otp ], diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php index 552c8995..d1ecad49 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google/Configure.php @@ -87,8 +87,7 @@ public function getConfigurationData(string $tfaToken): GoogleConfigurationData return $this->configurationDataFactory->create( [ 'data' => [ - GoogleConfigurationData::QR_CODE_URL => - 'data:image/png;base64,' . base64_encode($this->google->getQrCodeAsPng($user)), + GoogleConfigurationData::QR_CODE_BASE64 => base64_encode($this->google->getQrCodeAsPng($user)), GoogleConfigurationData::SECRET_CODE => $this->google->getSecretCode($user) ] ] @@ -98,7 +97,7 @@ public function getConfigurationData(string $tfaToken): GoogleConfigurationData /** * @inheritDoc */ - public function activate(string $tfaToken, string $otp): bool + public function activate(string $tfaToken, string $otp): void { $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, Google::CODE); @@ -116,8 +115,6 @@ public function activate(string $tfaToken, string $otp): bool AlertInterface::LEVEL_INFO, $user->getUserName() ); - - return true; } else { throw new AuthorizationException(__('Invalid code.')); } diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php index 7914b788..b4fd13b5 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php @@ -11,15 +11,15 @@ use Magento\Framework\DataObjectFactory; use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Serialize\Serializer\Json; -use Magento\Framework\Webapi\Exception as WebApiException; +use Magento\Framework\Exception\LocalizedException; use Magento\Integration\Api\AdminTokenServiceInterface; use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface; use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterfaceFactory; -use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\U2fKeyAuthenticateInterface; use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\AlertInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; +use Magento\TwoFactorAuth\Model\UserAuthenticator; use Magento\User\Api\Data\UserInterface; use Magento\User\Model\UserFactory; @@ -31,9 +31,9 @@ class Authenticate implements U2fKeyAuthenticateInterface private const AUTHENTICATION_CHALLENGE_KEY = 'webapi_authentication_challenge'; /** - * @var TfaInterface + * @var UserAuthenticator */ - private $tfa; + private $userAuthenticator; /** * @var U2fKey @@ -76,7 +76,7 @@ class Authenticate implements U2fKeyAuthenticateInterface private $adminTokenService; /** - * @param TfaInterface $tfa + * @param UserAuthenticator $userAuthenticator * @param U2fKey $u2fKey * @param AlertInterface $alert * @param DataObjectFactory $dataObjectFactory @@ -87,7 +87,7 @@ class Authenticate implements U2fKeyAuthenticateInterface * @param AdminTokenServiceInterface $adminTokenService */ public function __construct( - TfaInterface $tfa, + UserAuthenticator $userAuthenticator, U2fKey $u2fKey, AlertInterface $alert, DataObjectFactory $dataObjectFactory, @@ -97,7 +97,7 @@ public function __construct( UserConfigManagerInterface $configManager, AdminTokenServiceInterface $adminTokenService ) { - $this->tfa = $tfa; + $this->userAuthenticator = $userAuthenticator; $this->u2fKey = $u2fKey; $this->alert = $alert; $this->dataObjectFactory = $dataObjectFactory; @@ -113,17 +113,11 @@ public function __construct( */ public function getAuthenticationData(string $username, string $password): U2FWebAuthnRequestInterface { + $this->adminTokenService->createAdminAccessToken($username, $password); + $user = $this->getUser($username); $userId = (int)$user->getId(); - - if (!$this->tfa->getProviderIsAllowed($userId, U2fKey::CODE)) { - throw new WebApiException(__('Provider is not allowed.')); - } elseif (!$this->tfa->getProviderByCode(U2fKey::CODE)->isActive($userId)) { - throw new WebApiException(__('Provider is not configured.')); - } - - // No exception means valid - $this->adminTokenService->createAdminAccessToken($username, $password); + $this->userAuthenticator->assertProviderIsValidForUser($userId, U2fKey::CODE); $data = $this->u2fKey->getAuthenticateData($user); $this->configManager->addProviderConfig( @@ -146,23 +140,19 @@ public function getAuthenticationData(string $username, string $password): U2FWe /** * @inheritDoc */ - public function verify(string $username, string $password, string $publicKeyCredentialJson): string + public function createAdminAccessToken(string $username, string $password, string $publicKeyCredentialJson): string { + $token = $this->adminTokenService->createAdminAccessToken($username, $password); + $user = $this->getUser($username); $userId = (int)$user->getId(); + $this->userAuthenticator->assertProviderIsValidForUser($userId, U2fKey::CODE); $config = $this->configManager->getProviderConfig($userId, U2fKey::CODE); - if (!$this->tfa->getProviderIsAllowed($userId, U2fKey::CODE)) { - throw new WebApiException(__('Provider is not allowed.')); - } elseif (!$this->tfa->getProviderByCode(U2fKey::CODE)->isActive($userId)) { - throw new WebApiException(__('Provider is not configured.')); - } elseif (empty($config[self::AUTHENTICATION_CHALLENGE_KEY])) { - throw new WebApiException(__('U2f authentication prompt not sent.')); + if (empty($config[self::AUTHENTICATION_CHALLENGE_KEY])) { + throw new LocalizedException(__('U2f authentication prompt not sent.')); } - // Validates/throttles credentials - $token = $this->adminTokenService->createAdminAccessToken($username, $password); - try { $this->u2fKey->verify($user, $this->dataObjectFactory->create( [ diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/ConfigReader.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/ConfigReader.php index c134cdd2..ef4adaa2 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/ConfigReader.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/ConfigReader.php @@ -8,11 +8,39 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; +use Magento\Framework\Exception\LocalizedException; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TwoFactorAuth\Api\U2fKeyConfigReaderInterface; /** - * + * Read the configuration for u2f provider */ -class ConfigReader +class ConfigReader implements U2fKeyConfigReaderInterface { + /** + * @var StoreManagerInterface + */ + private $storeManager; + /** + * @param StoreManagerInterface $storeManager + */ + public function __construct(StoreManagerInterface $storeManager) + { + $this->storeManager = $storeManager; + } + + /** + * @inheritDoc + */ + public function getDomain(): string + { + $store = $this->storeManager->getStore(Store::ADMIN_CODE); + $baseUrl = $store->getBaseUrl(); + if (!preg_match('/^(https?:\/\/(?P.+?))\//', $baseUrl, $matches)) { + throw new LocalizedException(__('Could not determine domain name.')); + } + return $matches['domain']; + } } diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php index 657c02df..70217f41 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php @@ -9,7 +9,7 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; use Magento\Framework\Serialize\Serializer\Json; -use Magento\Framework\Webapi\Exception as WebApiException; +use Magento\Framework\Exception\LocalizedException; use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface; use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterfaceFactory; use Magento\TwoFactorAuth\Api\U2fKeyConfigureInterface; @@ -107,7 +107,7 @@ public function getRegistrationData(string $tfaToken): U2FWebAuthnRequestInterfa /** * @inheritDoc */ - public function activate(string $tfaToken, string $publicKeyCredentialJson): bool + public function activate(string $tfaToken, string $publicKeyCredentialJson): void { $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, U2fKey::CODE); $userId = (int)$user->getId(); @@ -115,7 +115,7 @@ public function activate(string $tfaToken, string $publicKeyCredentialJson): boo $config = $this->configManager->getProviderConfig($userId, U2fKey::CODE); if (empty($config[self::REGISTER_CHALLENGE_KEY])) { - throw new WebApiException(__('U2f key registration was not started.')); + throw new LocalizedException(__('U2f key registration was not started.')); } try { @@ -148,7 +148,5 @@ public function activate(string $tfaToken, string $publicKeyCredentialJson): boo U2fKey::CODE, [self::REGISTER_CHALLENGE_KEY => null] ); - - return true; } } diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebApiConfigReader.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebApiConfigReader.php index 6885b42f..1bdc7dda 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebApiConfigReader.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebApiConfigReader.php @@ -8,39 +8,45 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; -use Magento\Framework\Exception\LocalizedException; -use Magento\Store\Model\Store; -use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\TwoFactorAuth\Api\U2fKeyConfigReaderInterface; +use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; /** * Read the configuration for u2f provider */ -class ConfigReader implements U2fKeyConfigReaderInterface +class WebApiConfigReader implements U2fKeyConfigReaderInterface { /** - * @var StoreManagerInterface + * @var ConfigReader */ - private $storeManager; + private $configReader; /** - * @param StoreManagerInterface $storeManager + * @var ScopeConfigInterface */ - public function __construct(StoreManagerInterface $storeManager) + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + * @param ConfigReader $configReader + */ + public function __construct(ScopeConfigInterface $scopeConfig, ConfigReader $configReader) { - $this->storeManager = $storeManager; + $this->configReader = $configReader; + $this->scopeConfig = $scopeConfig; } /** * @inheritDoc */ - public function getDomain(): bool + public function getDomain(): string { - $store = $this->storeManager->getStore(Store::ADMIN_CODE); - $baseUrl = $store->getBaseUrl(); - if (!preg_match('/^(https?:\/\/(?P.+?))\//', $baseUrl, $matches)) { - throw new LocalizedException(__('Could not determine secure domain name.')); + $configValue = $this->scopeConfig->getValue(U2fKey::XML_PATH_WEBAPI_DOMAIN); + if ($configValue) { + return $configValue; } - return $matches['domain']; + + return $this->configReader->getDomain(); } } diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php index afab25e7..501f5713 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php @@ -9,14 +9,11 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; use CBOR\CBOREncoder; -use Magento\Framework\App\Area; -use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\App\State; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Validation\ValidationException; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; -use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; +use Magento\TwoFactorAuth\Api\U2fKeyConfigReaderInterface; use Magento\User\Api\Data\UserInterface; /** @@ -32,33 +29,25 @@ class WebAuthn private const PUBKEY_LEN = 65; /** - * @var StoreManagerInterface - */ - private $storeManager; - - /** - * @var ScopeConfigInterface + * @var U2fKeyConfigReaderInterface */ - private $scopeConfig; + private $config; /** - * @var State + * @var StoreManagerInterface */ - private $appState; + private $storeManager; /** * @param StoreManagerInterface $storeManager - * @param ScopeConfigInterface $scopeConfig - * @param State $appState + * @param U2fKeyConfigReaderInterface $u2fKeyConfig */ public function __construct( StoreManagerInterface $storeManager, - ScopeConfigInterface $scopeConfig, - State $appState + U2fKeyConfigReaderInterface $u2fKeyConfig ) { + $this->config = $u2fKeyConfig; $this->storeManager = $storeManager; - $this->scopeConfig = $scopeConfig; - $this->appState = $appState; } /** @@ -89,7 +78,7 @@ public function assertCredentialDataIsValid( throw new LocalizedException(__('Invalid U2F key.')); } - $domain = $this->getDomainName(); + $domain = $this->config->getDomain(); // Steps 7-9 if (rtrim(strtr(base64_encode($this->convertArrayToBytes($originalChallenge)), '+/', '-_'), '=') @@ -166,7 +155,7 @@ public function getAuthenticateData(array $publicKeys): array 'extensions' => [ 'txAuthSimple' => 'Authenticate with ' . $store->getName(), ], - 'rpId' => $this->getDomainName(), + 'rpId' => $this->config->getDomain(), ] ]; @@ -182,7 +171,7 @@ public function getAuthenticateData(array $publicKeys): array */ public function getRegisterData(UserInterface $user): array { - $domain = $this->getDomainName(); + $domain = $this->config->getDomain(); try { $challenge = random_bytes(16); @@ -237,7 +226,7 @@ public function getPublicKeyFromRegistrationData(array $data): array // Verification process as defined by w3 @see https://www.w3.org/TR/webauthn/#registering-a-new-credential $credentialData = $data['publicKeyCredential']; - $domain = $this->getDomainName(); + $domain = $this->config->getDomain(); if (rtrim(strtr(base64_encode($this->convertArrayToBytes($data['challenge'])), '+/', '-_'), '=') !== $credentialData['response']['clientData']['challenge'] @@ -265,7 +254,7 @@ public function getPublicKeyFromRegistrationData(array $data): array $attestationObject['flags'] = ord(substr($byteString, 32, 1)); $attestationObject['counter'] = substr($byteString, 33, 4); - $hashId = hash('sha256', $this->getDomainName(), true); + $hashId = hash('sha256', $this->config->getDomain(), true); if ($hashId !== $attestationObject['rpIdHash']) { throw new ValidationException(__('Invalid U2F key data')); } @@ -334,29 +323,6 @@ private function convertArrayToBytes(array $bytes): string return $byteString; } - /** - * Get the store domain but only if it's secure - * - * @return string - * @throws LocalizedException - */ - private function getDomainName(): string - { - $configValue = $this->scopeConfig->getValue(U2fKey::XML_PATH_WEBAPI_DOMAIN); - if ($configValue && - in_array($this->appState->getAreaCode(), [Area::AREA_WEBAPI_REST, Area::AREA_WEBAPI_SOAP]) - ) { - return $configValue; - } - - $store = $this->storeManager->getStore(Store::ADMIN_CODE); - $baseUrl = $store->getBaseUrl(); - if (!preg_match('/^(https?:\/\/(?P.+?))\//', $baseUrl, $matches)) { - throw new LocalizedException(__('Could not determine secure domain name.')); - } - return $matches['domain']; - } - /** * Convert a CBOR encoded public key to PKCS format * diff --git a/TwoFactorAuth/Model/UserAuthenticator.php b/TwoFactorAuth/Model/UserAuthenticator.php index 8cd270da..d10e7b60 100644 --- a/TwoFactorAuth/Model/UserAuthenticator.php +++ b/TwoFactorAuth/Model/UserAuthenticator.php @@ -10,6 +10,7 @@ use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Exception\AuthorizationException; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\Webapi\Exception as WebApiException; use Magento\TwoFactorAuth\Api\TfaInterface; @@ -82,9 +83,9 @@ public function authenticateWithTokenAndProvider(string $tfaToken, string $provi { try { ['user_id' => $userId] = $this->json->unserialize(explode('.', base64_decode($tfaToken))[0]); - } catch (\Exception $e) { + } catch (\Throwable $e) { throw new AuthorizationException( - __('Invalid tfa token') + __('Invalid two-factor authorization token') ); } @@ -94,7 +95,7 @@ public function authenticateWithTokenAndProvider(string $tfaToken, string $provi throw new WebApiException(__('Provider is already configured.')); } elseif (!$this->tokenManager->isValidFor($userId, $tfaToken)) { throw new AuthorizationException( - __('Invalid tfa token') + __('Invalid two-factor authorization token') ); } @@ -103,4 +104,20 @@ public function authenticateWithTokenAndProvider(string $tfaToken, string $provi return $user; } + + /** + * Validate the user is allowed to use the provider + * + * @param int $userId + * @param string $providerCode + * @throws LocalizedException + */ + public function assertProviderIsValidForUser(int $userId, string $providerCode): void + { + if (!$this->tfa->getProviderIsAllowed($userId, $providerCode)) { + throw new LocalizedException(__('Provider is not allowed.')); + } elseif (!$this->tfa->getProviderByCode($providerCode)->isActive($userId)) { + throw new LocalizedException(__('Provider is not configured.')); + } + } } diff --git a/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php b/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php index a37953a5..f08f3428 100644 --- a/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php +++ b/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php @@ -8,7 +8,6 @@ namespace Magento\TwoFactorAuth\Plugin; -use Magento\Backend\Model\Auth\Session; use Magento\Backend\Model\Session as SessionManager; use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory; use Magento\Framework\Stdlib\CookieManagerInterface; @@ -28,11 +27,6 @@ class DeleteCookieOnLogout */ private $cookieMetadataFactory; - /** - * @var Session - */ - private $session; - /** * @var SessionManager */ @@ -41,18 +35,15 @@ class DeleteCookieOnLogout /** * @param CookieManagerInterface $cookies * @param CookieMetadataFactory $cookieMetadataFactory - * @param Session $session * @param SessionManager $sessionManager */ public function __construct( CookieManagerInterface $cookies, CookieMetadataFactory $cookieMetadataFactory, - Session $session, SessionManager $sessionManager ) { $this->cookies = $cookies; $this->cookieMetadataFactory = $cookieMetadataFactory; - $this->session = $session; $this->sessionManager = $sessionManager; } diff --git a/TwoFactorAuth/Test/Api/GoogleActivateTest.php b/TwoFactorAuth/Test/Api/GoogleActivateTest.php index eaa120ad..15e77264 100644 --- a/TwoFactorAuth/Test/Api/GoogleActivateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleActivateTest.php @@ -71,7 +71,7 @@ public function testInvalidTfat() } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); self::assertEmpty(json_last_error()); - self::assertSame('Invalid tfa token', $response['message']); + self::assertSame('Invalid two-factor authorization token', $response['message']); } } @@ -136,8 +136,7 @@ public function testActivate() 'otp' => $otp ] ); - self::assertNotEmpty($response); - self::assertTrue($response); + self::assertEmpty($response); } private function getUserOtp(): string diff --git a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php index 6b20da8e..f770cf71 100644 --- a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php @@ -12,7 +12,6 @@ use Magento\TestFramework\TestCase\WebapiAbstract; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\User\Model\UserFactory; -use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use OTPHP\TOTP; class GoogleAuthenticateTest extends WebapiAbstract diff --git a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php index fdc67469..91dd8552 100644 --- a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php +++ b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php @@ -10,6 +10,7 @@ use Magento\Framework\Webapi\Rest\Request; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\TwoFactorAuth\Api\Data\GoogleConfigureInterface as GoogleConfigureData; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\User\Model\UserFactory; @@ -57,7 +58,7 @@ public function testInvalidTfat() } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); self::assertEmpty(json_last_error()); - self::assertSame('Invalid tfa token', $response['message']); + self::assertSame('Invalid two-factor authorization token', $response['message']); } } @@ -114,8 +115,8 @@ public function testValidRequest() $serviceInfo = $this->buildServiceInfo(); $response = $this->_webApiCall($serviceInfo, ['tfaToken' => $token]); - self::assertNotEmpty($response['qr_code_url']); - self::assertStringStartsWith('data:image/png', $response['qr_code_url']); + self::assertNotEmpty($response[GoogleConfigureData::QR_CODE_BASE64]); + self::assertRegExp('/^[a-zA-Z0-9+\/=]+$/', $response[GoogleConfigureData::QR_CODE_BASE64]); self::assertNotEmpty($response['secret_code']); } diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php index e4660035..68925432 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php @@ -41,16 +41,30 @@ class AuthenticateTest extends TestCase */ private $userFactory; + /** + * @var Token|MockObject + */ + private $authyToken; + + /** + * @var OneTouch|MockObject + */ + private $onetouch; + protected function setUp() { $objectManager = ObjectManager::getInstance(); $this->tfa = $objectManager->get(TfaInterface::class); $this->authy = $this->createMock(Authy::class); $this->userFactory = $objectManager->get(UserFactory::class); + $this->authyToken = $this->createMock(Token::class); + $this->onetouch = $this->createMock(OneTouch::class); $this->model = $objectManager->create( Authenticate::class, [ - 'authy' => $this->authy + 'authy' => $this->authy, + 'authyToken' => $this->authyToken, + 'oneTouch' => $this->onetouch ] ); } @@ -68,7 +82,7 @@ public function testAuthenticateInvalidCredentials() $this->authy ->expects($this->never()) ->method('verify'); - $this->model->authenticateWithToken( + $this->model->createAdminAccessTokenWithCredentials( 'adminUser', 'bad', 'abc' @@ -79,7 +93,7 @@ public function testAuthenticateInvalidCredentials() * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not configured. */ public function testAuthenticateNotConfiguredProvider() @@ -87,7 +101,7 @@ public function testAuthenticateNotConfiguredProvider() $this->authy ->expects($this->never()) ->method('verify'); - $this->model->authenticateWithToken( + $this->model->createAdminAccessTokenWithCredentials( 'adminUser', Bootstrap::ADMIN_PASSWORD, 'abc' @@ -97,7 +111,7 @@ public function testAuthenticateNotConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not allowed. */ public function testAuthenticateUnavailableProvider() @@ -105,7 +119,7 @@ public function testAuthenticateUnavailableProvider() $this->authy ->expects($this->never()) ->method('verify'); - $this->model->authenticateWithToken( + $this->model->createAdminAccessTokenWithCredentials( 'adminUser', Bootstrap::ADMIN_PASSWORD, 'abc' @@ -133,7 +147,7 @@ public function testAuthenticateValidRequest() return $value->getData('tfa_code') === 'abc'; }) ); - $result = $this->model->authenticateWithToken( + $result = $this->model->createAdminAccessTokenWithCredentials( 'adminUser', Bootstrap::ADMIN_PASSWORD, 'abc' @@ -142,6 +156,164 @@ public function testAuthenticateValidRequest() self::assertRegExp('/^[a-z0-9]{32}$/', $result); } + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\AuthenticationException + */ + public function testSendTokenInvalidCredentials() + { + $this->tfa->getProviderByCode(Authy::CODE) + ->activate($this->getUserId()); + $this->authy + ->expects($this->never()) + ->method('verify'); + $this->model->sendToken( + 'adminUser', + 'bad', + 'sms' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Provider is not configured. + */ + public function testSendTokenNotConfiguredProvider() + { + $this->authy + ->expects($this->never()) + ->method('verify'); + $this->model->sendToken( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'sms' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Provider is not allowed. + */ + public function testSendTokenUnavailableProvider() + { + $this->authy + ->expects($this->never()) + ->method('verify'); + $this->model->sendToken( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'sms' + ); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testSendTokenValidRequest() + { + $this->tfa->getProviderByCode(Authy::CODE) + ->activate($this->getUserId()); + $userId = $this->getUserId(); + $this->authyToken + ->expects($this->once()) + ->method('request') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }), + 'a method' + ); + $result = $this->model->sendToken( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'a method' + ); + + self::assertTrue($result); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testSendTokenValidRequestWithOneTouch() + { + $this->tfa->getProviderByCode(Authy::CODE) + ->activate($this->getUserId()); + $userId = $this->getUserId(); + $this->onetouch + ->expects($this->once()) + ->method('request') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ); + $result = $this->model->sendToken( + 'adminUser', + Bootstrap::ADMIN_PASSWORD, + 'onetouch' + ); + + self::assertTrue($result); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testCreateTokenWithOneTouch() + { + $this->tfa->getProviderByCode(Authy::CODE) + ->activate($this->getUserId()); + $userId = $this->getUserId(); + $this->onetouch + ->method('verify') + ->with( + $this->callback(function ($value) use ($userId) { + return (int)$value->getId() === $userId; + }) + ) + ->willReturn('approved'); + $result = $this->model->creatAdminAccessTokenWithOneTouch( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + + self::assertRegExp('/^[a-z0-9]{32}$/', $result); + } + + /** + * @magentoConfigFixture default/twofactorauth/general/force_providers authy + * @magentoConfigFixture default/twofactorauth/authy/api_key abc + * @magentoDataFixture Magento/User/_files/user_with_role.php + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Onetouch prompt was denied or timed out. + */ + public function testCreateTokenWithOneTouchError() + { + $this->tfa->getProviderByCode(Authy::CODE) + ->activate($this->getUserId()); + $this->onetouch + ->method('verify') + ->willReturn('denied'); + $this->model->creatAdminAccessTokenWithOneTouch( + 'adminUser', + Bootstrap::ADMIN_PASSWORD + ); + } + private function getUserId(): int { $user = $this->userFactory->create(); diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php index 05929238..419dc499 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php @@ -81,7 +81,7 @@ protected function setUp() * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage Invalid tfa token + * @expectedExceptionMessage Invalid two-factor authorization token */ public function testConfigureInvalidTfat() { @@ -106,7 +106,7 @@ public function testConfigureInvalidTfat() * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is already configured. */ public function testConfigureAlreadyConfiguredProvider() @@ -134,7 +134,7 @@ public function testConfigureAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not allowed. */ public function testConfigureUnavailableProvider() @@ -207,7 +207,7 @@ function ($userId, $country, $phone, $method, &$response) { * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage Invalid tfa token + * @expectedExceptionMessage Invalid two-factor authorization token */ public function testActivateInvalidTfat() { @@ -228,7 +228,7 @@ public function testActivateInvalidTfat() * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is already configured. */ public function testActivateAlreadyConfiguredProvider() @@ -251,7 +251,7 @@ public function testActivateAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not allowed. */ public function testActivateUnavailableProvider() @@ -293,12 +293,12 @@ public function testActivateValidRequest() return (int)$value->getId() === $userId; }) ); - $result = $this->model->activate( + $this->model->activate( $this->tokenManager->issueFor($userId), 'cba' ); - self::assertTrue($result); + // Mock enroll call above is assertion of activation } private function getUserId(): int diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php index 0d9db8fb..292f191e 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php @@ -90,7 +90,7 @@ public function testGetAuthenticateDataInvalidCredentials() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not configured. */ public function testGetAuthenticateDataNotConfiguredProvider() @@ -111,7 +111,7 @@ public function testGetAuthenticateDataNotConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not allowed. */ public function testGetAuthenticateDataUnavailableProvider() @@ -139,7 +139,7 @@ public function testVerifyInvalidCredentials() $this->duo ->expects($this->never()) ->method('getRequestSignature'); - $this->model->verify( + $this->model->createAdminAccessTokenWithCredentials( 'adminUser', 'abc', 'signature' @@ -152,7 +152,7 @@ public function testVerifyInvalidCredentials() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not configured. */ public function testVerifyNotConfiguredProvider() @@ -164,7 +164,7 @@ public function testVerifyNotConfiguredProvider() $this->duo ->expects($this->never()) ->method('getRequestSignature'); - $this->model->verify( + $this->model->createAdminAccessTokenWithCredentials( 'adminUser', Bootstrap::ADMIN_PASSWORD, 'signature' @@ -174,7 +174,7 @@ public function testVerifyNotConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not allowed. */ public function testVerifyUnavailableProvider() @@ -182,7 +182,7 @@ public function testVerifyUnavailableProvider() $this->duo ->expects($this->never()) ->method('getRequestSignature'); - $this->model->verify( + $this->model->createAdminAccessTokenWithCredentials( 'adminUser', Bootstrap::ADMIN_PASSWORD, 'signature' @@ -250,7 +250,7 @@ public function testVerifyValidRequest() ) ->willReturn(true); - $token = $this->model->verify( + $token = $this->model->createAdminAccessTokenWithCredentials( 'adminUser', Bootstrap::ADMIN_PASSWORD, $signature @@ -265,7 +265,7 @@ public function testVerifyValidRequest() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Invalid response */ public function testVerifyInvalidRequest() @@ -278,7 +278,7 @@ public function testVerifyInvalidRequest() $this->duo->method('verify') ->willReturn(false); - $token = $this->model->verify( + $token = $this->model->createAdminAccessTokenWithCredentials( 'adminUser', Bootstrap::ADMIN_PASSWORD, $signature diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php index f8662244..235ac098 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php @@ -76,7 +76,7 @@ protected function setUp() * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage Invalid tfa token + * @expectedExceptionMessage Invalid two-factor authorization token */ public function testGetConfigurationDataInvalidTfat() { @@ -94,7 +94,7 @@ public function testGetConfigurationDataInvalidTfat() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is already configured. */ public function testGetConfigurationDataAlreadyConfiguredProvider() @@ -114,7 +114,7 @@ public function testGetConfigurationDataAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not allowed. */ public function testGetConfigurationDataUnavailableProvider() @@ -134,7 +134,7 @@ public function testGetConfigurationDataUnavailableProvider() * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage Invalid tfa token + * @expectedExceptionMessage Invalid two-factor authorization token */ public function testActivateInvalidTfat() { @@ -153,7 +153,7 @@ public function testActivateInvalidTfat() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is already configured. */ public function testActivateAlreadyConfiguredProvider() @@ -173,7 +173,7 @@ public function testActivateAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not allowed. */ public function testActivateUnavailableProvider() @@ -241,9 +241,9 @@ public function testActivateValidRequest() $signature ); - $result = $this->model->activate($tfat, $signature); + $this->model->activate($tfat, $signature); - self::assertTrue($result); + self::assertTrue($this->tfa->getProviderByCode(DuoSecurity::CODE)->isActive($userId)); } /** diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php index a9df30fe..b76e994b 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php @@ -77,7 +77,7 @@ public function testGetDataInvalidCredentials() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not configured. */ public function testGetDataNotConfiguredProvider() @@ -94,7 +94,7 @@ public function testGetDataNotConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not allowed. */ public function testGetDataUnavailableProvider() @@ -123,7 +123,7 @@ public function testVerifyInvalidCredentials() $this->u2fkey->method('getAuthenticateData') ->willReturn(['credentialRequestOptions' => ['challenge' => [1, 2, 3]]]); $this->model->getAuthenticationData('adminUser', Bootstrap::ADMIN_PASSWORD); - $this->model->verify( + $this->model->createAdminAccessToken( 'adminUser', 'bad', 'I identify as JSON' @@ -133,7 +133,7 @@ public function testVerifyInvalidCredentials() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not configured. */ public function testVerifyNotConfiguredProvider() @@ -141,7 +141,7 @@ public function testVerifyNotConfiguredProvider() $this->u2fkey ->expects($this->never()) ->method('verify'); - $this->model->verify( + $this->model->createAdminAccessToken( 'adminUser', Bootstrap::ADMIN_PASSWORD, 'I identify as JSON' @@ -151,7 +151,7 @@ public function testVerifyNotConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not allowed. */ public function testVerifyUnavailableProvider() @@ -159,7 +159,7 @@ public function testVerifyUnavailableProvider() $this->u2fkey ->expects($this->never()) ->method('verify'); - $this->model->verify( + $this->model->createAdminAccessToken( 'adminUser', Bootstrap::ADMIN_PASSWORD, 'I identify as JSON' @@ -232,7 +232,7 @@ public function testVerifyValidRequest() }) ); - $token = $this->model->verify( + $token = $this->model->createAdminAccessToken( 'adminUser', Bootstrap::ADMIN_PASSWORD, json_encode($verifyData) @@ -261,7 +261,7 @@ public function testVerifyThrowsExceptionRequest() ->method('verify') ->willThrowException(new \InvalidArgumentException('Something')); - $result = $this->model->verify( + $result = $this->model->createAdminAccessToken( 'adminUser', Bootstrap::ADMIN_PASSWORD, json_encode(['foo' => 'bar']) diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php index 0dff8b7f..9af25cf7 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php @@ -66,7 +66,7 @@ protected function setUp() * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage Invalid tfa token + * @expectedExceptionMessage Invalid two-factor authorization token */ public function testGetRegistrationDataInvalidTfat() { @@ -82,7 +82,7 @@ public function testGetRegistrationDataInvalidTfat() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is already configured. */ public function testGetRegistrationDataAlreadyConfiguredProvider() @@ -101,7 +101,7 @@ public function testGetRegistrationDataAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not allowed. */ public function testGetRegistrationDataUnavailableProvider() @@ -119,7 +119,7 @@ public function testGetRegistrationDataUnavailableProvider() * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage Invalid tfa token + * @expectedExceptionMessage Invalid two-factor authorization token */ public function testActivateInvalidTfat() { @@ -136,7 +136,7 @@ public function testActivateInvalidTfat() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is already configured. */ public function testActivateAlreadyConfiguredProvider() @@ -156,7 +156,7 @@ public function testActivateAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Webapi\Exception + * @expectedException \Magento\Framework\Exception\LocalizedException * @expectedExceptionMessage Provider is not allowed. */ public function testActivateUnavailableProvider() @@ -225,9 +225,9 @@ public function testActivateValidRequest() ] ); - $result = $this->model->activate($tfat, json_encode($activateData)); + $this->model->activate($tfat, json_encode($activateData)); - self::assertTrue($result); + // Mock registerDevice call above is proof of activation } /** diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/ConfigReaderTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/ConfigReaderTest.php index 601dc9be..29199464 100644 --- a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/ConfigReaderTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/ConfigReaderTest.php @@ -6,10 +6,65 @@ declare(strict_types=1); -/** - * - */ -class ConfigReaderTest +namespace Magento\TwoFactorAuth\Test\Unit\Model\Provider\Engine\U2fKey; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey\ConfigReader; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ConfigReaderTest extends TestCase { + /** + * @var StoreManagerInterface|MockObject + */ + private $storeManager; + + /** + * @var ConfigReader + */ + private $reader; + + protected function setUp() + { + $objectManager = new ObjectManager($this); + $this->storeManager = $this->createMock(StoreManagerInterface::class); + $this->reader = $objectManager->getObject( + ConfigReader::class, + [ + 'storeManager' => $this->storeManager + ] + ); + } + + public function testGetValidDomain() + { + $store = $this->createMock(Store::class); + $store->method('getBaseUrl') + ->willReturn('https://domain.com/'); + $this->storeManager + ->method('getStore') + ->with(Store::ADMIN_CODE) + ->willReturn($store); + $result = $this->reader->getDomain(); + self::assertSame('domain.com', $result); + } + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Could not determine domain name. + */ + public function testGetInvalidDomain() + { + $store = $this->createMock(Store::class); + $store->method('getBaseUrl') + ->willReturn('foo'); + $this->storeManager + ->method('getStore') + ->with(Store::ADMIN_CODE) + ->willReturn($store); + $this->reader->getDomain(); + } } diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/WebApiConfigReaderTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/WebApiConfigReaderTest.php index 78a5b710..cc880e50 100644 --- a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/WebApiConfigReaderTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/WebApiConfigReaderTest.php @@ -8,70 +8,69 @@ namespace Magento\TwoFactorAuth\Test\Unit\Model\Provider\Engine\U2fKey; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Model\Store; -use Magento\Store\Model\StoreManagerInterface; +use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey\ConfigReader; +use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey\WebApiConfigReader; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -class ConfigReaderTest extends TestCase +class WebApiConfigReaderTest extends TestCase { /** - * @var StoreManagerInterface|MockObject + * @var ScopeConfigInterface|MockObject */ - private $storeManager; + private $scopeConfig; /** - * @var ConfigReader + * @var ConfigReader|MockObject + */ + private $defaultConfigReader; + + /** + * @var WebApiConfigReader */ private $reader; protected function setUp() { $objectManager = new ObjectManager($this); - $this->storeManager = $this->createMock(StoreManagerInterface::class); + $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); + $this->defaultConfigReader = $this->createMock(ConfigReader::class); $this->reader = $objectManager->getObject( - ConfigReader::class, + WebApiConfigReader::class, [ - 'storeManager' => $this->storeManager + 'scopeConfig' => $this->scopeConfig, + 'configReader' => $this->defaultConfigReader ] ); } - public function testGetValidDomain() + public function testDomainFromConfig() { - $store = $this->createMock(Store::class); - $store->method('getBaseUrl') - ->willReturn('https://domain.com/'); - $this->storeManager - ->method('getStore') - ->willReturn($store); + $this->defaultConfigReader + ->expects($this->never()) + ->method('getDomain'); + $this->scopeConfig + ->method('getValue') + ->with(U2fKey::XML_PATH_WEBAPI_DOMAIN) + ->willReturn('foo'); $result = $this->reader->getDomain(); - self::assertSame('domain.com', $result); + self::assertSame('foo', $result); } - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Could not determine secure domain name. - * @dataProvider invalidDomainProviders - */ - public function testGetInvalidDomain($domain) + public function testDomainFromDefault() { - $store = $this->createMock(Store::class); - $store->method('getBaseUrl') - ->willReturn($domain); - $this->storeManager - ->method('getStore') - ->willReturn($store); - $this->reader->getDomain(); - } - - public function invalidDomainProviders() - { - return [ - ['foo'], - ['http://domain.com/'], - ]; + $this->defaultConfigReader + ->method('getDomain') + ->willReturn('foo'); + $this->scopeConfig + ->method('getValue') + ->with(U2fKey::XML_PATH_WEBAPI_DOMAIN) + ->willReturn(null); + $result = $this->reader->getDomain(); + self::assertSame('foo', $result); } } diff --git a/TwoFactorAuth/composer.json b/TwoFactorAuth/composer.json index b6210711..1e9978fc 100644 --- a/TwoFactorAuth/composer.json +++ b/TwoFactorAuth/composer.json @@ -10,12 +10,13 @@ "magento/module-store": "*", "magento/module-ui": "*", "magento/module-user": "*", + "magento/module-integration": "*", "christian-riesen/base32": "^1.3", "spomky-labs/otphp": "~8.3", "endroid/qr-code": "^3.7", "donatj/phpuseragentparser": "~0.7", "2tvenom/cborencode": "^1.0", - "phpseclib/phpseclib": "~2.0" + "phpseclib/phpseclib": "2.0.*" }, "authors": [ { diff --git a/TwoFactorAuth/etc/adminhtml/di.xml b/TwoFactorAuth/etc/adminhtml/di.xml index 598d7b0d..bcb1a8dc 100644 --- a/TwoFactorAuth/etc/adminhtml/di.xml +++ b/TwoFactorAuth/etc/adminhtml/di.xml @@ -21,8 +21,4 @@ - - - - diff --git a/TwoFactorAuth/etc/di.xml b/TwoFactorAuth/etc/di.xml index 77b5c14b..cde97a3b 100644 --- a/TwoFactorAuth/etc/di.xml +++ b/TwoFactorAuth/etc/di.xml @@ -39,6 +39,7 @@ + diff --git a/TwoFactorAuth/etc/webapi.xml b/TwoFactorAuth/etc/webapi.xml index 4c5c081b..2c7cd443 100644 --- a/TwoFactorAuth/etc/webapi.xml +++ b/TwoFactorAuth/etc/webapi.xml @@ -64,14 +64,14 @@ - + - + @@ -86,7 +86,7 @@ - + @@ -114,7 +114,7 @@ - + @@ -128,7 +128,7 @@ - + @@ -156,7 +156,7 @@ - + @@ -184,7 +184,7 @@ - + diff --git a/TwoFactorAuth/etc/webapi_rest/di.xml b/TwoFactorAuth/etc/webapi_rest/di.xml index 65653904..45dde043 100644 --- a/TwoFactorAuth/etc/webapi_rest/di.xml +++ b/TwoFactorAuth/etc/webapi_rest/di.xml @@ -7,5 +7,5 @@ --> - + diff --git a/TwoFactorAuth/view/adminhtml/layout/tfa_screen.xml b/TwoFactorAuth/view/adminhtml/layout/tfa_screen.xml index 5d840c92..419048cc 100644 --- a/TwoFactorAuth/view/adminhtml/layout/tfa_screen.xml +++ b/TwoFactorAuth/view/adminhtml/layout/tfa_screen.xml @@ -14,9 +14,7 @@ - + Logout adminhtml/auth/logout diff --git a/TwoFactorAuth/view/adminhtml/web/css/tfa-screen.css b/TwoFactorAuth/view/adminhtml/web/css/tfa-screen.css index c7a4aaff..f2372469 100644 --- a/TwoFactorAuth/view/adminhtml/web/css/tfa-screen.css +++ b/TwoFactorAuth/view/adminhtml/web/css/tfa-screen.css @@ -3,6 +3,9 @@ * See COPYING.txt for license details. */ +.login-content > li { + list-style: none; +} .tfa-logout-link { position: absolute; top: 1em; diff --git a/TwoFactorAuth/view/adminhtml/web/template/google/configure.html b/TwoFactorAuth/view/adminhtml/web/template/google/configure.html index 759e26be..1f1d485c 100644 --- a/TwoFactorAuth/view/adminhtml/web/template/google/configure.html +++ b/TwoFactorAuth/view/adminhtml/web/template/google/configure.html @@ -12,7 +12,7 @@
- +

From e168a5e3455c83579ac43b0426546cb65691efd1 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 10 Apr 2020 17:08:52 -0500 Subject: [PATCH 16/58] MC-30536: Enable 2FA for web API - Fixed api-functional test SOAP compatibility - Refactored user notifier --- .../Api/U2fKeyAuthenticateInterface.php | 2 +- .../Api/U2fKeyConfigureInterface.php | 2 +- TwoFactorAuth/Model/Config/UserNotifier.php | 73 +++++++++++++++++++ .../Model/Config/WebApiUserNotifier.php | 49 +++++++++++++ TwoFactorAuth/Model/EmailUserNotifier.php | 36 ++++++--- TwoFactorAuth/Model/UserAuthenticator.php | 8 +- .../Test/Api/AdminIntegrationTokenTest.php | 44 ++++++++--- TwoFactorAuth/Test/Api/GoogleActivateTest.php | 28 +++++-- .../Test/Api/GoogleAuthenticateTest.php | 34 ++++++--- .../Test/Api/GoogleConfigureTest.php | 26 +++++-- TwoFactorAuth/etc/webapi_rest/di.xml | 6 ++ TwoFactorAuth/etc/webapi_soap/di.xml | 6 ++ 12 files changed, 259 insertions(+), 55 deletions(-) create mode 100644 TwoFactorAuth/Model/Config/UserNotifier.php create mode 100644 TwoFactorAuth/Model/Config/WebApiUserNotifier.php diff --git a/TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php b/TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php index 65708a4b..fe88da3b 100644 --- a/TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php +++ b/TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php @@ -20,7 +20,7 @@ interface U2fKeyAuthenticateInterface * * @param string $username * @param string $password - * @return U2FWebAuthnRequestInterface + * @return \Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface */ public function getAuthenticationData(string $username, string $password): U2FWebAuthnRequestInterface; diff --git a/TwoFactorAuth/Api/U2fKeyConfigureInterface.php b/TwoFactorAuth/Api/U2fKeyConfigureInterface.php index d4cb4c14..392181e5 100644 --- a/TwoFactorAuth/Api/U2fKeyConfigureInterface.php +++ b/TwoFactorAuth/Api/U2fKeyConfigureInterface.php @@ -19,7 +19,7 @@ interface U2fKeyConfigureInterface * Get the information to initiate a WebAuthn registration ceremony * * @param string $tfaToken - * @return U2FWebAuthnRequestInterface + * @return \Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface */ public function getRegistrationData(string $tfaToken): U2FWebAuthnRequestInterface; diff --git a/TwoFactorAuth/Model/Config/UserNotifier.php b/TwoFactorAuth/Model/Config/UserNotifier.php new file mode 100644 index 00000000..4a2487f5 --- /dev/null +++ b/TwoFactorAuth/Model/Config/UserNotifier.php @@ -0,0 +1,73 @@ +scopeConfig = $scopeConfig; + $this->url = $url; + } + + /** + * Get the url to send to the user for configuring personal 2fa settings + * + * @param string $tfaToken + * @return string|null + */ + public function getPersonalRequestConfigUrl(string $tfaToken): ?string + { + return $this->getRequestConfigUrl($tfaToken); + } + + /** + * Get the url to send to the user for configuring global 2fa settings + * + * @param string $tfaToken + * @return string|null + */ + public function getAppRequestConfigUrl(string $tfaToken): ?string + { + return $this->getRequestConfigUrl($tfaToken); + } + + /** + * Get the default config url + * + * @param string $tfaToken + * @return string + */ + private function getRequestConfigUrl(string $tfaToken) + { + return $this->url->getUrl('tfa/tfa/index', ['tfat' => $tfaToken]); + } +} diff --git a/TwoFactorAuth/Model/Config/WebApiUserNotifier.php b/TwoFactorAuth/Model/Config/WebApiUserNotifier.php new file mode 100644 index 00000000..c64acfb3 --- /dev/null +++ b/TwoFactorAuth/Model/Config/WebApiUserNotifier.php @@ -0,0 +1,49 @@ +scopeConfig = $scopeConfig; + } + + /** + * Get the url to send to the user for configuring personal 2fa settings + * + * @param string $tfaToken + * @return string|null + */ + public function getPersonalRequestConfigUrl(string $tfaToken): ?string + { + $userUrl = $this->scopeConfig->getValue(UserNotifierInterface::XML_PATH_WEBAPI_NOTIFICATION_URL); + + if ($userUrl) { + return str_replace(':tfat', $tfaToken, $userUrl); + } + + return parent::getPersonalRequestConfigUrl($tfaToken); + } +} diff --git a/TwoFactorAuth/Model/EmailUserNotifier.php b/TwoFactorAuth/Model/EmailUserNotifier.php index bd7cfefd..8c2d7917 100644 --- a/TwoFactorAuth/Model/EmailUserNotifier.php +++ b/TwoFactorAuth/Model/EmailUserNotifier.php @@ -13,6 +13,7 @@ use Magento\Framework\App\State; use Magento\Framework\UrlInterface; use Magento\Store\Model\StoreManagerInterface; +use Magento\TwoFactorAuth\Model\Config\UserNotifier as UserNotifierConfig; use Magento\User\Model\User; use Magento\TwoFactorAuth\Api\UserNotifierInterface; use Magento\Framework\Mail\Template\TransportBuilder; @@ -54,6 +55,11 @@ class EmailUserNotifier implements UserNotifierInterface */ private $appState; + /** + * @var UserNotifierConfig + */ + private $userNotifierConfig; + /** * @param ScopeConfigInterface $scopeConfig * @param TransportBuilder $transportBuilder @@ -61,6 +67,7 @@ class EmailUserNotifier implements UserNotifierInterface * @param LoggerInterface $logger * @param UrlInterface $url * @param State $appState + * @param UserNotifierConfig $userNotifierConfig */ public function __construct( ScopeConfigInterface $scopeConfig, @@ -68,7 +75,8 @@ public function __construct( StoreManagerInterface $storeManager, LoggerInterface $logger, UrlInterface $url, - State $appState + State $appState, + UserNotifierConfig $userNotifierConfig ) { $this->scopeConfig = $scopeConfig; $this->transportBuilder = $transportBuilder; @@ -76,6 +84,7 @@ public function __construct( $this->logger = $logger; $this->url = $url; $this->appState = $appState; + $this->userNotifierConfig = $userNotifierConfig; } /** @@ -84,23 +93,16 @@ public function __construct( * @param User $user * @param string $token * @param string $emailTemplateId - * @param bool $useWebApiUrl + * @param string $url * @return void */ private function sendConfigRequired( User $user, string $token, string $emailTemplateId, - bool $useWebApiUrl = false + string $url ): void { try { - $userUrl = $this->scopeConfig->getValue(UserNotifierInterface::XML_PATH_WEBAPI_NOTIFICATION_URL); - if ($useWebApiUrl && $userUrl) { - $url = str_replace(':tfat', $token, $userUrl); - } else { - $url = $this->url->getUrl('tfa/tfa/index', ['tfat' => $token]); - } - $transport = $this->transportBuilder ->setTemplateIdentifier($emailTemplateId) ->setTemplateOptions([ @@ -132,7 +134,12 @@ private function sendConfigRequired( */ public function sendUserConfigRequestMessage(User $user, string $token): void { - $this->sendConfigRequired($user, $token, 'tfa_admin_user_config_required', $this->isWebapi()); + $this->sendConfigRequired( + $user, + $token, + 'tfa_admin_user_config_required', + $this->userNotifierConfig->getPersonalRequestConfigUrl($token) + ); } /** @@ -140,7 +147,12 @@ public function sendUserConfigRequestMessage(User $user, string $token): void */ public function sendAppConfigRequestMessage(User $user, string $token): void { - $this->sendConfigRequired($user, $token, 'tfa_admin_app_config_required', false); + $this->sendConfigRequired( + $user, + $token, + 'tfa_admin_app_config_required', + $this->userNotifierConfig->getAppRequestConfigUrl($token) + ); } /** diff --git a/TwoFactorAuth/Model/UserAuthenticator.php b/TwoFactorAuth/Model/UserAuthenticator.php index d10e7b60..73b1d89c 100644 --- a/TwoFactorAuth/Model/UserAuthenticator.php +++ b/TwoFactorAuth/Model/UserAuthenticator.php @@ -8,11 +8,9 @@ namespace Magento\TwoFactorAuth\Model; -use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Exception\AuthorizationException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Serialize\Serializer\Json; -use Magento\Framework\Webapi\Exception as WebApiException; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\User\Model\ResourceModel\User as UserResource; @@ -77,7 +75,7 @@ public function __construct( * @param string $providerCode * @return User * @throws AuthorizationException - * @throws WebApiException + * @throws LocalizedException */ public function authenticateWithTokenAndProvider(string $tfaToken, string $providerCode): User { @@ -90,9 +88,9 @@ public function authenticateWithTokenAndProvider(string $tfaToken, string $provi } if (!$this->tfa->getProviderIsAllowed($userId, $providerCode)) { - throw new WebApiException(__('Provider is not allowed.')); + throw new LocalizedException(__('Provider is not allowed.')); } elseif ($this->tfa->getProviderByCode($providerCode)->isActive($userId)) { - throw new WebApiException(__('Provider is already configured.')); + throw new LocalizedException(__('Provider is already configured.')); } elseif (!$this->tokenManager->isValidFor($userId, $tfaToken)) { throw new AuthorizationException( __('Invalid two-factor authorization token') diff --git a/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php index d28f9e9d..cc20f380 100644 --- a/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php +++ b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php @@ -18,8 +18,8 @@ class AdminIntegrationTokenTest extends WebapiAbstract { const SERVICE_VERSION = 'V1'; - const SERVICE_NAME = 'integrationAdminTokenServiceV1'; - const OPERATION = 'CreateAdminAccessTokenRequest'; + const SERVICE_NAME = 'twoFactorAuthAdminTokenServiceV1'; + const OPERATION = 'CreateAdminAccessToken'; const RESOURCE_PATH = '/V1/integration/admin/token'; /** @@ -66,11 +66,15 @@ public function testDefaultBehaviorForInvalidCredentials() self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); + if (json_last_error()) { + $message = $exception->getMessage(); + } else { + $message = $response['message']; + } self::assertSame( 'The account sign-in was incorrect or your account is disabled temporarily. ' . 'Please wait and try again later.', - $response['message'] + $message ); } } @@ -92,12 +96,18 @@ public function testUserWithConfigured2fa() ); } catch (\Exception $e) { $response = json_decode($e->getMessage(), true); + if (json_last_error()) { + $message = $e->getMessage(); + } else { + $message = $response['message']; + self::assertCount(1, $response['parameters']['active_providers']); + self::assertSame('google', $response['parameters']['active_providers'][0]); + } + self::assertSame( 'Please use the 2fa provider-specific endpoints to obtain a token.', - $response['message'] + $message ); - self::assertCount(1, $response['parameters']['active_providers']); - self::assertSame('google', $response['parameters']['active_providers'][0]); } } @@ -118,13 +128,19 @@ public function testUserWithAvailableUnconfigured2fa() ); } catch (\Exception $e) { $response = json_decode($e->getMessage(), true); + if (json_last_error()) { + $message = $e->getMessage(); + } else { + $message = $response['message']; + self::assertCount(1, $response['parameters']['active_providers']); + self::assertSame('google', $response['parameters']['active_providers'][0]); + } + self::assertSame( 'You are required to configure personal Two-Factor Authorization in order to login. ' . 'Please check your email.', - $response['message'] + $message ); - self::assertCount(1, $response['parameters']['active_providers']); - self::assertSame('google', $response['parameters']['active_providers'][0]); } } @@ -147,10 +163,14 @@ public function testNoAvailable2faProviders() self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); + if (json_last_error()) { + $message = $exception->getMessage(); + } else { + $message = $response['message']; + } self::assertSame( 'Please ask an administrator with sufficient access to configure 2FA first', - $response['message'] + $message ); } } diff --git a/TwoFactorAuth/Test/Api/GoogleActivateTest.php b/TwoFactorAuth/Test/Api/GoogleActivateTest.php index 15e77264..7775bf00 100644 --- a/TwoFactorAuth/Test/Api/GoogleActivateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleActivateTest.php @@ -17,8 +17,8 @@ class GoogleActivateTest extends WebapiAbstract { const SERVICE_VERSION = 'V1'; - const SERVICE_NAME = 'twoFactorAuthGoogleActivateV1'; - const OPERATION = 'ActivateRequest'; + const SERVICE_NAME = 'twoFactorAuthGoogleConfigureV1'; + const OPERATION = 'Activate'; const RESOURCE_PATH = '/V1/tfa/provider/google/activate'; /** @@ -70,8 +70,12 @@ public function testInvalidTfat() self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); - self::assertSame('Invalid two-factor authorization token', $response['message']); + if (json_last_error()) { + $message = $exception->getMessage(); + } else { + $message = $response['message']; + } + self::assertSame('Invalid two-factor authorization token', $message); } } @@ -90,8 +94,12 @@ public function testUnavailableProvider() self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); - self::assertSame('Provider is not allowed.', $response['message']); + if (json_last_error()) { + $message = $exception->getMessage(); + } else { + $message = $response['message']; + } + self::assertSame('Provider is not allowed.', $message); } } @@ -113,8 +121,12 @@ public function testAlreadyActivatedProvider() self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); - self::assertSame('Provider is already configured.', $response['message']); + if (json_last_error()) { + $message = $exception->getMessage(); + } else { + $message = $response['message']; + } + self::assertSame('Provider is already configured.', $message); } } diff --git a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php index f770cf71..215e840b 100644 --- a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php @@ -18,7 +18,7 @@ class GoogleAuthenticateTest extends WebapiAbstract { const SERVICE_VERSION = 'V1'; const SERVICE_NAME = 'twoFactorAuthGoogleAuthenticateV1'; - const OPERATION = 'GetTokenRequest'; + const OPERATION = 'CreateAdminAccessToken'; const RESOURCE_PATH = '/V1/tfa/provider/google/authenticate'; /** @@ -69,11 +69,15 @@ public function testInvalidCredentials() self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); + if (json_last_error()) { + $message = $exception->getMessage(); + } else { + $message = $response['message']; + } self::assertSame( 'The account sign-in was incorrect or your account is disabled temporarily. ' . 'Please wait and try again later.', - $response['message'] + $message ); } } @@ -98,8 +102,12 @@ public function testUnavailableProvider() self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); - self::assertSame('Provider is not allowed.', $response['message']); + if (json_last_error()) { + $message = $exception->getMessage(); + } else { + $message = $response['message']; + } + self::assertSame('Provider is not allowed.', $message); } } @@ -126,8 +134,12 @@ public function testInvalidToken() self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); - self::assertSame('Invalid code.', $response['message']); + if (json_last_error()) { + $message = $exception->getMessage(); + } else { + $message = $response['message']; + } + self::assertSame('Invalid code.', $message); } } @@ -154,8 +166,12 @@ public function testNotConfiguredProvider() self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); - self::assertSame('Provider is not configured.', $response['message']); + if (json_last_error()) { + $message = $exception->getMessage(); + } else { + $message = $response['message']; + } + self::assertSame('Provider is not configured.', $message); } } diff --git a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php index 91dd8552..1826227e 100644 --- a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php +++ b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php @@ -18,7 +18,7 @@ class GoogleConfigureTest extends WebapiAbstract { const SERVICE_VERSION = 'V1'; const SERVICE_NAME = 'twoFactorAuthGoogleConfigureV1'; - const OPERATION = 'GetConfigurationDataRequest'; + const OPERATION = 'GetConfigurationData'; const RESOURCE_PATH = '/V1/tfa/provider/google/configure'; /** @@ -57,8 +57,12 @@ public function testInvalidTfat() self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); - self::assertSame('Invalid two-factor authorization token', $response['message']); + if (json_last_error()) { + $message = $exception->getMessage(); + } else { + $message = $response['message']; + } + self::assertSame('Invalid two-factor authorization token', $message); } } @@ -77,8 +81,12 @@ public function testUnavailableProvider() self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); - self::assertSame('Provider is not allowed.', $response['message']); + if (json_last_error()) { + $message = $exception->getMessage(); + } else { + $message = $response['message']; + } + self::assertSame('Provider is not allowed.', $message); } } @@ -99,8 +107,12 @@ public function testAlreadyConfiguredProvider() self::fail('Endpoint should have thrown an exception'); } catch (\Throwable $exception) { $response = json_decode($exception->getMessage(), true); - self::assertEmpty(json_last_error()); - self::assertSame('Provider is already configured.', $response['message']); + if (json_last_error()) { + $message = $exception->getMessage(); + } else { + $message = $response['message']; + } + self::assertSame('Provider is already configured.', $message); } } diff --git a/TwoFactorAuth/etc/webapi_rest/di.xml b/TwoFactorAuth/etc/webapi_rest/di.xml index 45dde043..1f87ae33 100644 --- a/TwoFactorAuth/etc/webapi_rest/di.xml +++ b/TwoFactorAuth/etc/webapi_rest/di.xml @@ -8,4 +8,10 @@ + + + + Magento\TwoFactorAuth\Model\Config\WebApiUserNotifier + + diff --git a/TwoFactorAuth/etc/webapi_soap/di.xml b/TwoFactorAuth/etc/webapi_soap/di.xml index 45dde043..1f87ae33 100644 --- a/TwoFactorAuth/etc/webapi_soap/di.xml +++ b/TwoFactorAuth/etc/webapi_soap/di.xml @@ -8,4 +8,10 @@ + + + + Magento\TwoFactorAuth\Model\Config\WebApiUserNotifier + + From 205430ce5d7f400abcc5a36c39afa3e1bc3a02d6 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Mon, 13 Apr 2020 11:01:02 -0500 Subject: [PATCH 17/58] MC-30536: Enable 2FA for web API - Changed signature --- TwoFactorAuth/Model/Config/UserNotifier.php | 8 ++++---- TwoFactorAuth/Model/Config/WebApiUserNotifier.php | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/TwoFactorAuth/Model/Config/UserNotifier.php b/TwoFactorAuth/Model/Config/UserNotifier.php index 4a2487f5..6564b1b9 100644 --- a/TwoFactorAuth/Model/Config/UserNotifier.php +++ b/TwoFactorAuth/Model/Config/UserNotifier.php @@ -42,9 +42,9 @@ public function __construct( * Get the url to send to the user for configuring personal 2fa settings * * @param string $tfaToken - * @return string|null + * @return string */ - public function getPersonalRequestConfigUrl(string $tfaToken): ?string + public function getPersonalRequestConfigUrl(string $tfaToken): string { return $this->getRequestConfigUrl($tfaToken); } @@ -53,9 +53,9 @@ public function getPersonalRequestConfigUrl(string $tfaToken): ?string * Get the url to send to the user for configuring global 2fa settings * * @param string $tfaToken - * @return string|null + * @return string */ - public function getAppRequestConfigUrl(string $tfaToken): ?string + public function getAppRequestConfigUrl(string $tfaToken): string { return $this->getRequestConfigUrl($tfaToken); } diff --git a/TwoFactorAuth/Model/Config/WebApiUserNotifier.php b/TwoFactorAuth/Model/Config/WebApiUserNotifier.php index c64acfb3..2713f064 100644 --- a/TwoFactorAuth/Model/Config/WebApiUserNotifier.php +++ b/TwoFactorAuth/Model/Config/WebApiUserNotifier.php @@ -9,6 +9,7 @@ namespace Magento\TwoFactorAuth\Model\Config; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\UrlInterface; use Magento\TwoFactorAuth\Api\UserNotifierInterface; /** @@ -22,11 +23,14 @@ class WebApiUserNotifier extends UserNotifier private $scopeConfig; /** + * @param UrlInterface $url * @param ScopeConfigInterface $scopeConfig */ public function __construct( + UrlInterface $url, ScopeConfigInterface $scopeConfig ) { + parent::__construct($url, $scopeConfig); $this->scopeConfig = $scopeConfig; } @@ -34,9 +38,9 @@ public function __construct( * Get the url to send to the user for configuring personal 2fa settings * * @param string $tfaToken - * @return string|null + * @return string */ - public function getPersonalRequestConfigUrl(string $tfaToken): ?string + public function getPersonalRequestConfigUrl(string $tfaToken): string { $userUrl = $this->scopeConfig->getValue(UserNotifierInterface::XML_PATH_WEBAPI_NOTIFICATION_URL); From 32aaa42ee6e6bf06a68ed2d7fb554c2adde8b6c1 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Mon, 13 Apr 2020 12:29:27 -0500 Subject: [PATCH 18/58] MC-30536: Enable 2FA for web API --- TwoFactorAuth/Api/AuthyAuthenticateInterface.php | 4 ++-- .../Model/Provider/Engine/Authy/Authenticate.php | 4 +--- .../Model/Provider/Engine/Authy/AuthenticateTest.php | 8 ++------ 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/TwoFactorAuth/Api/AuthyAuthenticateInterface.php b/TwoFactorAuth/Api/AuthyAuthenticateInterface.php index db6249d0..f56a1afc 100644 --- a/TwoFactorAuth/Api/AuthyAuthenticateInterface.php +++ b/TwoFactorAuth/Api/AuthyAuthenticateInterface.php @@ -33,13 +33,13 @@ public function createAdminAccessTokenWithCredentials( * @param string $username * @param string $password * @param string $via - * @return bool + * @return void */ public function sendToken( string $username, string $password, string $via - ): bool; + ): void; /** * Authenticate using the present one touch response and get an admin token diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php index 077e23a7..4c0c786e 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php @@ -127,7 +127,7 @@ public function createAdminAccessTokenWithCredentials(string $username, string $ /** * @inheritDoc */ - public function sendToken(string $username, string $password, string $via): bool + public function sendToken(string $username, string $password, string $via): void { $this->adminTokenService->createAdminAccessToken($username, $password); @@ -139,8 +139,6 @@ public function sendToken(string $username, string $password, string $via): bool } else { $this->authyToken->request($user, $via); } - - return true; } /** diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php index 68925432..ea7645cb 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php @@ -232,13 +232,11 @@ public function testSendTokenValidRequest() }), 'a method' ); - $result = $this->model->sendToken( + $this->model->sendToken( 'adminUser', Bootstrap::ADMIN_PASSWORD, 'a method' ); - - self::assertTrue($result); } /** @@ -259,13 +257,11 @@ public function testSendTokenValidRequestWithOneTouch() return (int)$value->getId() === $userId; }) ); - $result = $this->model->sendToken( + $this->model->sendToken( 'adminUser', Bootstrap::ADMIN_PASSWORD, 'onetouch' ); - - self::assertTrue($result); } /** From dbfc0318840c1633f6848bf7c704ee381fa60801 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Wed, 15 Apr 2020 11:08:33 -0500 Subject: [PATCH 19/58] MC-30537: Test automation with the new 2FA enabled by default --- TwoFactorAuth/Command/GoogleSecret.php | 98 ++++++++++++ .../Model/Provider/Engine/Google.php | 56 +++++-- .../ActionGroup/AdminLoginActionGroup.xml | 18 +++ .../Test/Mftf/Helper/GenerateOtp.php | 20 +++ .../Mftf/Section/AdminGoogle2faSection.xml | 17 +++ .../Unit/Model/Provider/Engine/GoogleTest.php | 140 +++++++++++++++++- TwoFactorAuth/etc/adminhtml/system.xml | 10 +- TwoFactorAuth/etc/config.xml | 3 + TwoFactorAuth/etc/di.xml | 2 + .../web/template/google/configure.html | 2 +- 10 files changed, 349 insertions(+), 17 deletions(-) create mode 100644 TwoFactorAuth/Command/GoogleSecret.php create mode 100644 TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml create mode 100644 TwoFactorAuth/Test/Mftf/Helper/GenerateOtp.php create mode 100644 TwoFactorAuth/Test/Mftf/Section/AdminGoogle2faSection.xml diff --git a/TwoFactorAuth/Command/GoogleSecret.php b/TwoFactorAuth/Command/GoogleSecret.php new file mode 100644 index 00000000..040fc911 --- /dev/null +++ b/TwoFactorAuth/Command/GoogleSecret.php @@ -0,0 +1,98 @@ +userConfigManager = $userConfigManager; + $this->userResource = $userResource; + $this->userFactory = $userFactory; + } + + /** + * @inheritDoc + */ + protected function configure() + { + $this->setName('security:tfa:google:set-secret'); + $this->setDescription('Set the secret used for Google OTP generation.'); + + $this->addArgument('user', InputArgument::REQUIRED, __('Username')); + $this->addArgument('secret', InputArgument::REQUIRED, __('Secret')); + + parent::configure(); + } + + /** + * Set the secret used for google otp generation + * + * @SuppressWarnings("PHPMD.UnusedFormalParameter") + * @param InputInterface $input + * @param OutputInterface $output + * @throws LocalizedException + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $userName = $input->getArgument('user'); + $secret = $input->getArgument('secret'); + + $user = $this->userFactory->create(); + + $this->userResource->load($user, $userName, 'username'); + if (!$user->getId()) { + throw new LocalizedException(__('Unknown user %1', $userName)); + } + + $this->userConfigManager->addProviderConfig( + (int)$user->getId(), + Google::CODE, + ['secret' => $secret] + ); + + $output->writeln((string)__('Google OTP secret has been set')); + } +} diff --git a/TwoFactorAuth/Model/Provider/Engine/Google.php b/TwoFactorAuth/Model/Provider/Engine/Google.php index 2713f2f1..784fa82d 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google.php @@ -12,6 +12,7 @@ use Endroid\QrCode\QrCode; use Endroid\QrCode\Writer\PngWriter; use Exception; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\DataObject; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Store\Model\StoreManagerInterface; @@ -19,13 +20,19 @@ use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Api\EngineInterface; use Base32\Base32; -use OTPHP\TOTP; +use OTPHP\TOTPInterfaceFactory; +use OTPHP\TOTPInterface; /** * Google authenticator engine */ class Google implements EngineInterface { + /** + * Config path for the OTP window + */ + const XML_PATH_OTP_WINDOW = 'twofactorauth/google/otp_window'; + /** * Engine code * @@ -33,11 +40,6 @@ class Google implements EngineInterface */ public const CODE = 'google'; - /** - * @var null - */ - private $totp = null; - /** * @var UserConfigManagerInterface */ @@ -48,22 +50,37 @@ class Google implements EngineInterface */ private $storeManager; + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var TOTPInterfaceFactory + */ + private $totpFactory; + /** * @param StoreManagerInterface $storeManager - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param ScopeConfigInterface $scopeConfig * @param UserConfigManagerInterface $configManager + * @param TOTPInterfaceFactory $totpFactory */ public function __construct( StoreManagerInterface $storeManager, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - UserConfigManagerInterface $configManager + ScopeConfigInterface $scopeConfig, + UserConfigManagerInterface $configManager, + TOTPInterfaceFactory $totpFactory ) { $this->configManager = $configManager; $this->storeManager = $storeManager; + $this->scopeConfig = $scopeConfig; + $this->totpFactory = $totpFactory; } /** * Generate random secret + * * @return string * @throws Exception */ @@ -75,11 +92,12 @@ private function generateSecret(): string /** * Get TOTP object + * * @param UserInterface $user - * @return TOTP + * @return TOTPInterface * @throws NoSuchEntityException */ - private function getTotp(UserInterface $user): TOTP + private function getTotp(UserInterface $user): TOTPInterface { $config = $this->configManager->getProviderConfig((int)$user->getId(), static::CODE); if (!isset($config['secret'])) { @@ -88,13 +106,19 @@ private function getTotp(UserInterface $user): TOTP if (!$config['secret']) { throw new NoSuchEntityException(__('Secret for user with ID#%1 was not found', $user->getId())); } - $totp = new TOTP($user->getEmail(), $config['secret']); + $totp = $this->totpFactory->create( + [ + 'label' => $user->getEmail(), + 'secret' => $config['secret'] + ] + ); return $totp; } /** * Get the secret code used for Google Authentication + * * @param UserInterface $user * @return string|null * @throws NoSuchEntityException @@ -114,6 +138,7 @@ public function getSecretCode(UserInterface $user): ?string /** * Get TFA provisioning URL + * * @param UserInterface $user * @return string * @throws NoSuchEntityException @@ -145,11 +170,16 @@ public function verify(UserInterface $user, DataObject $request): bool $totp = $this->getTotp($user); $config = $this->configManager->getProviderConfig((int)$user->getId(), static::CODE); - return $totp->verify($token, null, $config['window'] ?? null); + return $totp->verify( + $token, + null, + $config['window'] ?? (int)$this->scopeConfig->getValue(self::XML_PATH_OTP_WINDOW) ?: null + ); } /** * Render TFA QrCode + * * @param UserInterface $user * @return string * @throws NoSuchEntityException diff --git a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml new file mode 100644 index 00000000..4b95f313 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/TwoFactorAuth/Test/Mftf/Helper/GenerateOtp.php b/TwoFactorAuth/Test/Mftf/Helper/GenerateOtp.php new file mode 100644 index 00000000..cd7991bd --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Helper/GenerateOtp.php @@ -0,0 +1,20 @@ + + + + +
+ + + + +
+
diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php index 9f45e801..6b64e542 100644 --- a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php @@ -3,7 +3,14 @@ namespace Magento\TwoFactorAuth\Test\Unit\Model\Provider\Engine; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\DataObject; +use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; +use Magento\User\Api\Data\UserInterface; +use OTPHP\TOTPInterface; +use OTPHP\TOTPInterfaceFactory; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; @@ -14,6 +21,31 @@ class GoogleTest extends TestCase */ private $model; + /** + * @var UserInterface|MockObject + */ + private $user; + + /** + * @var UserConfigManagerInterface|MockObject + */ + private $configManager; + + /** + * @var TOTPInterfaceFactory|MockObject + */ + private $totpFactory; + + /** + * @var TOTPInterface|MockObject + */ + private $totp; + + /** + * @var ScopeConfigInterface|MockObject + */ + private $scopeConfig; + /** * @inheritDoc */ @@ -21,7 +53,23 @@ protected function setUp() { $objectManager = new ObjectManager($this); - $this->model = $objectManager->getObject(Google::class); + $this->totpFactory = $this->createMock(TOTPInterfaceFactory::class); + $this->totp = $this->createMock(TOTPInterface::class); + $this->user = $this->createMock(UserInterface::class); + $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); + $this->user->method('getId') + ->willReturn('5'); + $this->user->method('getEmail') + ->willReturn('john@example.com'); + $this->configManager = $this->createMock(UserConfigManagerInterface::class); + $this->model = $objectManager->getObject( + Google::class, + [ + 'configManager' => $this->configManager, + 'totpFactory' => $this->totpFactory, + 'scopeConfig' => $this->scopeConfig + ] + ); } /** @@ -29,7 +77,95 @@ protected function setUp() * * @return void */ - public function testIsEnabled(): void { + public function testIsEnabled(): void + { $this->assertTrue($this->model->isEnabled()); } + + public function testVerifyWithNoToken() + { + $valid = $this->model->verify($this->user, new DataObject(['tfa_code' => ''])); + + self::assertFalse($valid); + } + + public function testVerifyWithBadToken() + { + $this->configManager->method('getProviderConfig') + ->willReturn(['secret' => 'abc']); + $this->totpFactory->method('create') + ->willReturn($this->totp); + $this->totp->method('verify') + ->with('123456') + ->willReturn(false); + + $valid = $this->model->verify($this->user, new DataObject(['tfa_code' => '123456'])); + + self::assertFalse($valid); + } + + public function testVerifyWithGoodToken() + { + $this->configManager->method('getProviderConfig') + ->willReturn(['secret' => 'abc']); + $this->totpFactory->method('create') + ->with(['label' => 'john@example.com', 'secret' => 'abc']) + ->willReturn($this->totp); + $this->totp->method('verify') + ->with('123456') + ->willReturn(true); + + $valid = $this->model->verify($this->user, new DataObject(['tfa_code' => '123456'])); + + self::assertTrue($valid); + } + + public function testVerifyWithGoodTokenAndWindowFromUserConfig() + { + $this->configManager->method('getProviderConfig') + ->willReturn(['secret' => 'abc', 'window' => 800]); + $this->totpFactory->method('create') + ->willReturn($this->totp); + $this->totp->method('verify') + ->with('123456', null, 800) + ->willReturn(true); + + $valid = $this->model->verify($this->user, new DataObject(['tfa_code' => '123456'])); + + self::assertTrue($valid); + } + + public function testVerifyWithGoodTokenAndWindowFromScopeConfig() + { + $this->scopeConfig->method('getValue') + ->willReturn(800); + $this->configManager->method('getProviderConfig') + ->willReturn(['secret' => 'abc']); + $this->totpFactory->method('create') + ->willReturn($this->totp); + $this->totp->method('verify') + ->with('123456', null, 800) + ->willReturn(true); + + $valid = $this->model->verify($this->user, new DataObject(['tfa_code' => '123456'])); + + self::assertTrue($valid); + } + + public function testVerifyWindowFromUserConfigOverridesScopeConfig() + { + $this->scopeConfig->method('getValue') + ->willReturn(800); + $this->configManager->method('getProviderConfig') + ->willReturn(['secret' => 'abc', 'window' => 500]); + $this->totpFactory->method('create') + ->willReturn($this->totp); + $this->totp->method('verify') + ->with('123456', null, 500) + ->willReturn(true); + + $valid = $this->model->verify($this->user, new DataObject(['tfa_code' => '123456'])); + + self::assertTrue($valid); + } } diff --git a/TwoFactorAuth/etc/adminhtml/system.xml b/TwoFactorAuth/etc/adminhtml/system.xml index 51732aca..6144f83c 100644 --- a/TwoFactorAuth/etc/adminhtml/system.xml +++ b/TwoFactorAuth/etc/adminhtml/system.xml @@ -36,7 +36,15 @@ This can be used to override the default email configuration link that is sent when using the Magento Web API's to authenticate. Use the placeholder :tfat to indicate where the token should be injected - + + + + + This determines how long the one-time-passwords are valid for. + + diff --git a/TwoFactorAuth/etc/config.xml b/TwoFactorAuth/etc/config.xml index 9dba4e2b..13287ab7 100644 --- a/TwoFactorAuth/etc/config.xml +++ b/TwoFactorAuth/etc/config.xml @@ -20,6 +20,9 @@ + + 30 + diff --git a/TwoFactorAuth/etc/di.xml b/TwoFactorAuth/etc/di.xml index cde97a3b..b902d311 100644 --- a/TwoFactorAuth/etc/di.xml +++ b/TwoFactorAuth/etc/di.xml @@ -40,12 +40,14 @@ + Magento\TwoFactorAuth\Command\TfaReset Magento\TwoFactorAuth\Command\TfaProviders + Magento\TwoFactorAuth\Command\GoogleSecret diff --git a/TwoFactorAuth/view/adminhtml/web/template/google/configure.html b/TwoFactorAuth/view/adminhtml/web/template/google/configure.html index 1f1d485c..759e26be 100644 --- a/TwoFactorAuth/view/adminhtml/web/template/google/configure.html +++ b/TwoFactorAuth/view/adminhtml/web/template/google/configure.html @@ -12,7 +12,7 @@
- +

From 8cc17ad605eb9e3e05621dfbe38f8eb9bbc90806 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 17 Apr 2020 11:26:47 -0500 Subject: [PATCH 20/58] MC-30537: Test automation with the new 2FA enabled by default - fixed config backend model for providers to enable command line --- TwoFactorAuth/Model/Config/Backend/ForceProviders.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TwoFactorAuth/Model/Config/Backend/ForceProviders.php b/TwoFactorAuth/Model/Config/Backend/ForceProviders.php index 2009dae3..dc093b52 100644 --- a/TwoFactorAuth/Model/Config/Backend/ForceProviders.php +++ b/TwoFactorAuth/Model/Config/Backend/ForceProviders.php @@ -72,6 +72,9 @@ public function beforeSave() } $value = $this->getValue(); + if (is_string($value)) { + $value = explode(',', $value); + } $validValues = is_array($value) ? array_intersect($codes, $value) : []; if (empty($value) || !$validValues) { throw new ValidatorException(__('You have to select at least one Two-Factor Authorization provider')); From e675c2e3059aa0bb0742c988727df49cf576c803 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 24 Apr 2020 12:31:54 -0500 Subject: [PATCH 21/58] MC-30537: Test automation with the new 2FA enabled by default - Upgraded spomky-labs/otphp --- .../Model/Provider/Engine/Google.php | 13 ++++----- .../Provider/Engine/Google/TotpFactory.php | 29 +++++++++++++++++++ .../Test/Api/AdminIntegrationTokenTest.php | 3 ++ TwoFactorAuth/Test/Api/GoogleActivateTest.php | 2 +- .../Test/Api/GoogleAuthenticateTest.php | 2 +- .../Unit/Model/Provider/Engine/GoogleTest.php | 6 ++-- TwoFactorAuth/composer.json | 2 +- 7 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 TwoFactorAuth/Model/Provider/Engine/Google/TotpFactory.php diff --git a/TwoFactorAuth/Model/Provider/Engine/Google.php b/TwoFactorAuth/Model/Provider/Engine/Google.php index 784fa82d..0943fa78 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google.php @@ -16,11 +16,11 @@ use Magento\Framework\DataObject; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Store\Model\StoreManagerInterface; +use Magento\TwoFactorAuth\Model\Provider\Engine\Google\TotpFactory; use Magento\User\Api\Data\UserInterface; use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Api\EngineInterface; use Base32\Base32; -use OTPHP\TOTPInterfaceFactory; use OTPHP\TOTPInterface; /** @@ -70,7 +70,7 @@ public function __construct( StoreManagerInterface $storeManager, ScopeConfigInterface $scopeConfig, UserConfigManagerInterface $configManager, - TOTPInterfaceFactory $totpFactory + TotpFactory $totpFactory ) { $this->configManager = $configManager; $this->storeManager = $storeManager; @@ -106,12 +106,8 @@ private function getTotp(UserInterface $user): TOTPInterface if (!$config['secret']) { throw new NoSuchEntityException(__('Secret for user with ID#%1 was not found', $user->getId())); } - $totp = $this->totpFactory->create( - [ - 'label' => $user->getEmail(), - 'secret' => $config['secret'] - ] - ); + + $totp = $this->totpFactory->create($config['secret']); return $totp; } @@ -152,6 +148,7 @@ private function getProvisioningUrl(UserInterface $user): string // @codingStandardsIgnoreEnd $totp = $this->getTotp($user); + $totp->setLabel($user->getEmail()); $totp->setIssuer($issuer); return $totp->getProvisioningUri(); diff --git a/TwoFactorAuth/Model/Provider/Engine/Google/TotpFactory.php b/TwoFactorAuth/Model/Provider/Engine/Google/TotpFactory.php new file mode 100644 index 00000000..b0a72090 --- /dev/null +++ b/TwoFactorAuth/Model/Provider/Engine/Google/TotpFactory.php @@ -0,0 +1,29 @@ +userFactory->create(); $user->loadByUsername('customRoleUser'); - $totp = new TOTP($user->getEmail(), $this->google->getSecretCode($user)); + $totp = TOTP::create($this->google->getSecretCode($user)); // Enable longer window of valid tokens to prevent test race condition $this->userConfig->addProviderConfig((int)$user->getId(), Google::CODE, ['window' => 120]); diff --git a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php index 215e840b..3dd56cd3 100644 --- a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php @@ -233,7 +233,7 @@ private function getUserOtp(): string { $user = $this->userFactory->create(); $user->loadByUsername('customRoleUser'); - $totp = new TOTP($user->getEmail(), $this->google->getSecretCode($user)); + $totp = TOTP::create($this->google->getSecretCode($user)); // Enable longer window of valid tokens to prevent test race condition $this->userConfig->addProviderConfig((int)$user->getId(), Google::CODE, ['window' => 120]); diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php index 6b64e542..ebd65af7 100644 --- a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php @@ -7,9 +7,9 @@ use Magento\Framework\DataObject; use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; +use Magento\TwoFactorAuth\Model\Provider\Engine\Google\TotpFactory; use Magento\User\Api\Data\UserInterface; use OTPHP\TOTPInterface; -use OTPHP\TOTPInterfaceFactory; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; @@ -53,7 +53,7 @@ protected function setUp() { $objectManager = new ObjectManager($this); - $this->totpFactory = $this->createMock(TOTPInterfaceFactory::class); + $this->totpFactory = $this->createMock(TotpFactory::class); $this->totp = $this->createMock(TOTPInterface::class); $this->user = $this->createMock(UserInterface::class); $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); @@ -109,7 +109,7 @@ public function testVerifyWithGoodToken() $this->configManager->method('getProviderConfig') ->willReturn(['secret' => 'abc']); $this->totpFactory->method('create') - ->with(['label' => 'john@example.com', 'secret' => 'abc']) + ->with('abc') ->willReturn($this->totp); $this->totp->method('verify') ->with('123456') diff --git a/TwoFactorAuth/composer.json b/TwoFactorAuth/composer.json index 1e9978fc..a70d7243 100644 --- a/TwoFactorAuth/composer.json +++ b/TwoFactorAuth/composer.json @@ -12,7 +12,7 @@ "magento/module-user": "*", "magento/module-integration": "*", "christian-riesen/base32": "^1.3", - "spomky-labs/otphp": "~8.3", + "spomky-labs/otphp": "~9.1.4", "endroid/qr-code": "^3.7", "donatj/phpuseragentparser": "~0.7", "2tvenom/cborencode": "^1.0", From 1f32feda504125a19a539d53e6c3ae7d95ff59bc Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 24 Apr 2020 14:28:20 -0500 Subject: [PATCH 22/58] MC-30537: Test automation with the new 2FA enabled by default --- TwoFactorAuth/Command/GoogleSecret.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/TwoFactorAuth/Command/GoogleSecret.php b/TwoFactorAuth/Command/GoogleSecret.php index 040fc911..af56552d 100644 --- a/TwoFactorAuth/Command/GoogleSecret.php +++ b/TwoFactorAuth/Command/GoogleSecret.php @@ -90,7 +90,10 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->userConfigManager->addProviderConfig( (int)$user->getId(), Google::CODE, - ['secret' => $secret] + [ + 'secret' => $secret, + UserConfigManagerInterface::ACTIVE_CONFIG_KEY => true + ] ); $output->writeln((string)__('Google OTP secret has been set')); From 8371b1fdf28d16005aa5f6dd9cdfa22945c0b4c7 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Wed, 29 Apr 2020 10:18:59 -0500 Subject: [PATCH 23/58] MC-30537: Test automation with the new 2FA enabled by default --- .../ActionGroup/AdminLoginActionGroup.xml | 18 ----------------- .../Test/Mftf/Helper/GenerateOtp.php | 20 ------------------- .../Mftf/Section/AdminGoogle2faSection.xml | 17 ---------------- 3 files changed, 55 deletions(-) delete mode 100644 TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml delete mode 100644 TwoFactorAuth/Test/Mftf/Helper/GenerateOtp.php delete mode 100644 TwoFactorAuth/Test/Mftf/Section/AdminGoogle2faSection.xml diff --git a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml deleted file mode 100644 index 4b95f313..00000000 --- a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - diff --git a/TwoFactorAuth/Test/Mftf/Helper/GenerateOtp.php b/TwoFactorAuth/Test/Mftf/Helper/GenerateOtp.php deleted file mode 100644 index cd7991bd..00000000 --- a/TwoFactorAuth/Test/Mftf/Helper/GenerateOtp.php +++ /dev/null @@ -1,20 +0,0 @@ - - - - -
- - - - -
-
From c2e37cb13f8c195ac50d6b758accc445324f0a75 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 30 Apr 2020 08:48:30 -0500 Subject: [PATCH 24/58] MC-30537: Test automation with the new 2FA enabled by default --- .../Mftf/ActionGroup/AdminLoginActionGroup.xml | 15 +++++++++++++++ .../Test/Mftf/Section/AdminGoogleTfaSection.xml | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml create mode 100644 TwoFactorAuth/Test/Mftf/Section/AdminGoogleTfaSection.xml diff --git a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml new file mode 100644 index 00000000..99ccce2f --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/TwoFactorAuth/Test/Mftf/Section/AdminGoogleTfaSection.xml b/TwoFactorAuth/Test/Mftf/Section/AdminGoogleTfaSection.xml new file mode 100644 index 00000000..9297b203 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Section/AdminGoogleTfaSection.xml @@ -0,0 +1,15 @@ + + + + +
+ + +
+
From ecc19ce0a2337e4719056a78e541a4a734f1f71e Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 30 Apr 2020 09:33:36 -0500 Subject: [PATCH 25/58] MC-30537: Test automation with the new 2FA enabled by default - PHP 7.4 and PHPUnit 9 support --- .../Test/Api/AdminIntegrationTokenTest.php | 2 +- TwoFactorAuth/Test/Api/GoogleActivateTest.php | 2 +- .../Test/Api/GoogleAuthenticateTest.php | 4 +-- .../Test/Api/GoogleConfigureTest.php | 4 +-- .../Integration/Block/ChangeProviderTest.php | 4 +-- .../Integration/Block/ConfigureLaterTest.php | 4 +-- .../Controller/Adminhtml/Duo/AuthpostTest.php | 2 +- .../Adminhtml/Tfa/ConfigureLaterTest.php | 2 +- .../Adminhtml/Tfa/ConfigureTest.php | 4 +-- .../Adminhtml/Tfa/ConfigurepostTest.php | 4 +-- .../Controller/Adminhtml/Tfa/IndexTest.php | 2 +- .../Adminhtml/Tfa/RequestconfigTest.php | 9 +++--- .../ControllerActionPredispatchTest.php | 6 ++-- .../Engine/Authy/AuthenticateTest.php | 30 +++++++++---------- .../Provider/Engine/Authy/ConfigureTest.php | 27 ++++++++--------- .../Engine/DuoSecurity/AuthenticateTest.php | 28 ++++++++--------- .../Engine/DuoSecurity/ConfigureTest.php | 30 +++++++++---------- .../Engine/U2fKey/AuthenticateTest.php | 28 ++++++++--------- .../Provider/Engine/U2fKey/ConfigureTest.php | 30 +++++++++---------- .../Provider/Engine/U2fKey/SessionTest.php | 2 +- .../Test/Integration/Model/TfaSessionTest.php | 2 +- .../Integration/UserConfigManagerTest.php | 2 +- .../UserConfigRequestManagerTest.php | 8 ++--- .../UserConfigTokenManagerTest.php | 2 +- .../Config/Backend/Duo/ApiHostnameTest.php | 2 +- .../Config/Backend/ForceProvidersTest.php | 4 +-- .../Unit/Model/Provider/Engine/AuthyTest.php | 2 +- .../Model/Provider/Engine/DuoSecurityTest.php | 2 +- .../Unit/Model/Provider/Engine/GoogleTest.php | 2 +- .../Engine/U2fKey/ConfigReaderTest.php | 8 ++--- .../Engine/U2fKey/WebApiConfigReaderTest.php | 2 +- .../Unit/Model/Provider/Engine/U2fKeyTest.php | 2 +- TwoFactorAuth/Test/Unit/Model/TfaTest.php | 2 +- .../UserConfig/HtmlAreaTokenVerifierTest.php | 2 +- .../TestCase/AbstractBackendController.php | 2 +- .../AbstractConfigureBackendController.php | 2 +- 36 files changed, 133 insertions(+), 137 deletions(-) diff --git a/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php index 1c2c59fc..9678afbc 100644 --- a/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php +++ b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php @@ -42,7 +42,7 @@ class AdminIntegrationTokenTest extends WebapiAbstract */ private $config; - protected function setUp() + protected function setUp(): void { $objectManager = Bootstrap::getObjectManager(); $this->userFactory = $objectManager->get(UserFactory::class); diff --git a/TwoFactorAuth/Test/Api/GoogleActivateTest.php b/TwoFactorAuth/Test/Api/GoogleActivateTest.php index 76a6271e..35359324 100644 --- a/TwoFactorAuth/Test/Api/GoogleActivateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleActivateTest.php @@ -46,7 +46,7 @@ class GoogleActivateTest extends WebapiAbstract */ private $google; - protected function setUp() + protected function setUp(): void { $objectManager = Bootstrap::getObjectManager(); $this->userFactory = $objectManager->get(UserFactory::class); diff --git a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php index 3dd56cd3..3a3afe44 100644 --- a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php @@ -41,7 +41,7 @@ class GoogleAuthenticateTest extends WebapiAbstract */ private $tfa; - protected function setUp() + protected function setUp(): void { $objectManager = Bootstrap::getObjectManager(); $this->userFactory = $objectManager->get(UserFactory::class); @@ -196,7 +196,7 @@ public function testValidToken() ] ); self::assertNotEmpty($response); - self::assertRegExp('/^[a-z0-9]{32}$/', $response); + self::assertMatchesRegularExpression('/^[a-z0-9]{32}$/', $response); } /** diff --git a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php index 1826227e..5c445d6c 100644 --- a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php +++ b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php @@ -36,7 +36,7 @@ class GoogleConfigureTest extends WebapiAbstract */ private $tfa; - protected function setUp() + protected function setUp(): void { $objectManager = Bootstrap::getObjectManager(); $this->userFactory = $objectManager->get(UserFactory::class); @@ -128,7 +128,7 @@ public function testValidRequest() $response = $this->_webApiCall($serviceInfo, ['tfaToken' => $token]); self::assertNotEmpty($response[GoogleConfigureData::QR_CODE_BASE64]); - self::assertRegExp('/^[a-zA-Z0-9+\/=]+$/', $response[GoogleConfigureData::QR_CODE_BASE64]); + self::assertMatchesRegularExpression('/^[a-zA-Z0-9+\/=]+$/', $response[GoogleConfigureData::QR_CODE_BASE64]); self::assertNotEmpty($response['secret_code']); } diff --git a/TwoFactorAuth/Test/Integration/Block/ChangeProviderTest.php b/TwoFactorAuth/Test/Integration/Block/ChangeProviderTest.php index eb24c216..95507988 100644 --- a/TwoFactorAuth/Test/Integration/Block/ChangeProviderTest.php +++ b/TwoFactorAuth/Test/Integration/Block/ChangeProviderTest.php @@ -42,7 +42,7 @@ class ChangeProviderTest extends TestCase */ private $tfa; - protected function setUp() + protected function setUp(): void { $objectManager = Bootstrap::getObjectManager(); $objectManager->configure([ @@ -105,7 +105,7 @@ public function testBlockRendersWhenCurrentProviderIsActivated(): void $this->block->setData('provider', 'authy'); $html = $this->block->toHtml(); - self::assertContains('id="tfa', $html); + self::assertStringContainsString('id="tfa', $html); } /** diff --git a/TwoFactorAuth/Test/Integration/Block/ConfigureLaterTest.php b/TwoFactorAuth/Test/Integration/Block/ConfigureLaterTest.php index f76b25a7..be1ea6c8 100644 --- a/TwoFactorAuth/Test/Integration/Block/ConfigureLaterTest.php +++ b/TwoFactorAuth/Test/Integration/Block/ConfigureLaterTest.php @@ -41,7 +41,7 @@ class ConfigureLaterTest extends TestCase */ private $tfa; - protected function setUp() + protected function setUp(): void { $objectManager = Bootstrap::getObjectManager(); $objectManager->configure([ @@ -94,7 +94,7 @@ public function testBlockRendersWithCurrentInactiveAndOneOtherActive(): void $this->block->setData('provider', 'duo_security'); $html = $this->block->toHtml(); - self::assertContains('id="tfa', $html); + self::assertStringContainsString('id="tfa', $html); } /** diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Duo/AuthpostTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Duo/AuthpostTest.php index a6bcd0d5..7c2d0f77 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Duo/AuthpostTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Duo/AuthpostTest.php @@ -27,7 +27,7 @@ class AuthpostTest extends AbstractConfigureBackendController /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { parent::setUp(); $this->expectedNoAccessResponseCode = 302; diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/ConfigureLaterTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/ConfigureLaterTest.php index 220ec109..5add6179 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/ConfigureLaterTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/ConfigureLaterTest.php @@ -45,7 +45,7 @@ class ConfigureLaterTest extends AbstractBackendController */ private $tfaSession; - protected function setUp() + protected function setUp(): void { parent::setUp(); $this->tfa = $this->_objectManager->get(TfaInterface::class); diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/ConfigureTest.php index fe0a6cb4..ef2292d9 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/ConfigureTest.php @@ -39,7 +39,7 @@ class ConfigureTest extends AbstractBackendController /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -56,7 +56,7 @@ public function testList(): void $this->getRequest() ->setQueryValue('tfat', $this->tokenManager->issueFor((int)$this->_session->getUser()->getId())); $this->dispatch($this->uri); - $this->assertRegExp('/google/', $this->getResponse()->getBody()); + self::assertMatchesRegularExpression('/google/', $this->getResponse()->getBody()); } /** diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/ConfigurepostTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/ConfigurepostTest.php index dd0ac7a6..ab58d40c 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/ConfigurepostTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/ConfigurepostTest.php @@ -52,7 +52,7 @@ class ConfigurepostTest extends AbstractBackendController /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -130,7 +130,7 @@ public function testValidated(): void $this->dispatch($this->uri); $this->assertRedirect($this->stringContains('configure')); $this->assertEmpty($this->tfa->getForcedProviders()); - $this->assertSessionMessages($this->contains(__('Please select valid providers.')->render())); + $this->assertSessionMessages($this->containsEqual(__('Please select valid providers.')->render())); } /** diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/IndexTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/IndexTest.php index d0c05429..fc56e0a9 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/IndexTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/IndexTest.php @@ -43,7 +43,7 @@ class IndexTest extends AbstractBackendController /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/RequestconfigTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/RequestconfigTest.php index e6c66799..1dd22834 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/RequestconfigTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Tfa/RequestconfigTest.php @@ -36,7 +36,7 @@ class RequestconfigTest extends AbstractBackendController /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -52,7 +52,7 @@ protected function setUp() public function testAppConfigRequested(): void { $this->dispatch($this->uri); - $this->assertRegExp('/You need to configure Two\-Factor Authorization/', $this->getResponse()->getBody()); + self::assertMatchesRegularExpression('/You need to configure Two\-Factor Authorization/', $this->getResponse()->getBody()); } /** @@ -64,20 +64,19 @@ public function testAppConfigRequested(): void public function testUserConfigRequested(): void { $this->dispatch($this->uri); - $this->assertRegExp('/You need to configure Two\-Factor Authorization/', $this->getResponse()->getBody()); + self::assertMatchesRegularExpression('/You need to configure Two\-Factor Authorization/', $this->getResponse()->getBody()); } - /** * Verify that 2FA config is not requested when 2FA is configured. * * @return void * @magentoConfigFixture default/twofactorauth/general/force_providers google * @magentoDbIsolation enabled - * @expectedException \Magento\Framework\Exception\AuthorizationException */ public function testNotRequested(): void { + $this->expectException(\Magento\Framework\Exception\AuthorizationException::class); $this->tfa->getProvider(Google::CODE)->activate((int)$this->_session->getUser()->getId()); $this->dispatch($this->uri); } diff --git a/TwoFactorAuth/Test/Integration/ControllerActionPredispatchTest.php b/TwoFactorAuth/Test/Integration/ControllerActionPredispatchTest.php index 77489e9a..02c95e8d 100644 --- a/TwoFactorAuth/Test/Integration/ControllerActionPredispatchTest.php +++ b/TwoFactorAuth/Test/Integration/ControllerActionPredispatchTest.php @@ -42,7 +42,7 @@ class ControllerActionPredispatchTest extends AbstractBackendController /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -68,7 +68,7 @@ public function testTfaCompleted(): void //Accessing a page in adminhtml area $this->dispatch('backend/admin/user/'); //Authenticated user with 2FA configured and completed is taken to the Users page as requested. - $this->assertRegExp('/' .$this->_session->getUser()->getUserName() .'/i', $this->getResponse()->getBody()); + self::assertMatchesRegularExpression('/' .$this->_session->getUser()->getUserName() .'/i', $this->getResponse()->getBody()); } /** @@ -89,7 +89,7 @@ public function testUnauthenticated(): void $this->getRequest()->setDispatched(false); $this->getRequest()->setUri($properUrl); $this->dispatch($properUrl); - $this->assertContains('Welcome, please sign in', $this->getResponse()->getBody()); + $this->assertStringContainsString('Welcome, please sign in', $this->getResponse()->getBody()); } /** diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php index ea7645cb..a63a633f 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php @@ -51,7 +51,7 @@ class AuthenticateTest extends TestCase */ private $onetouch; - protected function setUp() + protected function setUp(): void { $objectManager = ObjectManager::getInstance(); $this->tfa = $objectManager->get(TfaInterface::class); @@ -73,10 +73,10 @@ protected function setUp() * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\AuthenticationException */ public function testAuthenticateInvalidCredentials() { + $this->expectException(\Magento\Framework\Exception\AuthenticationException::class); $this->tfa->getProviderByCode(Authy::CODE) ->activate($this->getUserId()); $this->authy @@ -93,11 +93,11 @@ public function testAuthenticateInvalidCredentials() * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not configured. */ public function testAuthenticateNotConfiguredProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not configured.'); $this->authy ->expects($this->never()) ->method('verify'); @@ -111,11 +111,11 @@ public function testAuthenticateNotConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not allowed. */ public function testAuthenticateUnavailableProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not allowed.'); $this->authy ->expects($this->never()) ->method('verify'); @@ -153,17 +153,17 @@ public function testAuthenticateValidRequest() 'abc' ); - self::assertRegExp('/^[a-z0-9]{32}$/', $result); + self::assertMatchesRegularExpression('/^[a-z0-9]{32}$/', $result); } /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\AuthenticationException */ public function testSendTokenInvalidCredentials() { + $this->expectException(\Magento\Framework\Exception\AuthenticationException::class); $this->tfa->getProviderByCode(Authy::CODE) ->activate($this->getUserId()); $this->authy @@ -180,11 +180,11 @@ public function testSendTokenInvalidCredentials() * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not configured. */ public function testSendTokenNotConfiguredProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not configured.'); $this->authy ->expects($this->never()) ->method('verify'); @@ -198,11 +198,11 @@ public function testSendTokenNotConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not allowed. */ public function testSendTokenUnavailableProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not allowed.'); $this->authy ->expects($this->never()) ->method('verify'); @@ -287,18 +287,18 @@ public function testCreateTokenWithOneTouch() Bootstrap::ADMIN_PASSWORD ); - self::assertRegExp('/^[a-z0-9]{32}$/', $result); + self::assertMatchesRegularExpression('/^[a-z0-9]{32}$/', $result); } /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Onetouch prompt was denied or timed out. */ public function testCreateTokenWithOneTouchError() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Onetouch prompt was denied or timed out.'); $this->tfa->getProviderByCode(Authy::CODE) ->activate($this->getUserId()); $this->onetouch diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php index 419dc499..06c3451c 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php @@ -58,7 +58,7 @@ class ConfigureTest extends TestCase */ private $tokenManager; - protected function setUp() + protected function setUp(): void { $objectManager = ObjectManager::getInstance(); $this->verification = $this->createMock(Verification::class); @@ -80,11 +80,11 @@ protected function setUp() * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage Invalid two-factor authorization token */ public function testConfigureInvalidTfat() { + $this->expectException(\Magento\Framework\Exception\AuthorizationException::class); + $this->expectExceptionMessage('Invalid two-factor authorization token'); $this->verification ->expects($this->never()) ->method('request'); @@ -106,11 +106,11 @@ public function testConfigureInvalidTfat() * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is already configured. */ public function testConfigureAlreadyConfiguredProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is already configured.'); $userId = $this->getUserId(); $this->tfa->getProviderByCode(Authy::CODE) ->activate($userId); @@ -134,11 +134,11 @@ public function testConfigureAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not allowed. */ public function testConfigureUnavailableProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not allowed.'); $userId = $this->getUserId(); $this->verification ->expects($this->never()) @@ -206,12 +206,11 @@ function ($userId, $country, $phone, $method, &$response) { * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage Invalid two-factor authorization token */ public function testActivateInvalidTfat() { - $userId = $this->getUserId(); + $this->expectException(\Magento\Framework\Exception\AuthorizationException::class); + $this->expectExceptionMessage('Invalid two-factor authorization token'); $this->verification ->expects($this->never()) ->method('request'); @@ -228,11 +227,11 @@ public function testActivateInvalidTfat() * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key abc * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is already configured. */ public function testActivateAlreadyConfiguredProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is already configured.'); $userId = $this->getUserId(); $this->tfa->getProviderByCode(Authy::CODE) ->activate($userId); @@ -251,11 +250,11 @@ public function testActivateAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not allowed. */ public function testActivateUnavailableProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not allowed.'); $userId = $this->getUserId(); $this->authy ->expects($this->never()) diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php index 292f191e..1c68bd8b 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php @@ -48,7 +48,7 @@ class AuthenticateTest extends TestCase */ private $tokenManager; - protected function setUp() + protected function setUp(): void { $objectManager = ObjectManager::getInstance(); $this->userFactory = $objectManager->get(UserFactory::class); @@ -69,10 +69,10 @@ protected function setUp() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\AuthenticationException */ public function testGetAuthenticateDataInvalidCredentials() { + $this->expectException(\Magento\Framework\Exception\AuthenticationException::class); $this->tfa->getProviderByCode(DuoSecurity::CODE) ->activate($this->getUserId()); $this->duo @@ -90,11 +90,11 @@ public function testGetAuthenticateDataInvalidCredentials() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not configured. */ public function testGetAuthenticateDataNotConfiguredProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not configured.'); $userId = $this->getUserId(); $this->tfa->getProviderByCode(DuoSecurity::CODE) ->resetConfiguration($userId); @@ -111,11 +111,11 @@ public function testGetAuthenticateDataNotConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not allowed. */ public function testGetAuthenticateDataUnavailableProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not allowed.'); $this->duo ->expects($this->never()) ->method('getRequestSignature'); @@ -130,10 +130,10 @@ public function testGetAuthenticateDataUnavailableProvider() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\AuthenticationException */ public function testVerifyInvalidCredentials() { + $this->expectException(\Magento\Framework\Exception\AuthenticationException::class); $this->tfa->getProviderByCode(DuoSecurity::CODE) ->activate($this->getUserId()); $this->duo @@ -152,11 +152,11 @@ public function testVerifyInvalidCredentials() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not configured. */ public function testVerifyNotConfiguredProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not configured.'); $userId = $this->getUserId(); $this->tfa->getProviderByCode(DuoSecurity::CODE) ->resetConfiguration($userId); @@ -174,11 +174,11 @@ public function testVerifyNotConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not allowed. */ public function testVerifyUnavailableProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not allowed.'); $this->duo ->expects($this->never()) ->method('getRequestSignature'); @@ -256,7 +256,7 @@ public function testVerifyValidRequest() $signature ); - self::assertRegExp('/^[a-z0-9]{32}$/', $token); + self::assertMatchesRegularExpression('/^[a-z0-9]{32}$/', $token); } /** @@ -265,11 +265,11 @@ public function testVerifyValidRequest() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Invalid response */ public function testVerifyInvalidRequest() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Invalid response'); $userId = $this->getUserId(); $this->tfa->getProviderByCode(DuoSecurity::CODE) ->activate($userId); diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php index 235ac098..62f2e984 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php @@ -52,7 +52,7 @@ class ConfigureTest extends TestCase */ private $authenticate; - protected function setUp() + protected function setUp(): void { $objectManager = ObjectManager::getInstance(); $this->userFactory = $objectManager->get(UserFactory::class); @@ -75,11 +75,11 @@ protected function setUp() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage Invalid two-factor authorization token */ public function testGetConfigurationDataInvalidTfat() { + $this->expectException(\Magento\Framework\Exception\AuthorizationException::class); + $this->expectExceptionMessage('Invalid two-factor authorization token'); $this->duo ->expects($this->never()) ->method('getRequestSignature'); @@ -94,11 +94,11 @@ public function testGetConfigurationDataInvalidTfat() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is already configured. */ public function testGetConfigurationDataAlreadyConfiguredProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is already configured.'); $userId = $this->getUserId(); $this->tfa->getProviderByCode(DuoSecurity::CODE) ->activate($userId); @@ -114,11 +114,11 @@ public function testGetConfigurationDataAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not allowed. */ public function testGetConfigurationDataUnavailableProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not allowed.'); $this->duo ->expects($this->never()) ->method('getRequestSignature'); @@ -133,11 +133,11 @@ public function testGetConfigurationDataUnavailableProvider() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage Invalid two-factor authorization token */ public function testActivateInvalidTfat() { + $this->expectException(\Magento\Framework\Exception\AuthorizationException::class); + $this->expectExceptionMessage('Invalid two-factor authorization token'); $this->duo ->expects($this->never()) ->method('getRequestSignature'); @@ -153,11 +153,11 @@ public function testActivateInvalidTfat() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is already configured. */ public function testActivateAlreadyConfiguredProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is already configured.'); $userId = $this->getUserId(); $this->tfa->getProviderByCode(DuoSecurity::CODE) ->activate($userId); @@ -173,11 +173,11 @@ public function testActivateAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not allowed. */ public function testActivateUnavailableProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not allowed.'); $userId = $this->getUserId(); $this->duo ->expects($this->never()) @@ -252,11 +252,11 @@ public function testActivateValidRequest() * @magentoConfigFixture default/twofactorauth/duo/api_hostname abc123 * @magentoConfigFixture default/twofactorauth/duo/secret_key abc123 * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Something */ public function testActivateInvalidDataThrowsException() { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Something'); $userId = $this->getUserId(); $tfat = $this->tokenManager->issueFor($userId); diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php index b76e994b..1c97ba13 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php @@ -42,7 +42,7 @@ class AuthenticateTest extends TestCase */ private $userFactory; - protected function setUp() + protected function setUp(): void { $objectManager = ObjectManager::getInstance(); $this->tfa = $objectManager->get(TfaInterface::class); @@ -59,10 +59,10 @@ protected function setUp() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\AuthenticationException */ public function testGetDataInvalidCredentials() { + $this->expectException(\Magento\Framework\Exception\AuthenticationException::class); $this->tfa->getProviderByCode(U2fKey::CODE) ->activate($this->getUserId()); $this->u2fkey @@ -77,11 +77,11 @@ public function testGetDataInvalidCredentials() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not configured. */ public function testGetDataNotConfiguredProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not configured.'); $this->u2fkey ->expects($this->never()) ->method('getAuthenticateData'); @@ -94,11 +94,11 @@ public function testGetDataNotConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not allowed. */ public function testGetDataUnavailableProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not allowed.'); $this->u2fkey ->expects($this->never()) ->method('getAuthenticateData'); @@ -111,10 +111,10 @@ public function testGetDataUnavailableProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\AuthenticationException */ public function testVerifyInvalidCredentials() { + $this->expectException(\Magento\Framework\Exception\AuthenticationException::class); $this->tfa->getProviderByCode(U2fKey::CODE) ->activate($this->getUserId()); $this->u2fkey @@ -133,11 +133,11 @@ public function testVerifyInvalidCredentials() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not configured. */ public function testVerifyNotConfiguredProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not configured.'); $this->u2fkey ->expects($this->never()) ->method('verify'); @@ -151,11 +151,11 @@ public function testVerifyNotConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not allowed. */ public function testVerifyUnavailableProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not allowed.'); $this->u2fkey ->expects($this->never()) ->method('verify'); @@ -237,17 +237,17 @@ public function testVerifyValidRequest() Bootstrap::ADMIN_PASSWORD, json_encode($verifyData) ); - self::assertRegExp('/^[a-z0-9]{32}$/', $token); + self::assertMatchesRegularExpression('/^[a-z0-9]{32}$/', $token); } /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Something */ public function testVerifyThrowsExceptionRequest() { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Something'); $this->tfa->getProviderByCode(U2fKey::CODE) ->activate($this->getUserId()); $userId = $this->getUserId(); diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php index 9af25cf7..21322614 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php @@ -47,7 +47,7 @@ class ConfigureTest extends TestCase */ private $tokenManager; - protected function setUp() + protected function setUp(): void { $objectManager = ObjectManager::getInstance(); $this->userFactory = $objectManager->get(UserFactory::class); @@ -65,11 +65,11 @@ protected function setUp() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage Invalid two-factor authorization token */ public function testGetRegistrationDataInvalidTfat() { + $this->expectException(\Magento\Framework\Exception\AuthorizationException::class); + $this->expectExceptionMessage('Invalid two-factor authorization token'); $userId = $this->getUserId(); $this->u2fkey ->expects($this->never()) @@ -82,11 +82,11 @@ public function testGetRegistrationDataInvalidTfat() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is already configured. */ public function testGetRegistrationDataAlreadyConfiguredProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is already configured.'); $userId = $this->getUserId(); $this->tfa->getProviderByCode(U2fKey::CODE) ->activate($userId); @@ -101,11 +101,11 @@ public function testGetRegistrationDataAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not allowed. */ public function testGetRegistrationDataUnavailableProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not allowed.'); $userId = $this->getUserId(); $this->u2fkey ->expects($this->never()) @@ -118,11 +118,11 @@ public function testGetRegistrationDataUnavailableProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\AuthorizationException - * @expectedExceptionMessage Invalid two-factor authorization token */ public function testActivateInvalidTfat() { + $this->expectException(\Magento\Framework\Exception\AuthorizationException::class); + $this->expectExceptionMessage('Invalid two-factor authorization token'); $userId = $this->getUserId(); $this->u2fkey ->expects($this->never()) @@ -136,11 +136,11 @@ public function testActivateInvalidTfat() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is already configured. */ public function testActivateAlreadyConfiguredProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is already configured.'); $userId = $this->getUserId(); $this->tfa->getProviderByCode(U2fKey::CODE) ->activate($userId); @@ -156,11 +156,11 @@ public function testActivateAlreadyConfiguredProvider() /** * @magentoConfigFixture default/twofactorauth/general/force_providers duo_security * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Provider is not allowed. */ public function testActivateUnavailableProvider() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Provider is not allowed.'); $userId = $this->getUserId(); $this->u2fkey ->expects($this->never()) @@ -233,11 +233,11 @@ public function testActivateValidRequest() /** * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey * @magentoDataFixture Magento/User/_files/user_with_role.php - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Something */ public function testActivateInvalidKeyDataThrowsException() { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Something'); $userId = $this->getUserId(); $tfat = $this->tokenManager->issueFor($userId); diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/SessionTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/SessionTest.php index b58b1ce6..068ab9bb 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/SessionTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/SessionTest.php @@ -19,7 +19,7 @@ class SessionTest extends TestCase */ private $session; - protected function setUp() + protected function setUp(): void { $this->session = ObjectManager::getInstance()->get(Session::class); } diff --git a/TwoFactorAuth/Test/Integration/Model/TfaSessionTest.php b/TwoFactorAuth/Test/Integration/Model/TfaSessionTest.php index 8b1c20ca..2900fee4 100644 --- a/TwoFactorAuth/Test/Integration/Model/TfaSessionTest.php +++ b/TwoFactorAuth/Test/Integration/Model/TfaSessionTest.php @@ -19,7 +19,7 @@ class TfaSessionTest extends TestCase */ private $session; - protected function setUp() + protected function setUp(): void { $this->session = ObjectManager::getInstance()->get(TfaSession::class); } diff --git a/TwoFactorAuth/Test/Integration/UserConfigManagerTest.php b/TwoFactorAuth/Test/Integration/UserConfigManagerTest.php index 2c0f4dc0..ff947d45 100644 --- a/TwoFactorAuth/Test/Integration/UserConfigManagerTest.php +++ b/TwoFactorAuth/Test/Integration/UserConfigManagerTest.php @@ -33,7 +33,7 @@ class UserConfigManagerTest extends TestCase /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { $this->userConfigManager = Bootstrap::getObjectManager()->get(UserConfigManagerInterface::class); $this->serializer = Bootstrap::getObjectManager()->get(SerializerInterface::class); diff --git a/TwoFactorAuth/Test/Integration/UserConfigRequestManagerTest.php b/TwoFactorAuth/Test/Integration/UserConfigRequestManagerTest.php index 4e576ac6..3c5c2c53 100644 --- a/TwoFactorAuth/Test/Integration/UserConfigRequestManagerTest.php +++ b/TwoFactorAuth/Test/Integration/UserConfigRequestManagerTest.php @@ -50,7 +50,7 @@ class UserConfigRequestManagerTest extends TestCase /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { /** @var User $user */ $user = Bootstrap::getObjectManager()->create(User::class); @@ -102,13 +102,13 @@ public function testIsRequiredWithConfig(): void * Check that app config request E-mail is NOT sent for a user that does not posses proper rights. * * @return void - * @expectedException \Magento\Framework\Exception\AuthorizationException * @throws \Throwable * @magentoAppArea adminhtml * @magentoAppIsolation enabled */ public function testFailAppConfigRequest(): void { + $this->expectException(\Magento\Framework\Exception\AuthorizationException::class); $this->aclBuilder->getAcl()->deny(null, 'Magento_TwoFactorAuth::config'); $this->manager->sendConfigRequestTo($this->user); } @@ -126,7 +126,7 @@ public function testSendAppConfigRequest(): void $this->assertNotEmpty($message = $this->transportBuilderMock->getSentMessage()); $messageHtml = $message->getBody()->getParts()[0]->getRawContent(); - $this->assertContains( + $this->assertStringContainsString( 'You are required to configure website-wide and personal Two-Factor Authorization in order to login to', $messageHtml ); @@ -156,7 +156,7 @@ public function testSendUserConfigRequest(): void $this->assertNotEmpty($message = $this->transportBuilderMock->getSentMessage()); $messageHtml = $message->getBody()->getParts()[0]->getRawContent(); - $this->assertContains( + $this->assertStringContainsString( 'You are required to configure personal Two-Factor Authorization in order to login to', $messageHtml ); diff --git a/TwoFactorAuth/Test/Integration/UserConfigTokenManagerTest.php b/TwoFactorAuth/Test/Integration/UserConfigTokenManagerTest.php index 901b4594..75f50f77 100644 --- a/TwoFactorAuth/Test/Integration/UserConfigTokenManagerTest.php +++ b/TwoFactorAuth/Test/Integration/UserConfigTokenManagerTest.php @@ -33,7 +33,7 @@ class UserConfigTokenManagerTest extends TestCase /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { $this->dateTimeMock = $this->getMockBuilder(DateTime::class)->disableOriginalConstructor()->getMock(); $this->userFactory = Bootstrap::getObjectManager()->get(UserFactory::class); diff --git a/TwoFactorAuth/Test/Unit/Model/Config/Backend/Duo/ApiHostnameTest.php b/TwoFactorAuth/Test/Unit/Model/Config/Backend/Duo/ApiHostnameTest.php index df640d54..b6a1ff30 100644 --- a/TwoFactorAuth/Test/Unit/Model/Config/Backend/Duo/ApiHostnameTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Config/Backend/Duo/ApiHostnameTest.php @@ -18,7 +18,7 @@ class ApiHostnameTest extends TestCase /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { $objectManager = new ObjectManager($this); $this->model = $objectManager->getObject(ApiHostname::class); diff --git a/TwoFactorAuth/Test/Unit/Model/Config/Backend/ForceProvidersTest.php b/TwoFactorAuth/Test/Unit/Model/Config/Backend/ForceProvidersTest.php index 467a6391..a41e4e5b 100644 --- a/TwoFactorAuth/Test/Unit/Model/Config/Backend/ForceProvidersTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Config/Backend/ForceProvidersTest.php @@ -25,7 +25,7 @@ class ForceProvidersTest extends TestCase /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { $objectManager = new ObjectManager($this); $this->tfa = $this->createMock(TfaInterface::class); @@ -42,10 +42,10 @@ protected function setUp() * Check that beforeSave validates values. * * @return void - * @expectedException \Magento\Framework\Exception\ValidatorException */ public function testBeforeSaveInvalid(): void { + $this->expectException(\Magento\Framework\Exception\ValidatorException::class); $this->model->setValue(''); $this->model->beforeSave(); } diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/AuthyTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/AuthyTest.php index f36e6bf8..031e9b28 100644 --- a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/AuthyTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/AuthyTest.php @@ -23,7 +23,7 @@ class AuthyTest extends TestCase /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { $objectManager = new ObjectManager($this); $this->serviceMock = $this->getMockBuilder(Authy\Service::class)->disableOriginalConstructor()->getMock(); diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/DuoSecurityTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/DuoSecurityTest.php index 6eef7bad..4c66ce86 100644 --- a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/DuoSecurityTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/DuoSecurityTest.php @@ -24,7 +24,7 @@ class DuoSecurityTest extends TestCase /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { $objectManager = new ObjectManager($this); $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class)->disableOriginalConstructor()->getMock(); diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php index ebd65af7..cc9a33d4 100644 --- a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php @@ -49,7 +49,7 @@ class GoogleTest extends TestCase /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { $objectManager = new ObjectManager($this); diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/ConfigReaderTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/ConfigReaderTest.php index 29199464..198d82d5 100644 --- a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/ConfigReaderTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/ConfigReaderTest.php @@ -27,7 +27,7 @@ class ConfigReaderTest extends TestCase */ private $reader; - protected function setUp() + protected function setUp(): void { $objectManager = new ObjectManager($this); $this->storeManager = $this->createMock(StoreManagerInterface::class); @@ -52,12 +52,10 @@ public function testGetValidDomain() self::assertSame('domain.com', $result); } - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Could not determine domain name. - */ public function testGetInvalidDomain() { + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); + $this->expectExceptionMessage('Could not determine domain name.'); $store = $this->createMock(Store::class); $store->method('getBaseUrl') ->willReturn('foo'); diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/WebApiConfigReaderTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/WebApiConfigReaderTest.php index cc880e50..a2c13442 100644 --- a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/WebApiConfigReaderTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKey/WebApiConfigReaderTest.php @@ -34,7 +34,7 @@ class WebApiConfigReaderTest extends TestCase */ private $reader; - protected function setUp() + protected function setUp(): void { $objectManager = new ObjectManager($this); $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKeyTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKeyTest.php index c7e6b18c..e79b30fe 100644 --- a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKeyTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/U2fKeyTest.php @@ -17,7 +17,7 @@ class U2fKeyTest extends TestCase /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { $objectManager = new ObjectManager($this); diff --git a/TwoFactorAuth/Test/Unit/Model/TfaTest.php b/TwoFactorAuth/Test/Unit/Model/TfaTest.php index 46ae8050..2452dfae 100644 --- a/TwoFactorAuth/Test/Unit/Model/TfaTest.php +++ b/TwoFactorAuth/Test/Unit/Model/TfaTest.php @@ -37,7 +37,7 @@ class TfaTest extends TestCase /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { $objectManager = new ObjectManager($this); $this->pool = $this->getMockForAbstractClass(ProviderPoolInterface::class); diff --git a/TwoFactorAuth/Test/Unit/Model/UserConfig/HtmlAreaTokenVerifierTest.php b/TwoFactorAuth/Test/Unit/Model/UserConfig/HtmlAreaTokenVerifierTest.php index 501e60a5..106a2041 100644 --- a/TwoFactorAuth/Test/Unit/Model/UserConfig/HtmlAreaTokenVerifierTest.php +++ b/TwoFactorAuth/Test/Unit/Model/UserConfig/HtmlAreaTokenVerifierTest.php @@ -56,7 +56,7 @@ class HtmlAreaTokenVerifierTest extends TestCase /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { $objectManager = new ObjectManager($this); $this->requestMock = $this->getMockForAbstractClass(RequestInterface::class); diff --git a/TwoFactorAuth/TestFramework/TestCase/AbstractBackendController.php b/TwoFactorAuth/TestFramework/TestCase/AbstractBackendController.php index 05a91a43..8f7432d2 100644 --- a/TwoFactorAuth/TestFramework/TestCase/AbstractBackendController.php +++ b/TwoFactorAuth/TestFramework/TestCase/AbstractBackendController.php @@ -15,7 +15,7 @@ class AbstractBackendController extends Base * * @throws \Magento\Framework\Exception\AuthenticationException */ - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/TwoFactorAuth/TestFramework/TestCase/AbstractConfigureBackendController.php b/TwoFactorAuth/TestFramework/TestCase/AbstractConfigureBackendController.php index 040f58b0..a0af6514 100644 --- a/TwoFactorAuth/TestFramework/TestCase/AbstractConfigureBackendController.php +++ b/TwoFactorAuth/TestFramework/TestCase/AbstractConfigureBackendController.php @@ -29,7 +29,7 @@ class AbstractConfigureBackendController extends AbstractBackendController /** * @inheritDoc */ - protected function setUp() + protected function setUp(): void { parent::setUp(); From afce6eed1388439a42ca0b14b16793ca5f661865 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 30 Apr 2020 10:02:16 -0500 Subject: [PATCH 26/58] MC-30537: Test automation with the new 2FA enabled by default - Spomky version bump --- TwoFactorAuth/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwoFactorAuth/composer.json b/TwoFactorAuth/composer.json index a70d7243..8e9d7d3d 100644 --- a/TwoFactorAuth/composer.json +++ b/TwoFactorAuth/composer.json @@ -12,7 +12,7 @@ "magento/module-user": "*", "magento/module-integration": "*", "christian-riesen/base32": "^1.3", - "spomky-labs/otphp": "~9.1.4", + "spomky-labs/otphp": "^10.0", "endroid/qr-code": "^3.7", "donatj/phpuseragentparser": "~0.7", "2tvenom/cborencode": "^1.0", From f7fe73a0effd2216aa0f10023263ab701f200225 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 30 Apr 2020 10:03:18 -0500 Subject: [PATCH 27/58] MC-30537: Test automation with the new 2FA enabled by default --- _metapackage/composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/_metapackage/composer.json b/_metapackage/composer.json index c169aafd..06f99f48 100644 --- a/_metapackage/composer.json +++ b/_metapackage/composer.json @@ -21,6 +21,7 @@ "magento/module-re-captcha-version-2-invisible": "*", "magento/module-re-captcha-version-3-invisible": "*", "magento/module-securitytxt": "*", + "magento/module-two-factor-auth": "*", "google/recaptcha": "^1.2" } } From ac673314dcb3b1113bf9e218b4b74f043c5c9cf1 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 30 Apr 2020 11:51:46 -0500 Subject: [PATCH 28/58] MC-30537: Test automation with the new 2FA enabled by default - Fixed data patch --- .../Setup/Patch/Data/{ResetU2fConfig.php => ResetU2FConfig.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename TwoFactorAuth/Setup/Patch/Data/{ResetU2fConfig.php => ResetU2FConfig.php} (100%) diff --git a/TwoFactorAuth/Setup/Patch/Data/ResetU2fConfig.php b/TwoFactorAuth/Setup/Patch/Data/ResetU2FConfig.php similarity index 100% rename from TwoFactorAuth/Setup/Patch/Data/ResetU2fConfig.php rename to TwoFactorAuth/Setup/Patch/Data/ResetU2FConfig.php From d2492b82ad981de90a63bf9b95b168a4b1361dda Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 30 Apr 2020 14:29:43 -0500 Subject: [PATCH 29/58] MC-30537: Test automation with the new 2FA enabled by default - Encrypted google shared secret --- TwoFactorAuth/Command/GoogleSecret.php | 27 ++--- .../Model/Provider/Engine/Google.php | 44 ++++++-- .../Setup/Patch/Data/EncryptGoogleSecrets.php | 105 ++++++++++++++++++ TwoFactorAuth/Test/Api/GoogleActivateTest.php | 10 +- .../Test/Api/GoogleAuthenticateTest.php | 10 +- .../Unit/Model/Provider/Engine/GoogleTest.php | 35 +++++- 6 files changed, 179 insertions(+), 52 deletions(-) create mode 100644 TwoFactorAuth/Setup/Patch/Data/EncryptGoogleSecrets.php diff --git a/TwoFactorAuth/Command/GoogleSecret.php b/TwoFactorAuth/Command/GoogleSecret.php index af56552d..e7944cc1 100644 --- a/TwoFactorAuth/Command/GoogleSecret.php +++ b/TwoFactorAuth/Command/GoogleSecret.php @@ -8,7 +8,6 @@ namespace Magento\TwoFactorAuth\Command; use Magento\Framework\Exception\LocalizedException; -use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -22,11 +21,6 @@ */ class GoogleSecret extends Command { - /** - * @var UserConfigManagerInterface - */ - private $userConfigManager; - /** * @var User */ @@ -36,21 +30,25 @@ class GoogleSecret extends Command * @var UserFactory */ private $userFactory; + /** + * @var Google + */ + private $google; /** - * @param UserConfigManagerInterface $userConfigManager * @param UserFactory $userFactory * @param User $userResource + * @param Google $google */ public function __construct( - UserConfigManagerInterface $userConfigManager, UserFactory $userFactory, - User $userResource + User $userResource, + Google $google ) { parent::__construct(); - $this->userConfigManager = $userConfigManager; $this->userResource = $userResource; $this->userFactory = $userFactory; + $this->google = $google; } /** @@ -87,14 +85,7 @@ protected function execute(InputInterface $input, OutputInterface $output) throw new LocalizedException(__('Unknown user %1', $userName)); } - $this->userConfigManager->addProviderConfig( - (int)$user->getId(), - Google::CODE, - [ - 'secret' => $secret, - UserConfigManagerInterface::ACTIVE_CONFIG_KEY => true - ] - ); + $this->google->setSharedSecret((int)$user->getId(), $secret); $output->writeln((string)__('Google OTP secret has been set')); } diff --git a/TwoFactorAuth/Model/Provider/Engine/Google.php b/TwoFactorAuth/Model/Provider/Engine/Google.php index 0943fa78..2d7069e8 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google.php @@ -14,6 +14,7 @@ use Exception; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\DataObject; +use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Store\Model\StoreManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google\TotpFactory; @@ -60,22 +61,30 @@ class Google implements EngineInterface */ private $totpFactory; + /** + * @var EncryptorInterface + */ + private $encryptor; + /** * @param StoreManagerInterface $storeManager * @param ScopeConfigInterface $scopeConfig * @param UserConfigManagerInterface $configManager - * @param TOTPInterfaceFactory $totpFactory + * @param TotpFactory $totpFactory + * @param EncryptorInterface $encryptor */ public function __construct( StoreManagerInterface $storeManager, ScopeConfigInterface $scopeConfig, UserConfigManagerInterface $configManager, - TotpFactory $totpFactory + TotpFactory $totpFactory, + EncryptorInterface $encryptor ) { $this->configManager = $configManager; $this->storeManager = $storeManager; $this->scopeConfig = $scopeConfig; $this->totpFactory = $totpFactory; + $this->encryptor = $encryptor; } /** @@ -99,15 +108,13 @@ private function generateSecret(): string */ private function getTotp(UserInterface $user): TOTPInterface { - $config = $this->configManager->getProviderConfig((int)$user->getId(), static::CODE); - if (!isset($config['secret'])) { - $config['secret'] = $this->getSecretCode($user); - } - if (!$config['secret']) { + $secret = $this->getSecretCode($user); + + if (!$secret) { throw new NoSuchEntityException(__('Secret for user with ID#%1 was not found', $user->getId())); } - $totp = $this->totpFactory->create($config['secret']); + $totp = $this->totpFactory->create($secret); return $totp; } @@ -126,10 +133,27 @@ public function getSecretCode(UserInterface $user): ?string if (!isset($config['secret'])) { $config['secret'] = $this->generateSecret(); - $this->configManager->setProviderConfig((int)$user->getId(), static::CODE, $config); + $this->setSharedSecret((int)$user->getId(), $config['secret']); + return $config['secret']; } - return $config['secret'] ?? null; + return $config['secret'] ? $this->encryptor->decrypt($config['secret']) : null; + } + + /** + * Set the secret used to generate OTP + * + * @param int $userId + * @param string $secret + * @throws NoSuchEntityException + */ + public function setSharedSecret(int $userId, string $secret): void + { + $this->configManager->addProviderConfig( + $userId, + static::CODE, + ['secret' => $this->encryptor->encrypt($secret)] + ); } /** diff --git a/TwoFactorAuth/Setup/Patch/Data/EncryptGoogleSecrets.php b/TwoFactorAuth/Setup/Patch/Data/EncryptGoogleSecrets.php new file mode 100644 index 00000000..7106d703 --- /dev/null +++ b/TwoFactorAuth/Setup/Patch/Data/EncryptGoogleSecrets.php @@ -0,0 +1,105 @@ +moduleDataSetup = $moduleDataSetup; + $this->userCollectionFactory = $userCollectionFactory; + $this->userConfigManager = $userConfigManager; + $this->encryptor = $encryptor; + } + + /** + * @inheritdoc + */ + public function apply() + { + $this->moduleDataSetup->startSetup(); + + /** @var \Magento\User\Model\ResourceModel\User\Collection $collection */ + $collection = $this->userCollectionFactory->create(); + + foreach ($collection as $user) { + /** @var $user User */ + + try { + $config = $this->userConfigManager->getProviderConfig((int)$user->getId(), Google::CODE); + if (empty($config) || empty($config['secret'])) { + continue; + } + $secret = $this->encryptor->encrypt($config['secret']); + $this->userConfigManager->addProviderConfig((int)$user->getId(), Google::CODE, ['secret' => $secret]); + } catch (NoSuchEntityException $e) { + continue; + } + } + + $this->moduleDataSetup->endSetup(); + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return []; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} diff --git a/TwoFactorAuth/Test/Api/GoogleActivateTest.php b/TwoFactorAuth/Test/Api/GoogleActivateTest.php index 35359324..c5ddba59 100644 --- a/TwoFactorAuth/Test/Api/GoogleActivateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleActivateTest.php @@ -36,11 +36,6 @@ class GoogleActivateTest extends WebapiAbstract */ private $tfa; - /** - * @var UserConfigManagerInterface - */ - private $userConfig; - /** * @var Google */ @@ -54,7 +49,6 @@ protected function setUp(): void $this->tfa = $objectManager->get(TfaInterface::class); $this->userFactory = $objectManager->get(UserFactory::class); $this->google = $objectManager->get(Google::class); - $this->userConfig = $objectManager->get(UserConfigManagerInterface::class); } /** @@ -133,6 +127,7 @@ public function testAlreadyActivatedProvider() /** * @magentoConfigFixture twofactorauth/general/force_providers google * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + * @magentoConfigFixture twofactorauth/google/otp_window 120 */ public function testActivate() { @@ -157,9 +152,6 @@ private function getUserOtp(): string $user->loadByUsername('customRoleUser'); $totp = TOTP::create($this->google->getSecretCode($user)); - // Enable longer window of valid tokens to prevent test race condition - $this->userConfig->addProviderConfig((int)$user->getId(), Google::CODE, ['window' => 120]); - return $totp->now(); } diff --git a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php index 3a3afe44..70055b90 100644 --- a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php @@ -31,11 +31,6 @@ class GoogleAuthenticateTest extends WebapiAbstract */ private $google; - /** - * @var UserConfigManagerInterface - */ - private $userConfig; - /** * @var TfaInterface */ @@ -46,7 +41,6 @@ protected function setUp(): void $objectManager = Bootstrap::getObjectManager(); $this->userFactory = $objectManager->get(UserFactory::class); $this->google = $objectManager->get(Google::class); - $this->userConfig = $objectManager->get(UserConfigManagerInterface::class); $this->tfa = $objectManager->get(TfaInterface::class); } @@ -178,6 +172,7 @@ public function testNotConfiguredProvider() /** * @magentoConfigFixture twofactorauth/general/force_providers google * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + * @magentoConfigFixture twofactorauth/google/otp_window 120 */ public function testValidToken() { @@ -235,9 +230,6 @@ private function getUserOtp(): string $user->loadByUsername('customRoleUser'); $totp = TOTP::create($this->google->getSecretCode($user)); - // Enable longer window of valid tokens to prevent test race condition - $this->userConfig->addProviderConfig((int)$user->getId(), Google::CODE, ['window' => 120]); - return $totp->now(); } } diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php index cc9a33d4..a773c2a0 100644 --- a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/GoogleTest.php @@ -5,6 +5,7 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\DataObject; +use Magento\Framework\Encryption\EncryptorInterface; use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\TwoFactorAuth\Model\Provider\Engine\Google\TotpFactory; @@ -46,6 +47,11 @@ class GoogleTest extends TestCase */ private $scopeConfig; + /** + * @var EncryptorInterface|MockObject + */ + private $encryptor; + /** * @inheritDoc */ @@ -57,6 +63,7 @@ protected function setUp(): void $this->totp = $this->createMock(TOTPInterface::class); $this->user = $this->createMock(UserInterface::class); $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); + $this->encryptor = $this->createMock(EncryptorInterface::class); $this->user->method('getId') ->willReturn('5'); $this->user->method('getEmail') @@ -67,7 +74,8 @@ protected function setUp(): void [ 'configManager' => $this->configManager, 'totpFactory' => $this->totpFactory, - 'scopeConfig' => $this->scopeConfig + 'scopeConfig' => $this->scopeConfig, + 'encryptor' => $this->encryptor ] ); } @@ -92,7 +100,10 @@ public function testVerifyWithNoToken() public function testVerifyWithBadToken() { $this->configManager->method('getProviderConfig') - ->willReturn(['secret' => 'abc']); + ->willReturn(['secret' => 'cba']); + $this->encryptor->method('decrypt') + ->with('cba') + ->willReturn('abc'); $this->totpFactory->method('create') ->willReturn($this->totp); $this->totp->method('verify') @@ -107,7 +118,10 @@ public function testVerifyWithBadToken() public function testVerifyWithGoodToken() { $this->configManager->method('getProviderConfig') - ->willReturn(['secret' => 'abc']); + ->willReturn(['secret' => 'cba']); + $this->encryptor->method('decrypt') + ->with('cba') + ->willReturn('abc'); $this->totpFactory->method('create') ->with('abc') ->willReturn($this->totp); @@ -123,7 +137,10 @@ public function testVerifyWithGoodToken() public function testVerifyWithGoodTokenAndWindowFromUserConfig() { $this->configManager->method('getProviderConfig') - ->willReturn(['secret' => 'abc', 'window' => 800]); + ->willReturn(['secret' => 'cba', 'window' => 800]); + $this->encryptor->method('decrypt') + ->with('cba') + ->willReturn('abc'); $this->totpFactory->method('create') ->willReturn($this->totp); $this->totp->method('verify') @@ -140,7 +157,10 @@ public function testVerifyWithGoodTokenAndWindowFromScopeConfig() $this->scopeConfig->method('getValue') ->willReturn(800); $this->configManager->method('getProviderConfig') - ->willReturn(['secret' => 'abc']); + ->willReturn(['secret' => 'cba']); + $this->encryptor->method('decrypt') + ->with('cba') + ->willReturn('abc'); $this->totpFactory->method('create') ->willReturn($this->totp); $this->totp->method('verify') @@ -157,7 +177,10 @@ public function testVerifyWindowFromUserConfigOverridesScopeConfig() $this->scopeConfig->method('getValue') ->willReturn(800); $this->configManager->method('getProviderConfig') - ->willReturn(['secret' => 'abc', 'window' => 500]); + ->willReturn(['secret' => 'cba', 'window' => 500]); + $this->encryptor->method('decrypt') + ->with('cba') + ->willReturn('abc'); $this->totpFactory->method('create') ->willReturn($this->totp); $this->totp->method('verify') From 67669596dea3992dec2f48c6c49d7ad1a8ed6ffa Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 1 May 2020 09:08:46 -0500 Subject: [PATCH 30/58] MC-30537: Test automation with the new 2FA enabled by default - more php 7.4 fixes - fixed missing file from bad merge --- .../Api/Data/GoogleAuthenticateInterface.php | 52 +++++++++++++++++++ .../Engine/Authy/RegistrationResponse.php | 4 +- .../Engine/Google/AuthenticateData.php | 4 +- .../Engine/Google/ConfigurationData.php | 4 +- TwoFactorAuth/Model/Provider/Engine/Authy.php | 15 ++++-- .../Model/Provider/Engine/Authy/OneTouch.php | 16 ++++-- .../Model/Provider/Engine/Authy/Service.php | 4 ++ .../Model/Provider/Engine/Authy/Token.php | 16 ++++-- .../Provider/Engine/Authy/Verification.php | 16 ++++-- 9 files changed, 111 insertions(+), 20 deletions(-) create mode 100644 TwoFactorAuth/Api/Data/GoogleAuthenticateInterface.php diff --git a/TwoFactorAuth/Api/Data/GoogleAuthenticateInterface.php b/TwoFactorAuth/Api/Data/GoogleAuthenticateInterface.php new file mode 100644 index 00000000..93cbece1 --- /dev/null +++ b/TwoFactorAuth/Api/Data/GoogleAuthenticateInterface.php @@ -0,0 +1,52 @@ +userConfigManager = $userConfigManager; $this->curlFactory = $curlFactory; $this->service = $service; $this->scopeConfig = $scopeConfig; $this->token = $token; + $this->json = $json; } /** * Enroll in Authy + * * @param UserInterface $user * @return bool * @throws LocalizedException @@ -97,7 +106,7 @@ public function enroll(UserInterface $user): bool 'user[country_code]' => $providerInfo['country_code'], ]); - $response = Json::decode($curl->getBody(), Json::TYPE_ARRAY); + $response = $this->json->unserialize($curl->getBody()); $errorMessage = $this->service->getErrorFromResponse($response); if ($errorMessage) { diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/OneTouch.php b/TwoFactorAuth/Model/Provider/Engine/Authy/OneTouch.php index bb9a3ed6..99867ec5 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/OneTouch.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/OneTouch.php @@ -10,11 +10,11 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\HTTP\Client\CurlFactory; +use Magento\Framework\Serialize\Serializer\Json; use Magento\Store\Model\StoreManagerInterface; use Magento\User\Api\Data\UserInterface; use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Authy; -use Zend\Json\Json; /** * Authy One Touch manager class @@ -51,6 +51,11 @@ class OneTouch */ private $scopeConfig; + /** + * @var Json + */ + private $json; + /** * OneTouch constructor. * @@ -59,19 +64,22 @@ class OneTouch * @param Service $service * @param StoreManagerInterface $storeManager * @param ScopeConfigInterface $scopeConfig + * @param Json $json */ public function __construct( CurlFactory $curlFactory, UserConfigManagerInterface $userConfigManager, Service $service, StoreManagerInterface $storeManager, - ScopeConfigInterface $scopeConfig + ScopeConfigInterface $scopeConfig, + Json $json ) { $this->curlFactory = $curlFactory; $this->userConfigManager = $userConfigManager; $this->storeManager = $storeManager; $this->service = $service; $this->scopeConfig = $scopeConfig; + $this->json = $json; } /** @@ -98,7 +106,7 @@ public function request(UserInterface $user): void 'seconds_to_expire' => 300, ]); - $response = Json::decode($curl->getBody(), Json::TYPE_ARRAY); + $response = $this->json->unserialize($curl->getBody()); $errorMessage = $this->service->getErrorFromResponse($response); if ($errorMessage) { @@ -142,7 +150,7 @@ public function verify(UserInterface $user): string $curl->addHeader('X-Authy-API-Key', $this->service->getApiKey()); $curl->get($url); - $response = Json::decode($curl->getBody(), Json::TYPE_ARRAY); + $response = $this->json->unserialize($curl->getBody()); $errorMessage = $this->service->getErrorFromResponse($response); if ($errorMessage) { diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Service.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Service.php index 38fafa3e..8278a02e 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Service.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Service.php @@ -39,6 +39,7 @@ public function __construct(ScopeConfigInterface $scopeConfig) /** * Get API key + * * @return string */ public function getApiKey(): string @@ -48,6 +49,7 @@ public function getApiKey(): string /** * Get authy API endpoint + * * @param string $path * @return string */ @@ -58,6 +60,7 @@ public function getProtectedApiEndpoint(string $path): string /** * Get authy API endpoint + * * @param string $path * @return string */ @@ -68,6 +71,7 @@ public function getOneTouchApiEndpoint(string $path): string /** * Get error from response + * * @param array|boolean $response * @return string|null */ diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Token.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Token.php index dd7d70ff..2397c0ef 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Token.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Token.php @@ -10,10 +10,10 @@ use Magento\Framework\DataObject; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\HTTP\Client\CurlFactory; +use Magento\Framework\Serialize\Serializer\Json; use Magento\User\Api\Data\UserInterface; use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Authy; -use Zend\Json\Json; /** * Authy token manager @@ -35,19 +35,27 @@ class Token */ private $service; + /** + * @var Json + */ + private $json; + /** * @param UserConfigManagerInterface $userConfigManager * @param Service $service * @param CurlFactory $curlFactory + * @param Json $json */ public function __construct( UserConfigManagerInterface $userConfigManager, Service $service, - CurlFactory $curlFactory + CurlFactory $curlFactory, + Json $json ) { $this->userConfigManager = $userConfigManager; $this->curlFactory = $curlFactory; $this->service = $service; + $this->json = $json; } /** @@ -73,7 +81,7 @@ public function request(UserInterface $user, string $via): void $curl->addHeader('X-Authy-API-Key', $this->service->getApiKey()); $curl->get($url); - $response = Json::decode($curl->getBody(), Json::TYPE_ARRAY); + $response = $this->json->unserialize($curl->getBody()); $errorMessage = $this->service->getErrorFromResponse($response); if ($errorMessage) { @@ -106,7 +114,7 @@ public function verify(UserInterface $user, DataObject $request): bool $curl->addHeader('X-Authy-API-Key', $this->service->getApiKey()); $curl->get($url); - $response = Json::decode($curl->getBody(), Json::TYPE_ARRAY); + $response = $this->json->unserialize($curl->getBody()); $errorMessage = $this->service->getErrorFromResponse($response); if ($errorMessage) { diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php index 5d46664e..9bdbe19b 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php @@ -9,11 +9,11 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\HTTP\Client\CurlFactory; +use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\Stdlib\DateTime\DateTime; use Magento\User\Api\Data\UserInterface; use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Authy; -use Zend\Json\Json; /** * Authy verification management @@ -40,26 +40,35 @@ class Verification */ private $dateTime; + /** + * @var Json + */ + private $json; + /** * @param CurlFactory $curlFactory * @param DateTime $dateTime * @param UserConfigManagerInterface $userConfigManager * @param Service $service + * @param Json $json */ public function __construct( CurlFactory $curlFactory, DateTime $dateTime, UserConfigManagerInterface $userConfigManager, - Service $service + Service $service, + Json $json ) { $this->curlFactory = $curlFactory; $this->service = $service; $this->userConfigManager = $userConfigManager; $this->dateTime = $dateTime; + $this->json = $json; } /** * Verify phone number + * * @param UserInterface $user * @param string $country * @param string $phoneNumber @@ -109,6 +118,7 @@ public function request( /** * Verify phone number + * * @param UserInterface $user * @param string $verificationCode * @throws LocalizedException @@ -130,7 +140,7 @@ public function verify(UserInterface $user, string $verificationCode): void 'verification_code' => $verificationCode, ])); - $response = Json::decode($curl->getBody(), Json::TYPE_ARRAY); + $response = $this->json->unserialize($curl->getBody()); $errorMessage = $this->service->getErrorFromResponse($response); if ($errorMessage) { From 3bdc59cf4cede7977a43e9e2dcf02fe54c3940ec Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 1 May 2020 10:33:34 -0500 Subject: [PATCH 31/58] MC-30537: Test automation with the new 2FA enabled by default - Cleanup --- TwoFactorAuth/Model/Provider/Engine/Google.php | 1 - TwoFactorAuth/composer.json | 14 ++------------ TwoFactorAuth/view/adminhtml/web/js/google/auth.js | 1 - 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/TwoFactorAuth/Model/Provider/Engine/Google.php b/TwoFactorAuth/Model/Provider/Engine/Google.php index 2d7069e8..eabdb244 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google.php @@ -125,7 +125,6 @@ private function getTotp(UserInterface $user): TOTPInterface * @param UserInterface $user * @return string|null * @throws NoSuchEntityException - * @author Konrad Skrzynski */ public function getSecretCode(UserInterface $user): ?string { diff --git a/TwoFactorAuth/composer.json b/TwoFactorAuth/composer.json index 8e9d7d3d..e9ba41e5 100644 --- a/TwoFactorAuth/composer.json +++ b/TwoFactorAuth/composer.json @@ -1,9 +1,9 @@ { "name": "magento/module-two-factor-auth", - "version": "5.0.0", + "version": "1.0.0", "description": "Two Factor Authentication module for Magento2", "require": { - "php": "~7.1.3||~7.2.0||~7.3.0", + "php": "~7.3.0||~7.4.0", "magento/magento-composer-installer": "*", "magento/module-backend": "*", "magento/module-config": "*", @@ -18,16 +18,6 @@ "2tvenom/cborencode": "^1.0", "phpseclib/phpseclib": "2.0.*" }, - "authors": [ - { - "name": "Riccardo Tempesta", - "email": "riccardo.tempesta@magespecialist.it" - }, - { - "name": "Giacomo Mirabassi", - "email": "giacomo.mirabassi@magespecialist.it" - } - ], "type": "magento2-module", "license": "OSL-3.0", "autoload": { diff --git a/TwoFactorAuth/view/adminhtml/web/js/google/auth.js b/TwoFactorAuth/view/adminhtml/web/js/google/auth.js index f97447a6..32837830 100644 --- a/TwoFactorAuth/view/adminhtml/web/js/google/auth.js +++ b/TwoFactorAuth/view/adminhtml/web/js/google/auth.js @@ -43,7 +43,6 @@ define([ /** * Get plain Secret Code * @returns {String} - * @author Konrad Skrzynski */ getSecretCode: function () { return this.secretCode; From 8816c6c04f4ae7133029fdd5454af2f096c2f337 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 1 May 2020 14:57:32 -0500 Subject: [PATCH 32/58] MC-30537: Test automation with the new 2FA enabled by default - Cleanup --- .../Api/Data/U2fWebAuthnRequestInterface.php | 10 +++++----- TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php | 6 +++--- TwoFactorAuth/Api/U2fKeyConfigureInterface.php | 6 +++--- TwoFactorAuth/Model/Data/Country.php | 10 +++++----- .../Model/Data/Provider/Engine/Authy/Device.php | 8 ++++---- .../Provider/Engine/Authy/RegistrationResponse.php | 6 +++--- .../Data/Provider/Engine/DuoSecurity/Data.php | 4 ++-- .../Provider/Engine/Google/AuthenticateData.php | 4 ++-- .../Provider/Engine/Google/ConfigurationData.php | 6 +++--- .../Provider/Engine/U2fkey/WebAuthnRequest.php | 14 +++++++------- TwoFactorAuth/Model/Data/UserConfig.php | 10 +++++----- .../Model/Provider/Engine/Authy/Verification.php | 2 +- .../Model/Provider/Engine/U2fKey/Authenticate.php | 14 +++++++------- .../Model/Provider/Engine/U2fKey/Configure.php | 14 +++++++------- .../Provider/Engine/U2fKey/AuthenticateTest.php | 4 ++-- .../Model/Provider/Engine/U2fKey/ConfigureTest.php | 4 ++-- .../Test/Unit/Model/Provider/Engine/AuthyTest.php | 2 +- TwoFactorAuth/etc/di.xml | 2 +- 18 files changed, 63 insertions(+), 63 deletions(-) diff --git a/TwoFactorAuth/Api/Data/U2fWebAuthnRequestInterface.php b/TwoFactorAuth/Api/Data/U2fWebAuthnRequestInterface.php index 0c793783..eedc296c 100644 --- a/TwoFactorAuth/Api/Data/U2fWebAuthnRequestInterface.php +++ b/TwoFactorAuth/Api/Data/U2fWebAuthnRequestInterface.php @@ -13,7 +13,7 @@ /** * Represents a WebAuthn dataset */ -interface U2FWebAuthnRequestInterface extends ExtensibleDataInterface +interface U2fWebAuthnRequestInterface extends ExtensibleDataInterface { const CREDENTIAL_REQUEST_OPTIONS_JSON = 'credential_request_options_json'; @@ -37,17 +37,17 @@ public function setCredentialRequestOptionsJson(string $value): void; * * Used fully qualified namespaces in annotations for proper work of extension interface/class code generation * - * @return \Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestExtensionInterface|null + * @return \Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestExtensionInterface|null */ - public function getExtensionAttributes(): ?U2FWebAuthnRequestExtensionInterface; + public function getExtensionAttributes(): ?U2fWebAuthnRequestExtensionInterface; /** * Set an extension attributes object * - * @param \Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestExtensionInterface $extensionAttributes + * @param \Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestExtensionInterface $extensionAttributes * @return void */ public function setExtensionAttributes( - U2FWebAuthnRequestExtensionInterface $extensionAttributes + U2fWebAuthnRequestExtensionInterface $extensionAttributes ): void; } diff --git a/TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php b/TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php index fe88da3b..ead9a29a 100644 --- a/TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php +++ b/TwoFactorAuth/Api/U2fKeyAuthenticateInterface.php @@ -8,7 +8,7 @@ namespace Magento\TwoFactorAuth\Api; -use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface; +use Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterface; /** * Represent Authentication for u2f key provider @@ -20,9 +20,9 @@ interface U2fKeyAuthenticateInterface * * @param string $username * @param string $password - * @return \Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface + * @return \Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterface */ - public function getAuthenticationData(string $username, string $password): U2FWebAuthnRequestInterface; + public function getAuthenticationData(string $username, string $password): U2fWebAuthnRequestInterface; /** * Authenticate with the provider and get a token diff --git a/TwoFactorAuth/Api/U2fKeyConfigureInterface.php b/TwoFactorAuth/Api/U2fKeyConfigureInterface.php index 392181e5..31ff6280 100644 --- a/TwoFactorAuth/Api/U2fKeyConfigureInterface.php +++ b/TwoFactorAuth/Api/U2fKeyConfigureInterface.php @@ -8,7 +8,7 @@ namespace Magento\TwoFactorAuth\Api; -use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface; +use Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterface; /** * Represent configuration for u2f key provider @@ -19,9 +19,9 @@ interface U2fKeyConfigureInterface * Get the information to initiate a WebAuthn registration ceremony * * @param string $tfaToken - * @return \Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface + * @return \Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterface */ - public function getRegistrationData(string $tfaToken): U2FWebAuthnRequestInterface; + public function getRegistrationData(string $tfaToken): U2fWebAuthnRequestInterface; /** * Activate the provider and get a token diff --git a/TwoFactorAuth/Model/Data/Country.php b/TwoFactorAuth/Model/Data/Country.php index 6a93dc58..35d30aa8 100644 --- a/TwoFactorAuth/Model/Data/Country.php +++ b/TwoFactorAuth/Model/Data/Country.php @@ -21,7 +21,7 @@ class Country extends AbstractExtensibleObject implements CountryInterface */ public function getId(): int { - return (int) $this->_get(self::ID); + return (int) $this->getData(self::ID); } /** @@ -37,7 +37,7 @@ public function setId(int $value): void */ public function getCode(): string { - return (string) $this->_get(self::CODE); + return (string) $this->getData(self::CODE); } /** @@ -53,7 +53,7 @@ public function setCode(string $value): void */ public function getName(): string { - return (string) $this->_get(self::NAME); + return (string) $this->getData(self::NAME); } /** @@ -69,7 +69,7 @@ public function setName(string $value): void */ public function getDialCode(): string { - return (string) $this->_get(self::DIAL_CODE); + return (string) $this->getData(self::DIAL_CODE); } /** @@ -85,7 +85,7 @@ public function setDialCode(string $value): void */ public function getExtensionAttributes(): ?CountryExtensionInterface { - return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); } /** diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/Authy/Device.php b/TwoFactorAuth/Model/Data/Provider/Engine/Authy/Device.php index bae70749..68eddac3 100644 --- a/TwoFactorAuth/Model/Data/Provider/Engine/Authy/Device.php +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Authy/Device.php @@ -22,7 +22,7 @@ class Device extends AbstractExtensibleModel implements AuthyDeviceInterface */ public function getCountry(): string { - return (string)$this->_getData(self::COUNTRY); + return (string)$this->getData(self::COUNTRY); } /** @@ -38,7 +38,7 @@ public function setCountry(string $value): void */ public function getPhoneNumber(): string { - return $this->_getData(self::PHONE); + return $this->getData(self::PHONE); } /** @@ -54,7 +54,7 @@ public function setPhoneNumber(string $value): void */ public function getMethod(): string { - return $this->_getData(self::METHOD); + return $this->getData(self::METHOD); } /** @@ -74,7 +74,7 @@ public function setMethod(string $value): void */ public function getExtensionAttributes(): ?AuthyDeviceExtensionInterface { - return $this->_getData(self::EXTENSION_ATTRIBUTES_KEY); + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); } /** diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php b/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php index ae5cfc9e..99b7f5a6 100644 --- a/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Authy/RegistrationResponse.php @@ -22,7 +22,7 @@ class RegistrationResponse extends AbstractExtensibleModel implements ResponseIn */ public function getMessage(): string { - return (string)$this->_get(self::MESSAGE); + return (string)$this->getData(self::MESSAGE); } /** @@ -38,7 +38,7 @@ public function setMessage(string $value): void */ public function getExpirationSeconds(): int { - return (int)$this->_get(self::EXPIRATION_SECONDS); + return (int)$this->getData(self::EXPIRATION_SECONDS); } /** @@ -58,7 +58,7 @@ public function setExpirationSeconds(int $value): void */ public function getExtensionAttributes(): ?AuthyRegistrationPromptResponseExtensionInterface { - return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); } /** diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/DuoSecurity/Data.php b/TwoFactorAuth/Model/Data/Provider/Engine/DuoSecurity/Data.php index 5834429d..a2bcda14 100644 --- a/TwoFactorAuth/Model/Data/Provider/Engine/DuoSecurity/Data.php +++ b/TwoFactorAuth/Model/Data/Provider/Engine/DuoSecurity/Data.php @@ -22,7 +22,7 @@ class Data extends AbstractExtensibleModel implements DuoDataInterface */ public function getSignature(): string { - return (string)$this->_getData(self::SIGNATURE); + return (string)$this->getData(self::SIGNATURE); } /** @@ -38,7 +38,7 @@ public function setSignature(string $value): void */ public function getApiHostname(): string { - return (string)$this->_getData(self::API_HOSTNAME); + return (string)$this->getData(self::API_HOSTNAME); } /** diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php b/TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php index ef0a4131..8c7c1afd 100644 --- a/TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php @@ -22,7 +22,7 @@ class AuthenticateData extends AbstractExtensibleModel implements GoogleAuthenti */ public function getOtp(): string { - return (string)$this->_get(self::OTP); + return (string)$this->getData(self::OTP); } /** @@ -42,7 +42,7 @@ public function setOtp(string $value): void */ public function getExtensionAttributes(): ?GoogleAuthenticateExtensionInterface { - return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); } /** diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php b/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php index ccabb1bd..44378481 100644 --- a/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Google/ConfigurationData.php @@ -24,7 +24,7 @@ class ConfigurationData extends AbstractExtensibleModel implements GoogleConfigu */ public function getQrCodeBase64(): string { - return (string)$this->_get(self::QR_CODE_BASE64); + return (string)$this->getData(self::QR_CODE_BASE64); } /** @@ -45,7 +45,7 @@ public function setQrCodeBase64(string $value): void */ public function getSecretCode(): string { - return (string)$this->_get(self::SECRET_CODE); + return (string)$this->getData(self::SECRET_CODE); } /** @@ -68,7 +68,7 @@ public function setSecretCode(string $value): void */ public function getExtensionAttributes(): ?GoogleConfigureExtensionInterface { - return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); } /** diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/U2fkey/WebAuthnRequest.php b/TwoFactorAuth/Model/Data/Provider/Engine/U2fkey/WebAuthnRequest.php index e1a82fbd..dc5de8a3 100644 --- a/TwoFactorAuth/Model/Data/Provider/Engine/U2fkey/WebAuthnRequest.php +++ b/TwoFactorAuth/Model/Data/Provider/Engine/U2fkey/WebAuthnRequest.php @@ -9,20 +9,20 @@ namespace Magento\TwoFactorAuth\Model\Data\Provider\Engine\U2fkey; use Magento\Framework\Model\AbstractExtensibleModel; -use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestExtensionInterface; -use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface; +use Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestExtensionInterface; +use Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterface; /** * WebAuthn data */ -class WebAuthnRequest extends AbstractExtensibleModel implements U2FWebAuthnRequestInterface +class WebAuthnRequest extends AbstractExtensibleModel implements U2fWebAuthnRequestInterface { /** * @inheritDoc */ public function getCredentialRequestOptionsJson(): string { - return (string)$this->_getData(self::CREDENTIAL_REQUEST_OPTIONS_JSON); + return (string)$this->getData(self::CREDENTIAL_REQUEST_OPTIONS_JSON); } /** @@ -36,15 +36,15 @@ public function setCredentialRequestOptionsJson(string $value): void /** * @inheritDoc */ - public function getExtensionAttributes(): ?U2FWebAuthnRequestExtensionInterface + public function getExtensionAttributes(): ?U2fWebAuthnRequestExtensionInterface { - return $this->_getData(self::EXTENSION_ATTRIBUTES_KEY); + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); } /** * @inheritDoc */ - public function setExtensionAttributes(U2FWebAuthnRequestExtensionInterface $extensionAttributes): void + public function setExtensionAttributes(U2fWebAuthnRequestExtensionInterface $extensionAttributes): void { $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); } diff --git a/TwoFactorAuth/Model/Data/UserConfig.php b/TwoFactorAuth/Model/Data/UserConfig.php index 487e3c2d..46f35649 100644 --- a/TwoFactorAuth/Model/Data/UserConfig.php +++ b/TwoFactorAuth/Model/Data/UserConfig.php @@ -21,7 +21,7 @@ class UserConfig extends AbstractExtensibleObject implements UserConfigInterface */ public function getId(): int { - return (int) $this->_get(self::ID); + return (int) $this->getData(self::ID); } /** @@ -37,7 +37,7 @@ public function setId(int $value): void */ public function getUserId(): int { - return (int) $this->_get(self::USER_ID); + return (int) $this->getData(self::USER_ID); } /** @@ -53,7 +53,7 @@ public function setUserId(int $value): void */ public function getEncodedProviders(): string { - return (string) $this->_get(self::ENCODED_PROVIDERS); + return (string) $this->getData(self::ENCODED_PROVIDERS); } /** @@ -69,7 +69,7 @@ public function setEncodedProviders(string $value): void */ public function getDefaultProvider(): string { - return (string) $this->_get(self::DEFAULT_PROVIDER); + return (string) $this->getData(self::DEFAULT_PROVIDER); } /** @@ -85,7 +85,7 @@ public function setDefaultProvider(string $value): void */ public function getExtensionAttributes(): ?UserConfigExtensionInterface { - return $this->_get(self::EXTENSION_ATTRIBUTES_KEY); + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); } /** diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php index 9bdbe19b..fde541d0 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php @@ -93,7 +93,7 @@ public function request( 'phone_number' => $phoneNumber ]); - $response = Json::decode($curl->getBody(), Json::TYPE_ARRAY); + $response = $this->json->unserialize($curl->getBody()); $errorMessage = $this->service->getErrorFromResponse($response); if ($errorMessage) { diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php index b4fd13b5..e5ebd435 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php @@ -13,8 +13,8 @@ use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\Exception\LocalizedException; use Magento\Integration\Api\AdminTokenServiceInterface; -use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface; -use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterfaceFactory; +use Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterface; +use Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterfaceFactory; use Magento\TwoFactorAuth\Api\U2fKeyAuthenticateInterface; use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\AlertInterface; @@ -56,7 +56,7 @@ class Authenticate implements U2fKeyAuthenticateInterface private $userFactory; /** - * @var U2FWebAuthnRequestInterfaceFactory + * @var U2fWebAuthnRequestInterfaceFactory */ private $authnRequestInterfaceFactory; @@ -81,7 +81,7 @@ class Authenticate implements U2fKeyAuthenticateInterface * @param AlertInterface $alert * @param DataObjectFactory $dataObjectFactory * @param UserFactory $userFactory - * @param U2FWebAuthnRequestInterfaceFactory $authnRequestInterfaceFactory + * @param U2fWebAuthnRequestInterfaceFactory $authnRequestInterfaceFactory * @param Json $json * @param UserConfigManagerInterface $configManager * @param AdminTokenServiceInterface $adminTokenService @@ -92,7 +92,7 @@ public function __construct( AlertInterface $alert, DataObjectFactory $dataObjectFactory, UserFactory $userFactory, - U2FWebAuthnRequestInterfaceFactory $authnRequestInterfaceFactory, + U2fWebAuthnRequestInterfaceFactory $authnRequestInterfaceFactory, Json $json, UserConfigManagerInterface $configManager, AdminTokenServiceInterface $adminTokenService @@ -111,7 +111,7 @@ public function __construct( /** * @inheritDoc */ - public function getAuthenticationData(string $username, string $password): U2FWebAuthnRequestInterface + public function getAuthenticationData(string $username, string $password): U2fWebAuthnRequestInterface { $this->adminTokenService->createAdminAccessToken($username, $password); @@ -131,7 +131,7 @@ public function getAuthenticationData(string $username, string $password): U2FWe return $this->authnRequestInterfaceFactory->create( [ 'data' => [ - U2FWebAuthnRequestInterface::CREDENTIAL_REQUEST_OPTIONS_JSON => $json + U2fWebAuthnRequestInterface::CREDENTIAL_REQUEST_OPTIONS_JSON => $json ] ] ); diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php index 70217f41..1fab3fc1 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Configure.php @@ -10,8 +10,8 @@ use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\Exception\LocalizedException; -use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface; -use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterfaceFactory; +use Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterface; +use Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterfaceFactory; use Magento\TwoFactorAuth\Api\U2fKeyConfigureInterface; use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\AlertInterface; @@ -41,7 +41,7 @@ class Configure implements U2fKeyConfigureInterface private $configManager; /** - * @var U2FWebAuthnRequestInterfaceFactory + * @var U2fWebAuthnRequestInterfaceFactory */ private $authnInterfaceFactory; @@ -59,7 +59,7 @@ class Configure implements U2fKeyConfigureInterface * @param U2fKey $u2fKey * @param UserAuthenticator $userAuthenticator * @param UserConfigManagerInterface $configManager - * @param U2FWebAuthnRequestInterfaceFactory $authnInterfaceFactory + * @param U2fWebAuthnRequestInterfaceFactory $authnInterfaceFactory * @param AlertInterface $alert * @param Json $json */ @@ -67,7 +67,7 @@ public function __construct( U2fKey $u2fKey, UserAuthenticator $userAuthenticator, UserConfigManagerInterface $configManager, - U2FWebAuthnRequestInterfaceFactory $authnInterfaceFactory, + U2fWebAuthnRequestInterfaceFactory $authnInterfaceFactory, AlertInterface $alert, Json $json ) { @@ -82,7 +82,7 @@ public function __construct( /** * @inheritDoc */ - public function getRegistrationData(string $tfaToken): U2FWebAuthnRequestInterface + public function getRegistrationData(string $tfaToken): U2fWebAuthnRequestInterface { $user = $this->userAuthenticator->authenticateWithTokenAndProvider($tfaToken, U2fKey::CODE); $userId = (int)$user->getId(); @@ -98,7 +98,7 @@ public function getRegistrationData(string $tfaToken): U2FWebAuthnRequestInterfa return $this->authnInterfaceFactory->create( [ 'data' => [ - U2FWebAuthnRequestInterface::CREDENTIAL_REQUEST_OPTIONS_JSON => $this->json->serialize($data) + U2fWebAuthnRequestInterface::CREDENTIAL_REQUEST_OPTIONS_JSON => $this->json->serialize($data) ] ] ); diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php index 1c97ba13..59a35efc 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php @@ -10,7 +10,7 @@ use Magento\Framework\App\ObjectManager; use Magento\TestFramework\Bootstrap; -use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface; +use Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; use Magento\User\Model\UserFactory; @@ -192,7 +192,7 @@ public function testGetDataValidRequest() Bootstrap::ADMIN_PASSWORD ); - self::assertInstanceOf(U2FWebAuthnRequestInterface::class, $result); + self::assertInstanceOf(U2fWebAuthnRequestInterface::class, $result); self::assertSame(json_encode($data), $result->getCredentialRequestOptionsJson()); } diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php index 21322614..c4a808ef 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php @@ -9,7 +9,7 @@ namespace Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; use Magento\Framework\App\ObjectManager; -use Magento\TwoFactorAuth\Api\Data\U2FWebAuthnRequestInterface; +use Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; @@ -193,7 +193,7 @@ public function testGetRegistrationDataValidRequest() $this->tokenManager->issueFor($userId) ); - self::assertInstanceOf(U2FWebAuthnRequestInterface::class, $result); + self::assertInstanceOf(U2fWebAuthnRequestInterface::class, $result); self::assertSame(json_encode($data), $result->getCredentialRequestOptionsJson()); } diff --git a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/AuthyTest.php b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/AuthyTest.php index 031e9b28..5519830c 100644 --- a/TwoFactorAuth/Test/Unit/Model/Provider/Engine/AuthyTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Provider/Engine/AuthyTest.php @@ -60,7 +60,7 @@ public function getIsEnabledTestDataSet(): array */ public function testIsEnabled(?string $apiKey, bool $expected): void { - $this->serviceMock->method('getApiKey')->willReturn($apiKey); + $this->serviceMock->method('getApiKey')->willReturn((string)$apiKey); $this->assertEquals($expected, $this->model->isEnabled()); } diff --git a/TwoFactorAuth/etc/di.xml b/TwoFactorAuth/etc/di.xml index b902d311..89f1ed97 100644 --- a/TwoFactorAuth/etc/di.xml +++ b/TwoFactorAuth/etc/di.xml @@ -34,7 +34,7 @@ - + From fd7f443d0e432b16ef757a7914f2a35f5515bc4d Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Mon, 4 May 2020 11:43:03 -0500 Subject: [PATCH 33/58] MC-30537: Test automation with the new 2FA enabled by default --- .../Model/Data/Provider/Engine/Google/AuthenticateData.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php b/TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php index 8c7c1afd..e01cade1 100644 --- a/TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php +++ b/TwoFactorAuth/Model/Data/Provider/Engine/Google/AuthenticateData.php @@ -8,7 +8,7 @@ namespace Magento\TwoFactorAuth\Model\Data\Provider\Engine\Google; -use Magento\Framework\Api\AbstractExtensibleModel; +use Magento\Framework\Model\AbstractExtensibleModel; use Magento\TwoFactorAuth\Api\Data\GoogleAuthenticateExtensionInterface; use Magento\TwoFactorAuth\Api\Data\GoogleAuthenticateInterface; From a67c0977cd31f0a8d4960f9993f5b5d124f4228e Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 8 May 2020 16:54:15 -0500 Subject: [PATCH 34/58] MC-30537: Test automation with the new 2FA enabled by default - set secret should activate the provider --- TwoFactorAuth/Command/GoogleSecret.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/TwoFactorAuth/Command/GoogleSecret.php b/TwoFactorAuth/Command/GoogleSecret.php index e7944cc1..ba48fbab 100644 --- a/TwoFactorAuth/Command/GoogleSecret.php +++ b/TwoFactorAuth/Command/GoogleSecret.php @@ -8,6 +8,7 @@ namespace Magento\TwoFactorAuth\Command; use Magento\Framework\Exception\LocalizedException; +use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -35,20 +36,28 @@ class GoogleSecret extends Command */ private $google; + /** + * @var UserConfigManagerInterface + */ + private $configManager; + /** * @param UserFactory $userFactory * @param User $userResource * @param Google $google + * @param UserConfigManagerInterface $configManager */ public function __construct( UserFactory $userFactory, User $userResource, - Google $google + Google $google, + UserConfigManagerInterface $configManager ) { parent::__construct(); $this->userResource = $userResource; $this->userFactory = $userFactory; $this->google = $google; + $this->configManager = $configManager; } /** @@ -86,6 +95,13 @@ protected function execute(InputInterface $input, OutputInterface $output) } $this->google->setSharedSecret((int)$user->getId(), $secret); + $this->configManager->addProviderConfig( + (int)$user->getId(), + Google::CODE, + [ + UserConfigManagerInterface::ACTIVE_CONFIG_KEY => true + ] + ); $output->writeln((string)__('Google OTP secret has been set')); } From fa5fad6061496dd4073f7f22451985e9208a56ec Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Fri, 8 May 2020 17:25:17 -0500 Subject: [PATCH 35/58] MC-30537: Test automation with the new 2FA enabled by default - test coverage for command --- .../Integration/Command/GoogleSecretTest.php | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 TwoFactorAuth/Test/Integration/Command/GoogleSecretTest.php diff --git a/TwoFactorAuth/Test/Integration/Command/GoogleSecretTest.php b/TwoFactorAuth/Test/Integration/Command/GoogleSecretTest.php new file mode 100644 index 00000000..2fb8387a --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Command/GoogleSecretTest.php @@ -0,0 +1,97 @@ +command = $objectManager->get(GoogleSecret::class); + $this->configManager = $objectManager->get(UserConfigManagerInterface::class); + $this->userFactory = $objectManager->get(UserFactory::class); + $this->consoleInput = $this->createMock(InputInterface::class); + $this->consoleOutput = $this->createMock(OutputInterface::class); + $this->google = $objectManager->get(Google::class); + } + + /** + * @magentoDataFixture Magento/User/_files/user_with_role.php + */ + public function testSetSecret() + { + $user = $this->userFactory->create(); + $user->loadByUsername('adminUser'); + $userId = (int)$user->getId(); + + self::assertFalse( + $this->configManager->isProviderConfigurationActive( + $userId, + Google::CODE + ) + ); + $this->command->run( + new ArgvInput(['security:tfa:google:set-secret', 'adminUser', 'MFRGGZDF']), + $this->consoleOutput + ); + self::assertTrue( + $this->configManager->isProviderConfigurationActive( + $userId, + Google::CODE + ) + ); + self::assertSame( + 'MFRGGZDF', + $this->google->getSecretCode($user) + ); + } +} From 3f826974b0667d8ae5fc1afc6ba3fdb31d559640 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Mon, 11 May 2020 12:09:37 -0500 Subject: [PATCH 36/58] MC-30537: Test automation with the new 2FA enabled by default - Fix variable --- TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml index 99ccce2f..897069c3 100644 --- a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml +++ b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - + From a036cc7d28367ca8dc2878b799d44c298fb7f70e Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Tue, 12 May 2020 16:28:12 -0500 Subject: [PATCH 37/58] MC-30537: Test automation with the new 2FA enabled by default - MFTF fixes --- .../Controller/Adminhtml/Duo/Configure.php | 2 +- .../Observer/ControllerActionPredispatch.php | 2 +- TwoFactorAuth/Plugin/FirstAvailableMenu.php | 34 ++++++++++++++++ .../Test/Integration/_files/overrides.xml | 11 ++++++ .../AdminCreateRoleActionGroup.xml | 16 ++++++++ ...minFillUserRoleRequiredDataActionGroup.xml | 16 ++++++++ .../ActionGroup/AdminLoginActionGroup.xml | 12 ++++-- .../ActionGroup/CreateInvoiceActionGroup.xml | 18 +++++++++ TwoFactorAuth/Test/Mftf/Helper/FillOtp.php | 39 +++++++++++++++++++ .../Test/Mftf/Helper/SetSharedSecret.php | 39 +++++++++++++++++++ .../Mftf/Section/AdminEditRoleInfoSection.xml | 12 ++++++ ...sibleForAdminUserWithLimitedAccessTest.xml | 16 ++++++++ ...edAdminCatalogMassActionPermissionTest.xml | 19 +++++++++ ...estrictedUserRoleForProductRemovalTest.xml | 16 ++++++++ ...RestrictedUserRoleProductAttributeTest.xml | 16 ++++++++ .../ControllerActionPredispatch.php | 2 +- TwoFactorAuth/etc/di.xml | 4 ++ 17 files changed, 268 insertions(+), 6 deletions(-) create mode 100644 TwoFactorAuth/Plugin/FirstAvailableMenu.php create mode 100644 TwoFactorAuth/Test/Integration/_files/overrides.xml create mode 100644 TwoFactorAuth/Test/Mftf/ActionGroup/AdminCreateRoleActionGroup.xml create mode 100644 TwoFactorAuth/Test/Mftf/ActionGroup/AdminFillUserRoleRequiredDataActionGroup.xml create mode 100644 TwoFactorAuth/Test/Mftf/ActionGroup/CreateInvoiceActionGroup.xml create mode 100644 TwoFactorAuth/Test/Mftf/Helper/FillOtp.php create mode 100644 TwoFactorAuth/Test/Mftf/Helper/SetSharedSecret.php create mode 100644 TwoFactorAuth/Test/Mftf/Section/AdminEditRoleInfoSection.xml create mode 100644 TwoFactorAuth/Test/Mftf/Test/AdminBulkOperationsLogIsNotAccessibleForAdminUserWithLimitedAccessTest.xml create mode 100644 TwoFactorAuth/Test/Mftf/Test/RestrictedAdminCatalogMassActionPermissionTest.xml create mode 100644 TwoFactorAuth/Test/Mftf/Test/RestrictedUserRoleForProductRemovalTest.xml create mode 100644 TwoFactorAuth/Test/Mftf/Test/RestrictedUserRoleProductAttributeTest.xml diff --git a/TwoFactorAuth/Controller/Adminhtml/Duo/Configure.php b/TwoFactorAuth/Controller/Adminhtml/Duo/Configure.php index a02debaf..aa1c690f 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Duo/Configure.php +++ b/TwoFactorAuth/Controller/Adminhtml/Duo/Configure.php @@ -18,7 +18,7 @@ class Configure extends AbstractAction implements HttpGetActionInterface /** * @see _isAllowed() */ - const ADMIN_RESOURCE = 'Magento_TwoFactorAuth::config'; + const ADMIN_RESOURCE = 'Magento_TwoFactorAuth::tfa'; /** * @inheritdoc diff --git a/TwoFactorAuth/Observer/ControllerActionPredispatch.php b/TwoFactorAuth/Observer/ControllerActionPredispatch.php index 784a6fd4..53b29956 100644 --- a/TwoFactorAuth/Observer/ControllerActionPredispatch.php +++ b/TwoFactorAuth/Observer/ControllerActionPredispatch.php @@ -122,7 +122,7 @@ public function execute(Observer $observer) /** @var $controllerAction AbstractAction */ $controllerAction = $observer->getEvent()->getData('controller_action'); $this->action = $controllerAction; - $fullActionName = $controllerAction->getRequest()->getFullActionName(); + $fullActionName = $observer->getEvent()->getData('request')->getFullActionName(); $userId = $this->userContext->getUserId(); $this->tokenManager->readConfigToken(); diff --git a/TwoFactorAuth/Plugin/FirstAvailableMenu.php b/TwoFactorAuth/Plugin/FirstAvailableMenu.php new file mode 100644 index 00000000..b2841c9f --- /dev/null +++ b/TwoFactorAuth/Plugin/FirstAvailableMenu.php @@ -0,0 +1,34 @@ + + + + + diff --git a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminCreateRoleActionGroup.xml b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminCreateRoleActionGroup.xml new file mode 100644 index 00000000..4dfe727f --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminCreateRoleActionGroup.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminFillUserRoleRequiredDataActionGroup.xml b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminFillUserRoleRequiredDataActionGroup.xml new file mode 100644 index 00000000..c756a363 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminFillUserRoleRequiredDataActionGroup.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml index 897069c3..6f63d35e 100644 --- a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml +++ b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml @@ -8,8 +8,14 @@ - - - + + {{username}} + + + {{AdminGoogleTfaSection.tfaAuthCode}} + {{AdminGoogleTfaSection.confirm}} + {{AdminLoginMessagesSection.messageByType('error')}} + {{username}} + diff --git a/TwoFactorAuth/Test/Mftf/ActionGroup/CreateInvoiceActionGroup.xml b/TwoFactorAuth/Test/Mftf/ActionGroup/CreateInvoiceActionGroup.xml new file mode 100644 index 00000000..b0d68bc3 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/ActionGroup/CreateInvoiceActionGroup.xml @@ -0,0 +1,18 @@ + + + + + + {{AdminGoogleTfaSection.tfaAuthCode}} + {{AdminGoogleTfaSection.confirm}} + {{AdminLoginMessagesSection.messageByType('error')}} + {{username}} + + + diff --git a/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php b/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php new file mode 100644 index 00000000..6503d60b --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php @@ -0,0 +1,39 @@ +getModule('\\' . MagentoWebDriver::class); + try { + $webDriver->seeElementInDOM($errorMessageSelector); + // Login failed so don't handle 2fa + } catch (\Exception $e) { + $otp = $webDriver->getOTP(); + $webDriver->fillField($tfaAuthCodeSelector, $otp); + $webDriver->click($confirmSelector); + $webDriver->waitForPageLoad(); + } + } +} diff --git a/TwoFactorAuth/Test/Mftf/Helper/SetSharedSecret.php b/TwoFactorAuth/Test/Mftf/Helper/SetSharedSecret.php new file mode 100644 index 00000000..db62a552 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Helper/SetSharedSecret.php @@ -0,0 +1,39 @@ +getModule('\\' . MagentoWebDriver::class); + $credentialStore = CredentialStore::getInstance(); + if ($username !== getenv('MAGENTO_ADMIN_USERNAME')) { + $sharedSecret = $credentialStore->decryptSecretValue( + $credentialStore->getSecret('magento/tfa/OTP_SHARED_SECRET') + ); + $webDriver->magentoCLI( + 'security:tfa:google:set-secret ' . $username .' ' . $sharedSecret + ); + } + } +} diff --git a/TwoFactorAuth/Test/Mftf/Section/AdminEditRoleInfoSection.xml b/TwoFactorAuth/Test/Mftf/Section/AdminEditRoleInfoSection.xml new file mode 100644 index 00000000..1d14b3d2 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Section/AdminEditRoleInfoSection.xml @@ -0,0 +1,12 @@ + + + +
+ +
+
diff --git a/TwoFactorAuth/Test/Mftf/Test/AdminBulkOperationsLogIsNotAccessibleForAdminUserWithLimitedAccessTest.xml b/TwoFactorAuth/Test/Mftf/Test/AdminBulkOperationsLogIsNotAccessibleForAdminUserWithLimitedAccessTest.xml new file mode 100644 index 00000000..c5e14532 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Test/AdminBulkOperationsLogIsNotAccessibleForAdminUserWithLimitedAccessTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/TwoFactorAuth/Test/Mftf/Test/RestrictedAdminCatalogMassActionPermissionTest.xml b/TwoFactorAuth/Test/Mftf/Test/RestrictedAdminCatalogMassActionPermissionTest.xml new file mode 100644 index 00000000..d6cdcf7c --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Test/RestrictedAdminCatalogMassActionPermissionTest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/TwoFactorAuth/Test/Mftf/Test/RestrictedUserRoleForProductRemovalTest.xml b/TwoFactorAuth/Test/Mftf/Test/RestrictedUserRoleForProductRemovalTest.xml new file mode 100644 index 00000000..93211094 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Test/RestrictedUserRoleForProductRemovalTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/TwoFactorAuth/Test/Mftf/Test/RestrictedUserRoleProductAttributeTest.xml b/TwoFactorAuth/Test/Mftf/Test/RestrictedUserRoleProductAttributeTest.xml new file mode 100644 index 00000000..cd828761 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Test/RestrictedUserRoleProductAttributeTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/TwoFactorAuth/TestFramework/ControllerActionPredispatch.php b/TwoFactorAuth/TestFramework/ControllerActionPredispatch.php index dc4353e4..6e484492 100644 --- a/TwoFactorAuth/TestFramework/ControllerActionPredispatch.php +++ b/TwoFactorAuth/TestFramework/ControllerActionPredispatch.php @@ -19,7 +19,7 @@ public function execute(Observer $observer) { /** @var $controllerAction AbstractAction */ $controllerAction = $observer->getEvent()->getData('controller_action'); - if (class_exists('Magento\TestFramework\Request') + if (method_exists($controllerAction, 'getRequest') && $controllerAction->getRequest() instanceof \Magento\TestFramework\Request && !$controllerAction->getRequest()->getParam('tfa_enabled') ) { diff --git a/TwoFactorAuth/etc/di.xml b/TwoFactorAuth/etc/di.xml index 89f1ed97..0b934e87 100644 --- a/TwoFactorAuth/etc/di.xml +++ b/TwoFactorAuth/etc/di.xml @@ -83,6 +83,10 @@
+ + + + Magento\TwoFactorAuth\Model\Provider\Engine\Google From 9633836ab43c4afdbc76f6defb86288b3c0565d8 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Wed, 13 May 2020 09:34:30 -0500 Subject: [PATCH 38/58] MC-30537: Test automation with the new 2FA enabled by default - Test fixes --- .../Test/Integration/Controller/SoapTest.php | 65 +++++++++++++++++++ .../Test/Integration/_files/overrides.xml | 5 +- .../AdminCreateRoleActionGroup.xml | 2 +- .../Test/Mftf/Helper/SetSharedSecret.php | 10 ++- .../Mftf/Section/AdminEditRoleInfoSection.xml | 1 + .../Test/AdminConfigurationPermissionTest.xml | 16 +++++ 6 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 TwoFactorAuth/Test/Integration/Controller/SoapTest.php create mode 100644 TwoFactorAuth/Test/Mftf/Test/AdminConfigurationPermissionTest.xml diff --git a/TwoFactorAuth/Test/Integration/Controller/SoapTest.php b/TwoFactorAuth/Test/Integration/Controller/SoapTest.php new file mode 100644 index 00000000..018f88c5 --- /dev/null +++ b/TwoFactorAuth/Test/Integration/Controller/SoapTest.php @@ -0,0 +1,65 @@ +objectManager = Bootstrap::getObjectManager(); + $this->soapController = $this->objectManager->get(Soap::class); + } + + /** + * Get the public wsdl with anonymous credentials + * + * @return void + */ + public function testDispatchWsdlRequest(): void + { + $request = $this->objectManager->get(Request::class); + $request->setParam(Server::REQUEST_PARAM_LIST_WSDL, true); + $response = $this->soapController->dispatch($request); + $decodedWsdl = json_decode($response->getContent(), true); + + $this->assertWsdlServices($decodedWsdl); + } + + /** + * Check wsdl available methods. + * + * @param array $decodedWsdl + * + * @return void + */ + protected function assertWsdlServices(array $decodedWsdl): void + { + $this->assertArrayHasKey('customerAccountManagementV1', $decodedWsdl); + $this->assertArrayNotHasKey('integrationAdminTokenServiceV1', $decodedWsdl); + $this->assertArrayHasKey('twoFactorAuthAdminTokenServiceV1', $decodedWsdl); + } +} diff --git a/TwoFactorAuth/Test/Integration/_files/overrides.xml b/TwoFactorAuth/Test/Integration/_files/overrides.xml index 56a5deb2..6199cb17 100644 --- a/TwoFactorAuth/Test/Integration/_files/overrides.xml +++ b/TwoFactorAuth/Test/Integration/_files/overrides.xml @@ -5,7 +5,6 @@ * See COPYING.txt for license details. */ --> - - + + diff --git a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminCreateRoleActionGroup.xml b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminCreateRoleActionGroup.xml index 4dfe727f..389480a8 100644 --- a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminCreateRoleActionGroup.xml +++ b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminCreateRoleActionGroup.xml @@ -9,7 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - + diff --git a/TwoFactorAuth/Test/Mftf/Helper/SetSharedSecret.php b/TwoFactorAuth/Test/Mftf/Helper/SetSharedSecret.php index db62a552..4d03d013 100644 --- a/TwoFactorAuth/Test/Mftf/Helper/SetSharedSecret.php +++ b/TwoFactorAuth/Test/Mftf/Helper/SetSharedSecret.php @@ -31,9 +31,13 @@ public function execute(string $username): void $sharedSecret = $credentialStore->decryptSecretValue( $credentialStore->getSecret('magento/tfa/OTP_SHARED_SECRET') ); - $webDriver->magentoCLI( - 'security:tfa:google:set-secret ' . $username .' ' . $sharedSecret - ); + try { + $webDriver->magentoCLI( + 'security:tfa:google:set-secret ' . $username .' ' . $sharedSecret + ); + } catch (\Throwable $exception) { + // Some tests intentionally use bad credentials. + } } } } diff --git a/TwoFactorAuth/Test/Mftf/Section/AdminEditRoleInfoSection.xml b/TwoFactorAuth/Test/Mftf/Section/AdminEditRoleInfoSection.xml index 1d14b3d2..38c36b9f 100644 --- a/TwoFactorAuth/Test/Mftf/Section/AdminEditRoleInfoSection.xml +++ b/TwoFactorAuth/Test/Mftf/Section/AdminEditRoleInfoSection.xml @@ -8,5 +8,6 @@
+
diff --git a/TwoFactorAuth/Test/Mftf/Test/AdminConfigurationPermissionTest.xml b/TwoFactorAuth/Test/Mftf/Test/AdminConfigurationPermissionTest.xml new file mode 100644 index 00000000..87588b48 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Test/AdminConfigurationPermissionTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + From 01b6164088f0004527cb33f617b0c5e05a5a35c6 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Wed, 13 May 2020 10:06:00 -0500 Subject: [PATCH 39/58] MC-30537: Test automation with the new 2FA enabled by default --- ...AdminCreateUserRoleWithReportsActionGroup.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 TwoFactorAuth/Test/Mftf/ActionGroup/AdminCreateUserRoleWithReportsActionGroup.xml diff --git a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminCreateUserRoleWithReportsActionGroup.xml b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminCreateUserRoleWithReportsActionGroup.xml new file mode 100644 index 00000000..905641de --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminCreateUserRoleWithReportsActionGroup.xml @@ -0,0 +1,16 @@ + + + + + + + + + + From a3dbb0174db1ac197ef8e33400e419925e7783f3 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Wed, 13 May 2020 10:07:53 -0500 Subject: [PATCH 40/58] MC-30537: Test automation with the new 2FA enabled by default --- TwoFactorAuth/composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/TwoFactorAuth/composer.json b/TwoFactorAuth/composer.json index e9ba41e5..46cc7ca8 100644 --- a/TwoFactorAuth/composer.json +++ b/TwoFactorAuth/composer.json @@ -1,6 +1,5 @@ { "name": "magento/module-two-factor-auth", - "version": "1.0.0", "description": "Two Factor Authentication module for Magento2", "require": { "php": "~7.3.0||~7.4.0", From 4b3406d9698ef6f12bd9cf998035a208360d4938 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Wed, 13 May 2020 11:15:07 -0500 Subject: [PATCH 41/58] MC-30537: Test automation with the new 2FA enabled by default --- .../ActionGroup/CreateInvoiceActionGroup.xml | 18 ------------------ ...eviewOrderWithOnlyReportsPermissionTest.xml | 16 ++++++++++++++++ .../Mftf/Test/StorefrontInvoiceFilterTest.xml | 16 ++++++++++++++++ 3 files changed, 32 insertions(+), 18 deletions(-) delete mode 100644 TwoFactorAuth/Test/Mftf/ActionGroup/CreateInvoiceActionGroup.xml create mode 100644 TwoFactorAuth/Test/Mftf/Test/AdminReviewOrderWithOnlyReportsPermissionTest.xml create mode 100644 TwoFactorAuth/Test/Mftf/Test/StorefrontInvoiceFilterTest.xml diff --git a/TwoFactorAuth/Test/Mftf/ActionGroup/CreateInvoiceActionGroup.xml b/TwoFactorAuth/Test/Mftf/ActionGroup/CreateInvoiceActionGroup.xml deleted file mode 100644 index b0d68bc3..00000000 --- a/TwoFactorAuth/Test/Mftf/ActionGroup/CreateInvoiceActionGroup.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - {{AdminGoogleTfaSection.tfaAuthCode}} - {{AdminGoogleTfaSection.confirm}} - {{AdminLoginMessagesSection.messageByType('error')}} - {{username}} - - - diff --git a/TwoFactorAuth/Test/Mftf/Test/AdminReviewOrderWithOnlyReportsPermissionTest.xml b/TwoFactorAuth/Test/Mftf/Test/AdminReviewOrderWithOnlyReportsPermissionTest.xml new file mode 100644 index 00000000..394ae495 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Test/AdminReviewOrderWithOnlyReportsPermissionTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/TwoFactorAuth/Test/Mftf/Test/StorefrontInvoiceFilterTest.xml b/TwoFactorAuth/Test/Mftf/Test/StorefrontInvoiceFilterTest.xml new file mode 100644 index 00000000..a3b3e593 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Test/StorefrontInvoiceFilterTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + From a16556590954d45f972d4368afcb39340fdb839d Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Wed, 13 May 2020 12:38:02 -0500 Subject: [PATCH 42/58] MC-30537: Test automation with the new 2FA enabled by default --- TwoFactorAuth/Test/Integration/_files/overrides.xml | 1 + TwoFactorAuth/Test/Mftf/Helper/FillOtp.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/TwoFactorAuth/Test/Integration/_files/overrides.xml b/TwoFactorAuth/Test/Integration/_files/overrides.xml index 6199cb17..c6109823 100644 --- a/TwoFactorAuth/Test/Integration/_files/overrides.xml +++ b/TwoFactorAuth/Test/Integration/_files/overrides.xml @@ -7,4 +7,5 @@ --> + diff --git a/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php b/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php index 6503d60b..7e966af7 100644 --- a/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php +++ b/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php @@ -23,7 +23,8 @@ class FillOtp extends Helper * @param string $confirmSelector * @param string $errorMessageSelector */ - public function execute(string $tfaAuthCodeSelector, string $confirmSelector, string $errorMessageSelector): void { + public function execute(string $tfaAuthCodeSelector, string $confirmSelector, string $errorMessageSelector): void + { /** @var MagentoWebDriver $webDriver */ $webDriver = $this->getModule('\\' . MagentoWebDriver::class); try { From 8aee78735a029add6d94eb8d928e252353fcda4e Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Wed, 13 May 2020 15:16:02 -0500 Subject: [PATCH 43/58] MC-30537: Test automation with the new 2FA enabled by default --- TwoFactorAuth/Test/Mftf/Helper/FillOtp.php | 1 + 1 file changed, 1 insertion(+) diff --git a/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php b/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php index 7e966af7..5f6d1036 100644 --- a/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php +++ b/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php @@ -32,6 +32,7 @@ public function execute(string $tfaAuthCodeSelector, string $confirmSelector, st // Login failed so don't handle 2fa } catch (\Exception $e) { $otp = $webDriver->getOTP(); + $webDriver->waitForElementVisible($tfaAuthCodeSelector); $webDriver->fillField($tfaAuthCodeSelector, $otp); $webDriver->click($confirmSelector); $webDriver->waitForPageLoad(); From 48e46f744abc02003e4a01039db4ae2741676ef7 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 14 May 2020 08:52:54 -0500 Subject: [PATCH 44/58] MC-30537: Test automation with the new 2FA enabled by default --- TwoFactorAuth/Test/Mftf/Helper/FillOtp.php | 1 + 1 file changed, 1 insertion(+) diff --git a/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php b/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php index 5f6d1036..3135b8fe 100644 --- a/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php +++ b/TwoFactorAuth/Test/Mftf/Helper/FillOtp.php @@ -32,6 +32,7 @@ public function execute(string $tfaAuthCodeSelector, string $confirmSelector, st // Login failed so don't handle 2fa } catch (\Exception $e) { $otp = $webDriver->getOTP(); + $webDriver->waitForPageLoad(); $webDriver->waitForElementVisible($tfaAuthCodeSelector); $webDriver->fillField($tfaAuthCodeSelector, $otp); $webDriver->click($confirmSelector); From 5d0f9dacfd72718b7a338017d7543cff4759c457 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 14 May 2020 12:06:03 -0500 Subject: [PATCH 45/58] MC-30537: Test automation with the new 2FA enabled by default --- .../Test/AdminLoginAfterJSMinificationTest.xml | 16 ++++++++++++++++ ...LevelAndOrderItemForAdditionalWebsiteTest.xml | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 TwoFactorAuth/Test/Mftf/Test/AdminLoginAfterJSMinificationTest.xml create mode 100644 TwoFactorAuth/Test/Mftf/Test/StorefrontGiftWrappingCanBeAppliedOnOrderLevelAndOrderItemForAdditionalWebsiteTest.xml diff --git a/TwoFactorAuth/Test/Mftf/Test/AdminLoginAfterJSMinificationTest.xml b/TwoFactorAuth/Test/Mftf/Test/AdminLoginAfterJSMinificationTest.xml new file mode 100644 index 00000000..883f3b58 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Test/AdminLoginAfterJSMinificationTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/TwoFactorAuth/Test/Mftf/Test/StorefrontGiftWrappingCanBeAppliedOnOrderLevelAndOrderItemForAdditionalWebsiteTest.xml b/TwoFactorAuth/Test/Mftf/Test/StorefrontGiftWrappingCanBeAppliedOnOrderLevelAndOrderItemForAdditionalWebsiteTest.xml new file mode 100644 index 00000000..9141a62e --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Test/StorefrontGiftWrappingCanBeAppliedOnOrderLevelAndOrderItemForAdditionalWebsiteTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + From e3e3d9d37d85f81455b8e21046862b1cddd021ae Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Mon, 18 May 2020 09:40:52 -0500 Subject: [PATCH 46/58] MC-30537: Test automation with the new 2FA enabled by default --- TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml index 6f63d35e..97bd9001 100644 --- a/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml +++ b/TwoFactorAuth/Test/Mftf/ActionGroup/AdminLoginActionGroup.xml @@ -15,7 +15,6 @@ {{AdminGoogleTfaSection.tfaAuthCode}} {{AdminGoogleTfaSection.confirm}} {{AdminLoginMessagesSection.messageByType('error')}} - {{username}} From 15233ae017729466cb135f739b1912ebd4a0fd54 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Mon, 18 May 2020 14:50:51 -0500 Subject: [PATCH 47/58] MC-30537: Test automation with the new 2FA enabled by default --- .../Setup/Patch/Data/{ResetU2FConfig.php => ResetU2fConfig.php} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename TwoFactorAuth/Setup/Patch/Data/{ResetU2FConfig.php => ResetU2fConfig.php} (97%) diff --git a/TwoFactorAuth/Setup/Patch/Data/ResetU2FConfig.php b/TwoFactorAuth/Setup/Patch/Data/ResetU2fConfig.php similarity index 97% rename from TwoFactorAuth/Setup/Patch/Data/ResetU2FConfig.php rename to TwoFactorAuth/Setup/Patch/Data/ResetU2fConfig.php index 9b0f041a..a6c911e8 100644 --- a/TwoFactorAuth/Setup/Patch/Data/ResetU2FConfig.php +++ b/TwoFactorAuth/Setup/Patch/Data/ResetU2fConfig.php @@ -18,7 +18,7 @@ /** * Reset the U2f data due to rewrite */ -class ResetU2FConfig implements DataPatchInterface +class ResetU2fConfig implements DataPatchInterface { /** * @var ModuleDataSetupInterface From f86acf7773590973f65780f4f4ce7cd41e824e7f Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Sat, 23 May 2020 20:39:00 -0500 Subject: [PATCH 48/58] MC-30537: Test automation with the new 2FA enabled by default - Static fixes --- .../Api/Data/AdminTokenResponseInterface.php | 1 + TwoFactorAuth/Api/Data/CountryInterface.php | 11 +++++++- TwoFactorAuth/Api/TfaInterface.php | 14 +++++++++- .../Controller/Adminhtml/Tfa/AccessDenied.php | 8 ++++++ .../Model/AdminAccessTokenService.php | 2 ++ TwoFactorAuth/Model/Data/Country.php | 26 +++++++++---------- .../Model/Provider/Engine/Authy/OneTouch.php | 2 ++ .../Model/Provider/Engine/Authy/Token.php | 2 ++ .../Provider/Engine/Authy/Verification.php | 2 +- .../Model/Provider/Engine/U2fKey/WebAuthn.php | 13 ++++++++++ TwoFactorAuth/Model/TfatActions.php | 1 + TwoFactorAuth/Model/UserAuthenticator.php | 1 + .../Model/UserConfig/SignedTokenManager.php | 1 + .../Setup/Patch/Data/ResetU2fConfig.php | 2 ++ .../Test/Api/AdminIntegrationTokenTest.php | 4 ++- TwoFactorAuth/Test/Api/GoogleActivateTest.php | 4 ++- .../Test/Api/GoogleAuthenticateTest.php | 3 ++- .../Test/Api/GoogleConfigureTest.php | 4 ++- .../Integration/Command/GoogleSecretTest.php | 3 ++- .../Adminhtml/Authy/ConfigureTest.php | 5 ++++ .../Adminhtml/Authy/ConfigurepostTest.php | 5 ++++ .../Authy/ConfigureverifypostTest.php | 5 ++++ .../Controller/Adminhtml/Duo/AuthTest.php | 5 ++++ .../Controller/Adminhtml/Duo/AuthpostTest.php | 5 ++++ .../Adminhtml/Google/ConfigureTest.php | 5 ++++ .../Adminhtml/Google/ConfigurepostTest.php | 5 ++++ .../Adminhtml/Tfa/ConfigureTest.php | 6 ++++- .../Adminhtml/Tfa/ConfigurepostTest.php | 5 ++++ .../Controller/Adminhtml/Tfa/IndexTest.php | 5 ++++ .../Adminhtml/Tfa/RequestconfigTest.php | 16 +++++++++--- .../Adminhtml/U2f/ConfigureTest.php | 5 ++++ .../Adminhtml/U2f/ConfigurepostTest.php | 5 ++++ .../Test/Integration/Controller/SoapTest.php | 2 +- .../ControllerActionPredispatchTest.php | 10 ++++++- .../Engine/Authy/AuthenticateTest.php | 3 ++- .../Provider/Engine/Authy/ConfigureTest.php | 4 ++- .../Engine/DuoSecurity/AuthenticateTest.php | 3 ++- .../Engine/DuoSecurity/ConfigureTest.php | 3 ++- .../Engine/U2fKey/AuthenticateTest.php | 3 ++- .../Provider/Engine/U2fKey/ConfigureTest.php | 3 ++- .../Provider/Engine/U2fKey/SessionTest.php | 3 +-- .../Test/Integration/Model/TfaSessionTest.php | 2 +- .../UserConfigRequestManagerTest.php | 5 ++++ .../UserConfigTokenManagerTest.php | 5 ++++ .../Unit/Model/Provider/Engine/U2fKeyTest.php | 3 ++- .../ControllerActionPredispatch.php | 5 ++++ .../TestCase/AbstractBackendController.php | 5 ++++ .../AbstractConfigureBackendController.php | 5 ++++ TwoFactorAuth/composer.json | 4 ++- .../view/adminhtml/web/js/google/auth.js | 2 +- 50 files changed, 212 insertions(+), 39 deletions(-) diff --git a/TwoFactorAuth/Api/Data/AdminTokenResponseInterface.php b/TwoFactorAuth/Api/Data/AdminTokenResponseInterface.php index 22a09b26..3abc222d 100644 --- a/TwoFactorAuth/Api/Data/AdminTokenResponseInterface.php +++ b/TwoFactorAuth/Api/Data/AdminTokenResponseInterface.php @@ -86,6 +86,7 @@ public function getExtensionAttributes(): ?AdminTokenResponseExtensionInterface; /** * Set an extension attributes object + * * @param \Magento\TwoFactorAuth\Api\Data\AdminTokenResponseExtensionInterface $extensionAttributes * @return void */ diff --git a/TwoFactorAuth/Api/Data/CountryInterface.php b/TwoFactorAuth/Api/Data/CountryInterface.php index a3744cdd..b423a851 100644 --- a/TwoFactorAuth/Api/Data/CountryInterface.php +++ b/TwoFactorAuth/Api/Data/CountryInterface.php @@ -36,48 +36,56 @@ interface CountryInterface extends ExtensibleDataInterface /** * Get value for tfa_country_codes_id + * * @return int */ public function getId(): int; /** * Set value for country_id + * * @param int $value */ - public function setId(int $value): void; + public function setId($value): void; /** * Get value for code + * * @return string */ public function getCode(): string; /** * Set value for code + * * @param string $value */ public function setCode(string $value): void; /** * Get value for name + * * @return string */ public function getName(): string; /** * Set value for name + * * @param string $value */ public function setName(string $value): void; /** * Get value for dial_code + * * @return string */ public function getDialCode(): string; /** * Set value for dial_code + * * @param string $value */ public function setDialCode(string $value): void; @@ -93,6 +101,7 @@ public function getExtensionAttributes(): ?CountryExtensionInterface; /** * Set an extension attributes object + * * @param \Magento\TwoFactorAuth\Api\Data\CountryExtensionInterface $extensionAttributes */ public function setExtensionAttributes( diff --git a/TwoFactorAuth/Api/TfaInterface.php b/TwoFactorAuth/Api/TfaInterface.php index e9977944..58394f62 100644 --- a/TwoFactorAuth/Api/TfaInterface.php +++ b/TwoFactorAuth/Api/TfaInterface.php @@ -31,6 +31,7 @@ public function isEnabled(): bool; /** * Get provider by code + * * @param string $providerCode * @param bool $onlyEnabled = true * @return ProviderInterface|null @@ -39,12 +40,14 @@ public function getProvider(string $providerCode, bool $onlyEnabled = true): ?Pr /** * Retrieve forced providers list + * * @return \Magento\TwoFactorAuth\Api\ProviderInterface[] */ public function getForcedProviders(): array; /** * Get a user provider + * * @param int $userId * @return \Magento\TwoFactorAuth\Api\ProviderInterface[] */ @@ -52,32 +55,36 @@ public function getUserProviders(int $userId): array; /** * Get a list of providers + * * @return \Magento\TwoFactorAuth\Api\ProviderInterface[] */ public function getAllProviders(): array; /** * Get a list of providers + * * @param string $code * @return \Magento\TwoFactorAuth\Api\ProviderInterface|null */ - public function getProviderByCode(string $code): ?ProviderInterface; /** * Get a list of providers + * * @return \Magento\TwoFactorAuth\Api\ProviderInterface[] */ public function getAllEnabledProviders(): array; /** * Get allowed URLs + * * @return array */ public function getAllowedUrls(): array; /** * Returns a list of providers to configure/enroll + * * @param int $userId * @return \Magento\TwoFactorAuth\Api\ProviderInterface[] */ @@ -85,6 +92,7 @@ public function getProvidersToActivate(int $userId): array; /** * Return true if a provider is allowed for a given user + * * @param int $userId * @param string $providerCode * @return bool @@ -93,6 +101,7 @@ public function getProviderIsAllowed(int $userId, string $providerCode): bool; /** * Get default provider code + * * @param int $userId * @return string */ @@ -100,6 +109,7 @@ public function getDefaultProviderCode(int $userId): string; /** * Set default provider code + * * @param int $userId * @param string $providerCode * @return bool @@ -108,6 +118,7 @@ public function setDefaultProviderCode(int $userId, string $providerCode): bool; /** * Set providers + * * @param int $userId * @param string $providersCodes * @return bool @@ -116,6 +127,7 @@ public function setProvidersCodes(int $userId, string $providersCodes): bool; /** * Reset default provider code + * * @param int $userId * @param string $providerCode * @return bool diff --git a/TwoFactorAuth/Controller/Adminhtml/Tfa/AccessDenied.php b/TwoFactorAuth/Controller/Adminhtml/Tfa/AccessDenied.php index e064bcc1..bce6590a 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Tfa/AccessDenied.php +++ b/TwoFactorAuth/Controller/Adminhtml/Tfa/AccessDenied.php @@ -23,4 +23,12 @@ public function execute() { return $this->resultFactory->create(ResultFactory::TYPE_PAGE); } + + /** + * @inheritDoc + */ + protected function _isAllowed() + { + return true; + } } diff --git a/TwoFactorAuth/Model/AdminAccessTokenService.php b/TwoFactorAuth/Model/AdminAccessTokenService.php index 4a385dea..bac1f3a0 100644 --- a/TwoFactorAuth/Model/AdminAccessTokenService.php +++ b/TwoFactorAuth/Model/AdminAccessTokenService.php @@ -90,6 +90,7 @@ public function createAdminAccessToken($username, $password): string } if (!$this->configRequestManager->isConfigurationRequiredFor($userId)) { + // @codingStandardsIgnoreStart throw new LocalizedException( __( 'Please use the 2fa provider-specific endpoints to obtain a token.', @@ -98,6 +99,7 @@ public function createAdminAccessToken($username, $password): string ] ) ); + // @codingStandardsIgnoreEnd } elseif (empty($this->tfa->getUserProviders($userId))) { // It is expected that available 2fa providers are selected via db or admin ui throw new LocalizedException( diff --git a/TwoFactorAuth/Model/Data/Country.php b/TwoFactorAuth/Model/Data/Country.php index 35d30aa8..35c43b79 100644 --- a/TwoFactorAuth/Model/Data/Country.php +++ b/TwoFactorAuth/Model/Data/Country.php @@ -7,17 +7,17 @@ namespace Magento\TwoFactorAuth\Model\Data; -use Magento\Framework\Api\AbstractExtensibleObject; +use Magento\Framework\Model\AbstractExtensibleModel; use Magento\TwoFactorAuth\Api\Data\CountryExtensionInterface; use Magento\TwoFactorAuth\Api\Data\CountryInterface; /** * @inheritDoc */ -class Country extends AbstractExtensibleObject implements CountryInterface +class Country extends AbstractExtensibleModel implements CountryInterface { /** - * {@inheritdoc} + * @inheritdoc */ public function getId(): int { @@ -25,15 +25,15 @@ public function getId(): int } /** - * {@inheritdoc} + * @inheritdoc */ - public function setId(int $value): void + public function setId($value): void { $this->setData(self::ID, $value); } /** - * {@inheritdoc} + * @inheritdoc */ public function getCode(): string { @@ -41,7 +41,7 @@ public function getCode(): string } /** - * {@inheritdoc} + * @inheritdoc */ public function setCode(string $value): void { @@ -49,7 +49,7 @@ public function setCode(string $value): void } /** - * {@inheritdoc} + * @inheritdoc */ public function getName(): string { @@ -57,7 +57,7 @@ public function getName(): string } /** - * {@inheritdoc} + * @inheritdoc */ public function setName(string $value): void { @@ -65,7 +65,7 @@ public function setName(string $value): void } /** - * {@inheritdoc} + * @inheritdoc */ public function getDialCode(): string { @@ -73,7 +73,7 @@ public function getDialCode(): string } /** - * {@inheritdoc} + * @inheritdoc */ public function setDialCode(string $value): void { @@ -81,7 +81,7 @@ public function setDialCode(string $value): void } /** - * {@inheritdoc} + * @inheritdoc */ public function getExtensionAttributes(): ?CountryExtensionInterface { @@ -89,7 +89,7 @@ public function getExtensionAttributes(): ?CountryExtensionInterface } /** - * {@inheritdoc} + * @inheritdoc */ public function setExtensionAttributes(CountryExtensionInterface $extensionAttributes): void { diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/OneTouch.php b/TwoFactorAuth/Model/Provider/Engine/Authy/OneTouch.php index 99867ec5..84c03d08 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/OneTouch.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/OneTouch.php @@ -84,6 +84,7 @@ public function __construct( /** * Request one-touch + * * @param UserInterface $user * @throws LocalizedException */ @@ -120,6 +121,7 @@ public function request(UserInterface $user): void /** * Verify one-touch + * * @param UserInterface $user * @return string * @throws LocalizedException diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Token.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Token.php index 2397c0ef..9dd58cb7 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Token.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Token.php @@ -60,6 +60,7 @@ public function __construct( /** * Request a token + * * @param UserInterface $user * @param string $via * @throws LocalizedException @@ -91,6 +92,7 @@ public function request(UserInterface $user, string $via): void /** * Return true on token validation + * * @param UserInterface $user * @param DataObject $request * @return bool diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php index fde541d0..e355b42c 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php @@ -73,7 +73,7 @@ public function __construct( * @param string $country * @param string $phoneNumber * @param string $method - * @param array &$response + * @param array &$response * @throws LocalizedException */ public function request( diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php index 501f5713..d2d3f31c 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php @@ -83,6 +83,7 @@ public function assertCredentialDataIsValid( // Steps 7-9 if (rtrim(strtr(base64_encode($this->convertArrayToBytes($originalChallenge)), '+/', '-_'), '=') !== $credentialData['response']['clientData']['challenge'] + // phpcs:ignore Magento2.Functions.DiscouragedFunction || $domain !== parse_url($credentialData['response']['clientData']['origin'], \PHP_URL_HOST) || $credentialData['response']['clientData']['type'] !== 'webauthn.get' ) { @@ -92,6 +93,7 @@ public function assertCredentialDataIsValid( // Step 10 not applicable // @see https://www.w3.org/TR/webauthn/#sec-authenticator-data + // phpcs:ignore Magento2.Functions.DiscouragedFunction $authenticatorDataBytes = base64_decode($credentialData['response']['authenticatorData']); $attestationObject = [ 'rpIdHash' => substr($authenticatorDataBytes, 0, 32), @@ -111,6 +113,7 @@ public function assertCredentialDataIsValid( $clientDataSha256 = hash('sha256', $credentialData['response']['clientDataJSON'], true); $isValidSignature = openssl_verify( $authenticatorDataBytes . $clientDataSha256, + // phpcs:ignore Magento2.Functions.DiscouragedFunction base64_decode($credentialData['response']['signature']), $key['key'], OPENSSL_ALGO_SHA256 @@ -142,6 +145,7 @@ public function getAuthenticateData(array $publicKeys): array foreach ($publicKeys as $key) { $allowedCredentials[] = [ 'type' => 'public-key', + // phpcs:ignore Magento2.Functions.DiscouragedFunction 'id' => $this->convertBytesToArray(base64_decode($key['id'])) ]; } @@ -230,6 +234,7 @@ public function getPublicKeyFromRegistrationData(array $data): array if (rtrim(strtr(base64_encode($this->convertArrayToBytes($data['challenge'])), '+/', '-_'), '=') !== $credentialData['response']['clientData']['challenge'] + // phpcs:ignore Magento2.Functions.DiscouragedFunction || $domain !== parse_url($credentialData['response']['clientData']['origin'], \PHP_URL_HOST) || $credentialData['response']['clientData']['type'] !== 'webauthn.create' ) { @@ -239,8 +244,11 @@ public function getPublicKeyFromRegistrationData(array $data): array if (empty($credentialData['response']['attestationObject']) || empty($credentialData['id'])) { throw new ValidationException(__('Invalid U2F key data')); } + // phpcs:ignore Magento2.Functions.DiscouragedFunction $byteString = base64_decode($credentialData['response']['attestationObject']); + //@codingStandardsIgnoreStart $attestationObject = CBOREncoder::decode($byteString); + //@codingStandardsIgnoreEnd if (empty($attestationObject['fmt']) || empty($attestationObject['authData']) ) { @@ -278,6 +286,7 @@ public function getPublicKeyFromRegistrationData(array $data): array $attestationObject['attestationData']['keyBytes'] = $this->COSEECDHAtoPKCS($cborPublicKey); if (empty($attestationObject['attestationData']['keyBytes']) + // phpcs:ignore Magento2.Functions.DiscouragedFunction || $attestationObject['attestationData']['credId'] !== base64_decode($credentialData['id']) ) { throw new ValidationException(__('Invalid U2F key data')); @@ -317,6 +326,7 @@ private function convertArrayToBytes(array $bytes): string $byteString = ''; foreach ($bytes as $byte) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction $byteString .= chr((int)$byte); } @@ -329,10 +339,13 @@ private function convertArrayToBytes(array $bytes): string * @param string $binary * @return string|null * @throws \Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function COSEECDHAtoPKCS(string $binary): ?string { + //@codingStandardsIgnoreStart $cosePubKey = CBOREncoder::decode($binary); + //@codingStandardsIgnoreEnd // Sections 7.1 and 13.1.1 of @see https://tools.ietf.org/html/rfc8152 if (!isset($cosePubKey[3]) diff --git a/TwoFactorAuth/Model/TfatActions.php b/TwoFactorAuth/Model/TfatActions.php index f95ddda0..c9856f0b 100644 --- a/TwoFactorAuth/Model/TfatActions.php +++ b/TwoFactorAuth/Model/TfatActions.php @@ -78,6 +78,7 @@ public function getProvidersToActivate(string $tfaToken): array */ private function validateTfat(string $tfat): int { + // phpcs:ignore Magento2.Functions.DiscouragedFunction ['user_id' => $userId] = $this->json->unserialize(explode('.', base64_decode($tfat))[0]); if (!$this->tokenManager->isValidFor($userId, $tfat)) { throw new AuthorizationException(__('Invalid token.')); diff --git a/TwoFactorAuth/Model/UserAuthenticator.php b/TwoFactorAuth/Model/UserAuthenticator.php index 73b1d89c..14152961 100644 --- a/TwoFactorAuth/Model/UserAuthenticator.php +++ b/TwoFactorAuth/Model/UserAuthenticator.php @@ -80,6 +80,7 @@ public function __construct( public function authenticateWithTokenAndProvider(string $tfaToken, string $providerCode): User { try { + // phpcs:ignore Magento2.Functions.DiscouragedFunction ['user_id' => $userId] = $this->json->unserialize(explode('.', base64_decode($tfaToken))[0]); } catch (\Throwable $e) { throw new AuthorizationException( diff --git a/TwoFactorAuth/Model/UserConfig/SignedTokenManager.php b/TwoFactorAuth/Model/UserConfig/SignedTokenManager.php index 0bda1904..3d742871 100644 --- a/TwoFactorAuth/Model/UserConfig/SignedTokenManager.php +++ b/TwoFactorAuth/Model/UserConfig/SignedTokenManager.php @@ -65,6 +65,7 @@ public function isValidFor(int $userId, string $token): bool { $isValid = false; try { + // phpcs:ignore Magento2.Functions.DiscouragedFunction [$encodedData, $signatureProvided] = explode('.', base64_decode($token)); $data = $this->json->unserialize($encodedData); if (array_key_exists('user_id', $data) diff --git a/TwoFactorAuth/Setup/Patch/Data/ResetU2fConfig.php b/TwoFactorAuth/Setup/Patch/Data/ResetU2fConfig.php index a6c911e8..18986427 100644 --- a/TwoFactorAuth/Setup/Patch/Data/ResetU2fConfig.php +++ b/TwoFactorAuth/Setup/Patch/Data/ResetU2fConfig.php @@ -71,6 +71,8 @@ public function apply() } $this->moduleDataSetup->endSetup(); + + return $this; } /** diff --git a/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php index 9678afbc..801d4a1f 100644 --- a/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php +++ b/TwoFactorAuth/Test/Api/AdminIntegrationTokenTest.php @@ -5,13 +5,15 @@ */ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Api; +namespace Magento\TwoFactorAuth\Test\Api; use Magento\Config\Model\Config; use Magento\Framework\Webapi\Rest\Request; use Magento\TestFramework\Bootstrap as TestBootstrap; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\TwoFactorAuth\Api\TfaInterface; +use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\User\Model\UserFactory; diff --git a/TwoFactorAuth/Test/Api/GoogleActivateTest.php b/TwoFactorAuth/Test/Api/GoogleActivateTest.php index c5ddba59..3253933f 100644 --- a/TwoFactorAuth/Test/Api/GoogleActivateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleActivateTest.php @@ -5,11 +5,13 @@ */ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Api; +namespace Magento\TwoFactorAuth\Test\Api; use Magento\Framework\Webapi\Rest\Request; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\TwoFactorAuth\Api\TfaInterface; +use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\User\Model\UserFactory; use OTPHP\TOTP; diff --git a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php index 70055b90..8fd0cd8b 100644 --- a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php @@ -5,11 +5,12 @@ */ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Api; +namespace Magento\TwoFactorAuth\Test\Api; use Magento\Framework\Webapi\Rest\Request; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\User\Model\UserFactory; use OTPHP\TOTP; diff --git a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php index 5c445d6c..e92c856c 100644 --- a/TwoFactorAuth/Test/Api/GoogleConfigureTest.php +++ b/TwoFactorAuth/Test/Api/GoogleConfigureTest.php @@ -5,12 +5,14 @@ */ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Api; +namespace Magento\TwoFactorAuth\Test\Api; use Magento\Framework\Webapi\Rest\Request; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; use Magento\TwoFactorAuth\Api\Data\GoogleConfigureInterface as GoogleConfigureData; +use Magento\TwoFactorAuth\Api\TfaInterface; +use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\User\Model\UserFactory; diff --git a/TwoFactorAuth/Test/Integration/Command/GoogleSecretTest.php b/TwoFactorAuth/Test/Integration/Command/GoogleSecretTest.php index 2fb8387a..d3d9daa9 100644 --- a/TwoFactorAuth/Test/Integration/Command/GoogleSecretTest.php +++ b/TwoFactorAuth/Test/Integration/Command/GoogleSecretTest.php @@ -6,10 +6,11 @@ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Command; +namespace Magento\TwoFactorAuth\Test\Integration\Command; use Magento\Framework\App\ObjectManager; use Magento\TwoFactorAuth\Api\UserConfigManagerInterface; +use Magento\TwoFactorAuth\Command\GoogleSecret; use Magento\TwoFactorAuth\Model\Provider\Engine\Google; use Magento\User\Model\UserFactory; use PHPUnit\Framework\MockObject\MockObject; diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigureTest.php index 7bd4bd0f..a1762e92 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigureTest.php @@ -1,4 +1,9 @@ dispatch($this->uri); - self::assertMatchesRegularExpression('/You need to configure Two\-Factor Authorization/', $this->getResponse()->getBody()); + self::assertMatchesRegularExpression( + '/You need to configure Two\-Factor Authorization/', + $this->getResponse()->getBody() + ); } /** @@ -64,7 +71,10 @@ public function testAppConfigRequested(): void public function testUserConfigRequested(): void { $this->dispatch($this->uri); - self::assertMatchesRegularExpression('/You need to configure Two\-Factor Authorization/', $this->getResponse()->getBody()); + self::assertMatchesRegularExpression( + '/You need to configure Two\-Factor Authorization/', + $this->getResponse()->getBody() + ); } /** diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/U2f/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/U2f/ConfigureTest.php index bbf35b4b..3164544c 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/U2f/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/U2f/ConfigureTest.php @@ -1,4 +1,9 @@ dispatch('backend/admin/user/'); //Authenticated user with 2FA configured and completed is taken to the Users page as requested. - self::assertMatchesRegularExpression('/' .$this->_session->getUser()->getUserName() .'/i', $this->getResponse()->getBody()); + self::assertMatchesRegularExpression( + '/' .$this->_session->getUser()->getUserName() .'/i', + $this->getResponse()->getBody() + ); } /** diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php index a63a633f..a99d8f39 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php @@ -6,12 +6,13 @@ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Model\Provider\Engine\Authy; +namespace Magento\TwoFactorAuth\Test\Integration\Model\Provider\Engine\Authy; use Magento\Framework\App\ObjectManager; use Magento\TestFramework\Bootstrap; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Authy; +use Magento\TwoFactorAuth\Model\Provider\Engine\Authy\Authenticate; use Magento\User\Model\UserFactory; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php index 06c3451c..5729095b 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php @@ -6,14 +6,16 @@ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Model\Provider\Engine\Authy; +namespace Magento\TwoFactorAuth\Test\Integration\Model\Provider\Engine\Authy; use Magento\Framework\App\ObjectManager; use Magento\TwoFactorAuth\Api\Data\AuthyDeviceInterface; use Magento\TwoFactorAuth\Api\Data\AuthyDeviceInterfaceFactory; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; +use Magento\TwoFactorAuth\Controller\Adminhtml\Authy\Configure; use Magento\TwoFactorAuth\Model\Provider\Engine\Authy; +use Magento\TwoFactorAuth\Model\Provider\Engine\Authy\Verification; use Magento\User\Model\UserFactory; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php index 1c68bd8b..0328a383 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php @@ -6,7 +6,7 @@ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity; +namespace Magento\TwoFactorAuth\Test\Integration\Model\Provider\Engine\DuoSecurity; use Magento\Framework\App\ObjectManager; use Magento\TestFramework\Bootstrap; @@ -14,6 +14,7 @@ use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity; +use Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity\Authenticate; use Magento\User\Model\UserFactory; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php index 62f2e984..a1adfe23 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php @@ -6,13 +6,14 @@ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity; +namespace Magento\TwoFactorAuth\Test\Integration\Model\Provider\Engine\DuoSecurity; use Magento\Framework\App\ObjectManager; use Magento\TwoFactorAuth\Api\Data\DuoDataInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity; +use Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity\Configure; use Magento\User\Model\UserFactory; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php index 59a35efc..93f93187 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php @@ -6,13 +6,14 @@ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; +namespace Magento\TwoFactorAuth\Test\Integration\Model\Provider\Engine\U2fKey; use Magento\Framework\App\ObjectManager; use Magento\TestFramework\Bootstrap; use Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; +use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey\Authenticate; use Magento\User\Model\UserFactory; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php index c4a808ef..b7969231 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php @@ -6,13 +6,14 @@ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; +namespace Magento\TwoFactorAuth\Test\Integration\Model\Provider\Engine\U2fKey; use Magento\Framework\App\ObjectManager; use Magento\TwoFactorAuth\Api\Data\U2fWebAuthnRequestInterface; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; +use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey\Configure; use Magento\User\Model\UserFactory; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/SessionTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/SessionTest.php index 068ab9bb..0c774bd0 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/SessionTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/SessionTest.php @@ -6,10 +6,9 @@ declare(strict_types=1); -namespace Magento\Model\Provider\Engine\U2fKey; +namespace Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; use Magento\Framework\App\ObjectManager; -use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey\Session; use PHPUnit\Framework\TestCase; class SessionTest extends TestCase diff --git a/TwoFactorAuth/Test/Integration/Model/TfaSessionTest.php b/TwoFactorAuth/Test/Integration/Model/TfaSessionTest.php index 2900fee4..73195242 100644 --- a/TwoFactorAuth/Test/Integration/Model/TfaSessionTest.php +++ b/TwoFactorAuth/Test/Integration/Model/TfaSessionTest.php @@ -6,7 +6,7 @@ declare(strict_types=1); -namespace Magento\Model; +namespace Magento\TwoFactorAuth\Test\Integration\Model; use Magento\Framework\App\ObjectManager; use Magento\TwoFactorAuth\Model\TfaSession; diff --git a/TwoFactorAuth/Test/Integration/UserConfigRequestManagerTest.php b/TwoFactorAuth/Test/Integration/UserConfigRequestManagerTest.php index 3c5c2c53..bc081fa0 100644 --- a/TwoFactorAuth/Test/Integration/UserConfigRequestManagerTest.php +++ b/TwoFactorAuth/Test/Integration/UserConfigRequestManagerTest.php @@ -1,4 +1,9 @@ assertTrue($this->model->isEnabled()); } } diff --git a/TwoFactorAuth/TestFramework/ControllerActionPredispatch.php b/TwoFactorAuth/TestFramework/ControllerActionPredispatch.php index 6e484492..ebea76b7 100644 --- a/TwoFactorAuth/TestFramework/ControllerActionPredispatch.php +++ b/TwoFactorAuth/TestFramework/ControllerActionPredispatch.php @@ -1,4 +1,9 @@ Date: Sun, 24 May 2020 09:22:43 -0500 Subject: [PATCH 49/58] MC-30537: Test automation with the new 2FA enabled by default - static fixes --- .../Model/Provider/Engine/Authy/Verification.php | 8 ++++---- TwoFactorAuth/Plugin/DeleteCookieOnLogout.php | 5 ++++- .../Setup/Patch/Schema/CopyTablesFromOldModule.php | 8 +++++--- .../Controller/Adminhtml/Authy/ConfigureTest.php | 3 +++ .../Controller/Adminhtml/Authy/ConfigurepostTest.php | 3 +++ .../Adminhtml/Authy/ConfigureverifypostTest.php | 3 +++ .../Integration/Controller/Adminhtml/Duo/AuthTest.php | 3 +++ .../Controller/Adminhtml/U2f/ConfigureTest.php | 6 +++--- .../Controller/Adminhtml/U2f/ConfigurepostTest.php | 6 +++--- .../Model/Provider/Engine/Authy/ConfigureTest.php | 4 +++- .../Model/Provider/Engine/U2fKey/AuthenticateTest.php | 1 - .../Model/Provider/Engine/U2fKey/ConfigureTest.php | 2 -- .../Unit/Model/Config/Backend/Duo/ApiHostnameTest.php | 4 ++++ .../Test/Unit/Model/Provider/Engine/AuthyTest.php | 4 ++++ .../Test/Unit/Model/Provider/Engine/DuoSecurityTest.php | 5 +++++ .../Test/Unit/Model/Provider/Engine/GoogleTest.php | 5 +++++ .../Test/Unit/Model/Provider/Engine/U2fKeyTest.php | 5 +++++ TwoFactorAuth/Test/Unit/Model/TfaTest.php | 5 +++++ .../Unit/Model/UserConfig/HtmlAreaTokenVerifierTest.php | 5 +++++ TwoFactorAuth/view/adminhtml/web/js/u2fkey/auth.js | 3 +++ TwoFactorAuth/view/adminhtml/web/js/u2fkey/configure.js | 3 +++ TwoFactorAuth/view/adminhtml/web/js/u2fkey/utils.js | 1 + 22 files changed, 74 insertions(+), 18 deletions(-) diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php index e355b42c..13730aca 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Verification.php @@ -70,10 +70,10 @@ public function __construct( * Verify phone number * * @param UserInterface $user - * @param string $country - * @param string $phoneNumber - * @param string $method - * @param array &$response + * @param string $country + * @param string $phoneNumber + * @param string $method + * @param array &$response * @throws LocalizedException */ public function request( diff --git a/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php b/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php index f08f3428..3551f5ec 100644 --- a/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php +++ b/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php @@ -49,8 +49,11 @@ public function __construct( /** * Delete the tfat cookie + * + * @param \Magento\Backend\Model\Auth $subject + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function beforeLogout() + public function beforeLogout(\Magento\Backend\Model\Auth $subject) { $metadata = $this->cookieMetadataFactory->createSensitiveCookieMetadata() ->setPath($this->sessionManager->getCookiePath()); diff --git a/TwoFactorAuth/Setup/Patch/Schema/CopyTablesFromOldModule.php b/TwoFactorAuth/Setup/Patch/Schema/CopyTablesFromOldModule.php index 81ad6b10..3392544e 100644 --- a/TwoFactorAuth/Setup/Patch/Schema/CopyTablesFromOldModule.php +++ b/TwoFactorAuth/Setup/Patch/Schema/CopyTablesFromOldModule.php @@ -12,6 +12,7 @@ /** * Copy table contents after migrating from MageSpecialist to Magento + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CopyTablesFromOldModule implements SchemaPatchInterface @@ -31,7 +32,8 @@ public function __construct( } /** - * {@inheritdoc} + * Migrate the old tables + * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -67,7 +69,7 @@ public function apply() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -75,7 +77,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigureTest.php index a1762e92..c22245b8 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigureTest.php @@ -33,6 +33,7 @@ class ConfigureTest extends AbstractConfigureBackendController * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key some-key + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testTokenAccess(): void { @@ -43,6 +44,7 @@ public function testTokenAccess(): void * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key some-key + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclHasAccess() { @@ -53,6 +55,7 @@ public function testAclHasAccess() * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key some-key + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclNoAccess() { diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigurepostTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigurepostTest.php index e273e997..e70afaa3 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigurepostTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigurepostTest.php @@ -33,6 +33,7 @@ class ConfigurepostTest extends AbstractConfigureBackendController * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key some-key + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testTokenAccess(): void { @@ -43,6 +44,7 @@ public function testTokenAccess(): void * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key some-key + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclHasAccess() { @@ -53,6 +55,7 @@ public function testAclHasAccess() * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key some-key + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclNoAccess() { diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigureverifypostTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigureverifypostTest.php index 0efe1642..b1517110 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigureverifypostTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Authy/ConfigureverifypostTest.php @@ -33,6 +33,7 @@ class ConfigureverifypostTest extends AbstractConfigureBackendController * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key some-key + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testTokenAccess(): void { @@ -43,6 +44,7 @@ public function testTokenAccess(): void * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key some-key + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclHasAccess() { @@ -53,6 +55,7 @@ public function testAclHasAccess() * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers authy * @magentoConfigFixture default/twofactorauth/authy/api_key some-key + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclNoAccess() { diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Duo/AuthTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Duo/AuthTest.php index 16313e83..c537c451 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Duo/AuthTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Duo/AuthTest.php @@ -36,6 +36,7 @@ class AuthTest extends AbstractConfigureBackendController * @magentoConfigFixture default/twofactorauth/duo/secret_key duo_security * @magentoConfigFixture default/twofactorauth/duo/api_hostname duo_security * @magentoConfigFixture default/twofactorauth/duo/application_key duo_security + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testTokenAccess(): void { @@ -49,6 +50,7 @@ public function testTokenAccess(): void * @magentoConfigFixture default/twofactorauth/duo/secret_key duo_security * @magentoConfigFixture default/twofactorauth/duo/api_hostname duo_security * @magentoConfigFixture default/twofactorauth/duo/application_key duo_security + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclHasAccess() { @@ -62,6 +64,7 @@ public function testAclHasAccess() * @magentoConfigFixture default/twofactorauth/duo/secret_key duo_security * @magentoConfigFixture default/twofactorauth/duo/api_hostname duo_security * @magentoConfigFixture default/twofactorauth/duo/application_key duo_security + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclNoAccess() { diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/U2f/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/U2f/ConfigureTest.php index 3164544c..2dbe3e89 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/U2f/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/U2f/ConfigureTest.php @@ -30,8 +30,8 @@ class ConfigureTest extends AbstractConfigureBackendController protected $httpMethod = Request::METHOD_GET; /** - * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testTokenAccess(): void { @@ -39,8 +39,8 @@ public function testTokenAccess(): void } /** - * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclHasAccess() { @@ -48,8 +48,8 @@ public function testAclHasAccess() } /** - * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclNoAccess() { diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/U2f/ConfigurepostTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/U2f/ConfigurepostTest.php index 53ffceb9..473fb882 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/U2f/ConfigurepostTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/U2f/ConfigurepostTest.php @@ -30,8 +30,8 @@ class ConfigurepostTest extends AbstractConfigureBackendController protected $httpMethod = Request::METHOD_POST; /** - * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testTokenAccess(): void { @@ -39,8 +39,8 @@ public function testTokenAccess(): void } /** - * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclHasAccess() { @@ -48,8 +48,8 @@ public function testAclHasAccess() } /** - * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers u2fkey + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclNoAccess() { diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php index 5729095b..a4f1ff69 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/ConfigureTest.php @@ -13,8 +13,8 @@ use Magento\TwoFactorAuth\Api\Data\AuthyDeviceInterfaceFactory; use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; -use Magento\TwoFactorAuth\Controller\Adminhtml\Authy\Configure; use Magento\TwoFactorAuth\Model\Provider\Engine\Authy; +use Magento\TwoFactorAuth\Model\Provider\Engine\Authy\Configure; use Magento\TwoFactorAuth\Model\Provider\Engine\Authy\Verification; use Magento\User\Model\UserFactory; use PHPUnit\Framework\MockObject\MockObject; @@ -184,6 +184,8 @@ function ($userId, $country, $phone, $method, &$response) { // These keys come from authy api not our model $response['message'] = 'foo'; $response['seconds_to_expire'] = 123; + // Fix static errors + unset($userId, $country, $phone, $method); } ); diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php index 93f93187..ca912912 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php @@ -251,7 +251,6 @@ public function testVerifyThrowsExceptionRequest() $this->expectExceptionMessage('Something'); $this->tfa->getProviderByCode(U2fKey::CODE) ->activate($this->getUserId()); - $userId = $this->getUserId(); $this->u2fkey ->method('getAuthenticateData') diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php index b7969231..f9f952a7 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/ConfigureTest.php @@ -71,7 +71,6 @@ public function testGetRegistrationDataInvalidTfat() { $this->expectException(\Magento\Framework\Exception\AuthorizationException::class); $this->expectExceptionMessage('Invalid two-factor authorization token'); - $userId = $this->getUserId(); $this->u2fkey ->expects($this->never()) ->method('getRegisterData'); @@ -124,7 +123,6 @@ public function testActivateInvalidTfat() { $this->expectException(\Magento\Framework\Exception\AuthorizationException::class); $this->expectExceptionMessage('Invalid two-factor authorization token'); - $userId = $this->getUserId(); $this->u2fkey ->expects($this->never()) ->method('registerDevice'); diff --git a/TwoFactorAuth/Test/Unit/Model/Config/Backend/Duo/ApiHostnameTest.php b/TwoFactorAuth/Test/Unit/Model/Config/Backend/Duo/ApiHostnameTest.php index b6a1ff30..9908a118 100644 --- a/TwoFactorAuth/Test/Unit/Model/Config/Backend/Duo/ApiHostnameTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Config/Backend/Duo/ApiHostnameTest.php @@ -1,4 +1,8 @@ Date: Sun, 24 May 2020 10:53:22 -0500 Subject: [PATCH 50/58] MC-30537: Test automation with the new 2FA enabled by default - static fixes --- TwoFactorAuth/Api/Data/UserConfigInterface.php | 11 ++++++++++- TwoFactorAuth/Model/Data/UserConfig.php | 6 +++--- .../Setup/Patch/Data/EncryptGoogleSecrets.php | 2 ++ .../Model/Provider/Engine/Authy/AuthenticateTest.php | 2 ++ .../Provider/Engine/DuoSecurity/ConfigureTest.php | 1 + .../Model/Provider/Engine/U2fKey/SessionTest.php | 3 ++- .../Unit/Model/Config/Backend/Duo/ApiHostnameTest.php | 2 +- .../Unit/Model/Config/Backend/ForceProvidersTest.php | 7 +++++++ 8 files changed, 28 insertions(+), 6 deletions(-) diff --git a/TwoFactorAuth/Api/Data/UserConfigInterface.php b/TwoFactorAuth/Api/Data/UserConfigInterface.php index 36235c1e..34442f74 100644 --- a/TwoFactorAuth/Api/Data/UserConfigInterface.php +++ b/TwoFactorAuth/Api/Data/UserConfigInterface.php @@ -36,48 +36,56 @@ interface UserConfigInterface extends ExtensibleDataInterface /** * Get value for config_id + * * @return int */ public function getId(): int; /** * Set value for config_id + * * @param int $value */ - public function setId(int $value): void; + public function setId($value): void; /** * Get value for user_id + * * @return int */ public function getUserId(): int; /** * Set value for user_id + * * @param int $value */ public function setUserId(int $value): void; /** * Get value for encoded_providers + * * @return string */ public function getEncodedProviders(): string; /** * Set value for encoded_providers + * * @param string $value */ public function setEncodedProviders(string $value): void; /** * Get value for default_provider + * * @return string */ public function getDefaultProvider(): string; /** * Set value for default_provider + * * @param string $value */ public function setDefaultProvider(string $value): void; @@ -93,6 +101,7 @@ public function getExtensionAttributes(): ?UserConfigExtensionInterface; /** * Set an extension attributes object + * * @param \Magento\TwoFactorAuth\Api\Data\UserConfigExtensionInterface $extensionAttributes */ public function setExtensionAttributes(UserConfigExtensionInterface $extensionAttributes): void; diff --git a/TwoFactorAuth/Model/Data/UserConfig.php b/TwoFactorAuth/Model/Data/UserConfig.php index 46f35649..f67e25f6 100644 --- a/TwoFactorAuth/Model/Data/UserConfig.php +++ b/TwoFactorAuth/Model/Data/UserConfig.php @@ -7,14 +7,14 @@ namespace Magento\TwoFactorAuth\Model\Data; -use Magento\Framework\Api\AbstractExtensibleObject; +use Magento\Framework\Model\AbstractExtensibleModel; use Magento\TwoFactorAuth\Api\Data\UserConfigExtensionInterface; use Magento\TwoFactorAuth\Api\Data\UserConfigInterface; /** * @inheritDoc */ -class UserConfig extends AbstractExtensibleObject implements UserConfigInterface +class UserConfig extends AbstractExtensibleModel implements UserConfigInterface { /** * @inheritDoc @@ -27,7 +27,7 @@ public function getId(): int /** * @inheritDoc */ - public function setId(int $value): void + public function setId($value): void { $this->setData(self::ID, $value); } diff --git a/TwoFactorAuth/Setup/Patch/Data/EncryptGoogleSecrets.php b/TwoFactorAuth/Setup/Patch/Data/EncryptGoogleSecrets.php index 7106d703..686bdf31 100644 --- a/TwoFactorAuth/Setup/Patch/Data/EncryptGoogleSecrets.php +++ b/TwoFactorAuth/Setup/Patch/Data/EncryptGoogleSecrets.php @@ -85,6 +85,8 @@ public function apply() } $this->moduleDataSetup->endSetup(); + + return $this; } /** diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php index a99d8f39..28478822 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php @@ -13,6 +13,8 @@ use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\Authy; use Magento\TwoFactorAuth\Model\Provider\Engine\Authy\Authenticate; +use Magento\TwoFactorAuth\Model\Provider\Engine\Authy\OneTouch; +use Magento\TwoFactorAuth\Model\Provider\Engine\Authy\Token; use Magento\User\Model\UserFactory; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php index a1adfe23..4a306f70 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/ConfigureTest.php @@ -13,6 +13,7 @@ use Magento\TwoFactorAuth\Api\TfaInterface; use Magento\TwoFactorAuth\Api\UserConfigTokenManagerInterface; use Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity; +use Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity\Authenticate; use Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity\Configure; use Magento\User\Model\UserFactory; use PHPUnit\Framework\MockObject\MockObject; diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/SessionTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/SessionTest.php index 0c774bd0..56039e51 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/SessionTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/SessionTest.php @@ -6,9 +6,10 @@ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey; +namespace Magento\TwoFactorAuth\Test\Integration\Model\Provider\Engine\U2fKey; use Magento\Framework\App\ObjectManager; +use Magento\TwoFactorAuth\Model\Provider\Engine\U2fKey\Session; use PHPUnit\Framework\TestCase; class SessionTest extends TestCase diff --git a/TwoFactorAuth/Test/Unit/Model/Config/Backend/Duo/ApiHostnameTest.php b/TwoFactorAuth/Test/Unit/Model/Config/Backend/Duo/ApiHostnameTest.php index 9908a118..5a670567 100644 --- a/TwoFactorAuth/Test/Unit/Model/Config/Backend/Duo/ApiHostnameTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Config/Backend/Duo/ApiHostnameTest.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\TwoFactorAuth\Test\Unit\Model\Config\Backend; +namespace Magento\TwoFactorAuth\Test\Unit\Model\Config\Backend\Duo; use Magento\Framework\Exception\ValidatorException; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; diff --git a/TwoFactorAuth/Test/Unit/Model/Config/Backend/ForceProvidersTest.php b/TwoFactorAuth/Test/Unit/Model/Config/Backend/ForceProvidersTest.php index a41e4e5b..a36677a6 100644 --- a/TwoFactorAuth/Test/Unit/Model/Config/Backend/ForceProvidersTest.php +++ b/TwoFactorAuth/Test/Unit/Model/Config/Backend/ForceProvidersTest.php @@ -1,4 +1,11 @@ Date: Sun, 24 May 2020 15:25:11 -0500 Subject: [PATCH 51/58] MC-30537: Test automation with the new 2FA enabled by default - Static fixes --- .../Api/CountryRepositoryInterface.php | 6 ++++++ .../Api/Data/CountrySearchResultsInterface.php | 4 +++- .../Data/UserConfigSearchResultsInterface.php | 2 ++ TwoFactorAuth/Api/EngineInterface.php | 1 + TwoFactorAuth/Api/ProviderPoolInterface.php | 2 ++ TwoFactorAuth/Api/TfaSessionInterface.php | 1 + .../Api/UserConfigManagerInterface.php | 16 +++++++++++++--- .../Api/UserConfigRepositoryInterface.php | 6 ++++++ TwoFactorAuth/Block/ChangeProvider.php | 4 +++- TwoFactorAuth/Block/Configure.php | 2 +- .../Block/Provider/Authy/Configure.php | 6 ++++++ TwoFactorAuth/Command/TfaProviders.php | 2 ++ TwoFactorAuth/Command/TfaReset.php | 2 ++ .../Controller/Adminhtml/Authy/Auth.php | 2 ++ .../Controller/Adminhtml/Authy/Authpost.php | 1 + .../Controller/Adminhtml/Authy/Configure.php | 1 + .../Adminhtml/Authy/Configurepost.php | 3 +++ .../Adminhtml/Authy/Configureverifypost.php | 3 +++ .../Controller/Adminhtml/Authy/Onetouch.php | 7 +++++-- .../Controller/Adminhtml/Authy/Token.php | 3 +++ .../Controller/Adminhtml/Authy/Verify.php | 4 ++++ .../Adminhtml/Authy/Verifyonetouch.php | 3 +++ .../Controller/Adminhtml/Duo/Authpost.php | 6 +++--- .../Controller/Adminhtml/Google/Auth.php | 2 ++ .../Controller/Adminhtml/Google/Authpost.php | 2 ++ .../Controller/Adminhtml/Google/Configure.php | 2 ++ .../Adminhtml/Google/Configurepost.php | 3 +++ .../Controller/Adminhtml/Google/Qr.php | 2 ++ .../Controller/Adminhtml/Tfa/Index.php | 1 + .../Controller/Adminhtml/Tfa/Reset.php | 2 ++ .../Controller/Adminhtml/U2f/Configure.php | 5 ++++- .../Controller/Adminhtml/U2f/Configurepost.php | 2 ++ TwoFactorAuth/Model/Alert.php | 1 + TwoFactorAuth/Model/AlertInterface.php | 1 + TwoFactorAuth/Model/CountryRegistry.php | 4 ++++ TwoFactorAuth/Model/Provider.php | 1 + .../Model/Provider/Engine/DuoSecurity.php | 7 +++++++ TwoFactorAuth/Model/ResourceModel/Country.php | 4 ++++ .../Model/ResourceModel/CountryRepository.php | 13 ++++++++----- .../Model/ResourceModel/UserConfig.php | 18 +++++++++++++----- .../ResourceModel/UserConfigRepository.php | 11 ++++++----- TwoFactorAuth/Model/Tfa.php | 2 ++ TwoFactorAuth/Model/UserConfigManager.php | 5 +++-- TwoFactorAuth/Model/UserConfigRegistry.php | 4 ++++ TwoFactorAuth/Observer/AdminUserLoadAfter.php | 5 +++++ TwoFactorAuth/Plugin/AddTabToAdminUserEdit.php | 2 ++ .../Plugin/AvoidRecursionOnPasswordChange.php | 2 ++ .../Patch/Data/CopyConfigFromOldModule.php | 9 ++++++--- .../Setup/Patch/Data/EncryptConfiguration.php | 11 +++++++---- .../Patch/Data/GenerateDuoSecurityKey.php | 9 ++++++--- .../Setup/Patch/Data/PopulateCountryTable.php | 9 ++++++--- .../Adminhtml/Google/ConfigureTest.php | 3 +++ .../Adminhtml/Google/ConfigurepostTest.php | 2 ++ .../Ui/Component/Form/User/DataProvider.php | 3 ++- .../templates/tfa/change_provider.phtml | 2 +- .../adminhtml/templates/tfa/configure.phtml | 9 +++++++-- .../templates/tfa/configure_later.phtml | 4 +++- .../templates/tfa/provider/auth.phtml | 2 +- .../templates/tfa/provider/configure.phtml | 2 +- .../templates/tfa/request_config.phtml | 8 ++++++-- 60 files changed, 210 insertions(+), 51 deletions(-) diff --git a/TwoFactorAuth/Api/CountryRepositoryInterface.php b/TwoFactorAuth/Api/CountryRepositoryInterface.php index 6a12aca6..a014c581 100644 --- a/TwoFactorAuth/Api/CountryRepositoryInterface.php +++ b/TwoFactorAuth/Api/CountryRepositoryInterface.php @@ -14,12 +14,14 @@ /** * Countries repository + * * @SuppressWarnings(PHPMD.ShortVariable) */ interface CountryRepositoryInterface { /** * Save object + * * @param CountryInterface $object * @return CountryInterface */ @@ -27,6 +29,7 @@ public function save(CountryInterface $object): CountryInterface; /** * Get object by id + * * @param int $id * @return CountryInterface */ @@ -34,6 +37,7 @@ public function getById(int $id): CountryInterface; /** * Get by Code value + * * @param string $value * @return CountryInterface */ @@ -41,6 +45,7 @@ public function getByCode(string $value): CountryInterface; /** * Delete object + * * @param CountryInterface $object * @return void */ @@ -48,6 +53,7 @@ public function delete(CountryInterface $object): void; /** * Get a list of object + * * @param SearchCriteriaInterface $searchCriteria * @return CountrySearchResultsInterface */ diff --git a/TwoFactorAuth/Api/Data/CountrySearchResultsInterface.php b/TwoFactorAuth/Api/Data/CountrySearchResultsInterface.php index 92cb0b59..70ab6b02 100644 --- a/TwoFactorAuth/Api/Data/CountrySearchResultsInterface.php +++ b/TwoFactorAuth/Api/Data/CountrySearchResultsInterface.php @@ -10,18 +10,20 @@ use Magento\Framework\Api\SearchResultsInterface; /** - * Country search results interface + * Represent country search results */ interface CountrySearchResultsInterface extends SearchResultsInterface { /** * Get an array of objects + * * @return CountryInterface[] */ public function getItems(): array; /** * Set objects list + * * @param CountryInterface[] $items * @return CountrySearchResultsInterface */ diff --git a/TwoFactorAuth/Api/Data/UserConfigSearchResultsInterface.php b/TwoFactorAuth/Api/Data/UserConfigSearchResultsInterface.php index 5385400a..18c776f9 100644 --- a/TwoFactorAuth/Api/Data/UserConfigSearchResultsInterface.php +++ b/TwoFactorAuth/Api/Data/UserConfigSearchResultsInterface.php @@ -16,12 +16,14 @@ interface UserConfigSearchResultsInterface extends SearchResultsInterface { /** * Get an array of objects + * * @return UserConfigInterface[] */ public function getItems(): array; /** * Set objects list + * * @param UserConfigInterface[] $items * @return UserConfigSearchResultsInterface */ diff --git a/TwoFactorAuth/Api/EngineInterface.php b/TwoFactorAuth/Api/EngineInterface.php index 87fb5e8c..e42ccac8 100644 --- a/TwoFactorAuth/Api/EngineInterface.php +++ b/TwoFactorAuth/Api/EngineInterface.php @@ -24,6 +24,7 @@ public function isEnabled(): bool; /** * Return true on token validation + * * @param UserInterface $user * @param DataObject $request * @return bool diff --git a/TwoFactorAuth/Api/ProviderPoolInterface.php b/TwoFactorAuth/Api/ProviderPoolInterface.php index c9ac8bbf..9c77a8b0 100644 --- a/TwoFactorAuth/Api/ProviderPoolInterface.php +++ b/TwoFactorAuth/Api/ProviderPoolInterface.php @@ -16,12 +16,14 @@ interface ProviderPoolInterface { /** * Get a list of providers + * * @return \Magento\TwoFactorAuth\Api\ProviderInterface[] */ public function getProviders(): array; /** * Get provider by code + * * @param string $code * @return \Magento\TwoFactorAuth\Api\ProviderInterface * @throws NoSuchEntityException diff --git a/TwoFactorAuth/Api/TfaSessionInterface.php b/TwoFactorAuth/Api/TfaSessionInterface.php index 89cc7318..8bc1e9d5 100644 --- a/TwoFactorAuth/Api/TfaSessionInterface.php +++ b/TwoFactorAuth/Api/TfaSessionInterface.php @@ -24,6 +24,7 @@ public function grantAccess(): void; /** * Return true if 2FA session has been passed + * * @return bool */ public function isGranted(): bool; diff --git a/TwoFactorAuth/Api/UserConfigManagerInterface.php b/TwoFactorAuth/Api/UserConfigManagerInterface.php index 789d6d02..a18e8703 100644 --- a/TwoFactorAuth/Api/UserConfigManagerInterface.php +++ b/TwoFactorAuth/Api/UserConfigManagerInterface.php @@ -21,6 +21,7 @@ interface UserConfigManagerInterface /** * Get a provider configuration for a given user + * * @param int $userId * @param string $providerCode * @return array|null @@ -30,26 +31,29 @@ public function getProviderConfig(int $userId, string $providerCode): ?array; /** * Set provider configuration + * * @param int $userId * @param string $providerCode * @param array|null $config * @return bool * @throws NoSuchEntityException */ - public function setProviderConfig(int $userId, string $providerCode, ?array $config=null): bool; + public function setProviderConfig(int $userId, string $providerCode, ?array $config = null): bool; /** * Set provider configuration + * * @param int $userId * @param string $providerCode * @param array|null $config * @return bool * @throws NoSuchEntityException */ - public function addProviderConfig(int $userId, string $providerCode, ?array $config=null): bool; + public function addProviderConfig(int $userId, string $providerCode, ?array $config = null): bool; /** * Reset provider configuration + * * @param int $userId * @param string $providerCode * @return bool @@ -59,6 +63,7 @@ public function resetProviderConfig(int $userId, string $providerCode): bool; /** * Set providers list for a given user + * * @param int $userId * @param string|array $providersCodes * @return bool @@ -68,6 +73,7 @@ public function setProvidersCodes(int $userId, $providersCodes): bool; /** * Set providers list for a given user + * * @param int $userId * @return string[] */ @@ -75,6 +81,7 @@ public function getProvidersCodes(int $userId): array; /** * Activate a provider configuration + * * @param int $userId * @param string $providerCode * @return bool @@ -84,6 +91,7 @@ public function activateProviderConfiguration(int $userId, string $providerCode) /** * Return true if a provider configuration has been activated + * * @param int $userId * @param string $providerCode * @return bool @@ -93,6 +101,7 @@ public function isProviderConfigurationActive(int $userId, string $providerCode) /** * Set default provider + * * @param int $userId * @param string $providerCode * @return bool @@ -101,7 +110,8 @@ public function isProviderConfigurationActive(int $userId, string $providerCode) public function setDefaultProvider(int $userId, string $providerCode): bool; /** - * get default provider + * Get default provider + * * @param int $userId * @return string */ diff --git a/TwoFactorAuth/Api/UserConfigRepositoryInterface.php b/TwoFactorAuth/Api/UserConfigRepositoryInterface.php index 569b3078..f55abfab 100644 --- a/TwoFactorAuth/Api/UserConfigRepositoryInterface.php +++ b/TwoFactorAuth/Api/UserConfigRepositoryInterface.php @@ -14,12 +14,14 @@ /** * User configuration repository + * * @SuppressWarnings(PHPMD.ShortVariable) */ interface UserConfigRepositoryInterface { /** * Save object + * * @param UserConfigInterface $object * @return UserConfigInterface */ @@ -27,6 +29,7 @@ public function save(UserConfigInterface $object): UserConfigInterface; /** * Get object by id + * * @param int $id * @return UserConfigInterface */ @@ -34,6 +37,7 @@ public function getById(int $id): UserConfigInterface; /** * Get by UserId value + * * @param int $value * @return UserConfigInterface */ @@ -41,6 +45,7 @@ public function getByUserId(int $value): UserConfigInterface; /** * Delete object + * * @param UserConfigInterface $object * @return bool */ @@ -48,6 +53,7 @@ public function delete(UserConfigInterface $object): bool; /** * Get a list of object + * * @param SearchCriteriaInterface $searchCriteria * @return UserConfigSearchResultsInterface */ diff --git a/TwoFactorAuth/Block/ChangeProvider.php b/TwoFactorAuth/Block/ChangeProvider.php index e7c9a7c7..6b251cc8 100644 --- a/TwoFactorAuth/Block/ChangeProvider.php +++ b/TwoFactorAuth/Block/ChangeProvider.php @@ -14,6 +14,8 @@ use Magento\TwoFactorAuth\Api\ProviderInterface; /** + * Represent the change providers block for authentication workflow + * * @api */ class ChangeProvider extends Template @@ -34,7 +36,6 @@ class ChangeProvider extends Template private $session; /** - * ChangeProvider constructor. * @param Template\Context $context * @param Session $session * @param UserContextInterface $userContext @@ -97,6 +98,7 @@ public function getJsLayout() /** * Get a list of available providers + * * @return ProviderInterface[] */ private function getProvidersList(): array diff --git a/TwoFactorAuth/Block/Configure.php b/TwoFactorAuth/Block/Configure.php index 6a12dacd..2032cfa9 100644 --- a/TwoFactorAuth/Block/Configure.php +++ b/TwoFactorAuth/Block/Configure.php @@ -27,8 +27,8 @@ class Configure extends Template /** * @param Template\Context $context - * @param array $data * @param TfaInterface $tfa + * @param array $data */ public function __construct(Template\Context $context, TfaInterface $tfa, array $data = []) { diff --git a/TwoFactorAuth/Block/Provider/Authy/Configure.php b/TwoFactorAuth/Block/Provider/Authy/Configure.php index 87c84ed5..7672eb6d 100644 --- a/TwoFactorAuth/Block/Provider/Authy/Configure.php +++ b/TwoFactorAuth/Block/Provider/Authy/Configure.php @@ -20,6 +20,11 @@ class Configure extends Template */ private $countryCollectionFactory; + /** + * @param Template\Context $context + * @param CountryCollectionFactory $countryCollectionFactory + * @param array $data + */ public function __construct( Template\Context $context, CountryCollectionFactory $countryCollectionFactory, @@ -31,6 +36,7 @@ public function __construct( /** * Get a country list + * * return array */ private function getCountriesList() diff --git a/TwoFactorAuth/Command/TfaProviders.php b/TwoFactorAuth/Command/TfaProviders.php index d076b76e..1824f6e2 100644 --- a/TwoFactorAuth/Command/TfaProviders.php +++ b/TwoFactorAuth/Command/TfaProviders.php @@ -44,6 +44,8 @@ protected function configure() } /** + * @inheritDoc + * * @SuppressWarnings("PHPMD.UnusedFormalParameter") */ protected function execute(InputInterface $input, OutputInterface $output) diff --git a/TwoFactorAuth/Command/TfaReset.php b/TwoFactorAuth/Command/TfaReset.php index 67050c45..74cebe05 100644 --- a/TwoFactorAuth/Command/TfaReset.php +++ b/TwoFactorAuth/Command/TfaReset.php @@ -76,6 +76,8 @@ protected function configure() } /** + * @inheritDoc + * * @SuppressWarnings("PHPMD.UnusedFormalParameter") * @throws LocalizedException */ diff --git a/TwoFactorAuth/Controller/Adminhtml/Authy/Auth.php b/TwoFactorAuth/Controller/Adminhtml/Authy/Auth.php index 7c68ea64..e4f25cd8 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Authy/Auth.php +++ b/TwoFactorAuth/Controller/Adminhtml/Authy/Auth.php @@ -66,6 +66,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser() @@ -75,6 +76,7 @@ private function getUser() /** * @inheritdoc + * * @throws NoSuchEntityException */ public function execute() diff --git a/TwoFactorAuth/Controller/Adminhtml/Authy/Authpost.php b/TwoFactorAuth/Controller/Adminhtml/Authy/Authpost.php index 08c17fc0..1fd18f93 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Authy/Authpost.php +++ b/TwoFactorAuth/Controller/Adminhtml/Authy/Authpost.php @@ -92,6 +92,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser(): ?User diff --git a/TwoFactorAuth/Controller/Adminhtml/Authy/Configure.php b/TwoFactorAuth/Controller/Adminhtml/Authy/Configure.php index a35c52ab..07356384 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Authy/Configure.php +++ b/TwoFactorAuth/Controller/Adminhtml/Authy/Configure.php @@ -59,6 +59,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser(): ?User diff --git a/TwoFactorAuth/Controller/Adminhtml/Authy/Configurepost.php b/TwoFactorAuth/Controller/Adminhtml/Authy/Configurepost.php index 63efa4f6..aa0bab38 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Authy/Configurepost.php +++ b/TwoFactorAuth/Controller/Adminhtml/Authy/Configurepost.php @@ -21,6 +21,8 @@ use Magento\TwoFactorAuth\Model\UserConfig\HtmlAreaTokenVerifier; /** + * Configure authy + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Configurepost extends AbstractConfigureAction implements HttpPostActionInterface @@ -78,6 +80,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser(): ?User diff --git a/TwoFactorAuth/Controller/Adminhtml/Authy/Configureverifypost.php b/TwoFactorAuth/Controller/Adminhtml/Authy/Configureverifypost.php index 6b6c091e..c94fffd9 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Authy/Configureverifypost.php +++ b/TwoFactorAuth/Controller/Adminhtml/Authy/Configureverifypost.php @@ -21,6 +21,8 @@ use Magento\TwoFactorAuth\Model\UserConfig\HtmlAreaTokenVerifier; /** + * Verify authy + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Configureverifypost extends AbstractConfigureAction implements HttpPostActionInterface @@ -94,6 +96,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser(): ?User diff --git a/TwoFactorAuth/Controller/Adminhtml/Authy/Onetouch.php b/TwoFactorAuth/Controller/Adminhtml/Authy/Onetouch.php index 45c9fd5c..52da562d 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Authy/Onetouch.php +++ b/TwoFactorAuth/Controller/Adminhtml/Authy/Onetouch.php @@ -18,6 +18,8 @@ use Magento\User\Model\User; /** + * One touch process + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Onetouch extends AbstractAction implements HttpGetActionInterface @@ -65,6 +67,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser(): ?User @@ -80,8 +83,8 @@ public function execute() $result = $this->jsonFactory->create(); try { - $approvalCode = $this->oneTouch->request($this->getUser()); - $res = ['success' => true, 'code' => $approvalCode]; + $this->oneTouch->request($this->getUser()); + $res = ['success' => true]; } catch (Exception $e) { $result->setHttpResponseCode(500); $res = ['success' => false, 'message' => $e->getMessage()]; diff --git a/TwoFactorAuth/Controller/Adminhtml/Authy/Token.php b/TwoFactorAuth/Controller/Adminhtml/Authy/Token.php index b1aa7700..f4fca06f 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Authy/Token.php +++ b/TwoFactorAuth/Controller/Adminhtml/Authy/Token.php @@ -19,6 +19,8 @@ use Magento\User\Model\User; /** + * Verify with authy token + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Token extends AbstractAction implements HttpGetActionInterface, HttpPostActionInterface @@ -66,6 +68,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser(): ?User diff --git a/TwoFactorAuth/Controller/Adminhtml/Authy/Verify.php b/TwoFactorAuth/Controller/Adminhtml/Authy/Verify.php index 21b8f4c0..badb4e45 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Authy/Verify.php +++ b/TwoFactorAuth/Controller/Adminhtml/Authy/Verify.php @@ -21,6 +21,8 @@ use Magento\User\Model\User; /** + * Verify authy code + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Verify extends AbstractAction implements HttpPostActionInterface, HttpGetActionInterface @@ -77,6 +79,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser(): ?User @@ -86,6 +89,7 @@ private function getUser(): ?User /** * Get verify information + * * @return verify payload * @throws NoSuchEntityException */ diff --git a/TwoFactorAuth/Controller/Adminhtml/Authy/Verifyonetouch.php b/TwoFactorAuth/Controller/Adminhtml/Authy/Verifyonetouch.php index ee0befce..1812c1e6 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Authy/Verifyonetouch.php +++ b/TwoFactorAuth/Controller/Adminhtml/Authy/Verifyonetouch.php @@ -21,6 +21,8 @@ use Magento\User\Model\User; /** + * Verify one touch response + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Verifyonetouch extends AbstractAction implements HttpGetActionInterface, HttpPostActionInterface @@ -85,6 +87,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser(): ?User diff --git a/TwoFactorAuth/Controller/Adminhtml/Duo/Authpost.php b/TwoFactorAuth/Controller/Adminhtml/Duo/Authpost.php index f21d5a8a..1928332d 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Duo/Authpost.php +++ b/TwoFactorAuth/Controller/Adminhtml/Duo/Authpost.php @@ -21,6 +21,7 @@ /** * Duo security authentication post controller + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Authpost extends AbstractAction implements HttpPostActionInterface @@ -107,6 +108,7 @@ public function __construct( /** * Get current user + * * @return \Magento\User\Model\User|null */ private function getUser() @@ -140,9 +142,7 @@ public function execute() } /** - * Check if admin has permissions to visit related pages - * - * @return bool + * @inheritDoc */ protected function _isAllowed() { diff --git a/TwoFactorAuth/Controller/Adminhtml/Google/Auth.php b/TwoFactorAuth/Controller/Adminhtml/Google/Auth.php index 75a788ad..df652239 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Google/Auth.php +++ b/TwoFactorAuth/Controller/Adminhtml/Google/Auth.php @@ -19,6 +19,7 @@ /** * Google authenticator page + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Auth extends AbstractAction implements HttpGetActionInterface @@ -66,6 +67,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser(): ?User diff --git a/TwoFactorAuth/Controller/Adminhtml/Google/Authpost.php b/TwoFactorAuth/Controller/Adminhtml/Google/Authpost.php index 75eac2e3..f0437f1d 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Google/Authpost.php +++ b/TwoFactorAuth/Controller/Adminhtml/Google/Authpost.php @@ -22,6 +22,7 @@ /** * Google authenticator post controller + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Authpost extends AbstractAction implements HttpPostActionInterface @@ -92,6 +93,7 @@ public function __construct( /** * @inheritdoc + * * @throws NoSuchEntityException */ public function execute() diff --git a/TwoFactorAuth/Controller/Adminhtml/Google/Configure.php b/TwoFactorAuth/Controller/Adminhtml/Google/Configure.php index 121469b5..1b3e6743 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Google/Configure.php +++ b/TwoFactorAuth/Controller/Adminhtml/Google/Configure.php @@ -19,6 +19,7 @@ /** * Google authenticator configuration page + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Configure extends AbstractConfigureAction implements HttpGetActionInterface @@ -60,6 +61,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser(): ?User diff --git a/TwoFactorAuth/Controller/Adminhtml/Google/Configurepost.php b/TwoFactorAuth/Controller/Adminhtml/Google/Configurepost.php index 7467660b..e4d823d3 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Google/Configurepost.php +++ b/TwoFactorAuth/Controller/Adminhtml/Google/Configurepost.php @@ -25,6 +25,7 @@ /** * Google authenticator configuration post controller + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Configurepost extends AbstractConfigureAction implements HttpPostActionInterface @@ -98,6 +99,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser(): ?User @@ -107,6 +109,7 @@ private function getUser(): ?User /** * @inheritdoc + * * @return ResponseInterface|ResultInterface * @throws NoSuchEntityException */ diff --git a/TwoFactorAuth/Controller/Adminhtml/Google/Qr.php b/TwoFactorAuth/Controller/Adminhtml/Google/Qr.php index 53f30bdf..f5eb03e6 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Google/Qr.php +++ b/TwoFactorAuth/Controller/Adminhtml/Google/Qr.php @@ -18,6 +18,7 @@ /** * QR code generator for google authenticator + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Qr extends AbstractAction implements HttpGetActionInterface @@ -65,6 +66,7 @@ public function __construct( /** * Get current user + * * @return User|null */ private function getUser(): ?User diff --git a/TwoFactorAuth/Controller/Adminhtml/Tfa/Index.php b/TwoFactorAuth/Controller/Adminhtml/Tfa/Index.php index 1b57203c..11c5e19f 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Tfa/Index.php +++ b/TwoFactorAuth/Controller/Adminhtml/Tfa/Index.php @@ -87,6 +87,7 @@ public function __construct( /** * @inheritdoc + * * @throws LocalizedException */ public function execute() diff --git a/TwoFactorAuth/Controller/Adminhtml/Tfa/Reset.php b/TwoFactorAuth/Controller/Adminhtml/Tfa/Reset.php index 57a99c04..6f2d3a9d 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Tfa/Reset.php +++ b/TwoFactorAuth/Controller/Adminhtml/Tfa/Reset.php @@ -18,6 +18,7 @@ /** * Reset 2FA configuration controller + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Reset extends AbstractAction implements HttpGetActionInterface, HttpPostActionInterface @@ -57,6 +58,7 @@ public function __construct( /** * @inheritdoc + * * @throws LocalizedException */ public function execute() diff --git a/TwoFactorAuth/Controller/Adminhtml/U2f/Configure.php b/TwoFactorAuth/Controller/Adminhtml/U2f/Configure.php index 072492f2..7c33c2c6 100644 --- a/TwoFactorAuth/Controller/Adminhtml/U2f/Configure.php +++ b/TwoFactorAuth/Controller/Adminhtml/U2f/Configure.php @@ -18,7 +18,8 @@ use Magento\TwoFactorAuth\Model\UserConfig\HtmlAreaTokenVerifier; /** - * CUbiKey configuration page controller + * Configuration page for U2f + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Configure extends AbstractConfigureAction implements HttpGetActionInterface @@ -68,6 +69,8 @@ public function execute() } /** + * Get the current user + * * @return User|null */ private function getUser(): ?User diff --git a/TwoFactorAuth/Controller/Adminhtml/U2f/Configurepost.php b/TwoFactorAuth/Controller/Adminhtml/U2f/Configurepost.php index ecc8ef7e..91978915 100644 --- a/TwoFactorAuth/Controller/Adminhtml/U2f/Configurepost.php +++ b/TwoFactorAuth/Controller/Adminhtml/U2f/Configurepost.php @@ -142,6 +142,8 @@ public function execute() } /** + * Get the current user + * * @return User|null */ private function getUser(): ?User diff --git a/TwoFactorAuth/Model/Alert.php b/TwoFactorAuth/Model/Alert.php index f07efd55..f6d28044 100644 --- a/TwoFactorAuth/Model/Alert.php +++ b/TwoFactorAuth/Model/Alert.php @@ -30,6 +30,7 @@ public function __construct( /** * Trigger a security suite event + * * @param string $module * @param string $message * @param string $level diff --git a/TwoFactorAuth/Model/AlertInterface.php b/TwoFactorAuth/Model/AlertInterface.php index 1d48f900..ce07377d 100644 --- a/TwoFactorAuth/Model/AlertInterface.php +++ b/TwoFactorAuth/Model/AlertInterface.php @@ -59,6 +59,7 @@ interface AlertInterface /** * Trigger a security suite event + * * @param string $module * @param string $message * @param string $level diff --git a/TwoFactorAuth/Model/CountryRegistry.php b/TwoFactorAuth/Model/CountryRegistry.php index da6364ba..d45f11cd 100644 --- a/TwoFactorAuth/Model/CountryRegistry.php +++ b/TwoFactorAuth/Model/CountryRegistry.php @@ -30,6 +30,7 @@ class CountryRegistry /** * Remove registry entity by id + * * @param int $id */ public function removeById(int $id): void @@ -48,6 +49,7 @@ public function removeById(int $id): void /** * Push one object into registry + * * @param int $id * @return CountryInterface|null */ @@ -62,6 +64,7 @@ public function retrieveById(int $id): ?CountryInterface /** * Retrieve by Code value + * * @param string $value * @return CountryInterface|null */ @@ -76,6 +79,7 @@ public function retrieveByCode(string $value): ?CountryInterface /** * Push one object into registry + * * @param Country $country */ public function push(Country $country): void diff --git a/TwoFactorAuth/Model/Provider.php b/TwoFactorAuth/Model/Provider.php index 266da84f..90195248 100644 --- a/TwoFactorAuth/Model/Provider.php +++ b/TwoFactorAuth/Model/Provider.php @@ -161,6 +161,7 @@ public function isConfigured(int $userId): bool /** * Retrieve user's configuration + * * @param int $userId * @return array|null * @throws NoSuchEntityException diff --git a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity.php b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity.php index 1be0abe1..362c444c 100644 --- a/TwoFactorAuth/Model/Provider/Engine/DuoSecurity.php +++ b/TwoFactorAuth/Model/Provider/Engine/DuoSecurity.php @@ -88,6 +88,7 @@ public function __construct( /** * Get API hostname + * * @return string */ public function getApiHostname(): string @@ -97,6 +98,7 @@ public function getApiHostname(): string /** * Get application key + * * @return string */ private function getApplicationKey(): string @@ -106,6 +108,7 @@ private function getApplicationKey(): string /** * Get secret key + * * @return string */ private function getSecretKey(): string @@ -115,6 +118,7 @@ private function getSecretKey(): string /** * Get integration key + * * @return string */ private function getIntegrationKey(): string @@ -124,6 +128,7 @@ private function getIntegrationKey(): string /** * Sign values + * * @param string $key * @param string $values * @param string $prefix @@ -142,6 +147,7 @@ private function signValues(string $key, string $values, string $prefix, int $ex /** * Parse signed values and return username + * * @param string $key * @param string $val * @param string $prefix @@ -190,6 +196,7 @@ private function parseValues(string $key, string $val, string $prefix, int $time /** * Get request signature + * * @param UserInterface $user * @return string */ diff --git a/TwoFactorAuth/Model/ResourceModel/Country.php b/TwoFactorAuth/Model/ResourceModel/Country.php index d2c7d0c6..ec5bdc26 100644 --- a/TwoFactorAuth/Model/ResourceModel/Country.php +++ b/TwoFactorAuth/Model/ResourceModel/Country.php @@ -11,10 +11,14 @@ /** * Country model + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class Country extends AbstractDb { + /** + * @inheritDoc + */ protected function _construct() { $this->_init('tfa_country_codes', 'country_id'); diff --git a/TwoFactorAuth/Model/ResourceModel/CountryRepository.php b/TwoFactorAuth/Model/ResourceModel/CountryRepository.php index cc5b3b49..1b4abb0c 100644 --- a/TwoFactorAuth/Model/ResourceModel/CountryRepository.php +++ b/TwoFactorAuth/Model/ResourceModel/CountryRepository.php @@ -25,6 +25,7 @@ /** * @inheritDoc + * * @SuppressWarnings(PHPMD.ShortVariable) * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -102,7 +103,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function save(CountryInterface $country): CountryInterface { @@ -124,7 +125,8 @@ public function save(CountryInterface $country): CountryInterface } /** - * {@inheritdoc} + * @inheritdoc + * * @throws NoSuchEntityException */ public function getById(int $id): CountryInterface @@ -145,7 +147,8 @@ public function getById(int $id): CountryInterface } /** - * {@inheritdoc} + * @inheritdoc + * * @throws NoSuchEntityException */ public function getByCode(string $value): CountryInterface @@ -166,7 +169,7 @@ public function getByCode(string $value): CountryInterface } /** - * {@inheritdoc} + * @inheritdoc */ public function delete(CountryInterface $country): void { @@ -185,7 +188,7 @@ public function delete(CountryInterface $country): void } /** - * {@inheritdoc} + * @inheritdoc */ public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface { diff --git a/TwoFactorAuth/Model/ResourceModel/UserConfig.php b/TwoFactorAuth/Model/ResourceModel/UserConfig.php index 08eb44a6..a97ddada 100644 --- a/TwoFactorAuth/Model/ResourceModel/UserConfig.php +++ b/TwoFactorAuth/Model/ResourceModel/UserConfig.php @@ -16,6 +16,8 @@ use Magento\Framework\Serialize\SerializerInterface; /** + * User config model + * * @SuppressWarnings(PHPMD.CamelCaseMethodName) */ class UserConfig extends AbstractDb @@ -27,16 +29,12 @@ class UserConfig extends AbstractDb /** * @param Context $context - * @param null $decoder - * @param null $encoder - * @param null $connectionName + * @param string $connectionName * @param EncryptorInterface $encryptor * @param SerializerInterface $serializer */ public function __construct( Context $context, - $decoder = null, - $encoder = null, $connectionName = null, EncryptorInterface $encryptor = null, SerializerInterface $serializer = null @@ -57,6 +55,8 @@ protected function _construct() } /** + * Encode the provided config + * * @param array $config * @return string */ @@ -66,6 +66,8 @@ private function encodeConfig(array $config): string } /** + * Decode the provided config + * * @param string $config * @return array */ @@ -81,6 +83,9 @@ private function decodeConfig(string $config): array return $this->serializer->unserialize($config); } + /** + * @inheritDoc + */ public function _afterLoad(AbstractModel $object) { parent::_afterLoad($object); @@ -100,6 +105,9 @@ public function _afterLoad(AbstractModel $object) return $this; } + /** + * @inheritDoc + */ public function _beforeSave(AbstractModel $object) { $object->setData('encoded_config', $this->encodeConfig($object->getData('config') ?? [])); diff --git a/TwoFactorAuth/Model/ResourceModel/UserConfigRepository.php b/TwoFactorAuth/Model/ResourceModel/UserConfigRepository.php index d47d3263..dd865ec1 100644 --- a/TwoFactorAuth/Model/ResourceModel/UserConfigRepository.php +++ b/TwoFactorAuth/Model/ResourceModel/UserConfigRepository.php @@ -25,6 +25,7 @@ /** * @inheritDoc + * * @SuppressWarnings(PHPMD.ShortVariable) * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -102,7 +103,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function save(UserConfigInterface $userConfig): UserConfigInterface { @@ -124,7 +125,7 @@ public function save(UserConfigInterface $userConfig): UserConfigInterface } /** - * {@inheritdoc} + * @inheritdoc */ public function getById(int $id): UserConfigInterface { @@ -144,7 +145,7 @@ public function getById(int $id): UserConfigInterface } /** - * {@inheritdoc} + * @inheritdoc */ public function getByUserId(int $value): UserConfigInterface { @@ -164,7 +165,7 @@ public function getByUserId(int $value): UserConfigInterface } /** - * {@inheritdoc} + * @inheritdoc */ public function delete(UserConfigInterface $userConfig): bool { @@ -185,7 +186,7 @@ public function delete(UserConfigInterface $userConfig): bool } /** - * {@inheritdoc} + * @inheritdoc */ public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface { diff --git a/TwoFactorAuth/Model/Tfa.php b/TwoFactorAuth/Model/Tfa.php index 4bb945e1..cd7c567c 100644 --- a/TwoFactorAuth/Model/Tfa.php +++ b/TwoFactorAuth/Model/Tfa.php @@ -231,6 +231,7 @@ public function isEnabled(): bool /** * Return true if a provider code is allowed + * * @param int $userId * @param string $providerCode * @throws NoSuchEntityException @@ -252,6 +253,7 @@ public function getDefaultProviderCode(int $userId): string /** * Set default provider code + * * @param int $userId * @param string $providerCode * @return boolean diff --git a/TwoFactorAuth/Model/UserConfigManager.php b/TwoFactorAuth/Model/UserConfigManager.php index c3875747..2160d576 100644 --- a/TwoFactorAuth/Model/UserConfigManager.php +++ b/TwoFactorAuth/Model/UserConfigManager.php @@ -61,7 +61,7 @@ public function getProviderConfig(int $userId, string $providerCode): ?array /** * @inheritdoc */ - public function setProviderConfig(int $userId, string $providerCode, ?array $config=null): bool + public function setProviderConfig(int $userId, string $providerCode, ?array $config = null): bool { $userConfig = $this->getUserConfiguration($userId); $providersConfig = $userConfig->getData('config'); @@ -83,7 +83,7 @@ public function setProviderConfig(int $userId, string $providerCode, ?array $con /** * @inheritdoc */ - public function addProviderConfig(int $userId, string $providerCode, ?array $config=null): bool + public function addProviderConfig(int $userId, string $providerCode, ?array $config = null): bool { $userConfig = $this->getProviderConfig($userId, $providerCode); if ($userConfig === null) { @@ -106,6 +106,7 @@ public function resetProviderConfig(int $userId, string $providerCode): bool /** * Get user TFA config + * * @param int $userId * @return UserConfig */ diff --git a/TwoFactorAuth/Model/UserConfigRegistry.php b/TwoFactorAuth/Model/UserConfigRegistry.php index f283ba48..2cfe1e2c 100644 --- a/TwoFactorAuth/Model/UserConfigRegistry.php +++ b/TwoFactorAuth/Model/UserConfigRegistry.php @@ -30,6 +30,7 @@ class UserConfigRegistry /** * Remove registry entity by id + * * @param int $id */ public function removeById(int $id): void @@ -48,6 +49,7 @@ public function removeById(int $id): void /** * Push one object into registry + * * @param int $id * @return UserConfigInterface|null */ @@ -58,6 +60,7 @@ public function retrieveById(int $id): ?UserConfigInterface /** * Retrieve by UserId value + * * @param int $value * @return UserConfigInterface|null */ @@ -72,6 +75,7 @@ public function retrieveByUserId(int $value): ?UserConfigInterface /** * Push one object into registry + * * @param UserConfig $userConfig */ public function push(UserConfig $userConfig): void diff --git a/TwoFactorAuth/Observer/AdminUserLoadAfter.php b/TwoFactorAuth/Observer/AdminUserLoadAfter.php index 151d53b3..d9c1dbc3 100644 --- a/TwoFactorAuth/Observer/AdminUserLoadAfter.php +++ b/TwoFactorAuth/Observer/AdminUserLoadAfter.php @@ -21,6 +21,9 @@ class AdminUserLoadAfter implements ObserverInterface */ private $userConfigManager; + /** + * @param UserConfigManagerInterface $userConfigManager + */ public function __construct( UserConfigManagerInterface $userConfigManager ) { @@ -28,6 +31,8 @@ public function __construct( } /** + * @inheritDoc + * * @param Observer $observer * @return void */ diff --git a/TwoFactorAuth/Plugin/AddTabToAdminUserEdit.php b/TwoFactorAuth/Plugin/AddTabToAdminUserEdit.php index f788c95a..99bb9f3d 100644 --- a/TwoFactorAuth/Plugin/AddTabToAdminUserEdit.php +++ b/TwoFactorAuth/Plugin/AddTabToAdminUserEdit.php @@ -40,6 +40,8 @@ public function __construct( } /** + * Check if tab should be displayed + * * @param Tabs $subject * @throws LocalizedException */ diff --git a/TwoFactorAuth/Plugin/AvoidRecursionOnPasswordChange.php b/TwoFactorAuth/Plugin/AvoidRecursionOnPasswordChange.php index 597d9688..0caa8bd9 100644 --- a/TwoFactorAuth/Plugin/AvoidRecursionOnPasswordChange.php +++ b/TwoFactorAuth/Plugin/AvoidRecursionOnPasswordChange.php @@ -41,6 +41,8 @@ public function __construct( } /** + * Prevent recursion + * * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @param ForceAdminPasswordChangeObserver $subject * @param Closure $proceed diff --git a/TwoFactorAuth/Setup/Patch/Data/CopyConfigFromOldModule.php b/TwoFactorAuth/Setup/Patch/Data/CopyConfigFromOldModule.php index caeab859..a627574b 100644 --- a/TwoFactorAuth/Setup/Patch/Data/CopyConfigFromOldModule.php +++ b/TwoFactorAuth/Setup/Patch/Data/CopyConfigFromOldModule.php @@ -71,7 +71,8 @@ private function copyConfig(array $paths): void } /** - * {@inheritdoc} + * Copy the old config + * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -86,10 +87,12 @@ public function apply() ]); $this->moduleDataSetup->endSetup(); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -97,7 +100,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/TwoFactorAuth/Setup/Patch/Data/EncryptConfiguration.php b/TwoFactorAuth/Setup/Patch/Data/EncryptConfiguration.php index f2a16736..513b652e 100644 --- a/TwoFactorAuth/Setup/Patch/Data/EncryptConfiguration.php +++ b/TwoFactorAuth/Setup/Patch/Data/EncryptConfiguration.php @@ -12,7 +12,7 @@ use Magento\Framework\Setup\Patch\DataPatchInterface; /** - * Encrypt configuration + * Encrypt the configuration */ class EncryptConfiguration implements DataPatchInterface { @@ -39,7 +39,8 @@ public function __construct( } /** - * {@inheritdoc} + * Encrypt the config + * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -65,10 +66,12 @@ public function apply() } $this->moduleDataSetup->endSetup(); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -76,7 +79,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/TwoFactorAuth/Setup/Patch/Data/GenerateDuoSecurityKey.php b/TwoFactorAuth/Setup/Patch/Data/GenerateDuoSecurityKey.php index e92e2a7e..f987a424 100644 --- a/TwoFactorAuth/Setup/Patch/Data/GenerateDuoSecurityKey.php +++ b/TwoFactorAuth/Setup/Patch/Data/GenerateDuoSecurityKey.php @@ -49,7 +49,8 @@ public function __construct( } /** - * {@inheritdoc} + * Create and set the duo key + * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -71,10 +72,12 @@ public function apply() } $this->moduleDataSetup->endSetup(); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -84,7 +87,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/TwoFactorAuth/Setup/Patch/Data/PopulateCountryTable.php b/TwoFactorAuth/Setup/Patch/Data/PopulateCountryTable.php index fa5c6474..d49855dd 100644 --- a/TwoFactorAuth/Setup/Patch/Data/PopulateCountryTable.php +++ b/TwoFactorAuth/Setup/Patch/Data/PopulateCountryTable.php @@ -57,7 +57,8 @@ public function __construct( } /** - * {@inheritdoc} + * Install the country table + * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -84,10 +85,12 @@ public function apply() // @codingStandardsIgnoreEnd $this->moduleDataSetup->endSetup(); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -95,7 +98,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Google/ConfigureTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Google/ConfigureTest.php index 01e404e4..a82c6348 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Google/ConfigureTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Google/ConfigureTest.php @@ -32,6 +32,7 @@ class ConfigureTest extends AbstractConfigureBackendController /** * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers google + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testTokenAccess(): void { @@ -41,6 +42,7 @@ public function testTokenAccess(): void /** * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers google + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclHasAccess() { @@ -50,6 +52,7 @@ public function testAclHasAccess() /** * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers google + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclNoAccess() { diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Google/ConfigurepostTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Google/ConfigurepostTest.php index 4bca9411..bb9dd8e8 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Google/ConfigurepostTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Google/ConfigurepostTest.php @@ -41,6 +41,7 @@ public function testTokenAccess(): void /** * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers google + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclHasAccess() { @@ -50,6 +51,7 @@ public function testAclHasAccess() /** * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers google + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAclNoAccess() { diff --git a/TwoFactorAuth/Ui/Component/Form/User/DataProvider.php b/TwoFactorAuth/Ui/Component/Form/User/DataProvider.php index a43cfaa5..ee3a5dbc 100644 --- a/TwoFactorAuth/Ui/Component/Form/User/DataProvider.php +++ b/TwoFactorAuth/Ui/Component/Form/User/DataProvider.php @@ -40,7 +40,6 @@ class DataProvider extends AbstractDataProvider private $url; /** - * DataProvider constructor. * @param CollectionFactory $collectionFactory * @param EnabledProvider $enabledProvider * @param UserConfigManagerInterface $userConfigManager @@ -75,6 +74,7 @@ public function __construct( /** * Get a list of forced providers + * * @return array */ private function getForcedProviders() @@ -92,6 +92,7 @@ private function getForcedProviders() /** * Get reset provider urls + * * @param User $user * @return array */ diff --git a/TwoFactorAuth/view/adminhtml/templates/tfa/change_provider.phtml b/TwoFactorAuth/view/adminhtml/templates/tfa/change_provider.phtml index d06fbd8e..c9fda998 100644 --- a/TwoFactorAuth/view/adminhtml/templates/tfa/change_provider.phtml +++ b/TwoFactorAuth/view/adminhtml/templates/tfa/change_provider.phtml @@ -11,7 +11,7 @@ diff --git a/TwoFactorAuth/view/adminhtml/templates/tfa/configure.phtml b/TwoFactorAuth/view/adminhtml/templates/tfa/configure.phtml index 29200c3b..89fe426c 100644 --- a/TwoFactorAuth/view/adminhtml/templates/tfa/configure.phtml +++ b/TwoFactorAuth/view/adminhtml/templates/tfa/configure.phtml @@ -9,12 +9,17 @@ $list = $block->getProvidersList(); ?>

- escapeHtml(__('Please select one or more Two-Factor Authorization providers to be used to authorize admin users')); ?> + escapeHtml(__( + 'Please select one or more Two-Factor Authorization providers to be used to authorize admin users' + )); ?>

- escapeHtml(__('No providers are available to select, please configure required 2FA provider settings on the server via CLI')); ?> + escapeHtml(__( + 'No providers are available to select, please ' + . 'configure required 2FA provider settings on the server via CLI' + )); ?>

diff --git a/TwoFactorAuth/view/adminhtml/templates/tfa/configure_later.phtml b/TwoFactorAuth/view/adminhtml/templates/tfa/configure_later.phtml index e9ea5a46..8eeb2049 100644 --- a/TwoFactorAuth/view/adminhtml/templates/tfa/configure_later.phtml +++ b/TwoFactorAuth/view/adminhtml/templates/tfa/configure_later.phtml @@ -9,5 +9,7 @@ /** @var $escaper Magento\Framework\Escaper */ ?>
- +
diff --git a/TwoFactorAuth/view/adminhtml/templates/tfa/provider/auth.phtml b/TwoFactorAuth/view/adminhtml/templates/tfa/provider/auth.phtml index 7ddc2c47..c1dfcd48 100644 --- a/TwoFactorAuth/view/adminhtml/templates/tfa/provider/auth.phtml +++ b/TwoFactorAuth/view/adminhtml/templates/tfa/provider/auth.phtml @@ -13,7 +13,7 @@ diff --git a/TwoFactorAuth/view/adminhtml/templates/tfa/provider/configure.phtml b/TwoFactorAuth/view/adminhtml/templates/tfa/provider/configure.phtml index 017cc618..ecd00dd5 100644 --- a/TwoFactorAuth/view/adminhtml/templates/tfa/provider/configure.phtml +++ b/TwoFactorAuth/view/adminhtml/templates/tfa/provider/configure.phtml @@ -11,7 +11,7 @@ diff --git a/TwoFactorAuth/view/adminhtml/templates/tfa/request_config.phtml b/TwoFactorAuth/view/adminhtml/templates/tfa/request_config.phtml index 7a45baec..6d50eee3 100644 --- a/TwoFactorAuth/view/adminhtml/templates/tfa/request_config.phtml +++ b/TwoFactorAuth/view/adminhtml/templates/tfa/request_config.phtml @@ -7,6 +7,10 @@ /** @var \Magento\Backend\Block\Template $block */ ?>
-

escapeHtml(__('You need to configure Two-Factor Authorization in order to proceed to your store\'s admin area')); ?>

-

escapeHtml(__('An E-mail was sent to you with further instructions')); ?>

+

escapeHtml(__( + 'You need to configure Two-Factor Authorization in order to proceed to your store\'s admin area' + )); ?>

+

escapeHtml(__( + 'An E-mail was sent to you with further instructions' + )); ?>

From a91106f5eb4392f3e61ee32ec3b0771edd7f1939 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Sun, 24 May 2020 17:29:11 -0500 Subject: [PATCH 52/58] MC-30537: Test automation with the new 2FA enabled by default - Static fixes --- TwoFactorAuth/Api/TfaInterface.php | 1 + .../Block/Provider/Authy/Configure.php | 2 +- TwoFactorAuth/Command/TfaProviders.php | 2 ++ TwoFactorAuth/Command/TfaReset.php | 2 ++ .../Controller/Adminhtml/Duo/Auth.php | 9 ++++++++ .../Controller/Adminhtml/Tfa/Index.php | 2 ++ TwoFactorAuth/Model/EmailUserNotifier.php | 23 ------------------- .../Provider/Engine/Authy/Authenticate.php | 2 ++ .../Model/Provider/Engine/Google.php | 2 ++ .../Provider/Engine/U2fKey/Authenticate.php | 2 ++ .../Model/Provider/Engine/U2fKey/Session.php | 2 ++ .../Model/Provider/Engine/U2fKey/WebAuthn.php | 3 +++ .../Model/ResourceModel/UserConfig.php | 2 +- TwoFactorAuth/Model/TfaSession.php | 2 ++ .../UserConfig/HtmlAreaTokenVerifier.php | 3 +++ .../Observer/ControllerActionPredispatch.php | 2 ++ TwoFactorAuth/Plugin/DeleteCookieOnLogout.php | 2 ++ .../Setup/Patch/Data/EncryptSecrets.php | 2 ++ .../Patch/Data/GenerateDuoSecurityKey.php | 2 +- .../Adminhtml/Google/ConfigurepostTest.php | 1 + .../adminhtml/templates/tfa/configure.phtml | 2 +- .../templates/tfa/configure_later.phtml | 4 ++-- .../templates/tfa/provider/auth.phtml | 2 +- .../templates/tfa/request_config.phtml | 4 ++-- 24 files changed, 48 insertions(+), 32 deletions(-) diff --git a/TwoFactorAuth/Api/TfaInterface.php b/TwoFactorAuth/Api/TfaInterface.php index 58394f62..ab819ff3 100644 --- a/TwoFactorAuth/Api/TfaInterface.php +++ b/TwoFactorAuth/Api/TfaInterface.php @@ -96,6 +96,7 @@ public function getProvidersToActivate(int $userId): array; * @param int $userId * @param string $providerCode * @return bool + * @SuppressWarnings(PHPMD.BooleanGetMethodName) */ public function getProviderIsAllowed(int $userId, string $providerCode): bool; diff --git a/TwoFactorAuth/Block/Provider/Authy/Configure.php b/TwoFactorAuth/Block/Provider/Authy/Configure.php index 7672eb6d..40e97ae8 100644 --- a/TwoFactorAuth/Block/Provider/Authy/Configure.php +++ b/TwoFactorAuth/Block/Provider/Authy/Configure.php @@ -37,7 +37,7 @@ public function __construct( /** * Get a country list * - * return array + * @return array */ private function getCountriesList() { diff --git a/TwoFactorAuth/Command/TfaProviders.php b/TwoFactorAuth/Command/TfaProviders.php index 1824f6e2..a2aeebe7 100644 --- a/TwoFactorAuth/Command/TfaProviders.php +++ b/TwoFactorAuth/Command/TfaProviders.php @@ -55,5 +55,7 @@ protected function execute(InputInterface $input, OutputInterface $output) foreach ($providers as $provider) { $output->writeln(sprintf("%16s: %s", $provider->getCode(), $provider->getName())); } + + return 0; } } diff --git a/TwoFactorAuth/Command/TfaReset.php b/TwoFactorAuth/Command/TfaReset.php index 74cebe05..c3020ee0 100644 --- a/TwoFactorAuth/Command/TfaReset.php +++ b/TwoFactorAuth/Command/TfaReset.php @@ -98,5 +98,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->userConfigManager->resetProviderConfig((int) $user->getId(), $providerCode); $output->writeln('' . __('Provider %1 has been reset for user %2', $provider->getName(), $userName)); + + return 0; } } diff --git a/TwoFactorAuth/Controller/Adminhtml/Duo/Auth.php b/TwoFactorAuth/Controller/Adminhtml/Duo/Auth.php index 6c9bb734..e5fbfbaa 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Duo/Auth.php +++ b/TwoFactorAuth/Controller/Adminhtml/Duo/Auth.php @@ -48,6 +48,14 @@ class Auth extends AbstractAction implements HttpGetActionInterface */ private $tokenVerifier; + /** + * @param Action\Context $context + * @param Session $session + * @param PageFactory $pageFactory + * @param UserConfigManagerInterface $userConfigManager + * @param TfaInterface $tfa + * @param HtmlAreaTokenVerifier $tokenVerifier + */ public function __construct( Action\Context $context, Session $session, @@ -66,6 +74,7 @@ public function __construct( /** * Get current user + * * @return \Magento\User\Model\User|null */ private function getUser() diff --git a/TwoFactorAuth/Controller/Adminhtml/Tfa/Index.php b/TwoFactorAuth/Controller/Adminhtml/Tfa/Index.php index 11c5e19f..f428d6c4 100644 --- a/TwoFactorAuth/Controller/Adminhtml/Tfa/Index.php +++ b/TwoFactorAuth/Controller/Adminhtml/Tfa/Index.php @@ -89,6 +89,8 @@ public function __construct( * @inheritdoc * * @throws LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function execute() { diff --git a/TwoFactorAuth/Model/EmailUserNotifier.php b/TwoFactorAuth/Model/EmailUserNotifier.php index 8c2d7917..96c669af 100644 --- a/TwoFactorAuth/Model/EmailUserNotifier.php +++ b/TwoFactorAuth/Model/EmailUserNotifier.php @@ -45,16 +45,6 @@ class EmailUserNotifier implements UserNotifierInterface */ private $logger; - /** - * @var UrlInterface - */ - private $url; - - /** - * @var State - */ - private $appState; - /** * @var UserNotifierConfig */ @@ -66,7 +56,6 @@ class EmailUserNotifier implements UserNotifierInterface * @param StoreManagerInterface $storeManager * @param LoggerInterface $logger * @param UrlInterface $url - * @param State $appState * @param UserNotifierConfig $userNotifierConfig */ public function __construct( @@ -75,7 +64,6 @@ public function __construct( StoreManagerInterface $storeManager, LoggerInterface $logger, UrlInterface $url, - State $appState, UserNotifierConfig $userNotifierConfig ) { $this->scopeConfig = $scopeConfig; @@ -83,7 +71,6 @@ public function __construct( $this->storeManager = $storeManager; $this->logger = $logger; $this->url = $url; - $this->appState = $appState; $this->userNotifierConfig = $userNotifierConfig; } @@ -154,14 +141,4 @@ public function sendAppConfigRequestMessage(User $user, string $token): void $this->userNotifierConfig->getAppRequestConfigUrl($token) ); } - - /** - * Determine if the environment is webapi or not - * - * @return bool - */ - private function isWebapi(): bool - { - return in_array($this->appState->getAreaCode(), [Area::AREA_WEBAPI_REST, Area::AREA_WEBAPI_SOAP]); - } } diff --git a/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php index 4c0c786e..6318608e 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/Authy/Authenticate.php @@ -21,6 +21,8 @@ /** * Authenticate a user with authy + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Authenticate implements AuthyAuthenticateInterface { diff --git a/TwoFactorAuth/Model/Provider/Engine/Google.php b/TwoFactorAuth/Model/Provider/Engine/Google.php index eabdb244..5762ab0e 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google.php @@ -26,6 +26,8 @@ /** * Google authenticator engine + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Google implements EngineInterface { diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php index e5ebd435..7b6abe21 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Authenticate.php @@ -25,6 +25,8 @@ /** * Authenticate with the u2f provider and get an admin token + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Authenticate implements U2fKeyAuthenticateInterface { diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Session.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Session.php index 8ec49a84..7bec3c6f 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/Session.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/Session.php @@ -12,6 +12,8 @@ /** * Represents u2f key session data + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Session extends SessionManager { diff --git a/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php b/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php index d2d3f31c..c314f8ca 100644 --- a/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php +++ b/TwoFactorAuth/Model/Provider/Engine/U2fKey/WebAuthn.php @@ -57,6 +57,7 @@ public function __construct( * @param array $publicKeys * @param array $originalChallenge * @throws LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function assertCredentialDataIsValid( array $credentialData, @@ -224,6 +225,8 @@ public function getRegisterData(UserInterface $user): array * @param array $data * @return array * @throws ValidationException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function getPublicKeyFromRegistrationData(array $data): array { diff --git a/TwoFactorAuth/Model/ResourceModel/UserConfig.php b/TwoFactorAuth/Model/ResourceModel/UserConfig.php index a97ddada..44da7016 100644 --- a/TwoFactorAuth/Model/ResourceModel/UserConfig.php +++ b/TwoFactorAuth/Model/ResourceModel/UserConfig.php @@ -113,6 +113,6 @@ public function _beforeSave(AbstractModel $object) $object->setData('encoded_config', $this->encodeConfig($object->getData('config') ?? [])); $object->setData('encoded_providers', $this->serializer->serialize($object->getData('providers') ?? [])); - parent::_beforeSave($object); + return parent::_beforeSave($object); } } diff --git a/TwoFactorAuth/Model/TfaSession.php b/TwoFactorAuth/Model/TfaSession.php index 2c6a7266..67a337eb 100644 --- a/TwoFactorAuth/Model/TfaSession.php +++ b/TwoFactorAuth/Model/TfaSession.php @@ -12,6 +12,8 @@ /** * @inheritDoc + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class TfaSession extends SessionManager implements TfaSessionInterface { diff --git a/TwoFactorAuth/Model/UserConfig/HtmlAreaTokenVerifier.php b/TwoFactorAuth/Model/UserConfig/HtmlAreaTokenVerifier.php index e461adb4..1192df29 100644 --- a/TwoFactorAuth/Model/UserConfig/HtmlAreaTokenVerifier.php +++ b/TwoFactorAuth/Model/UserConfig/HtmlAreaTokenVerifier.php @@ -19,6 +19,8 @@ * Finds and verifies token allowing users to configure 2FA. * * Works for adminhtml area. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class HtmlAreaTokenVerifier { @@ -90,6 +92,7 @@ public function isConfigTokenProvided(): bool * Read configuration token provided by user. * * @return string|null + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function readConfigToken(): ?string { diff --git a/TwoFactorAuth/Observer/ControllerActionPredispatch.php b/TwoFactorAuth/Observer/ControllerActionPredispatch.php index 53b29956..7923586a 100644 --- a/TwoFactorAuth/Observer/ControllerActionPredispatch.php +++ b/TwoFactorAuth/Observer/ControllerActionPredispatch.php @@ -24,6 +24,8 @@ /** * Handle redirection to 2FA page if required + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ControllerActionPredispatch implements ObserverInterface { diff --git a/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php b/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php index 3551f5ec..cc15737d 100644 --- a/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php +++ b/TwoFactorAuth/Plugin/DeleteCookieOnLogout.php @@ -14,6 +14,8 @@ /** * Deletes the tfat cookie on logout + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class DeleteCookieOnLogout { diff --git a/TwoFactorAuth/Setup/Patch/Data/EncryptSecrets.php b/TwoFactorAuth/Setup/Patch/Data/EncryptSecrets.php index 15ae99f9..e28d4eac 100644 --- a/TwoFactorAuth/Setup/Patch/Data/EncryptSecrets.php +++ b/TwoFactorAuth/Setup/Patch/Data/EncryptSecrets.php @@ -73,6 +73,8 @@ public function apply() } $this->moduleDataSetup->endSetup(); + + return $this; } /** diff --git a/TwoFactorAuth/Setup/Patch/Data/GenerateDuoSecurityKey.php b/TwoFactorAuth/Setup/Patch/Data/GenerateDuoSecurityKey.php index f987a424..bb2f4e68 100644 --- a/TwoFactorAuth/Setup/Patch/Data/GenerateDuoSecurityKey.php +++ b/TwoFactorAuth/Setup/Patch/Data/GenerateDuoSecurityKey.php @@ -14,7 +14,7 @@ use Magento\TwoFactorAuth\Model\Provider\Engine\DuoSecurity; /** - * Generate duo security key + * Generate initial duo security key */ class GenerateDuoSecurityKey implements DataPatchInterface { diff --git a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Google/ConfigurepostTest.php b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Google/ConfigurepostTest.php index bb9dd8e8..7c96f339 100644 --- a/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Google/ConfigurepostTest.php +++ b/TwoFactorAuth/Test/Integration/Controller/Adminhtml/Google/ConfigurepostTest.php @@ -32,6 +32,7 @@ class ConfigurepostTest extends AbstractConfigureBackendController /** * @inheritDoc * @magentoConfigFixture default/twofactorauth/general/force_providers google + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testTokenAccess(): void { diff --git a/TwoFactorAuth/view/adminhtml/templates/tfa/configure.phtml b/TwoFactorAuth/view/adminhtml/templates/tfa/configure.phtml index 89fe426c..9a444b42 100644 --- a/TwoFactorAuth/view/adminhtml/templates/tfa/configure.phtml +++ b/TwoFactorAuth/view/adminhtml/templates/tfa/configure.phtml @@ -27,7 +27,7 @@ $list = $block->getProvidersList();
  • -getChildHtml('change-provider') ?> +getChildHtml('change-provider') ?>