diff --git a/stepup/tests/behat/features/adfs.feature b/stepup/tests/behat/features/adfs.feature new file mode 100644 index 0000000..ac7d0a6 --- /dev/null +++ b/stepup/tests/behat/features/adfs.feature @@ -0,0 +1,30 @@ +Feature: As an institution that uses ADFS support on the second factor only feature + In order to do ADFS second factor authentications + I must be able to successfully authenticate with my second factor tokens + + Scenario: A user logs in using ADFS parameters + Given a service provider configured for second-factor-only + When I visit the ADFS service provider + And I verify the "yubikey" second factor + Then I am logged on the service provider + + Scenario: A user logs in using ADFS parameters with a gssp token + Given a user "jane-a-ra" identified by "urn:collab:person:institution-a.example.com:jane-a-ra" from institution "institution-a.example.com" with UUID "00000000-0000-4000-8000-000000000001" + And the user "urn:collab:person:institution-a.example.com:jane-a-ra" has a vetted "demo-gssp" with identifier "gssp-identifier123" + And a service provider configured for second-factor-only + When I start an ADFS authentication for "urn:collab:person:institution-a.example.com:jane-a-ra" + And I verify the "demo-gssp" second factor + Then I am logged on the service provider + + Scenario: A user cancels ADFS authn with a gssp token + And a service provider configured for second-factor-only + When I start an ADFS authentication for "urn:collab:person:institution-a.example.com:jane-a-ra" + And I cancel the "demo-gssp" second factor authentication + Then I see an ADFS error at the service provider + + Scenario: A user cancels ADFS authn with a yubikey token + Given a service provider configured for second-factor-only + When I visit the ADFS service provider + And I cancel the "yubikey" second factor authentication + Then I see an ADFS error at the service provider + diff --git a/stepup/tests/behat/features/bootstrap/SecondFactorAuthContext.php b/stepup/tests/behat/features/bootstrap/SecondFactorAuthContext.php index 0c19541..1cc2ff6 100644 --- a/stepup/tests/behat/features/bootstrap/SecondFactorAuthContext.php +++ b/stepup/tests/behat/features/bootstrap/SecondFactorAuthContext.php @@ -12,6 +12,7 @@ class SecondFactorAuthContext implements Context const SSO_SP = 'default-sp'; const SFO_SP = 'second-sp'; const TEST_NAMEID = 'urn:collab:person:institution-a.example.com:jane-a1'; + const TEST_NAMEID_ADFS = 'urn:collab:person:dev.openconext.local:admin'; /** * @var \Behat\MinkExtension\Context\MinkContext @@ -100,6 +101,53 @@ public function visitServiceProvider() $this->minkContext->fillField('subject', self::TEST_NAMEID); } $this->minkContext->pressButton('Login'); + if ($this->activeIdp === self::SFO_IDP) { + // Pass through the SFO endpoint in Gateway + $this->minkContext->pressButton('Submit'); + $this->diePrintingContent(); + } + } + + /** + * @When I start an SFO authentication for :arg1 + */ + public function startASfoAuthenticationFor(string $userIdentifier) + { + $this->minkContext->visit($this->spTestUrl); + $this->minkContext->fillField('idp', $this->activeIdp); + $this->minkContext->fillField('sp', $this->activeSp); + $this->minkContext->fillField('loa', $this->requiredLoa); + $this->minkContext->fillField('subject', $userIdentifier); + $this->minkContext->pressButton('Login'); + } + + /** + * @When I visit the ADFS service provider + */ + public function visitAdfsServiceProvider() + { + $nameId = self::TEST_NAMEID; + if ($this->activeIdp === self::SFO_IDP) { + $nameId = self::TEST_NAMEID_ADFS; + } + $this->logInSimulatingAdfsFor($nameId); + } + + /** + * @When I start an ADFS authentication for :arg1 + */ + public function logInSimulatingAdfsFor(string $userIdentifier) + { + $this->minkContext->visit($this->spTestUrl); + $this->minkContext->fillField('idp', $this->activeIdp); + $this->minkContext->selectOption('sp', $this->activeSp); + $this->minkContext->fillField('loa', $this->requiredLoa); + $this->minkContext->selectOption('ssobinding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'); + $this->minkContext->checkOption('emulateadfs'); + + $this->minkContext->fillField('subject', $userIdentifier); + $this->minkContext->pressButton('Login'); + $this->minkContext->pressButton('Yes, continue'); } private function fillField($session, $field, $value) @@ -135,7 +183,7 @@ public function verifySpecifiedSecondFactor($tokenType, $smsChallenge = null) case "yubikey": $this->authenticateUserYubikeyInGateway(); break; - case "dummy": + case "demo-gssp": $this->authenticateUserInDummyGsspApplication(); break; default: @@ -145,7 +193,6 @@ public function verifySpecifiedSecondFactor($tokenType, $smsChallenge = null) $tokenType ) ); - break; } } @@ -171,7 +218,7 @@ public function cancelSecondFactorAuthentication($tokenType) case "yubikey": $this->cancelYubikeySsoAuthentication(); break; - case "dummy": + case "demo-gssp": $this->cancelAuthenticationInDummyGsspApplication(); break; default: @@ -205,19 +252,24 @@ public function selectYubikeySecondFactorOnTokenSelectionScreen() public function authenticateUserInDummyGsspApplication() { - $this->minkContext->assertPageAddress('http://localhost:1234/authentication'); + $this->minkContext->assertPageAddress('https://demogssp.dev.openconext.local/authentication'); // Trigger the dummy authentication action. $this->minkContext->pressButton('Authenticate user'); // Pass through the 'return to sp' redirection page. $this->minkContext->pressButton('Submit'); + // And continue back to the SP via Gateway + $this->minkContext->pressButton('Submit'); } public function authenticateUserYubikeyInGateway() { - $this->minkContext->assertPageAddress('https://gateway.dev.openconext.local/verify-second-factor/sso/yubikey'); - + try { + $this->minkContext->assertPageAddress('https://gateway.dev.openconext.local/verify-second-factor/sso/yubikey'); + } catch (Exception $e) { + $this->minkContext->assertPageAddress('https://gateway.dev.openconext.local/verify-second-factor/sfo/yubikey'); + } // Give an OTP $this->minkContext->fillField('gateway_verify_yubikey_otp_otp', 'ccccccdhgrbtucnfhrhltvfkchlnnrndcbnfnnljjdgf'); // Simulate the enter press the yubikey otp generator @@ -251,18 +303,29 @@ public function authenticateUserSmsInGateway(string $challenge) public function cancelAuthenticationInDummyGsspApplication() { - $this->minkContext->assertPageAddress('http://localhost:1234/authentication'); + $this->minkContext->assertPageAddress('https://demogssp.dev.openconext.local/authentication'); // Cancel the dummy authentication action. $this->minkContext->pressButton('Return authentication failed'); - // Pass through the 'return to sp' redirection page. + // Pass through the gssp + $this->minkContext->pressButton('Submit'); + // Pass through the Gateway $this->minkContext->pressButton('Submit'); } public function cancelYubikeySsoAuthentication() { - $this->minkContext->assertPageAddress('https://gateway.dev.openconext.local/verify-second-factor/sso/yubikey'); + switch ($this->activeSp) { + case 'second-sp': + $this->minkContext->assertPageAddress('/verify-second-factor/sfo/yubikey'); + break; + case 'default-sp': + $this->minkContext->assertPageAddress('/verify-second-factor/sso/yubikey'); + break; + default: + throw new Exception($this->activeSp . ' is not supported for yubikey cancellation'); + } // Cancel the yubikey authentication action. $this->minkContext->pressButton('Cancel'); // Pass through the 'return to sp' redirection page. @@ -347,12 +410,60 @@ public function assertLoggedInOnServiceProvider() */ public function assertErrorAtServiceProvider() { - $this->minkContext->assertPageAddress('https://ssp.dev.openconext.local/simplesaml/module.php/saml/sp/saml2-acs.php/default-sp'); + switch ($this->activeSp) { + case 'second-sp': + $this->minkContext->assertPageAddress('/simplesaml/module.php/debugsp/acs/second-sp'); + break; + case 'default-sp': + $this->minkContext->assertPageAddress('/simplesaml/module.php/saml/sp/saml2-acs.php/default-sp'); + break; + default: + throw new Exception($this->activeSp . ' is not supported'); + } + + $this->minkContext->assertPageContainsText( + sprintf('Unhandled exception') + ); + + $this->minkContext->assertPageNotContainsText( + sprintf('You are logged in to SP') + ); + } + + /** + * @Then I see an ADFS error at the service provider + */ + public function assertAdfsErrorAtServiceProvider() + { + switch ($this->activeSp) { + case 'second-sp': + $this->minkContext->assertPageAddress('/simplesaml/module.php/debugsp/acs/second-sp'); + break; + case 'default-sp': + $this->minkContext->assertPageAddress('/simplesaml/module.php/debugsp/acs/default-sp'); + break; + default: + throw new Exception($this->activeSp . ' is not supported'); + } $this->minkContext->assertPageContainsText( sprintf('Unhandled exception') ); + $currentUrl = $this->minkContext->getSession()->getCurrentUrl(); + // With behat we can not verify if the Context and AuthMethod are present in the POST request data, but we can + // check if Gateway added back the original query string + $expectedAdfsParams = '?SAMLRequest=request_that_must_be_kept&Context=context_value_that_must_be_kept'; + // The original parameters sent by the ADFS client should still be on the Url + if (strpos($currentUrl, $expectedAdfsParams) === false) { + throw new Exception( + sprintf( + 'The original Adfs parameters are not on the error page (expected: "%s")', + $expectedAdfsParams + ) + ); + } + $this->minkContext->assertPageNotContainsText( sprintf('You are logged in to SP') ); diff --git a/stepup/tests/behat/features/sfo.feature b/stepup/tests/behat/features/sfo.feature index 0104c3b..37b8b70 100644 --- a/stepup/tests/behat/features/sfo.feature +++ b/stepup/tests/behat/features/sfo.feature @@ -1,12 +1,30 @@ -@SKIP -# Skipped awaiting a fix of the SSP, allowing for SFO authentications Feature: A user authenticates with a service provider configured for second-factor-only In order to login on a service provider As a user I must verify the second factor without authenticating with an identity provider - Scenario: A user logs in using SFO + Scenario: A user logs in using SFO using a GSSP token Given a service provider configured for second-factor-only - When I visit the service provider + And a user "jane-a-ra" identified by "urn:collab:person:institution-a.example.com:jane-a-ra" from institution "institution-a.example.com" with UUID "00000000-0000-4000-8000-000000000001" + And the user "urn:collab:person:institution-a.example.com:jane-a-ra" has a vetted "demo-gssp" with identifier "gssp-identifier123" + When I start an SFO authentication for "urn:collab:person:institution-a.example.com:jane-a-ra" + And I verify the "demo-gssp" second factor + Then I am logged on the service provider + + Scenario: A user cancels SFO authn with a gssp token + And a service provider configured for second-factor-only + When I start an SFO authentication for "urn:collab:person:institution-a.example.com:jane-a-ra" + And I cancel the "demo-gssp" second factor authentication + Then I see an error at the service provider + + Scenario: Admin logs in using SFO using a Yubikey token + Given a service provider configured for second-factor-only + When I start an SFO authentication for "urn:collab:person:dev.openconext.local:admin" And I verify the "yubikey" second factor Then I am logged on the service provider + + Scenario: Admin user cancels SFO authn with a Yubikey token + And a service provider configured for second-factor-only + When I start an SFO authentication for "urn:collab:person:dev.openconext.local:admin" + And I cancel the "yubikey" second factor authentication + Then I see an error at the service provider diff --git a/stepup/tests/behat/fixtures/middleware-config.json b/stepup/tests/behat/fixtures/middleware-config.json index 772825b..eb421b6 100644 --- a/stepup/tests/behat/fixtures/middleware-config.json +++ b/stepup/tests/behat/fixtures/middleware-config.json @@ -231,7 +231,7 @@ "entity_id": "https://ssp.dev.openconext.local/simplesaml/module.php/saml/sp/metadata.php/second-sp", "public_key": "MIIECTCCAnECFCUUC/wBq6SyLyGi8sMZl2bB0cFmMA0GCSqGSIb3DQEBCwUAMEExFjAUBgNVBAMMDXNpbXBsZXNhbWwgU1AxJzAlBgNVBAoMHkRldmVsb3BtZW50IERvY2tlciBlbnZpcm9ubWVudDAeFw0yMzA1MjUwOTMzMTZaFw0yODA1MjMwOTMzMTZaMEExFjAUBgNVBAMMDXNpbXBsZXNhbWwgU1AxJzAlBgNVBAoMHkRldmVsb3BtZW50IERvY2tlciBlbnZpcm9ubWVudDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKY41F7F6YJrqUTHJw45X1Y3gsN0ZgryOZfPXGSOq2xEttVU8RIzWNJmtVZc6j3NlffKp8HOTgko5JAjSN3sK36K8a0yY4aOrPfwv67/8XkHbaQCH/CiCOigSyE2BJIskMToaamPXjWRl3K+ma/kShw7lMGmvmQxnHsc79zmJDPrT9chC4Ypkq828/DiEkRNXThvfe+61tI0rvXY4HQZ4BPlag7/oMUng4AehPe4r5GEpdh5b4872fIiTxOMUSjIt2zl8OFAazhigmZwk48MiYyCUbUvaAAqZ7ewX4/DmMdfqdSW7gscwIWV02k27lJzhNSqt1iR92e58tf377Ufl46JCvAzrtol4lwmMgoDIqe9WCMDrraEZ1jvDrHuX+sJjEe0qIbzSjQBa+jZc1AN6Gdrzf1HYCBr+wj75NtwP0La2o6p8yJbNsTcs8nE4GBrdVJF87BxI1OdoWjNLwhhDJsVsY1BUJtQGmyEWffVI1qPBPuv4H99cUNwlCS/I1TEgQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBgQAdCxhHPfexwQwQXMhliZ1G3nxEedQ6rnvi2wA9yHxHBeKqX5TnIZUh2OYIdNon0x4XveLYUdAhoG6mBxk2QeQ3xNanwAuWnCRB5ssYqf7Jyu1EXMycZDkhVl9r01ApCFNFkF1p6wEgmXDG5A3KRsIWK4eU6ZLsywXksgCi0Hl63Rdz9+wVCqOBdufxgG0dxNA3GtE37Yz0pKuNJYch7onsfX5OCnI/lwbJc+A/500Rp01leYOx58KJGCyrVS5fuQmwuG7FC9ok8uSnS0gODCxQw26K+/uVK0X9HBJDn5DkXI2fsXChUV7rT12ZUukrn9CYZ2i71VMIopJI6DBQ7g4n7tljz5DcXbdHkHrrjHz2owXKKSFMOvEQ/216oxKaT8dF8BW2/EmXYd6Rnchyf4vmTFR/WaY2cg3k0leIXwPMI2sCBfgLoJHUBWh/RqaACMGdqhq93kzjy/FIzJvId3gcrUbK3NsFCVf5bXc32TkalocvEyZ4Id81IUcTi6RepEA=", "acs": [ - "https://ssp.dev.openconext.local/simplesaml/module.php/saml/sp/saml2-acs.php/second-sp" + "https://ssp.dev.openconext.local/simplesaml/module.php/debugsp/acs/second-sp" ], "loa": { "__default__": "http://dev.openconext.local/assurance/loa1"