Skip to content

Commit aa3fb55

Browse files
committed
Fix feedbackInfo session bleed-through between auth flows (#1795)
feedbackInfo was stored as a flat dict in $_SESSION['feedbackInfo'] and merged with existing data on each write. This meant info collected during one auth flow (e.g. which IdP was involved) could appear on error pages in a completely separate flow. Additionally, currentServiceProvider and currentIdentityProvider were never cleared from the session after a successful login, so they would show up on error pages for unrelated early errors. The fix: - feedbackInfo is now keyed per SAML request ID so each auth flow has its own isolated bucket with no merging across flows - clearFlowContext() is called by ProcessedAssertionConsumer after a successful auth, clearing all flow context from the session - All session access moved from raw $_SESSION to the Symfony session - Session ops for feedbackInfo and flow context are centralised in a new FeedbackStateHelper class, split out from ProcessingStateHelper - FeedbackStateHelper is wired through DiContainerRuntime instead of the legacy DiContainer
1 parent 23170e4 commit aa3fb55

27 files changed

Lines changed: 1072 additions & 221 deletions

File tree

config/services/bridge.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ services:
77
- '@engineblock.compat.application'
88
- '@logger'
99
- '@request_stack'
10+
- '@OpenConext\EngineBlock\Service\FeedbackStateHelper'
11+
- '@OpenConext\EngineBlock\Service\FeedbackInfoCollector'
1012

1113
OpenConext\EngineBlockBridge\Logger\AuthenticationLoggerAdapter:
1214
arguments:

config/services/ci/controllers.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ services:
2727
- '@translator'
2828
- '@twig'
2929
- '@engineblock.compat.logger'
30+
- '@OpenConext\EngineBlock\Service\FeedbackStateHelper'
3031

3132
engineblock.functional_test.controller.consent:
3233
class: OpenConext\EngineBlockFunctionalTestingBundle\Controllers\ConsentController

config/services/controllers/authentication.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ services:
3232
arguments:
3333
- '@translator'
3434
- '@twig'
35-
- '@engineblock.compat.logger'
35+
- '@OpenConext\EngineBlock\Service\FeedbackStateHelper'
3636

3737
OpenConext\EngineBlockBundle\Controller\MetadataController:
3838
arguments:

config/services/services.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,24 @@ services:
6969
arguments:
7070
- '@request_stack'
7171

72+
OpenConext\EngineBlock\Service\FeedbackStateHelper:
73+
arguments:
74+
- '@request_stack'
75+
76+
OpenConext\EngineBlock\Service\FeedbackStateHelperInterface:
77+
alias: OpenConext\EngineBlock\Service\FeedbackStateHelper
78+
79+
OpenConext\EngineBlock\Service\FeedbackInfoCollector:
80+
arguments:
81+
- '@request_stack'
82+
- '@OpenConext\EngineBlock\Request\RequestId'
83+
- '@OpenConext\EngineBlock\Metadata\MetadataRepository\CachedDoctrineMetadataRepository'
84+
- '@OpenConext\EngineBlockBundle\Localization\LocaleProvider'
85+
- '@OpenConext\EngineBlock\Service\FeedbackStateHelper'
86+
87+
OpenConext\EngineBlock\Service\FeedbackInfoCollectorInterface:
88+
alias: OpenConext\EngineBlock\Service\FeedbackInfoCollector
89+
7290
OpenConext\EngineBlock\Stepup\StepupGatewayCallOutHelper:
7391
arguments:
7492
- '@OpenConext\EngineBlock\Stepup\StepupGatewayLoaMapping'
@@ -319,6 +337,7 @@ services:
319337
- '@OpenConext\EngineBlockBundle\Configuration\ErrorFeedbackConfiguration'
320338
- '@engineblock.compat.repository.metadata'
321339
- '@OpenConext\EngineBlockBundle\Authentication\Service\SamlResponseHelper'
340+
- '@OpenConext\EngineBlock\Service\FeedbackStateHelper'
322341

323342
OpenConext\EngineBlockBundle\Twig\Extensions\Extension\GlobalSiteNotice:
324343
autoconfigure: true

library/EngineBlock/ApplicationSingleton.php

Lines changed: 4 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@
1717
*/
1818

1919
use OpenConext\EngineBlock\Logger\Handler\FingersCrossed\ManualOrDecoratedActivationStrategy;
20-
use OpenConext\EngineBlock\Metadata\MetadataRepository\EntityNotFoundException;
2120
use OpenConext\EngineBlock\Request\RequestId;
2221
use OpenConext\EngineBlockBundle\Bridge\DiContainerRuntime;
23-
use OpenConext\EngineBlockBundle\Exception\Art;
2422
use Psr\Log\LoggerInterface;
2523
use Symfony\Component\DependencyInjection\ContainerInterface;
2624
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
@@ -238,7 +236,10 @@ public function reportError(Throwable $exception, $messageSuffix = '')
238236
// Store some valuable debug info in session so it can be displayed on feedback pages
239237
if($this->hasSession()) {
240238
// In CLI context, the session is not available
241-
$this->getSession()->set('feedbackInfo', $this->collectFeedbackInfo($exception));
239+
$feedbackHelper = $this->getDiContainerRuntime()->feedbackStateHelper;
240+
$feedbackHelper->storeFeedbackInfo(
241+
$this->getDiContainerRuntime()->feedbackInfoCollector->collect($exception)
242+
);
242243
}
243244

244245
// flush all messages in queue, something went wrong!
@@ -247,51 +248,6 @@ public function reportError(Throwable $exception, $messageSuffix = '')
247248
return true;
248249
}
249250

