Skip to content

Commit

Permalink
Support ForceAuthn to Stepup Gateway callout
Browse files Browse the repository at this point in the history
Manage can push SP Metadata containing the coin:stepup:forceauthn coin.

That value is picked up in the Assembler, and saved on the SP eb5 roles,
coin column.

During the StepUp round trip via StepUp Gateway, EB checks if the
FoceAuthn flag should be added to that AuthNRequest. If this is
specified for that SP, it is added. Otherwise no additional logic is
performed.

When dealing with a trusted proxy setup, the stepup settings for the
requesting sp are used, not that of the TP.

In order to get insghts in the outgoing AR to the StepUp Gateway, the
mock gateway page that is used in the funcitonal tests now displays the
received AR as an unsigned xml string. That string is used to perform
xpath queries on, determiniing the presense or absence of the forceauthn
flag.

See: https://www.pivotaltracker.com/story/show/184369636
  • Loading branch information
MKodde authored and thijskh committed Apr 19, 2023
1 parent 89eeca6 commit e4a0fae
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,13 @@ public function serve($serviceName, Request $httpRequest)
$nameId = clone $receivedResponse->getNameId();
$authnClassRef = $this->_stepupGatewayCallOutHelper->getStepupLoa($idp, $sp, $authnRequestLoas, $pdpLoas);

$this->_server->sendStepupAuthenticationRequest($receivedRequest, $currentProcessStep->getRole(), $authnClassRef, $nameId);
$this->_server->sendStepupAuthenticationRequest(
$receivedRequest,
$currentProcessStep->getRole(),
$authnClassRef,
$nameId,
$sp->getCoins()->isStepupForceAuthn()
);
}

/**
Expand Down
5 changes: 4 additions & 1 deletion library/EngineBlock/Corto/ProxyServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,8 @@ public function sendStepupAuthenticationRequest(
EngineBlock_Saml2_AuthnRequestAnnotationDecorator $spRequest,
IdentityProvider $identityProvider,
Loa $authnContextClassRef,
NameID $nameId
NameID $nameId,
bool $isForceAuthn
) {
$ebRequest = EngineBlock_Saml2_AuthnRequestFactory::createFromRequest(
$spRequest,
Expand All @@ -467,6 +468,8 @@ public function sendStepupAuthenticationRequest(
'stepupAssertionConsumerService'
);

$ebRequest->setForceAuthn($isForceAuthn);

$sspMessage = $ebRequest->getSspMessage();
if (!$sspMessage instanceof AuthnRequest) {
throw new EngineBlock_Corto_ProxyServer_Exception(sprintf('Unknown message type: "%s"', get_class($sspMessage)));
Expand Down
5 changes: 5 additions & 0 deletions library/EngineBlock/Saml2/AuthnRequestAnnotationDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,9 @@ public function isTransparent()
{
return $this->transparent;
}

public function setForceAuthn(bool $isForceAuthn)
{
$this->sspMessage->setForceAuthn($isForceAuthn);
}
}
13 changes: 12 additions & 1 deletion src/OpenConext/EngineBlock/Metadata/Coins.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public static function createForServiceProvider(
$stepupRequireLoa,
$disableScoping,
$additionalLogging,
$signatureMethod
$signatureMethod,
$stepupForceAuthn
) {
return new self([
'isConsentRequired' => $isConsentRequired,
Expand All @@ -60,6 +61,7 @@ public static function createForServiceProvider(
'signatureMethod' => $signatureMethod,
'stepupAllowNoToken' => $stepupAllowNoToken,
'stepupRequireLoa' => $stepupRequireLoa,
'stepupForceAuthn' => $stepupForceAuthn,
]);
}

Expand Down Expand Up @@ -178,6 +180,15 @@ public function stepupRequireLoa()
return $this->getValue('stepupRequireLoa', '');
}

/**
* Should the Stepup authentication request (to the Stepup Gateway)
* have the ForceAuthn attribute in the AuthnRequest?
*/
public function isStepupForceAuthn()
{
return $this->getValue('stepupForceAuthn', false);
}

