Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions stepup/seed-test-identity.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail

# Seeds a fully vetted Demo GSSP identity directly via Middleware's command API,
# bypassing registration UI, RA app, e-mail, and any physical/virtual second factor
# hardware entirely. Mirrors the "has a vetted demo-gssp" step in
# tests/behat/features/bootstrap/FeatureContext.php (theUserHasAVettedWithIdentifier),
# adapted from the smoketest DB to this environment's real dev DB/credentials.
#
# Usage: ./seed-test-identity.sh <slug> [institution] [gssf-id]
#
# After running, log in via the ssp test SP (https://ssp.dev.openconext.local/simplesaml/sp.php)
# as <slug>/<slug>, request an LoA that Demo GSSP satisfies, and pick "Demo GSSP" as the
# second factor -- no registration/vetting/hardware step needed.

SLUG="${1:?Usage: $0 <slug> [institution] [gssf-id]}"
INSTITUTION="${2:-dev.openconext.local}"
GSSF_ID="${3:-seed-$SLUG}"
NAME_ID="urn:collab:person:${INSTITUTION}:${SLUG}"
IDENTITY_ID=$(uuidgen | tr 'A-Z' 'a-z')
SECOND_FACTOR_ID=$(uuidgen | tr 'A-Z' 'a-z')

# Real SRAA identity in this environment's dev DB (has RA authority everywhere).
# Look it up fresh rather than hardcoding, in case the admin identity_id ever changes.
ACTOR_ID=$(docker exec stepup-mariadb-1 mysql -uroot -psecret middleware -N -B \
-e "SELECT id FROM identity WHERE name_id='urn:collab:person:dev.openconext.local:admin';")

if [ -z "$ACTOR_ID" ]; then
echo "Could not find the admin/SRAA identity in the middleware DB -- is the environment bootstrapped?" >&2
exit 1
fi

MW=https://middleware.dev.openconext.local
DB="docker exec stepup-mariadb-1 mysql -uroot -psecret -N -B middleware"

post() {
local user=$1 pass=$2 body=$3
curl -sk -u "$user:$pass" -H 'Content-Type: application/json' -H 'Accept: application/json' -X POST "$MW/command" -d "$body"
echo
}

echo "== Creating identity $NAME_ID ($IDENTITY_ID) =="
post ss sa_secret "$(printf '{"meta":{"actor_id":null,"actor_institution":null},"command":{"name":"Identity:CreateIdentity","uuid":"%s","payload":{"id":"%s","name_id":"%s","institution":"%s","email":"%s@dev.openconext.local","common_name":"%s","preferred_locale":"en_GB"}}}' \
"$(uuidgen)" "$IDENTITY_ID" "$NAME_ID" "$INSTITUTION" "$SLUG" "$SLUG")"

echo "== Proving possession of Demo GSSP token (gssf_id=$GSSF_ID) =="
post ss sa_secret "$(printf '{"meta":{"actor_id":"%s","actor_institution":"%s"},"command":{"name":"Identity:ProveGssfPossession","uuid":"%s","payload":{"identity_id":"%s","second_factor_id":"%s","stepup_provider":"demo_gssp","gssf_id":"%s"}}}' \
"$IDENTITY_ID" "$INSTITUTION" "$(uuidgen)" "$IDENTITY_ID" "$SECOND_FACTOR_ID" "$GSSF_ID")"

# Unlike yubikey/sms, GSSF possession (Identity:ProveGssfPossession) is proven-and-verified
# in a single event (GssfPossessionProvenAndVerifiedEvent) -- no separate e-mail/nonce step.
REG_CODE=$($DB -e "SELECT registration_code FROM verified_second_factor WHERE identity_id='$IDENTITY_ID' ORDER BY registration_requested_at DESC LIMIT 1;")
if [ -z "$REG_CODE" ]; then
echo "No verified_second_factor row found for $IDENTITY_ID -- VerifyEmail likely failed, see output above." >&2
exit 1
fi

