Skip to content

Commit e4a0fae

Browse files
MKoddethijskh
authored andcommitted
Support ForceAuthn to Stepup Gateway callout
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
1 parent 89eeca6 commit e4a0fae

File tree

14 files changed

+205
-97
lines changed

14 files changed

+205
-97
lines changed

library/EngineBlock/Corto/Module/Service/AssertionConsumer.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,13 @@ public function serve($serviceName, Request $httpRequest)
207207
$nameId = clone $receivedResponse->getNameId();
208208
$authnClassRef = $this->_stepupGatewayCallOutHelper->getStepupLoa($idp, $sp, $authnRequestLoas, $pdpLoas);
209209

210-
$this->_server->sendStepupAuthenticationRequest($receivedRequest, $currentProcessStep->getRole(), $authnClassRef, $nameId);
210+
$this->_server->sendStepupAuthenticationRequest(
211+
$receivedRequest,
212+
$currentProcessStep->getRole(),
213+
$authnClassRef,
214+
$nameId,
215+
$sp->getCoins()->isStepupForceAuthn()
216+
);
211217
}
212218

213219
/**

library/EngineBlock/Corto/ProxyServer.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,8 @@ public function sendStepupAuthenticationRequest(
457457
EngineBlock_Saml2_AuthnRequestAnnotationDecorator $spRequest,
458458
IdentityProvider $identityProvider,
459459
Loa $authnContextClassRef,
460-
NameID $nameId
460+
NameID $nameId,
461+
bool $isForceAuthn
461462
) {
462463
$ebRequest = EngineBlock_Saml2_AuthnRequestFactory::createFromRequest(
463464
$spRequest,
@@ -467,6 +468,8 @@ public function sendStepupAuthenticationRequest(
467468
'stepupAssertionConsumerService'
468469
);
469470

471+
$ebRequest->setForceAuthn($isForceAuthn);
472+
470473
$sspMessage = $ebRequest->getSspMessage();
471474
if (!$sspMessage instanceof AuthnRequest) {
472475
throw new EngineBlock_Corto_ProxyServer_Exception(sprintf('Unknown message type: "%s"', get_class($sspMessage)));

library/EngineBlock/Saml2/AuthnRequestAnnotationDecorator.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,9 @@ public function isTransparent()
179179
{
180180
return $this->transparent;
181181
}
182+
183+
public function setForceAuthn(bool $isForceAuthn)
184+
{
185+
$this->sspMessage->setForceAuthn($isForceAuthn);
186+
}
182187
}

src/OpenConext/EngineBlock/Metadata/Coins.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ public static function createForServiceProvider(
4343
$stepupRequireLoa,
4444
$disableScoping,
4545
$additionalLogging,
46-
$signatureMethod
46+
$signatureMethod,
47+
$stepupForceAuthn
4748
) {
4849
return new self([
4950
'isConsentRequired' => $isConsentRequired,
@@ -60,6 +61,7 @@ public static function createForServiceProvider(
6061
'signatureMethod' => $signatureMethod,
6162
'stepupAllowNoToken' => $stepupAllowNoToken,
6263
'stepupRequireLoa' => $stepupRequireLoa,
64+
'stepupForceAuthn' => $stepupForceAuthn,
6365
]);
6466
}
6567

@@ -178,6 +180,15 @@ public function stepupRequireLoa()
178180
return $this->getValue('stepupRequireLoa', '');
179181
}
180182

183+
/**
184+
* Should the Stepup authentication request (to the Stepup Gateway)
185+
* have the ForceAuthn attribute in the AuthnRequest?
186+
*/
187+
public function isStepupForceAuthn()
188+
{
189+
return $this->getValue('stepupForceAuthn', false);
190+
}
191+
181192
// IDP
182193
public function guestQualifier()
183194
{

src/OpenConext/EngineBlock/Metadata/Entity/Assembler/PushMetadataAssembler.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,13 @@ private function assembleSp(stdClass $connection)
273273
),
274274
'stepupAllowNoToken'
275275
);
276-
276+
$properties += $this->setPathFromObjectBool(
277+
array(
278+
$connection,
279+
'metadata:coin:stepup:forceauthn'
280+
),
281+
'stepupForceAuthn'
282+
);
277283
return Utils::instantiate(
278284
ServiceProvider::class,
279285
$properties

src/OpenConext/EngineBlock/Metadata/Entity/ServiceProvider.php

Lines changed: 39 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -105,106 +105,59 @@ class ServiceProvider extends AbstractRole
105105
/**
106106
* WARNING: Please don't use this entity directly but use the dedicated factory instead.
107107
* @see \OpenConext\EngineBlock\Factory\Factory\ServiceProviderFactory
108-
*
109-
* @param string $entityId
110-
* @param Organization $organizationEn
111-
* @param Organization $organizationNl
112-
* @param Organization $organizationPt
113-
* @param Service $singleLogoutService
114-
* @param bool $additionalLogging
115-
* @param X509Certificate[] $certificates
116-
* @param ContactPerson[] $contactPersons
117-
* @param string $descriptionEn
118-
* @param string $descriptionNl
119-
* @param string $descriptionPt
120-
* @param bool $disableScoping
121-
* @param string $displayNameEn
122-
* @param string $displayNameNl
123-
* @param string $displayNamePt
124-
* @param string $keywordsEn
125-
* @param string $keywordsNl
126-
* @param string $keywordsPt
127-
* @param Logo $logo
128-
* @param string $nameEn
129-
* @param string $nameNl
130-
* @param string $namePt
131-
* @param null $nameIdFormat
132-
* @param array $supportedNameIdFormats
133-
* @param bool $requestsMustBeSigned
134-
* @param string $signatureMethod
135-
* @param string $workflowState
136-
* @param array $allowedIdpEntityIds
137-
* @param bool $allowAll
138-
* @param array $assertionConsumerServices
139-
* @param bool $displayUnconnectedIdpsWayf
140-
* @param null $termsOfServiceUrl
141-
* @param bool $isConsentRequired
142-
* @param bool $isTransparentIssuer
143-
* @param bool $isTrustedProxy
144-
* @param null $requestedAttributes
145-
* @param bool $skipDenormalization
146-
* @param bool $policyEnforcementDecisionRequired
147-
* @param bool $requesteridRequired
148-
* @param bool $signResponse
149-
* @param string $manipulation
150-
* @param AttributeReleasePolicy $attributeReleasePolicy
151-
* @param string|null $supportUrlEn
152-
* @param string|null $supportUrlNl
153-
* @param string|null $supportUrlPt
154-
* @param bool|null $stepupAllowNoToken
155-
* @param bool|null $stepupRequireLoa
156108
*/
157109
public function __construct(
158110
$entityId,
159111
Organization $organizationEn = null,
160112
Organization $organizationNl = null,
161113
Organization $organizationPt = null,
162114
Service $singleLogoutService = null,
163-
$additionalLogging = false,
115+
bool $additionalLogging = false,
164116
array $certificates = array(),
165117
array $contactPersons = array(),
166-
$descriptionEn = '',
167-
$descriptionNl = '',
168-
$descriptionPt = '',
169-
$disableScoping = false,
170-
$displayNameEn = '',
171-
$displayNameNl = '',
172-
$displayNamePt = '',
173-
$keywordsEn = '',
174-
$keywordsNl = '',
175-
$keywordsPt = '',
176-
Logo $logo = null,
177-
$nameEn = '',
178-
$nameNl = '',
179-
$namePt = '',
180-
$nameIdFormat = null,
181-
$supportedNameIdFormats = array(
118+
string $descriptionEn = '',
119+
string $descriptionNl = '',
120+
string $descriptionPt = '',
121+
bool $disableScoping = false,
122+
?string $displayNameEn = '',
123+
?string $displayNameNl = '',
124+
?string $displayNamePt = '',
125+
?string $keywordsEn = '',
126+
?string $keywordsNl = '',
127+
?string $keywordsPt = '',
128+
?Logo $logo = null,
129+
?string $nameEn = '',
130+
?string $nameNl = '',
131+
?string $namePt = '',
132+
?string $nameIdFormat = null,
133+
array $supportedNameIdFormats = array(
182134
Constants::NAMEID_TRANSIENT,
183135
Constants::NAMEID_PERSISTENT,
184136
),
185-
$requestsMustBeSigned = false,
186-
$signatureMethod = XMLSecurityKey::RSA_SHA256,
187-
$workflowState = self::WORKFLOW_STATE_DEFAULT,
137+
bool $requestsMustBeSigned = false,
138+
string $signatureMethod = XMLSecurityKey::RSA_SHA256,
139+
string $workflowState = self::WORKFLOW_STATE_DEFAULT,
188140
array $allowedIdpEntityIds = array(),
189-
$allowAll = false,
141+
bool $allowAll = false,
190142
array $assertionConsumerServices = array(),
191-
$displayUnconnectedIdpsWayf = false,
192-
$termsOfServiceUrl = null,
193-
$isConsentRequired = true,
194-
$isTransparentIssuer = false,
195-
$isTrustedProxy = false,
143+
bool $displayUnconnectedIdpsWayf = false,
144+
string $termsOfServiceUrl = null,
145+
bool $isConsentRequired = true,
146+
bool $isTransparentIssuer = false,
147+
bool $isTrustedProxy = false,
196148
$requestedAttributes = null,
197-
$skipDenormalization = false,
198-
$policyEnforcementDecisionRequired = false,
199-
$requesteridRequired = false,
200-
$signResponse = false,
201-
$manipulation = '',
149+
bool $skipDenormalization = false,
150+
bool $policyEnforcementDecisionRequired = false,
151+
bool $requesteridRequired = false,
152+
bool $signResponse = false,
153+
string $manipulation = '',
202154
AttributeReleasePolicy $attributeReleasePolicy = null,
203-
$supportUrlEn = null,
204-
$supportUrlNl = null,
205-
$supportUrlPt = null,
206-
$stepupAllowNoToken = null,
207-
$stepupRequireLoa = null
155+
string $supportUrlEn = null,
156+
string $supportUrlNl = null,
157+
string $supportUrlPt = null,
158+
bool $stepupAllowNoToken = null,
159+
string $stepupRequireLoa = null,
160+
bool $stepupForceAuthn = false
208161
) {
209162
parent::__construct(
210163
$entityId,
@@ -257,7 +210,8 @@ public function __construct(
257210
$stepupRequireLoa,
258211
$disableScoping,
259212
$additionalLogging,
260-
$signatureMethod
213+
$signatureMethod,
214+
$stepupForceAuthn
261215
);
262216
}
263217

src/OpenConext/EngineBlockFunctionalTestingBundle/Controllers/StepupMockController.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Exception;
2121
use OpenConext\EngineBlockFunctionalTestingBundle\Mock\MockStepupGateway;
2222
use SAML2\Constants;
23+
use SAML2\HTTPRedirect;
2324
use SAML2\Response as SamlResponse;
2425
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
2526
use Symfony\Component\HttpFoundation\Request;
@@ -62,10 +63,14 @@ public function ssoAction(Request $request)
6263
// Parse available responses
6364
$responses = $this->getAvailableResponses($request);
6465

66+
$redirectBinding = new HTTPRedirect();
67+
$message = $redirectBinding->receive();
68+
6569
// Present response
6670
$body = $this->twig->render(
6771
'@OpenConextEngineBlockFunctionalTesting/Sso/consumeAssertion.html.twig',
6872
[
73+
'receivedAuthnRequest' => $message->toUnsignedXML()->ownerDocument->saveXml(),
6974
'responses' => $responses,
7075
]
7176
);

src/OpenConext/EngineBlockFunctionalTestingBundle/Features/Context/EngineBlockContext.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
use RuntimeException;
4040
use SAML2\Constants;
4141
use SAML2\DOMDocumentFactory;
42+
use function assertStringNotMatchesFormat;
43+
use function assertStringStartsWith;
44+
use function preg_match;
4245
use function sprintf;
4346

4447
/**
@@ -584,14 +587,41 @@ public function cleanUpAuthenticationLoopGuard()
584587
}
585588
}
586589

590+
/**
591+
* @Then /^the received AuthnRequest should not match xpath '([^']*)'$/
592+
*/
593+
public function theReceivedAuthnRequestShouldNotMatchXpath($xpath)
594+
{
595+
$session = $this->getMinkContext()->getSession();
596+
try {
597+
$this->theAuthnRequestToSubmitShouldMatchXpath($xpath);
598+
throw new RuntimeException('The xpath was found in the AuthnRequest, it should not');
599+
} catch (ExpectationException $e) {
600+
if (false === preg_match('/The xpath "(w+)" did not result in at least one match./', $e->getMessage())) {
601+
throw new ExpectationException(
602+
'Unexepected match on the xpath that should NOT match the AuthnRequest xml',
603+
$session,
604+
$e
605+
);
606+
}
607+
}
608+
}
609+
610+
/**
611+
* @Then /^the received AuthnRequest should match xpath '([^']*)'$/
612+
*/
613+
public function theReceivedAuthnRequestShouldMatchXpath($xpath)
614+
{
615+
return $this->theAuthnRequestToSubmitShouldMatchXpath($xpath);
616+
}
617+
587618
/**
588619
* @Then /^the AuthnRequest to submit should match xpath '([^']*)'$/
589620
*/
590621
public function theAuthnRequestToSubmitShouldMatchXpath($xpath)
591622
{
592623
$session = $this->getMinkContext()->getSession();
593624
$mink = $session->getPage();
594-
595625
$authnRequestElement = $mink->find('css', 'input[name="authnRequestXml"]');
596626
if ($authnRequestElement === null) {
597627
throw new ExpectationException('Element with the name "authnRequestXml" could not be found', $session);

src/OpenConext/EngineBlockFunctionalTestingBundle/Features/Context/StepupContext.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,19 @@ public function spAllowsNoStepup($spName)
135135
->save();
136136
}
137137

138+
/**
139+
* @Given /^the SP "([^"]*)" forces stepup authentication$/
140+
*/
141+
public function spForcesAuthn($spName)
142+
{
143+
/** @var MockServiceProvider $mockSp */
144+
$mockSp = $this->mockSpRegistry->get($spName);
145+
146+
$this->serviceRegistryFixture
147+
->setStepupForceAuthn($mockSp->entityId(), true)
148+
->save();
149+
}
150+
138151
/**
139152
* @Given /^the SP "([^"]*)" requires Stepup LoA "([^"]*)"$/
140153
*/

src/OpenConext/EngineBlockFunctionalTestingBundle/Features/Stepup.feature

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,24 @@ Feature:
8181
And I pass through EngineBlock
8282
Then the url should match "/functional-testing/SSO-SP/acs"
8383

84-
Scenario: LoA 1 is allowed, but refrains from doing a step up callout
84+
Scenario: Stepup authentication is forced when coin:stepup:forceauthn is configured for the SP
85+
Given SP "SSO-SP" requests LoA "http://vm.openconext.org/assurance/loa3"
86+
And the SP "SSO-SP" forces stepup authentication
87+
When I log in at "SSO-SP"
88+
And I select "SSO-IdP" on the WAYF
89+
And I pass through EngineBlock
90+
And I pass through the IdP
91+
Then the received AuthnRequest should match xpath '/samlp:AuthnRequest[@ForceAuthn="true"]'
92+
93+
Scenario: Stepup authentication is NOT forced when coin:stepup:forceauthn is not configured for the SP
94+
Given SP "SSO-SP" requests LoA "http://vm.openconext.org/assurance/loa3"
95+
When I log in at "SSO-SP"
96+
And I select "SSO-IdP" on the WAYF
97+
And I pass through EngineBlock
98+
And I pass through the IdP
99+
Then the received AuthnRequest should not match xpath '/samlp:AuthnRequest[@ForceAuthn="true"]'
100+
101+
Scenario: LoA 1 is allowed, but refrains from doing a step up callout
85102
Given SP "SSO-SP" requests LoA "http://vm.openconext.org/assurance/loa1"
86103
When I log in at "SSO-SP"
87104
And I select "SSO-IdP" on the WAYF
@@ -191,6 +208,29 @@ Feature:
191208
And I pass through EngineBlock
192209
Then the url should match "/functional-testing/Proxy-SP/acs"
193210

211+
Scenario: Step-up ForceAuthn should be requested for the proxied SP when using a trusted proxy setup and if configured in the proxied SP
212+
Given the SP "SSO-SP" requires Stepup LoA "http://vm.openconext.org/assurance/loa2"
213+
And the SP "SSO-SP" forces stepup authentication
214+
And SP "Proxy-SP" is authenticating for SP "SSO-SP"
215+
And SP "Proxy-SP" is a trusted proxy
216+
And SP "Proxy-SP" signs its requests
217+
When I log in at "Proxy-SP"
218+
And I select "SSO-IdP" on the WAYF
219+
And I pass through EngineBlock
220+
And I pass through the IdP
221+
Then the received AuthnRequest should match xpath '/samlp:AuthnRequest[@ForceAuthn="true"]'
222+
223+
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
224+
Given the SP "SSO-SP" requires Stepup LoA "http://vm.openconext.org/assurance/loa2"
225+
And SP "Proxy-SP" is authenticating for SP "SSO-SP"
226+
And SP "Proxy-SP" is a trusted proxy
227+
And SP "Proxy-SP" signs its requests
228+
When I log in at "Proxy-SP"
229+
And I select "SSO-IdP" on the WAYF
230+
And I pass through EngineBlock
231+
And I pass through the IdP
232+
Then the received AuthnRequest should not match xpath '/samlp:AuthnRequest[@ForceAuthn="true"]'
233+
194234
Scenario: Stepup authentication should fail when stepup is misconfigured
195235
Given the SP "SSO-SP" requires Stepup LoA "http://typo-in-config.org/assurance/laos3"
196236
When I log in at "SSO-SP"

0 commit comments

Comments
 (0)