diff --git a/ReCaptchaUser/Test/Integration/LoginFormTest.php b/ReCaptchaUser/Test/Integration/LoginFormTest.php index ea8e7524..f0ff0437 100644 --- a/ReCaptchaUser/Test/Integration/LoginFormTest.php +++ b/ReCaptchaUser/Test/Integration/LoginFormTest.php @@ -179,6 +179,11 @@ private function checkSuccessfulGetResponse($shouldContainReCaptcha = false): vo $this->getRequest()->setUri($this->backendUrl->getUrl('admin')); $this->dispatch('backend/admin/auth/login'); + + if ($this->getResponse()->getHeader('Location')) { + $this->dispatch($this->getResponse()->getHeader('Location')->uri()->getPath()); + } + $content = $this->getResponse()->getBody(); self::assertNotEmpty($content); diff --git a/ReCaptchaVersion2Checkbox/etc/csp_whitelist.xml b/ReCaptchaVersion2Checkbox/etc/csp_whitelist.xml new file mode 100644 index 00000000..8a07aca9 --- /dev/null +++ b/ReCaptchaVersion2Checkbox/etc/csp_whitelist.xml @@ -0,0 +1,23 @@ + + + + + + + https://www.google.com/recaptcha/ + + + + + https://www.gstatic.com/recaptcha/ + https://www.google.com/recaptcha/ + + + + + diff --git a/ReCaptchaVersion2Invisible/etc/csp_whitelist.xml b/ReCaptchaVersion2Invisible/etc/csp_whitelist.xml new file mode 100644 index 00000000..8a07aca9 --- /dev/null +++ b/ReCaptchaVersion2Invisible/etc/csp_whitelist.xml @@ -0,0 +1,23 @@ + + + + + + + https://www.google.com/recaptcha/ + + + + + https://www.gstatic.com/recaptcha/ + https://www.google.com/recaptcha/ + + + + + diff --git a/ReCaptchaVersion3Invisible/etc/csp_whitelist.xml b/ReCaptchaVersion3Invisible/etc/csp_whitelist.xml new file mode 100644 index 00000000..8a07aca9 --- /dev/null +++ b/ReCaptchaVersion3Invisible/etc/csp_whitelist.xml @@ -0,0 +1,23 @@ + + + + + + + https://www.google.com/recaptcha/ + + + + + https://www.gstatic.com/recaptcha/ + https://www.google.com/recaptcha/ + + + + + diff --git a/TwoFactorAuth/Model/Provider/Engine/Google.php b/TwoFactorAuth/Model/Provider/Engine/Google.php index 5762ab0e..a982c764 100644 --- a/TwoFactorAuth/Model/Provider/Engine/Google.php +++ b/TwoFactorAuth/Model/Provider/Engine/Google.php @@ -98,7 +98,10 @@ public function __construct( private function generateSecret(): string { $secret = random_bytes(128); - return preg_replace('/[^A-Za-z0-9]/', '', Base32::encode($secret)); + // seed for iOS devices to avoid errors with barcode + $seed = 'abcd'; + + return preg_replace('/[^A-Za-z0-9]/', '', Base32::encode($seed . $secret)); } /** diff --git a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php index 8fd0cd8b..bd6e5eca 100644 --- a/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php +++ b/TwoFactorAuth/Test/Api/GoogleAuthenticateTest.php @@ -7,7 +7,12 @@ namespace Magento\TwoFactorAuth\Test\Api; +use Magento\Framework\HTTP\ClientInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\UrlInterface; use Magento\Framework\Webapi\Rest\Request; +use Magento\Integration\Model\Oauth\TokenFactory; +use Magento\Integration\Model\ResourceModel\Oauth\Token as TokenResource; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; use Magento\TwoFactorAuth\Api\TfaInterface; @@ -15,6 +20,9 @@ use Magento\User\Model\UserFactory; use OTPHP\TOTP; +/** + * Class checks google authentication behaviour + */ class GoogleAuthenticateTest extends WebapiAbstract { const SERVICE_VERSION = 'V1'; @@ -37,18 +45,53 @@ class GoogleAuthenticateTest extends WebapiAbstract */ private $tfa; + /** + * @var ClientInterface + */ + private $client; + + /** + * @var UrlInterface + */ + private $url; + + /** + * @var SerializerInterface + */ + private $json; + + /** + * @var TokenResource + */ + private $tokenResource; + + /** + * @var TokenFactory + */ + private $tokenFactory; + + /** + * @inheritdoc + */ protected function setUp(): void { $objectManager = Bootstrap::getObjectManager(); $this->userFactory = $objectManager->get(UserFactory::class); $this->google = $objectManager->get(Google::class); $this->tfa = $objectManager->get(TfaInterface::class); + $this->client = $objectManager->get(ClientInterface::class); + $this->url = $objectManager->get(UrlInterface::class); + $this->json = $objectManager->get(SerializerInterface::class); + $this->tokenResource = $objectManager->get(TokenResource::class); + $this->tokenFactory = $objectManager->get(TokenFactory::class); } /** * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + * + * @return void */ - public function testInvalidCredentials() + public function testInvalidCredentials(): void { $serviceInfo = $this->buildServiceInfo(); @@ -80,8 +123,10 @@ public function testInvalidCredentials() /** * @magentoConfigFixture twofactorauth/general/force_providers duo_security * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + * + * @return void */ - public function testUnavailableProvider() + public function testUnavailableProvider(): void { $serviceInfo = $this->buildServiceInfo(); @@ -109,8 +154,10 @@ public function testUnavailableProvider() /** * @magentoConfigFixture twofactorauth/general/force_providers google * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + * + * @return void */ - public function testInvalidToken() + public function testInvalidToken(): void { $userId = $this->getUserId(); $serviceInfo = $this->buildServiceInfo(); @@ -141,8 +188,10 @@ public function testInvalidToken() /** * @magentoConfigFixture twofactorauth/general/force_providers google * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php + * + * @return void */ - public function testNotConfiguredProvider() + public function testNotConfiguredProvider(): void { $userId = $this->getUserId(); $serviceInfo = $this->buildServiceInfo(); @@ -174,8 +223,10 @@ public function testNotConfiguredProvider() * @magentoConfigFixture twofactorauth/general/force_providers google * @magentoApiDataFixture Magento/User/_files/user_with_custom_role.php * @magentoConfigFixture twofactorauth/google/otp_window 120 + * + * @return void */ - public function testValidToken() + public function testValidToken(): void { $userId = $this->getUserId(); $otp = $this->getUserOtp(); @@ -195,6 +246,37 @@ public function testValidToken() self::assertMatchesRegularExpression('/^[a-z0-9]{32}$/', $response); } + /** + * @magentoConfigFixture default/oauth/access_token_lifetime/admin 1 + * @magentoConfigFixture twofactorauth/general/force_providers google + * + * @magentoApiDataFixture Magento/Webapi/_files/webapi_user.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @return void + */ + public function testAdminTokenLifetime(): void + { + $this->_markTestAsRestOnly(); + $this->tfa->getProviderByCode(Google::CODE)->activate($this->getUserId('webapi_user')); + $otp = $this->getUserOtp('webapi_user'); + $serviceInfo = $this->buildServiceInfo(); + $requestData = [ + 'otp' => $otp, + 'username' => 'webapi_user', + 'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, + ]; + $accessToken = $this->_webApiCall($serviceInfo, $requestData); + $result = $this->doCustomerRequest($accessToken, 1); + $this->assertContains('customer@example.com', $this->json->unserialize($result)); + $this->updateTokenCreatedTime($accessToken); + $result = $this->doCustomerRequest($accessToken, 1); + $this->assertContains( + 'The consumer isn\'t authorized to access %resources.', + $this->json->unserialize($result) + ); + } + /** * @return array */ @@ -217,20 +299,61 @@ private function buildServiceInfo(): array ]; } - private function getUserId(): int + /** + * Get user id + * + * @param string $userName + * @return int + */ + private function getUserId($userName = 'customRoleUser'): int { $user = $this->userFactory->create(); - $user->loadByUsername('customRoleUser'); + $user->loadByUsername($userName); return (int)$user->getId(); } - private function getUserOtp(): string + /** + * Get user otp + * + * @param string $userName + * @return string + */ + private function getUserOtp($userName = 'customRoleUser'): string { $user = $this->userFactory->create(); - $user->loadByUsername('customRoleUser'); + $user->loadByUsername($userName); $totp = TOTP::create($this->google->getSecretCode($user)); return $totp->now(); } + + /** + * Perform request to customers endpoint + * + * @param string $accessToken + * @return string + */ + private function doCustomerRequest(string $accessToken, $customerId): string + { + $this->client->addHeader('Authorization', 'Bearer ' . $accessToken); + $this->client->get($this->url->getBaseUrl() . 'rest/V1/customers/' . $customerId); + + return $this->client->getBody(); + } + + /** + * Update token created time + * + * @param string $accessToken + * @return void + */ + private function updateTokenCreatedTime(string $accessToken): void + { + $token = $this->tokenFactory->create(); + $token->loadByToken($accessToken); + $createdAt = (new \DateTime('-1 day'))->format('Y-m-d H:i:s'); + $token->setCreatedAt($createdAt); + $this->tokenResource->save($token); + } } diff --git a/TwoFactorAuth/Test/Integration/ControllerActionPredispatchTest.php b/TwoFactorAuth/Test/Integration/ControllerActionPredispatchTest.php index a52d473f..f2c27962 100644 --- a/TwoFactorAuth/Test/Integration/ControllerActionPredispatchTest.php +++ b/TwoFactorAuth/Test/Integration/ControllerActionPredispatchTest.php @@ -91,7 +91,7 @@ public function testUnauthenticated(): void $this->dispatch('backend/admin/index/index'); //Login controller redirects to full start-up URL $this->assertRedirect($this->stringContains('index')); - $properUrl = $this->getResponse()->getHeader('Location')->getFieldValue(); + $properUrl = $this->getResponse()->getHeader('Location')->uri()->getPath(); //Login page must be rendered without redirects $this->getRequest()->setDispatched(false); diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php index 28478822..4f3dcd37 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/Authy/AuthenticateTest.php @@ -156,7 +156,7 @@ public function testAuthenticateValidRequest() 'abc' ); - self::assertMatchesRegularExpression('/^[a-z0-9]{32}$/', $result); + self::assertNotEmpty($result); } /** @@ -290,7 +290,7 @@ public function testCreateTokenWithOneTouch() Bootstrap::ADMIN_PASSWORD ); - self::assertMatchesRegularExpression('/^[a-z0-9]{32}$/', $result); + self::assertNotEmpty($result); } /** diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php index 0328a383..ff62192c 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/DuoSecurity/AuthenticateTest.php @@ -257,7 +257,7 @@ public function testVerifyValidRequest() $signature ); - self::assertMatchesRegularExpression('/^[a-z0-9]{32}$/', $token); + self::assertNotEmpty($token); } /** diff --git a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php index ca912912..3448fc6a 100644 --- a/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php +++ b/TwoFactorAuth/Test/Integration/Model/Provider/Engine/U2fKey/AuthenticateTest.php @@ -238,7 +238,7 @@ public function testVerifyValidRequest() Bootstrap::ADMIN_PASSWORD, json_encode($verifyData) ); - self::assertMatchesRegularExpression('/^[a-z0-9]{32}$/', $token); + self::assertNotEmpty($token); } /** diff --git a/TwoFactorAuth/Test/Integration/UserConfigManagerTest.php b/TwoFactorAuth/Test/Integration/UserConfigManagerTest.php index ff947d45..0ef7d110 100644 --- a/TwoFactorAuth/Test/Integration/UserConfigManagerTest.php +++ b/TwoFactorAuth/Test/Integration/UserConfigManagerTest.php @@ -170,8 +170,8 @@ public function testShouldEncryptConfiguration(): void $encryptor = Bootstrap::getObjectManager()->create(EncryptorInterface::class); /** @var ResourceConnection $resourceConnection */ - $connection = Bootstrap::getObjectManager()->get(ResourceConnection::class) - ->getConnection(ResourceConnection::DEFAULT_CONNECTION); + $resourceConnection = Bootstrap::getObjectManager()->get(ResourceConnection::class); + $connection = $resourceConnection->getConnection(ResourceConnection::DEFAULT_CONNECTION); $configPayload = ['a' => 1, 'b' => 2]; @@ -181,8 +181,10 @@ public function testShouldEncryptConfiguration(): void $configPayload ); + $tfaUserConfig = $resourceConnection->getTableName('tfa_user_config'); + $qry = $connection->select() - ->from('tfa_user_config', 'encoded_config') + ->from($tfaUserConfig, 'encoded_config') ->where('user_id = ?', (int)$dummyUser->getId()); $res = $connection->fetchOne($qry); diff --git a/TwoFactorAuth/Test/Mftf/Test/AdminUpdateUserRoleTest.xml b/TwoFactorAuth/Test/Mftf/Test/AdminUpdateUserRoleTest.xml new file mode 100644 index 00000000..d07e2b46 --- /dev/null +++ b/TwoFactorAuth/Test/Mftf/Test/AdminUpdateUserRoleTest.xml @@ -0,0 +1,24 @@ + + + + + + + + <description value="Change full access role for admin user to custom one"/> + <severity value="AVERAGE"/> + </annotations> + <before> + <actionGroup ref="AdminChooseUserRoleResourceActionGroup" before="saveNewRole" stepKey="enableTfa"> + <argument name="resourceId" value="Magento_TwoFactorAuth::tfa"/> + <argument name="resourceName" value="Two Factor Auth"/> + </actionGroup> + </before> + </test> +</tests>