Skip to content

Commit da272f3

Browse files
committed
Implement sbs integration flow
See #1804
1 parent 1830a47 commit da272f3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1607
-438
lines changed

app/config/parameters.yml.dist

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -316,8 +316,16 @@ parameters:
316316
##########################################################################################
317317
## SRAM Settings
318318
##########################################################################################
319-
## Currently this is used for the outgoing requests with the PDP and AA client
320-
sram.api_token: "xxx"
321-
sram.authz_location: "http://127.0.0.1:12345/api"
322-
sram.interrupt_location: "http://127.0.0.1:12345/interrupt"
323-
sram.entitlements_location: "http://127.0.0.1:12345/entitlements"
319+
## Config for connecting with SBS server
320+
## base_url must end with /. Locations must not start with /.
321+
sram.api_token: xxx
322+
sram.base_url: 'https://engine.dev.openconext.local/functional-testing/'
323+
sram.authz_location: authz
324+
sram.interrupt_location: interrupt
325+
sram.entitlements_location: entitlements
326+
sram.verify_peer: false
327+
sram.allowed_attributes:
328+
- eduPersonEntitlement
329+
- eduPersonPrincipalName
330+
- uid
331+
- sshkey

docs/filter_commands.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# EngineBlock Input and Output Command Chains
22

33
EngineBlock pre-processes incoming and outgoing SAML Responses using so-called Filters. These filters provide specific,
4-
critical functionality, by invoking a sequence of Filter Commands. However, it is not easily discoverable what these
5-
Filters and Filter Commands exactly do and how they work. This document outlines how these Filters and Filter Commands
4+
critical functionality, by invoking a sequence of Filter Commands. However, it is not easily discoverable what these
5+
Filters and Filter Commands exactly do and how they work. This document outlines how these Filters and Filter Commands
66
work and what each filter command does.
77