250-
/**
251-
* @param Exception $exception
252-
* @return array
253-
*/
254-
public function collectFeedbackInfo(Throwable $exception)
255-
{
256-
$logRequestId = $this->getLogRequestId();
257-
if ($logRequestId === null) {
258-
$logRequestId = 'N/A - application not yet bootstrapped';
259-
} else {
260-
$logRequestId = $logRequestId->get();
261-
}
262-
263-
$request = $this->getDiContainer()->getSymfonyRequest();
264-
265-
$feedbackInfo = array();
266-
$feedbackInfo['datetime'] = (new DateTime())->format(DateTimeInterface::ATOM);
267-
$feedbackInfo['requestUrl'] = sprintf('%s%s', $request->getSchemeAndHttpHost(), $request->getPathInfo());
268-
$feedbackInfo['requestId'] = $logRequestId;
269-
$feedbackInfo['ipAddress'] = $this->getClientIpAddress();
270-
$feedbackInfo['artCode'] = Art::forException($exception);
271-
272-
// @todo reset this when login is succesful
273-
// Find the current identity provider
274-
$spEntityId = $_SESSION['originalServiceProvider'] ?? $_SESSION['currentServiceProvider'] ?? null;
275-
if ($spEntityId !== null) {
276-
$feedbackInfo['serviceProvider'] = $spEntityId;
277-
$feedbackInfo['serviceProviderName'] = $this->getServiceProviderName($spEntityId);
278-
}
279-
280-
if (isset($_SESSION['proxyServiceProvider'])) {
281-
$feedbackInfo['proxyServiceProvider'] = $_SESSION['proxyServiceProvider'];
282-
}
283-
284-
// @todo reset this when login is succesful
285-
// Find the current identity provider
286-
if (isset($_SESSION['currentIdentityProvider'])) {
287-
$idpEntityId = $_SESSION['currentIdentityProvider'];
288-
$feedbackInfo['identityProvider'] = $idpEntityId;
289-
$feedbackInfo['identityProviderName'] = $this->getidentityProviderName($idpEntityId);
290-
}
291-
292-
return $feedbackInfo;
293-
}
294-
295251
/**
296252
* Get the IP address for the HTTP client (optionally taking into account proxies).
297253
*
@@ -441,26 +397,4 @@ public function getDiContainer()
441397
return $this->_diContainer;
442398
}
443399

444-
/**
445-
* @param string $serviceProviderId
446-
* @return string
447-
*/
448-
private function getServiceProviderName(string $serviceProviderId){
449-
try {
450-
$serviceProvider = $this->getDiContainer()->getMetadataRepository()->fetchServiceProviderByEntityId($serviceProviderId);
451-
return $serviceProvider->getDisplayName($this->getDiContainer()->getLocaleProvider()->getLocale());
452-
} catch (EntityNotFoundException $e) {}
453-
454-
return '';
455-
}
456-
457-
private function getIdentityProviderName(string $identityProviderId): string
458-
{
459-
try {
460-
$identityProvider = $this->getDiContainer()->getMetadataRepository()->fetchIdentityProviderByEntityId($identityProviderId);
461-
return $identityProvider->getDisplayName($this->getDiContainer()->getLocaleProvider()->getLocale());
462-
} catch (EntityNotFoundException $e) {}
463-
464-
return '';
465-
}
466400
}