// IDP
public function guestQualifier()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,13 @@ private function assembleSp(stdClass $connection)
),
'stepupAllowNoToken'
);

$properties += $this->setPathFromObjectBool(
array(
$connection,
'metadata:coin:stepup:forceauthn'
),
'stepupForceAuthn'
);
return Utils::instantiate(
ServiceProvider::class,
$properties
Expand Down
124 changes: 39 additions & 85 deletions src/OpenConext/EngineBlock/Metadata/Entity/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,106 +105,59 @@ class ServiceProvider extends AbstractRole
/**
* WARNING: Please don't use this entity directly but use the dedicated factory instead.
* @see \OpenConext\EngineBlock\Factory\Factory\ServiceProviderFactory
*
* @param string $entityId
* @param Organization $organizationEn
* @param Organization $organizationNl
* @param Organization $organizationPt
* @param Service $singleLogoutService
* @param bool $additionalLogging
* @param X509Certificate[] $certificates
* @param ContactPerson[] $contactPersons
* @param string $descriptionEn
* @param string $descriptionNl
* @param string $descriptionPt
* @param bool $disableScoping
* @param string $displayNameEn
* @param string $displayNameNl
* @param string $displayNamePt
* @param string $keywordsEn
* @param string $keywordsNl
* @param string $keywordsPt
* @param Logo $logo
* @param string $nameEn
* @param string $nameNl
* @param string $namePt
* @param null $nameIdFormat
* @param array $supportedNameIdFormats
* @param bool $requestsMustBeSigned
* @param string $signatureMethod
* @param string $workflowState
* @param array $allowedIdpEntityIds
* @param bool $allowAll
* @param array $assertionConsumerServices
* @param bool $displayUnconnectedIdpsWayf
* @param null $termsOfServiceUrl
* @param bool $isConsentRequired
* @param bool $isTransparentIssuer
* @param bool $isTrustedProxy
* @param null $requestedAttributes
* @param bool $skipDenormalization
* @param bool $policyEnforcementDecisionRequired
* @param bool $requesteridRequired
* @param bool $signResponse
* @param string $manipulation
* @param AttributeReleasePolicy $attributeReleasePolicy
* @param string|null $supportUrlEn
* @param string|null $supportUrlNl
* @param string|null $supportUrlPt
* @param bool|null $stepupAllowNoToken
* @param bool|null $stepupRequireLoa
*/
public function __construct(
$entityId,
Organization $organizationEn = null,
Organization $organizationNl = null,
Organization $organizationPt = null,
Service $singleLogoutService = null,
$additionalLogging = false,
bool $additionalLogging = false,
array $certificates = array(),
array $contactPersons = array(),
$descriptionEn = '',
$descriptionNl = '',
$descriptionPt = '',
$disableScoping = false,
$displayNameEn = '',
$displayNameNl = '',
$displayNamePt = '',
$keywordsEn = '',
$keywordsNl = '',
$keywordsPt = '',
Logo $logo = null,
$nameEn = '',
$nameNl = '',
$namePt = '',
$nameIdFormat = null,
$supportedNameIdFormats = array(
string $descriptionEn = '',
string $descriptionNl = '',
string $descriptionPt = '',
bool $disableScoping = false,
?string $displayNameEn = '',
?string $displayNameNl = '',
?string $displayNamePt = '',
?string $keywordsEn = '',
?string $keywordsNl = '',
?string $keywordsPt = '',
?Logo $logo = null,
?string $nameEn = '',
?string $nameNl = '',
?string $namePt = '',
?string $nameIdFormat = null,
array $supportedNameIdFormats = array(
Constants::NAMEID_TRANSIENT,
Constants::NAMEID_PERSISTENT,
),
$requestsMustBeSigned = false,
$signatureMethod = XMLSecurityKey::RSA_SHA256,
$workflowState = self::WORKFLOW_STATE_DEFAULT,
bool $requestsMustBeSigned = false,
string $signatureMethod = XMLSecurityKey::RSA_SHA256,
string $workflowState = self::WORKFLOW_STATE_DEFAULT,
array $allowedIdpEntityIds = array(),
$allowAll = false,
bool $allowAll = false,
array $assertionConsumerServices = array(),
$displayUnconnectedIdpsWayf = false,
$termsOfServiceUrl = null,
$isConsentRequired = true,
$isTransparentIssuer = false,
$isTrustedProxy = false,
bool $displayUnconnectedIdpsWayf = false,
string $termsOfServiceUrl = null,
bool $isConsentRequired = true,
bool $isTransparentIssuer = false,
bool $isTrustedProxy = false,
$requestedAttributes = null,
$skipDenormalization = false,
$policyEnforcementDecisionRequired = false,
$requesteridRequired = false,
$signResponse = false,
$manipulation = '',
bool $skipDenormalization = false,
bool $policyEnforcementDecisionRequired = false,
bool $requesteridRequired = false,
bool $signResponse = false,
string $manipulation = '',
AttributeReleasePolicy $attributeReleasePolicy = null,
$supportUrlEn = null,
$supportUrlNl = null,
$supportUrlPt = null,
$stepupAllowNoToken = null,
$stepupRequireLoa = null
string $supportUrlEn = null,
string $supportUrlNl = null,
string $supportUrlPt = null,
bool $stepupAllowNoToken = null,
string $stepupRequireLoa = null,
bool $stepupForceAuthn = false
) {
parent::__construct(
$entityId,
Expand Down Expand Up @@ -257,7 +210,8 @@ public function __construct(
$stepupRequireLoa,
$disableScoping,
$additionalLogging,
$signatureMethod
$signatureMethod,
$stepupForceAuthn
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Exception;
use OpenConext\EngineBlockFunctionalTestingBundle\Mock\MockStepupGateway;
use SAML2\Constants;
use SAML2\HTTPRedirect;
use SAML2\Response as SamlResponse;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
Expand Down Expand Up @@ -62,10 +63,14 @@ public function ssoAction(Request $request)
// Parse available responses
$responses = $this->getAvailableResponses($request);

$redirectBinding = new HTTPRedirect();
$message = $redirectBinding->receive();

// Present response
$body = $this->twig->render(
'@OpenConextEngineBlockFunctionalTesting/Sso/consumeAssertion.html.twig',
[
'receivedAuthnRequest' => $message->toUnsignedXML()->ownerDocument->saveXml(),
'responses' => $responses,
]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
use RuntimeException;
use SAML2\Constants;
use SAML2\DOMDocumentFactory;
use function assertStringNotMatchesFormat;
use function assertStringStartsWith;
use function preg_match;
use function sprintf;

/**
Expand Down Expand Up @@ -584,14 +587,41 @@ public function cleanUpAuthenticationLoopGuard()
}
}

/**
* @Then /^the received AuthnRequest should not match xpath '([^']*)'$/
*/
public function theReceivedAuthnRequestShouldNotMatchXpath($xpath)
{
$session = $this->getMinkContext()->getSession();
try {
$this->theAuthnRequestToSubmitShouldMatchXpath($xpath);
throw new RuntimeException('The xpath was found in the AuthnRequest, it should not');
} catch (ExpectationException $e) {
if (false === preg_match('/The xpath "(w+)" did not result in at least one match./', $e->getMessage())) {
throw new ExpectationException(
'Unexepected match on the xpath that should NOT match the AuthnRequest xml',
$session,
$e
);
}
}
}

/**
* @Then /^the received AuthnRequest should match xpath '([^']*)'$/
*/
public function theReceivedAuthnRequestShouldMatchXpath($xpath)
{
return $this->theAuthnRequestToSubmitShouldMatchXpath($xpath);
}

/**
* @Then /^the AuthnRequest to submit should match xpath '([^']*)'$/
*/
public function theAuthnRequestToSubmitShouldMatchXpath($xpath)
{
$session = $this->getMinkContext()->getSession();
$mink = $session->getPage();

$authnRequestElement = $mink->find('css', 'input[name="authnRequestXml"]');
if ($authnRequestElement === null) {
throw new ExpectationException('Element with the name "authnRequestXml" could not be found', $session);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,19 @@ public function spAllowsNoStepup($spName)
->save();
}

/**
* @Given /^the SP "([^"]*)" forces stepup authentication$/
*/
public function spForcesAuthn($spName)
{
/** @var MockServiceProvider $mockSp */
$mockSp = $this->mockSpRegistry->get($spName);

$this->serviceRegistryFixture
->setStepupForceAuthn($mockSp->entityId(), true)
->save();
}

/**
* @Given /^the SP "([^"]*)" requires Stepup LoA "([^"]*)"$/
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,24 @@ Feature:
And I pass through EngineBlock
Then the url should match "/functional-testing/SSO-SP/acs"

Scenario: LoA 1 is allowed, but refrains from doing a step up callout
Scenario: Stepup authentication is forced when coin:stepup:forceauthn is configured for the SP
Given SP "SSO-SP" requests LoA "http://vm.openconext.org/assurance/loa3"
And the SP "SSO-SP" forces stepup authentication
When I log in at "SSO-SP"
And I select "SSO-IdP" on the WAYF
And I pass through EngineBlock
And I pass through the IdP
Then the received AuthnRequest should match xpath '/samlp:AuthnRequest[@ForceAuthn="true"]'

Scenario: Stepup authentication is NOT forced when coin:stepup:forceauthn is not configured for the SP
Given SP "SSO-SP" requests LoA "http://vm.openconext.org/assurance/loa3"
When I log in at "SSO-SP"
And I select "SSO-IdP" on the WAYF
And I pass through EngineBlock
And I pass through the IdP
Then the received AuthnRequest should not match xpath '/samlp:AuthnRequest[@ForceAuthn="true"]'

Scenario: LoA 1 is allowed, but refrains from doing a step up callout
Given SP "SSO-SP" requests LoA "http://vm.openconext.org/assurance/loa1"
When I log in at "SSO-SP"
And I select "SSO-IdP" on the WAYF
Expand Down Expand Up @@ -191,6 +208,29 @@ Feature:
And I pass through EngineBlock
Then the url should match "/functional-testing/Proxy-SP/acs"

Scenario: Step-up ForceAuthn should be requested for the proxied SP when using a trusted proxy setup and if configured in the proxied SP
Given the SP "SSO-SP" requires Stepup LoA "http://vm.openconext.org/assurance/loa2"
And the SP "SSO-SP" forces stepup authentication
And SP "Proxy-SP" is authenticating for SP "SSO-SP"
And SP "Proxy-SP" is a trusted proxy
And SP "Proxy-SP" signs its requests
When I log in at "Proxy-SP"
And I select "SSO-IdP" on the WAYF
And I pass through EngineBlock
And I pass through the IdP
Then the received AuthnRequest should match xpath '/samlp:AuthnRequest[@ForceAuthn="true"]'

Scenario: Step-up ForceAuthn should not be requested for the proxied SP when using a trusted proxy setup and if configured in the proxied SP
Given the SP "SSO-SP" requires Stepup LoA "http://vm.openconext.org/assurance/loa2"
And SP "Proxy-SP" is authenticating for SP "SSO-SP"
And SP "Proxy-SP" is a trusted proxy
And SP "Proxy-SP" signs its requests
When I log in at "Proxy-SP"
And I select "SSO-IdP" on the WAYF
And I pass through EngineBlock
And I pass through the IdP
Then the received AuthnRequest should not match xpath '/samlp:AuthnRequest[@ForceAuthn="true"]'

Scenario: Stepup authentication should fail when stepup is misconfigured
Given the SP "SSO-SP" requires Stepup LoA "http://typo-in-config.org/assurance/laos3"
When I log in at "SSO-SP"
Expand Down
Loading

0 comments on commit e4a0fae

Please sign in to comment.