echo "== Vetting (registration code $REG_CODE, authority $ACTOR_ID) =="
post ra ra_secret "$(printf '{"meta":{"actor_id":"%s","actor_institution":"%s"},"command":{"name":"Identity:VetSecondFactor","uuid":"%s","payload":{"authority_id":"%s","identity_id":"%s","second_factor_id":"%s","registration_code":"%s","second_factor_type":"demo_gssp","second_factor_identifier":"%s","document_number":"123456","identity_verified":true}}}' \
"$ACTOR_ID" "$INSTITUTION" "$(uuidgen)" "$ACTOR_ID" "$IDENTITY_ID" "$SECOND_FACTOR_ID" "$REG_CODE" "$GSSF_ID")"

VETTED=$($DB -e "SELECT id FROM vetted_second_factor WHERE identity_id='$IDENTITY_ID';")
echo
if [ -n "$VETTED" ]; then
echo "Done. $SLUG now has a vetted Demo GSSP token (second_factor_id=$VETTED)."
echo "Log in at https://ssp.dev.openconext.local/simplesaml/sp.php as ${SLUG}/${SLUG}, pick a Request LOA Demo GSSP satisfies, and select Demo GSSP as the second factor."
else
echo "Vetting did not produce a vetted_second_factor row -- check the command output above for an error." >&2
exit 1
fi
70 changes: 66 additions & 4 deletions stepup/tests/behat/features/bootstrap/SecondFactorAuthContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,24 @@ public function visitServiceProvider()
}
}

/**
* @When I visit the service provider with service name :arg1
*/
public function visitServiceProviderWithServiceName(string $serviceName)
{
$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('mdui_displayname', $serviceName);

if ($this->activeIdp === self::SFO_IDP) {
$this->minkContext->fillField('subject', self::TEST_NAMEID);
}
$this->minkContext->pressButton('Login');
}

/**
* @When I start an SFO authentication for :arg1 with GSSP extension subject :arg2 and institution :arg3
*/
Expand All @@ -137,6 +155,38 @@ public function startASfoAuthenticationWithGsspExtension(string $userIdentifier,
$this->minkContext->pressButton('Login');
}

/**
* @When I start an SFO authentication for :arg1 with service name :arg2
*/
public function startASfoAuthenticationWithServiceName(string $userIdentifier, string $serviceName)
{
$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->fillField('mdui_displayname', $serviceName);
$this->minkContext->pressButton('Login');
}

/**
* @Then I see service name :arg1 on the GSSP authentication page
*/
public function iSeeServiceNameOnTheGsspAuthenticationPage(string $serviceName)
{
$this->minkContext->assertPageAddress('https://demogssp.dev.openconext.local/authentication');
$this->minkContext->assertPageContainsText($serviceName);
}

/**
* @Then I see service name :arg1 on the GSSP registration page
*/
public function iSeeServiceNameOnTheGsspRegistrationPage(string $serviceName)
{
$this->minkContext->assertPageAddress('https://demogssp.dev.openconext.local/registration');
$this->minkContext->assertPageContainsText($serviceName);
}

/**
* @When I start an SFO authentication for :arg1
*/
Expand Down Expand Up @@ -342,13 +392,13 @@ public function authenticateUserYubikeyInGateway()
$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');
$this->minkContext->fillField('gateway_verify_yubikey_yubikeyInput', 'ccccccdhgrbtucnfhrhltvfkchlnnrndcbnfnnljjdgf');
// Simulate the enter press the yubikey otp generator
$form = $this->minkContext->getSession()->getPage()->find('css', '[id="gateway_verify_yubikey_otp_otp"]');
$form = $this->minkContext->getSession()->getPage()->find('css', '[id="gateway_verify_yubikey_yubikeyInput"]');
if (!$form) {
throw new ElementNotFoundException('Yubikey OTP Submit form could not be found on the page');
}
$this->minkContext->pressButton('gateway_verify_yubikey_otp_submit');
$this->minkContext->pressButton('gateway_verify_yubikey_submit');
// Pass through the 'return to sp' redirection page.
$this->minkContext->pressButton('Submit');
}
Expand Down Expand Up @@ -489,8 +539,20 @@ public function authenticateWithIdentityProviderFor($userName)
$this->minkContext->fillField('password', $userName);

$this->minkContext->pressButton('Login');
$this->minkContext->pressButton('Yes, continue');
$this->pressConsentIfShown();
}

/**
* SimpleSAMLphp's consent module remembers a given SP+attribute-set combination for the
* browser session, so a consent screen may or may not appear depending on what earlier
* scenarios in the same feature already consented to.
*/
private function pressConsentIfShown(): void
{
try {
$this->minkContext->pressButton('Yes, continue');
} catch (ElementNotFoundException $e) {
}
}