library/EngineBlock/Corto/Module/Bindings.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,11 @@ public function receiveRequest()
180180
'Missing <saml:Issuer> in message delivered to AssertionConsumerService.'
181181
);
182182
}
183-
// Remember sp for debugging
184-
$_SESSION['currentServiceProvider'] = $ebRequest->getIssuer()->getValue();
183+
184+
EngineBlock_ApplicationSingleton::getInstance()->getDiContainerRuntime()->feedbackStateHelper->startNewFlow(
185+
$ebRequest->getId(),
186+
$ebRequest->getIssuer()->getValue()
187+
);
185188

186189
// Verify that we know this SP and have metadata for it.
187190
$serviceProvider = $this->_verifyKnownSP($spEntityId->getValue());
@@ -324,7 +327,7 @@ public function receiveResponse($serviceEntityId, $expectedDestination)
324327
}
325328

326329
// Remember idp for debugging
327-
$_SESSION['currentIdentityProvider'] = $idpEntityId;
330+
EngineBlock_ApplicationSingleton::getInstance()->getDiContainerRuntime()->feedbackStateHelper->setCurrentIdentityProvider($idpEntityId->getValue());
328331

329332
// Verify that we know this IdP and have metadata for it.
330333
$cortoIdpMetadata = $this->_verifyKnownIdP(

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

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use OpenConext\EngineBlock\Metadata\Entity\ServiceProvider;
2020
use OpenConext\EngineBlock\Metadata\Factory\Factory\ServiceProviderFactory;
2121
use OpenConext\EngineBlock\Metadata\X509\KeyPairFactory;
22+
use OpenConext\EngineBlock\Service\FeedbackStateHelperInterface;
2223
use OpenConext\EngineBlock\Service\ProcessingStateHelperInterface;
2324
use OpenConext\EngineBlock\Stepup\StepupGatewayCallOutHelper;
2425
use OpenConext\EngineBlockBundle\Authentication\AuthenticationState;
@@ -31,41 +32,28 @@
3132

3233
class EngineBlock_Corto_Module_Service_AssertionConsumer implements EngineBlock_Corto_Module_Service_ServiceInterface
3334
{
34-
/**
35-
* @var EngineBlock_Corto_ProxyServer
36-
*/
37-
private $_server;
35+
private EngineBlock_Corto_ProxyServer $_server;
3836

39-
/**
40-
* @var EngineBlock_Corto_XmlToArray
41-
*/
42-
private $_xmlConverter;
37+
private EngineBlock_Corto_XmlToArray $_xmlConverter;
4338

44-
/**
45-
* @var Session
46-
*/
47-
private $_session;
39+
private Session $_session;
4840

49-
/**
50-
* @var ProcessingStateHelperInterface
51-
*/
52-
private $_processingStateHelper;
53-
/**
54-
* @var StepupGatewayCallOutHelper
55-
*/
56-
private $_stepupGatewayCallOutHelper;
57-
/**
58-
* @var ServiceProviderFactory
59-
*/
60-
private $_serviceProviderFactory;
41+
private ProcessingStateHelperInterface $_processingStateHelper;
42+
43+
private StepupGatewayCallOutHelper $_stepupGatewayCallOutHelper;
44+
45+
private ServiceProviderFactory $_serviceProviderFactory;
46+
47+
private FeedbackStateHelperInterface $_feedbackStateHelper;
6148

6249
public function __construct(
6350
EngineBlock_Corto_ProxyServer $server,
6451
EngineBlock_Corto_XmlToArray $xmlConverter,
6552
Session $session,
6653
ProcessingStateHelperInterface $processingStateHelper,
6754
StepupGatewayCallOutHelper $stepupGatewayCallOutHelper,
68-
ServiceProviderFactory $serviceProviderFactory
55+
ServiceProviderFactory $serviceProviderFactory,
56+
FeedbackStateHelperInterface $feedbackStateHelper
6957
)
7058
{
7159
$this->_server = $server;
@@ -74,6 +62,7 @@ public function __construct(
7462
$this->_processingStateHelper = $processingStateHelper;
7563
$this->_stepupGatewayCallOutHelper = $stepupGatewayCallOutHelper;
7664
$this->_serviceProviderFactory = $serviceProviderFactory;
65+
$this->_feedbackStateHelper = $feedbackStateHelper;
7766
}
7867

7968
/**
@@ -126,8 +115,7 @@ public function serve($serviceName, Request $httpRequest)
126115
if ($sp->getCoins()->isTrustedProxy()) {
127116
$proxySp = $sp;
128117
$sp = $this->_server->findOriginalServiceProvider($receivedRequest, $log);
129-
$_SESSION['originalServiceProvider'] = $sp->entityId;
130-
$_SESSION['proxyServiceProvider'] = $proxySp->entityId;
118+
$this->_feedbackStateHelper->setProxyContext($sp->entityId, $proxySp->entityId);
131119
}
132120

133121
// Verify the SP requester chain.

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* limitations under the License.
1717
*/
1818

19+
use OpenConext\EngineBlock\Service\FeedbackStateHelperInterface;
1920
use OpenConext\EngineBlock\Service\ProcessingStateHelperInterface;
2021
use Symfony\Component\HttpFoundation\Request;
2122

@@ -31,12 +32,19 @@ class EngineBlock_Corto_Module_Service_ProcessedAssertionConsumer implements Eng
3132
*/
3233
private $_processingStateHelper;
3334

35+
/**
36+
* @var FeedbackStateHelperInterface
37+
*/
38+
private $_feedbackStateHelper;
39+
3440
public function __construct(
3541
EngineBlock_Corto_ProxyServer $server,
36-
ProcessingStateHelperInterface $processingStateHelper
42+
ProcessingStateHelperInterface $processingStateHelper,
43+
FeedbackStateHelperInterface $feedbackStateHelper
3744
) {
3845
$this->_server = $server;
3946
$this->_processingStateHelper = $processingStateHelper;
47+
$this->_feedbackStateHelper = $feedbackStateHelper;
4048
}
4149

4250
/**
@@ -84,6 +92,8 @@ public function serve($serviceName, Request $httpRequest)
8492

8593
$sentResponse = $this->_server->createEnhancedResponse($receivedRequest, $response);
8694
$this->_server->sendResponseToRequestIssuer($receivedRequest, $sentResponse);
95+
96+
$this->_feedbackStateHelper->clearFlowContext();
8797
return;
8898
}
8999
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,7 @@ public function serve($serviceName, Request $httpRequest)
185185
// When a trusted proxy is used, the originalServiceProvider is set to the entityId of the original issuing
186186
// SP. This prevents the display of the Proxy in the feedback information.
187187
if (isset($proxySp) && $proxySp->getCoins()->isTrustedProxy()) {
188-
$_SESSION['originalServiceProvider'] = $sp->entityId;
189-
$_SESSION['proxyServiceProvider'] = $proxySp->entityId;
188+
EngineBlock_ApplicationSingleton::getInstance()->getDiContainerRuntime()->feedbackStateHelper->setProxyContext($sp->entityId, $proxySp->entityId);
190189
}
191190
throw new EngineBlock_Corto_Module_Service_SingleSignOn_NoIdpsException('No candidate IdPs found');
192191
}

library/EngineBlock/Corto/Module/Services.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,14 @@ private function factoryService($className, EngineBlock_Corto_ProxyServer $serve
110110
$diContainer->getSession(),
111111
$diContainer->getProcessingStateHelper(),
112112
$diContainer->getStepupGatewayCallOutHelper(),
113-
$diContainer->getServiceProviderFactory()
113+
$diContainer->getServiceProviderFactory(),
114+
$diContainerRuntime->feedbackStateHelper
114115
);
115116
case EngineBlock_Corto_Module_Service_ProcessedAssertionConsumer::class :
116117
return new EngineBlock_Corto_Module_Service_ProcessedAssertionConsumer(
117118
$server,
118-
$diContainer->getProcessingStateHelper()
119+
$diContainer->getProcessingStateHelper(),
120+
$diContainerRuntime->feedbackStateHelper
119121
);
120122
case EngineBlock_Corto_Module_Service_StepupAssertionConsumer::class :
121123
return new EngineBlock_Corto_Module_Service_StepupAssertionConsumer(

0 commit comments

Comments
 (0)