From 3436e87102610e762fbdfde6158f9932ea25a3af Mon Sep 17 00:00:00 2001 From: Michiel Kodde Date: Thu, 8 Feb 2024 12:02:29 +0100 Subject: [PATCH] Include SelfService SAT tests --- stepup/tests/behat/behat | 2 +- stepup/tests/behat/config/behat.yml | 2 + .../behat/features/bootstrap/RaContext.php | 29 +-- .../features/bootstrap/SelfServiceContext.php | 227 ++++++++++++++++-- .../tests/behat/features/selfservice.feature | 16 +- .../behat/features/selfservice_sat.feature | 33 +++ 6 files changed, 260 insertions(+), 49 deletions(-) create mode 100644 stepup/tests/behat/features/selfservice_sat.feature diff --git a/stepup/tests/behat/behat b/stepup/tests/behat/behat index 352d9cf..b9be949 100755 --- a/stepup/tests/behat/behat +++ b/stepup/tests/behat/behat @@ -4,4 +4,4 @@ # For now the only feature is that it pipes on any argument provided # to the script, into the behat command -./vendor/bin/behat --config config/behat.yml --strict $1 +./vendor/bin/behat --config config/behat.yml --tags=~SKIP $1 diff --git a/stepup/tests/behat/config/behat.yml b/stepup/tests/behat/config/behat.yml index 61af118..cab68af 100644 --- a/stepup/tests/behat/config/behat.yml +++ b/stepup/tests/behat/config/behat.yml @@ -6,6 +6,8 @@ default: paths: false suites: default: + filters: + tags: "~@wip&&~@skip" paths: - '%paths.base%/../features' contexts: diff --git a/stepup/tests/behat/features/bootstrap/RaContext.php b/stepup/tests/behat/features/bootstrap/RaContext.php index cb31869..7279519 100644 --- a/stepup/tests/behat/features/bootstrap/RaContext.php +++ b/stepup/tests/behat/features/bootstrap/RaContext.php @@ -49,30 +49,21 @@ public function gatherContexts(BeforeScenarioScope $scope) } /** - * @Given I vet my :tokenType second factor :vettingType + * @Given I vet my :tokenType second factor at the information desk */ - public function iVetMySecondFactor(string $tokenType, string $vettingType) + public function iVetMySecondFactorAtTheInformationDesk(string $tokenType) { - switch ($vettingType) { - case "at the information desk": - // We visit the RA location url - $this->minkContext->visit($this->raUrl); + // We visit the RA location url + $this->minkContext->visit($this->raUrl); + $this->iAmLoggedInIntoTheRaPortalAs('admin', 'yubikey'); - $this->iAmLoggedInIntoTheRaPortalAs('admin', 'yubikey'); - $this->iVetASpecificSecondFactor( - $tokenType, - $this->selfServiceContext->getVerifiedSecondFactorId(), - $this->selfServiceContext->getActivationCode() - ); - break; - case "using self asserted token registration": - break; - default: - throw new Exception(sprintf('The %s vettingType type is not supported', $vettingType)); - } + $this->iVetASpecificSecondFactor( + $tokenType, + $this->selfServiceContext->getVerifiedSecondFactorId(), + $this->selfServiceContext->getActivationCode() + ); } - /** * @Given /^I vet the last added second factor$/ */ diff --git a/stepup/tests/behat/features/bootstrap/SelfServiceContext.php b/stepup/tests/behat/features/bootstrap/SelfServiceContext.php index 5fae014..7038df9 100644 --- a/stepup/tests/behat/features/bootstrap/SelfServiceContext.php +++ b/stepup/tests/behat/features/bootstrap/SelfServiceContext.php @@ -106,12 +106,12 @@ public function registerNewToken(string $tokenType) $this->minkContext->getSession() ->getPage() ->find('css', '[href="/registration/sms/send-challenge"]')->click(); - $this->minkContext->assertPageAddress('/registration/sms/send-challenge'); // Start registration - $this->minkContext->assertPageContainsText('Send SMS code'); + $this->minkContext->assertPageContainsText('Send code'); $this->minkContext->fillField('ss_send_sms_challenge_subscriber', '612345678'); $this->minkContext->pressButton('Send code'); + // Now we should be on the prove possession page where we enter our OTP $this->minkContext->assertPageAddress('/registration/sms/prove-possession'); $this->minkContext->assertPageContainsText('Enter SMS code'); @@ -141,10 +141,6 @@ public function registerNewToken(string $tokenType) default: throw new Exception(sprintf('The %s token type is not supported', $tokenType)); } - - ## And we should now be on the mail verification page - $this->minkContext->assertPageContainsText('Verify your e-mail'); - $this->minkContext->assertPageContainsText('Check your inbox'); } /** @@ -225,35 +221,123 @@ public function iTryToSelfVetANewYubikeyTokenWithMySMSToken() } /** - * @When I verify my e-mail address + * @When I verify my e-mail address and choose the :vettingType vetting type */ - public function verifyEmailAddress() + public function verifyEmailAddress(string $vettingType) { + ## And we should now be on the mail verification page + $this->minkContext->assertPageContainsText('Verify your e-mail'); + $this->minkContext->assertPageContainsText('Check your inbox'); + $this->minkContext->visit( $this->getEmailVerificationUrl() ); - $this->iChooseToActivateMyTokenUsingServiceDeskVetting(); - $this->minkContext->printCurrentUrl(); - $this->minkContext->assertPageContainsText('Thank you for registering your token.'); - - $page = $this->minkContext->getSession()->getPage(); - $activationCodeCell = $page->find('xpath', '//th[text()="Activation code"]/../td'); - if (!$activationCodeCell) { - throw new Exception('Could not find a activation code table on the page'); + switch ($vettingType) { + case "RA vetting": + $this->iChooseToActivateMyTokenUsingServiceDeskVetting(); + $this->minkContext->assertPageContainsText('Thank you for registering your token.'); + + $page = $this->minkContext->getSession()->getPage(); + $activationCodeCell = $page->find('xpath', '//th[text()="Activation code"]/../td'); + if (!$activationCodeCell) { + throw new Exception('Could not find a activation code table on the page'); + } + + $url = $this->minkContext->getSession()->getCurrentUrl(); + $matches = []; + preg_match('#[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}#', $url, $matches); + if (empty($matches)) { + throw new Exception('Could not find a valid second factor verification id in the url'); + } + $this->activationCode = $activationCodeCell->getText(); + $this->verifiedSecondFactorId = reset($matches); + + if (!preg_match('#[A-Z0-9]{8}#', $this->activationCode)) { + throw new Exception('Could not find a valid activation code'); + } + break; + case "Self Asserted Token registration": + $this->iChooseToActivateMyTokenUsingSat(); + break; + default: + throw new Exception(sprintf('Vetting type "%s" is not supported', $vettingType)); + } + } + + /** + * @When I choose the :vettingType vetting type + */ + public function chooseVettingType(string $vettingType) + { + switch ($vettingType) { + case "RA vetting": + $this->iChooseToActivateMyTokenUsingServiceDeskVetting(); + $this->minkContext->assertPageContainsText('Thank you for registering your token.'); + + $page = $this->minkContext->getSession()->getPage(); + $activationCodeCell = $page->find('xpath', '//th[text()="Activation code"]/../td'); + if (!$activationCodeCell) { + throw new Exception('Could not find a activation code table on the page'); + } + + $url = $this->minkContext->getSession()->getCurrentUrl(); + $matches = []; + preg_match('#[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}#', $url, $matches); + if (empty($matches)) { + throw new Exception('Could not find a valid second factor verification id in the url'); + } + $this->activationCode = $activationCodeCell->getText(); + $this->verifiedSecondFactorId = reset($matches); + + if (!preg_match('#[A-Z0-9]{8}#', $this->activationCode)) { + throw new Exception('Could not find a valid activation code'); + } + break; + case "Self Asserted Token registration": + $this->iChooseToActivateMyTokenUsingSat(); + break; + default: + throw new Exception(sprintf('Vetting type "%s" is not supported', $vettingType)); } + } - $url = $this->minkContext->getSession()->getCurrentUrl(); + /** + * @Given I vet my :tokenType second factor in selfservice + */ + public function iVetMySecondFactorInSelfService(string $tokenType) + { + $url = $this->minkContext->getSession()->getCurrentUrl(); $matches = []; preg_match('#[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}#', $url, $matches); if (empty($matches)) { throw new Exception('Could not find a valid second factor verification id in the url'); } - $this->activationCode = $activationCodeCell->getText(); - $this->verifiedSecondFactorId = reset($matches); + $secondFactorId = reset($matches); + $this->minkContext->assertPageAddress(sprintf('/second-factor/%s/new-recovery-token', $secondFactorId)); - if (!preg_match('#[A-Z0-9]{8}#', $this->activationCode)) { - throw new Exception('Could not find a valid activation code'); + $page = $this->minkContext->getSession()->getPage(); + $form = $page->find('css', 'form[name="safe-store"]'); + $form->submit(); + // Todo store the safe store password for later use? + + $this->minkContext->assertPageAddress(sprintf('/second-factor/%s/safe-store', $secondFactorId)); + // Promise we safely stored the secret + $this->minkContext->checkOption('ss_promise_recovery_token_possession_promisedPossession'); + preg_match('#[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}#', $url, $matches); + if (empty($matches)) { + throw new Exception('Could not find a valid second factor verification id in the url'); } + // Update the SF id, it now refers to the vetted second factor id + $secondFactorId = reset($matches); + $this->minkContext->pressButton('Continue'); + // We are back on the overview page. The revoke button is on the page (indicating the token was vetted) + $this->minkContext->assertPageAddress('/overview'); + echo $secondFactorId; + $this->minkContext->assertResponseContains(sprintf('/second-factor/vetted/%s/revoke', $secondFactorId)); + // The recovery token should also be present + $this->minkContext->assertPageContainsText('Recovery methods'); + $this->minkContext->assertPageContainsText('Recovery code'); + $this->minkContext->assertResponseContains('/recovery-token/delete/'); } public function iChooseToActivateMyTokenUsingServiceDeskVetting() @@ -261,7 +345,108 @@ public function iChooseToActivateMyTokenUsingServiceDeskVetting() $this->minkContext->assertPageContainsText('Activate your token'); $this->minkContext->pressButton('ra-vetting-button'); } + public function iChooseToActivateMyTokenUsingSAT() + { + $this->minkContext->assertPageContainsText('Activate your token yourself'); + $this->minkContext->pressButton('sat-button'); + } + + /** + * @Then I can add an :recoveryTokenType recovery token using :tokenType + */ + public function iCanAddAnRecoveryToken(string $recoveryTokenType, string $tokenType) + { + $this->minkContext->assertPageContainsText('Add recovery method'); + $this->minkContext->clickLink('Add recovery method'); + switch ($recoveryTokenType){ + case 'SMS': + $this->minkContext->assertPageContainsText('You\'ll receive a text message with a verification code'); + $page = $this->minkContext->getSession()->getPage(); + $form = $page->find('css', 'form[name="sms"]'); + $form->submit(); + $this->provePossession($tokenType); + // Now you need to register your SMS recovery token + $this->minkContext->assertPageContainsText('Register an SMS recovery token'); + $this->minkContext->fillField('ss_send_sms_challenge_subscriber', '615056898'); + $this->minkContext->pressButton('Send code'); + + $this->minkContext->assertPageAddress('/recovery-token/prove-sms-possession'); + $this->minkContext->assertPageContainsText('Enter the code that was sent to your phone'); + $this->minkContext->fillField('ss_verify_sms_challenge_challenge', '123456'); + $this->minkContext->pressButton('Verify'); + break; + default: + throw new Exception(sprintf('Recovery token type %s is not supported', $recoveryTokenType)); + } + } + + private function provePossession(string $tokenType) + { + switch ($tokenType) { + case "Demo GSSP": + // We first need to prove we are in possession of our 2nd factor + $this->minkContext->pressButton('Yes, continue'); + // Press the Authenticate button on the Demo authentication page + $this->minkContext->assertPageAddress('https://demogssp.dev.openconext.local/authentication'); + $this->minkContext->pressButton('button_authenticate'); + // Pass through the Demo Gssp redirection page. + $this->minkContext->assertPageAddress('https://demogssp.dev.openconext.local/saml/sso_return'); + $this->minkContext->pressButton('Submit'); + // Pass through the 'return to sp' redirection page. + $this->minkContext->assertPageAddress('https://gateway.dev.openconext.local/gssp/demo_gssp/consume-assertion'); + $this->minkContext->pressButton('Submit'); + + break; + case "Yubikey": + $this->minkContext->pressButton('Yes, continue'); + $this->performYubikeyAuthentication(); + break; + + default: + throw new Exception(sprintf('Prove possession is not yet supported for token type %s', $tokenType)); + } + } + /** + * @Then I can not add a :recoveryTokenType recovery token + */ + public function iCanNotAddARecoveryToken($recoveryTokenType) + { + $this->minkContext->visit('/overview'); + $this->minkContext->assertPageNotContainsText('Add recovery method'); + } + + /** + * @Then :nofRecoveryTokens recovery tokens are activated + */ + public function numberOfTokensRegistered(int $nofRecoveryTokens) + { + $page = $this->minkContext->getMink()->getSession()->getPage(); + $tokenTypes = $page->findAll('css', 'tr[data-test_tokentype]'); + + if (empty($tokenTypes)) { + throw new Exception('No active recovery tokens found on the page'); + } + + if (count($tokenTypes) != $nofRecoveryTokens) { + throw new Exception( + sprintf( + 'Excpected to find %d recovery tokens on the page, actually found %d tokens', + $nofRecoveryTokens, + count($tokenTypes) + ) + ); + } + } + + private function performYubikeyAuthentication() + { + $this->minkContext->fillField('gateway_verify_yubikey_otp_otp', 'ccccccdhgrbtfddefpkffhkkukbgfcdilhiltrrncmig'); + $page = $this->minkContext->getSession()->getPage(); + $form = $page->find('css', 'form[name="gateway_verify_yubikey_otp"]'); + $form->submit(); + $this->minkContext->pressButton('Submit'); + } /** * @Given /^I visit the "([^"]*)" page in the selfservice portal$/ diff --git a/stepup/tests/behat/features/selfservice.feature b/stepup/tests/behat/features/selfservice.feature index a7944db..fcb99ed 100644 --- a/stepup/tests/behat/features/selfservice.feature +++ b/stepup/tests/behat/features/selfservice.feature @@ -6,26 +6,26 @@ Feature: A user manages his tokens in the SelfService portal Scenario: A user registers a SMS token in selfservice using RA vetting Given I am logged in into the selfservice portal as "joe-a1" When I register a new "SMS" token - And I verify my e-mail address + And I verify my e-mail address and choose the "RA vetting" vetting type And I vet my "SMS" second factor at the information desk Scenario: A user registers a Yubikey token in selfservice using RA vetting Given I am logged in into the selfservice portal as "joe-a2" When I register a new "Yubikey" token - And I verify my e-mail address + And I verify my e-mail address and choose the "RA vetting" vetting type And I vet my "Yubikey" second factor at the information desk Scenario: A user registers a Demo GSSP token in selfservice using RA vetting Given I am logged in into the selfservice portal as "joe-a3" When I register a new "Demo GSSP" token - And I verify my e-mail address + And I verify my e-mail address and choose the "RA vetting" vetting type And I vet my "Demo GSSP" second factor at the information desk - Scenario: A user registers a Demo GSSP token in selfservice using RA vetting - Given I am logged in into the selfservice portal as "joe-a3" - When I register a new "Demo GSSP" token - And I verify my e-mail address - And I vet my "Demo GSSP" second factor at the information desk + Scenario: A user registers a SMS token in selfservice using RA vetting without mail verification + Given I am logged in into the selfservice portal as "joe-b1" + When I register a new "Yubikey" token + And I choose the "RA vetting" vetting type + And I vet my "Yubikey" second factor at the information desk Scenario: After token registration, the token can be viewed on the token overview page Given I am logged in into the selfservice portal as "joe-a1" diff --git a/stepup/tests/behat/features/selfservice_sat.feature b/stepup/tests/behat/features/selfservice_sat.feature new file mode 100644 index 0000000..2631611 --- /dev/null +++ b/stepup/tests/behat/features/selfservice_sat.feature @@ -0,0 +1,33 @@ +Feature: A user manages his tokens in the SelfService portal + In order to SAT register a second factor token + As a user + I must be able to manage my second factor tokens + + Scenario: A user registers a SMS token in selfservice using SAT + Given I am logged in into the selfservice portal as "user-a1" + When I register a new "SMS" token + And I verify my e-mail address and choose the "Self Asserted Token registration" vetting type + And I vet my "SMS" second factor in selfservice + And "1" recovery tokens are activated + + Scenario: A user registers a Yubikey token in selfservice using SAT + Given I am logged in into the selfservice portal as "user-a2" + When I register a new "Yubikey" token + And I verify my e-mail address and choose the "Self Asserted Token registration" vetting type + And I vet my "Yubikey" second factor in selfservice + And "1" recovery tokens are activated + + Scenario: A user registers a Demo GSSP token in selfservice using SAT + Given I am logged in into the selfservice portal as "user-a3" + When I register a new "Demo GSSP" token + And I verify my e-mail address and choose the "Self Asserted Token registration" vetting type + And I vet my "Demo GSSP" second factor in selfservice + And "1" recovery tokens are activated + + Scenario: A user can register an additional recovery token + Given I am logged in into the selfservice portal as "user-a4" + When I register a new "Yubikey" token + And I verify my e-mail address and choose the "Self Asserted Token registration" vetting type + And I vet my "Yubikey" second factor in selfservice + Then I can add an "SMS" recovery token using "Yubikey" + And "2" recovery tokens are activated