public function authenticateWithIdentityProviderForWithStepup($userName)
Expand Down
4 changes: 2 additions & 2 deletions stepup/tests/behat/features/bootstrap/SelfServiceContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -491,9 +491,9 @@ public function removeRecoveryToken(string $recoveryTokenType)

private function performYubikeyAuthentication()
{
$this->minkContext->fillField('gateway_verify_yubikey_otp_otp', 'ccccccdhgrbtfddefpkffhkkukbgfcdilhiltrrncmig');
$this->minkContext->fillField('gateway_verify_yubikey_yubikeyInput', 'ccccccdhgrbtfddefpkffhkkukbgfcdilhiltrrncmig');
$page = $this->minkContext->getSession()->getPage();
$form = $page->find('css', 'form[name="gateway_verify_yubikey_otp"]');
$form = $page->find('css', 'form[name="gateway_verify_yubikey"]');
$form->submit();
$this->minkContext->pressButton('Submit');
}
Expand Down
49 changes: 49 additions & 0 deletions stepup/tests/behat/features/gssp_service_name.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Tagged SKIP until Stepup-Gateway PR #624 (append_service_name_to_authnrequest) is merged
# and released in the test image. (OpenConext-devssp's mdui_displayname field already merged
# and is in the stock devssp image, so no local sp.php override is needed anymore.)
# Until then, run locally with:
# ./start-dev-env.sh gateway:<Stepup-Gateway checkout> demogssp:<Stepup-gssp-example checkout>
# docker compose exec behat ./vendor/bin/behat --config config/behat.yml --tags='~@wip' features/gssp_service_name.feature
@SKIP
Feature: The GSSP shows the name of the service the user is authenticating for

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What i expect to see here: All 3 of the LoginService::singleSignOn methods should be triggered from here to see if the gateway applies (or discards????) the mdui information.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stepup/tests/behat/features/sfo.feature stepup/tests/behat/features/sfo_gssp_fallback.feature

In order to know which service I am authenticating for
As a user
I want the GSSP authentication page to show the service name from the AuthnRequest

# Covers the cross-repo flow of the mdui:UIInfo service name:
# the SP sends an AuthnRequest with an mdui:UIInfo/mdui:DisplayName extension,
# the Stepup-Gateway (feature flag append_service_name_to_authnrequest)
# reads it and forwards it in the proxy AuthnRequest to the GSSP, where the
# GSSP (Stepup-gssp-example via Stepup-gssp-bundle and Stepup-saml-bundle)
# displays it on the authentication page.
Scenario: Service name from the AuthnRequest mdui:UIInfo is shown on the GSSP authentication page
Given a service provider configured for second-factor-only
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" with service name "Behat Test Service"
Then I see service name "Behat Test Service" on the GSSP authentication page
When I verify the "demo-gssp" second factor
Then I am logged on the service provider

# Reuses the identity vetted in the previous scenario, like sfo.feature does.
Scenario: No service name is shown when the AuthnRequest carries no mdui:UIInfo
Given 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"
Then I should not see "Behat Test Service"
When I verify the "demo-gssp" second factor
Then I am logged on the service provider

# Gateway has three independent LoginService::singleSignOn implementations that each
# read the mdui:UIInfo extension behind the same feature flag: GatewayBundle (plain
# SSO, exercised here), SecondFactorOnlyBundle, and SamlStepupProviderBundle (both
# exercised by the SFO scenarios above). Without this scenario, a regression in the
# SSO copy specifically would go undetected even with the SFO scenarios passing.
Scenario: Service name from the AuthnRequest mdui:UIInfo is shown on the GSSP authentication page via the plain SSO flow
Given a service provider configured for single-signon
And a user "Jane Toppan" identified by "urn:collab:person:institution-a.example.com:jane-a2" from institution "institution-a.example.com"
And the user "urn:collab:person:institution-a.example.com:jane-a2" has a vetted "demo-gssp" with identifier "gssp-identifier-sso1"
When I visit the service provider with service name "SSO Flow Service Name"
And I authenticate as "jane-a2" with the identity provider
Then I see service name "SSO Flow Service Name" on the GSSP authentication page
When I verify the "demo-gssp" second factor
Then I am logged on the service provider
Loading