88
The chains are:
@@ -13,11 +13,11 @@ The specific commands can be found in the [`library\EngineBlock\Corto\Filter\Com
1313

1414
## Input and Output Filters
1515

16-
These are called by [`ProxyServer`][ps], through [`filterOutputAssertionAttributes`][fOAA] and
16+
These are called by [`ProxyServer`][ps], through [`filterOutputAssertionAttributes`][fOAA] and
1717
[`filterInputAssertionAttributes`][fIAA] calling [`callAttributeFilter`][cAF], which invokes the actual Filter Commands.
1818

1919
Each Filter then executes Filter Commands in a specified order for Input (between receiving Assertion from IdP and
20-
Consent) and Output (after Consent, before sending Response to SP).
20+
Consent) and Output (after Consent, before sending Response to SP).
2121
What the filter does is:
2222
```
2323
Loop over given Filter Commands, for each Command:
@@ -30,7 +30,7 @@ Loop over given Filter Commands, for each Command:
3030
set the collabPersonId (either: string stored in session, string found in Response, string found in responseAttributes, string found in nameId response or null, in that order)
3131
execute the command
3232
```
33-
During the loop, the Response, responseAttributes and collabPersonId are retrieved from the previous command and are
33+
During the loop, the Response, responseAttributes and collabPersonId are retrieved from the previous command and are
3434
used by the commands that follows.
3535

3636
A command can also stop filtering by calling `$this->stopFiltering();`
@@ -67,7 +67,7 @@ Uses:
6767
- EngineBlock_Saml2_ResponseAnnotationDecorator
6868
- responseAttributes
6969

70-
### NormalizeAttributes
70+
### NormalizeAttributes
7171
Convert all OID attributes to URN and remove the OID variant
7272

7373
Depends on:
@@ -193,7 +193,7 @@ Modifies:
193193
See: [Engineblock Attribute Aggregation](attribute_aggregation.md) for more information.
194194

195195
### EnforcePolicy
196-
Makes a call to the external PolicyDecisionPoint service. This returns a response which details whether or not the
196+
Makes a call to the external PolicyDecisionPoint service. This returns a response which details whether or not the
197197
current User is allowed access to the Service Provider. For more information see [the PDP repository README][pdp-repo]
198198

199199
Depends On:
@@ -343,7 +343,8 @@ Uses:
343343
- OpenConext\EngineBlock\Metadata\Entity\IdentityProvider
344344
- EngineBlock_Saml2_AuthnRequestAnnotationDecorator
345345

346-
346+
### SRAM test filter
347+
When enabled and the SP has the collab_enabled coin, the SBS integration flow will be activated allowing SRAM integration.
347348

348349

349350

library/EngineBlock/Application/DiContainer.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use OpenConext\EngineBlock\Stepup\StepupEntityFactory;
2727
use OpenConext\EngineBlock\Stepup\StepupGatewayCallOutHelper;
2828
use OpenConext\EngineBlock\Validator\AllowedSchemeValidator;
29+
use OpenConext\EngineBlockBundle\Sbs\SbsAttributeMerger;
2930
use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface;
3031

3132
class EngineBlock_Application_DiContainer extends Pimple
@@ -306,6 +307,16 @@ protected function getSymfonyContainer()
306307
return $this->container;
307308
}
308309

310+
public function getSbsAttributeMerger(): SbsAttributeMerger
311+
{
312+
return $this->container->get('engineblack.sbs.attribute_merger');
313+
}
314+
315+
public function getSbsClient(): \OpenConext\EngineBlockBundle\Sbs\SbsClientInterface
316+
{
317+
return $this->container->get('engineblock.sbs.sbs_client');
318+
}
319+
309320
public function getPdpClient()
310321
{
311322
return $this->container->get('engineblock.pdp.pdp_client');
@@ -538,12 +549,6 @@ protected function getStepupEndpoint()
538549
return $this->container->get('engineblock.configuration.stepup.endpoint');
539550
}
540551

541-
/** @return \OpenConext\EngineBlock\SRAM\SRAMEndpoint $sramEndpoint */
542-
public function getSRAMEndpoint()
543-
{
544-
return $this->container->get('engineblock.configuration.sram.endpoint');
545-
}
546-
547552
/** @return string */
548553
public function getStepupEntityIdOverrideValue()
549554
{

library/EngineBlock/Application/TestDiContainer.php

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
*/
1818

1919
use OpenConext\EngineBlock\Stepup\StepupEndpoint;
20+
use OpenConext\EngineBlockBundle\Configuration\FeatureConfigurationInterface;
2021
use OpenConext\EngineBlockBundle\Pdp\PdpClientInterface;
22+
use OpenConext\EngineBlockBundle\Sbs\SbsClientInterface;
2123

2224
/**
2325
* Creates mocked versions of dependencies for unit testing
@@ -29,6 +31,16 @@ class EngineBlock_Application_TestDiContainer extends EngineBlock_Application_Di
2931
*/
3032
private $pdpClient;
3133

34+
/**
35+
* @var SbsClientInterface|null
36+
*/
37+
private $sbsClient;
38+
39+
/**
40+
* @var FeatureConfigurationInterface|null
41+
*/
42+
private $featureConfiguration;
43+
3244
public function getXmlConverter()
3345
{
3446
return Phake::mock('EngineBlock_Corto_XmlToArray');
@@ -49,11 +61,31 @@ public function getPdpClient()
4961
return $this->pdpClient ?? parent::getPdpClient();
5062
}
5163

52-
public function setPdpClient(PdpClientInterface $pdpClient)
64+
public function setPdpClient(?PdpClientInterface $pdpClient)
5365
{
5466
$this->pdpClient = $pdpClient;
5567
}
5668

69+
public function setSbsClient(?SbsClientInterface $sbsClient)
70+
{
71+
$this->sbsClient = $sbsClient;
72+
}
73+
74+
public function getSbsClient(): SbsClientInterface
75+
{
76+
return $this->sbsClient ?? parent::getSbsClient();
77+
}
78+
79+
public function setFeatureConfiguration(?FeatureConfigurationInterface $featureConfiguration)
80+
{
81+
$this->featureConfiguration = $featureConfiguration;
82+
}
83+
84+
public function getFeatureConfiguration(): FeatureConfigurationInterface
85+
{
86+
return $this->featureConfiguration ?? parent::getFeatureConfiguration();
87+
}
88+
5789
public function getConsentFactory()
5890
{
5991
$consentFactoryMock = Phake::mock('EngineBlock_Corto_Model_Consent_Factory');

library/EngineBlock/Corto/Filter/Command/EnforcePolicy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public function execute()
6363

6464
try {
6565
$pdp = $this->getPdpClient();
66-
$policyDecision = $pdp->requestInterruptDecisionFor($pdpRequest);
66+
$policyDecision = $pdp->requestDecisionFor($pdpRequest);
6767
} catch (\OpenConext\EngineBlock\Http\Exception\HttpException $e) {
6868
throw new EngineBlock_Exception_PdpCheckFailed(
6969
'Policy Enforcement Point: Could not perform PDP check: ' . $e->getMessage()
Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<?php
22

3+
use OpenConext\EngineBlockBundle\Configuration\FeatureConfigurationInterface;
4+
use OpenConext\EngineBlockBundle\Exception\InvalidSbsResponseException;
5+
use OpenConext\EngineBlockBundle\Sbs\Dto\AuthzRequest;
6+
use OpenConext\EngineBlockBundle\Sbs\SbsAttributeMerger;
7+
38
/**
49
* Copyright 2021 Stichting Kennisnet
510
*
@@ -27,70 +32,72 @@ public function getResponseAttributes()
2732
return $this->_responseAttributes;
2833
}
2934

35+
public function getResponse()
36+
{
37+
return $this->_response;
38+
}
39+
3040
public function execute(): void
3141
{
32-
$application = EngineBlock_ApplicationSingleton::getInstance();
42+
if (!$this->getFeatureConfiguration()->isEnabled('eb.feature_enable_sram_interrupt')) {
43+
return;
44+
}
3345

34-
$sramEndpoint = $application->getDiContainer()->getSRAMEndpoint();
35-
$sramApiToken = $sramEndpoint->getApiToken();
36-
$sramAuthzLocation = $sramEndpoint->getAuthzLocation();
37-
// $sramAuthzLocation = 'http://192.168.0.1:12345/api';
46+
if ($this->_serviceProvider->getCoins()->collabEnabled() === false) {
47+
return;
48+
}
3849

39-
error_log("SRAMTestFilter execute");
50+
try {
51+
$request = $this->buildRequest();
4052

41-
$attributes = $this->getResponseAttributes();
53+
$interruptResponse = $this->getSbsClient()->authz($request);
54+
55+
if ($interruptResponse->msg === 'interrupt') {
56+
$this->_response->setSRAMInterruptNonce($interruptResponse->nonce);
57+
} elseif ($interruptResponse->msg === 'authorized' && !empty($interruptResponse->attributes)) {
58+
$this->_responseAttributes = $this->getSbsAttributeMerger()->mergeAttributes($this->_responseAttributes, $interruptResponse->attributes);
59+
} else {
60+
throw new InvalidSbsResponseException(sprintf('Invalid SBS response received: %s', $interruptResponse->msg));
61+
}
62+
}catch (Throwable $e){
63+
throw new EngineBlock_Exception_SbsCheckFailed('The SBS server could not be queried: ' . $e->getMessage());
64+
}
65+
}
4266

67+
private function getSbsClient()
68+
{
69+
return EngineBlock_ApplicationSingleton::getInstance()->getDiContainer()->getSbsClient();
70+
}
71+
72+
private function getFeatureConfiguration(): FeatureConfigurationInterface
73+
{
74+
return EngineBlock_ApplicationSingleton::getInstance()->getDiContainer()->getFeatureConfiguration();
75+
}
76+
77+
private function getSbsAttributeMerger(): SbsAttributeMerger
78+
{
79+
return EngineBlock_ApplicationSingleton::getInstance()->getDiContainer()->getSbsAttributeMerger();
80+
}
81+
82+
/**
83+
* @return AuthzRequest
84+
* @throws EngineBlock_Corto_ProxyServer_Exception
85+
*/
86+
private function buildRequest(): AuthzRequest
87+
{
88+
$attributes = $this->getResponseAttributes();
4389
$id = $this->_request->getId();
4490

4591
$user_id = $attributes['urn:mace:dir:attribute-def:uid'][0];
4692
$continue_url = $this->_server->getUrl('SRAMInterruptService', '') . "?ID=$id";
4793
$service_id = $this->_serviceProvider->entityId;
48-
// @TODO at the very start of this function, check if the SP has `coin:collab_enabled`, skip otherwise?
4994
$issuer_id = $this->_identityProvider->entityId;
5095

51-
/***
52-
* @TODO Move all curl related things to new HttpClient. See PDPClient as an example.
53-
* @TODO Make sure it has tests
54-
* @TODO add tests for this Input Filter
55-
*/
56-
57-
$headers = array(
58-
"Authorization: $sramApiToken"
59-
);
60-
61-
$post = array(
62-
'user_id' => $user_id,
63-
'continue_url' => $continue_url,
64-
'service_id' => $service_id,
65-
'issuer_id' => $issuer_id
96+
return AuthzRequest::create(
97+
$user_id,
98+
$continue_url,
99+
$service_id,
100+
$issuer_id
66101
);
67-
68-
$options = [
69-
CURLOPT_HEADER => false,
70-
CURLOPT_RETURNTRANSFER => true,
71-
CURLOPT_HTTPHEADER => $headers,
72-
CURLOPT_POST => true,
73-
CURLOPT_POSTFIELDS => $post,
74-
];
75-
76-
77-
$ch = curl_init($sramAuthzLocation);
78-
curl_setopt_array($ch, $options);
79-
80-
$data = curl_exec($ch);
81-
curl_close($ch);
82-
83-
$body = json_decode($data, false);
84-
// error_log("SRAMTestFilter " . var_export($body, true));
85-
86-
// @TODO Add integration test: Assert the redirect url on the saml response is SRAM
87-
88-
$msg = $body->msg;
89-
if ($msg === 'interrupt') {
90-
$this->_response->setSRAMInterruptNonce($body->nonce);
91-
} elseif ($body->attributes) {
92-
$this->_responseAttributes = array_merge_recursive($this->_responseAttributes, (array) $body->attributes);
93-
}
94-
95102
}
96103
}

library/EngineBlock/Corto/Filter/Input.php

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,22 +90,15 @@ public function getCommands()
9090
$diContainer->getAttributeAggregationClient()
9191
),
9292

93+
new EngineBlock_Corto_Filter_Command_SRAMTestFilter(),
94+
9395
// Check if the Policy Decision Point needs to be consulted for this request
9496
new EngineBlock_Corto_Filter_Command_EnforcePolicy(),
9597

9698
// Apply the Attribute Release Policy before we do consent.
9799
new EngineBlock_Corto_Filter_Command_AttributeReleasePolicy(),
98-
99100
);
100101

101-
// SRAM Test filter
102-
// When feature_enable_sram_interrupt enabled
103-
// @TODO Should this check be here, or in the filter itself like \EngineBlock_Corto_Filter_Command_SsoNotificationCookieFilter
104-
// @TODO if it stays here, add test to make sure it's in the command[] or not
105-
if ($featureConfiguration->isEnabled('eb.feature_enable_sram_interrupt')) {
106-
$commands[] = new EngineBlock_Corto_Filter_Command_SRAMTestFilter();
107-
}
108-
109102
if (!$featureConfiguration->isEnabled('eb.run_all_manipulations_prior_to_consent')) {
110103
return $commands;
111104
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,14 @@ public function serve($serviceName, Request $httpRequest)
175175
$log->info('Handle SRAM Interrupt callout');
176176

177177
// Add the SRAM step
178-
$currentProcessStep = $this->_processingStateHelper->addStep(
178+
$this->_processingStateHelper->addStep(
179179
$receivedRequest->getId(),
180180
ProcessingStateHelperInterface::STEP_SRAM,
181181
$this->getEngineSpRole($this->_server),
182182
$receivedResponse
183183
);
184184

185+
// Redirect to SRAM
185186
$this->_server->sendSRAMInterruptRequest($receivedResponse, $receivedRequest);
186187
}
187188

@@ -229,7 +230,6 @@ public function serve($serviceName, Request $httpRequest)
229230
$nameId,
230231
$sp->getCoins()->isStepupForceAuthn()
231232
);
232-
233233
}
234234

235235
/**

0 commit comments

Comments
 (0)