diff --git a/.php_cs b/.php_cs index 4d91b54..cfb3c9d 100644 --- a/.php_cs +++ b/.php_cs @@ -16,7 +16,7 @@ EOT; Symfony\CS\Fixer\Contrib\HeaderCommentFixer::setHeader($header); return Symfony\CS\Config\Config::create() - ->setUsingCache(true) + ->setUsingCache(false) ->level(Symfony\CS\FixerInterface::SYMFONY_LEVEL) ->fixers(array('-empty_return', '-phpdoc_no_empty_return', 'header_comment')) ->finder($finder) diff --git a/src/LightSaml/Logout/Action/Profile/Inbound/LogoutResponse/RemoveSsoSessionFromStoreAction.php b/src/LightSaml/Logout/Action/Profile/Inbound/LogoutResponse/RemoveSsoSessionFromStoreAction.php new file mode 100644 index 0000000..ab0cf73 --- /dev/null +++ b/src/LightSaml/Logout/Action/Profile/Inbound/LogoutResponse/RemoveSsoSessionFromStoreAction.php @@ -0,0 +1,87 @@ + + * + * This source file is subject to the GPL-3 license that is bundled + * with this source code in the file LICENSE. + */ + +namespace LightSaml\Logout\Action\Profile\Inbound\LogoutResponse; + +use LightSaml\Action\Profile\AbstractProfileAction; +use LightSaml\Context\Profile\Helper\LogHelper; +use LightSaml\Context\Profile\Helper\MessageContextHelper; +use LightSaml\Context\Profile\ProfileContext; +use LightSaml\Error\LightSamlContextException; +use LightSaml\Logout\Resolver\Logout\LogoutSessionResolverInterface; +use LightSaml\State\Request\RequestStateParameters; +use LightSaml\Store\Request\RequestStateStoreInterface; +use Psr\Log\LoggerInterface; + +class RemoveSsoSessionFromStoreAction extends AbstractProfileAction +{ + /** @var RequestStateStoreInterface */ + private $requestStore; + + /** @var LogoutSessionResolverInterface */ + private $logoutResolver; + + /** + * @param LoggerInterface $logger + * @param RequestStateStoreInterface $requestStore + * @param LogoutSessionResolverInterface $logoutResolver + */ + public function __construct(LoggerInterface $logger, RequestStateStoreInterface $requestStore, LogoutSessionResolverInterface $logoutResolver) + { + parent::__construct($logger); + + $this->requestStore = $requestStore; + $this->logoutResolver = $logoutResolver; + } + + protected function doExecute(ProfileContext $context) + { + $logoutResponse = MessageContextHelper::asLogoutResponse($context->getInboundContext()); + $id = $logoutResponse->getInResponseTo(); + $requestState = $this->requestStore->get($id); + $partyEntityId = $requestState->getParameters()->get(RequestStateParameters::PARTY); + if ($partyEntityId && $logoutResponse->getIssuer() && $partyEntityId != $logoutResponse->getIssuer()->getValue()) { + $message = sprintf( + 'LogoutRequest sent to %s but LogoutResponse for that request was issued by %s', + $partyEntityId, + $logoutResponse->getIssuer()->getValue() + ); + $this->logger->critical($message, LogHelper::getActionErrorContext($context, $this, [ + 'sent_to' => $partyEntityId, + 'received_from' => $logoutResponse->getIssuer()->getValue(), + ])); + throw new LightSamlContextException($context, $message); + } + + $nameId = $requestState->getParameters()->get(RequestStateParameters::NAME_ID); + $nameIdFormat = $requestState->getParameters()->get(RequestStateParameters::NAME_ID_FORMAT); + $sessionIndex = $requestState->getParameters()->get(RequestStateParameters::SESSION_INDEX); + + $numberOfTerminatedSessions = $this->logoutResolver->terminateSession( + $logoutResponse->getIssuer()->getValue(), + $nameId, + $nameIdFormat, + $sessionIndex + ); + + $this->logger->debug( + sprintf( + 'Processing LogoutResponse from %s for %s in format %s and session index %s resulted in termination of %s sso session from the store', + $partyEntityId, + $nameId, + $nameIdFormat, + $sessionIndex, + $numberOfTerminatedSessions + ), + LogHelper::getActionContext($context, $this) + ); + } +} diff --git a/src/LightSaml/Logout/Action/Profile/Outbound/LogoutRequest/LogoutResolveAction.php b/src/LightSaml/Logout/Action/Profile/Outbound/LogoutRequest/LogoutResolveAction.php index 048bda1..801ed0a 100644 --- a/src/LightSaml/Logout/Action/Profile/Outbound/LogoutRequest/LogoutResolveAction.php +++ b/src/LightSaml/Logout/Action/Profile/Outbound/LogoutRequest/LogoutResolveAction.php @@ -13,16 +13,17 @@ use LightSaml\Action\ActionInterface; use LightSaml\Action\Profile\AbstractProfileAction; +use LightSaml\Context\Profile\Helper\LogHelper; use LightSaml\Context\Profile\ProfileContext; use LightSaml\Logout\Resolver\Logout\LogoutSessionResolverInterface; use Psr\Log\LoggerInterface; class LogoutResolveAction extends AbstractProfileAction { - /** @var LogoutSessionResolverInterface */ + /** @var LogoutSessionResolverInterface */ protected $logoutSessionResolver; - /** @var ActionInterface */ + /** @var ActionInterface */ protected $logoutProceedAction; /** @@ -46,12 +47,37 @@ public function __construct( */ protected function doExecute(ProfileContext $context) { - $ssoSessionState = $this->logoutSessionResolver->resolve($context->getOwnEntityDescriptor()->getEntityID()); $logoutContext = $context->getLogoutContext(); + $ssoSessionState = $logoutContext->getSsoSessionState(); if ($ssoSessionState) { + $this->logger->debug( + 'SSO session already set', + LogHelper::getActionContext($context, $this, array( + 'sso_session' => $ssoSessionState, + )) + ); + } else { + $this->logger->debug( + 'SSO session not defined, about to resolve it', + LogHelper::getActionContext($context, $this, array()) + ); + $ssoSessionState = $this->logoutSessionResolver->resolve($context->getOwnEntityDescriptor()->getEntityID()); + } + + if ($ssoSessionState) { + $this->logger->debug( + 'SSO session resolved and being used for logout profile', + LogHelper::getActionContext($context, $this, array( + 'sso_session' => $ssoSessionState, + )) + ); $logoutContext->setSsoSessionState($ssoSessionState); $this->logoutProceedAction->execute($context); } else { + $this->logger->debug( + 'There is no SSO session for logout', + LogHelper::getActionContext($context, $this, array()) + ); $logoutContext->setAllSsoSessionsTerminated(true); } } diff --git a/src/LightSaml/Logout/Action/Profile/Outbound/LogoutRequest/ResolveLogoutPartyAction.php b/src/LightSaml/Logout/Action/Profile/Outbound/LogoutRequest/ResolveLogoutPartyAction.php index cc4672e..92b1dc1 100644 --- a/src/LightSaml/Logout/Action/Profile/Outbound/LogoutRequest/ResolveLogoutPartyAction.php +++ b/src/LightSaml/Logout/Action/Profile/Outbound/LogoutRequest/ResolveLogoutPartyAction.php @@ -26,7 +26,7 @@ class ResolveLogoutPartyAction extends AbstractProfileAction /** @var EntityDescriptorStoreInterface */ private $spEntityDescriptorStore; - /** @var TrustOptionsStoreInterface */ + /** @var TrustOptionsStoreInterface */ protected $trustOptionsProvider; /** diff --git a/src/LightSaml/Logout/Action/Profile/Outbound/LogoutRequest/SetNotOnOrAfterAction.php b/src/LightSaml/Logout/Action/Profile/Outbound/LogoutRequest/SetNotOnOrAfterAction.php index 68e23d6..99e5b5a 100644 --- a/src/LightSaml/Logout/Action/Profile/Outbound/LogoutRequest/SetNotOnOrAfterAction.php +++ b/src/LightSaml/Logout/Action/Profile/Outbound/LogoutRequest/SetNotOnOrAfterAction.php @@ -22,10 +22,10 @@ */ class SetNotOnOrAfterAction extends AbstractProfileAction { - /** @var TimeProviderInterface */ + /** @var TimeProviderInterface */ protected $timeProvider; - /** @var int */ + /** @var int */ protected $secondsSkew; /** diff --git a/src/LightSaml/Logout/Builder/Action/Profile/SingleLogout/SloRequestActionBuilder.php b/src/LightSaml/Logout/Builder/Action/Profile/SingleLogout/SloRequestActionBuilder.php index c51ee83..5e7fcf9 100644 --- a/src/LightSaml/Logout/Builder/Action/Profile/SingleLogout/SloRequestActionBuilder.php +++ b/src/LightSaml/Logout/Builder/Action/Profile/SingleLogout/SloRequestActionBuilder.php @@ -14,6 +14,7 @@ use LightSaml\Action\Profile\Outbound\Message\CreateMessageIssuerAction; use LightSaml\Action\Profile\Outbound\Message\DestinationAction; use LightSaml\Action\Profile\Outbound\Message\ResolveEndpointSloAction; +use LightSaml\Action\Profile\Outbound\Message\SaveRequestStateAction; use LightSaml\Logout\Action\Profile\Outbound\LogoutRequest\CreateLogoutRequestAction; use LightSaml\Logout\Action\Profile\Outbound\LogoutRequest\LogoutResolveAction; use LightSaml\Logout\Action\Profile\Outbound\LogoutRequest\ResolveLogoutPartyAction; @@ -31,8 +32,6 @@ class SloRequestActionBuilder extends AbstractProfileActionBuilder { - /** - */ protected function doInitialize() { $proceedActionBuilder = new CompositeActionBuilder(); @@ -77,6 +76,11 @@ protected function doInitialize() $this->buildContainer->getSystemContainer()->getTimeProvider(), 120 )); + $proceedActionBuilder->add(new SaveRequestStateAction( + $this->buildContainer->getSystemContainer()->getLogger(), + $this->buildContainer->getStoreContainer()->getRequestStateStore() + )); + $proceedActionBuilder->add(new SignMessageAction( $this->buildContainer->getSystemContainer()->getLogger(), $this->buildContainer->getServiceContainer()->getSignatureResolver() diff --git a/src/LightSaml/Logout/Builder/Action/Profile/SingleLogout/SloResponseActionBuilder.php b/src/LightSaml/Logout/Builder/Action/Profile/SingleLogout/SloResponseActionBuilder.php new file mode 100644 index 0000000..57bef20 --- /dev/null +++ b/src/LightSaml/Logout/Builder/Action/Profile/SingleLogout/SloResponseActionBuilder.php @@ -0,0 +1,71 @@ + + * + * This source file is subject to the GPL-3 license that is bundled + * with this source code in the file LICENSE. + */ + +namespace LightSaml\Logout\Builder\Action\Profile\SingleLogout; + +use LightSaml\Action\Profile\FlushRequestStatesAction; +use LightSaml\Action\Profile\Inbound\Message\EntityIdFromMessageIssuerAction; +use LightSaml\Action\Profile\Inbound\Message\IssuerValidatorAction; +use LightSaml\Action\Profile\Inbound\Message\MessageSignatureValidatorAction; +use LightSaml\Action\Profile\Inbound\Message\ReceiveMessageAction; +use LightSaml\Action\Profile\Inbound\Message\ResolvePartyEntityIdAction; +use LightSaml\Action\Profile\Inbound\StatusResponse\InResponseToValidatorAction; +use LightSaml\Action\Profile\Inbound\StatusResponse\StatusAction; +use LightSaml\Builder\Action\Profile\AbstractProfileActionBuilder; +use LightSaml\Logout\Action\Profile\Inbound\LogoutResponse\RemoveSsoSessionFromStoreAction; +use LightSaml\SamlConstants; + +class SloResponseActionBuilder extends AbstractProfileActionBuilder +{ + protected function doInitialize() + { + $this->add(new ReceiveMessageAction( + $this->buildContainer->getSystemContainer()->getLogger(), + $this->buildContainer->getServiceContainer()->getBindingFactory() + ), 100); + + // Response validation + $this->add(new IssuerValidatorAction( + $this->buildContainer->getSystemContainer()->getLogger(), + $this->buildContainer->getServiceContainer()->getNameIdValidator(), + SamlConstants::NAME_ID_FORMAT_ENTITY + ), 200); + $this->add(new EntityIdFromMessageIssuerAction( + $this->buildContainer->getSystemContainer()->getLogger() + )); + $this->add(new ResolvePartyEntityIdAction( + $this->buildContainer->getSystemContainer()->getLogger(), + $this->buildContainer->getPartyContainer()->getSpEntityDescriptorStore(), + $this->buildContainer->getPartyContainer()->getIdpEntityDescriptorStore(), + $this->buildContainer->getPartyContainer()->getTrustOptionsStore() + )); + $this->add(new InResponseToValidatorAction( + $this->buildContainer->getSystemContainer()->getLogger(), + $this->buildContainer->getStoreContainer()->getRequestStateStore() + )); + $this->add(new StatusAction( + $this->buildContainer->getSystemContainer()->getLogger() + )); + $this->add(new MessageSignatureValidatorAction( + $this->buildContainer->getSystemContainer()->getLogger(), + $this->buildContainer->getServiceContainer()->getSignatureValidator() + )); + $this->add(new RemoveSsoSessionFromStoreAction( + $this->buildContainer->getSystemContainer()->getLogger(), + $this->buildContainer->getStoreContainer()->getRequestStateStore(), + $this->buildContainer->getServiceContainer()->getLogoutSessionResolver() + )); + $this->add(new FlushRequestStatesAction( + $this->buildContainer->getSystemContainer()->getLogger(), + $this->buildContainer->getStoreContainer()->getRequestStateStore() + )); + } +} diff --git a/src/LightSaml/Logout/Builder/Profile/WebBrowserSlo/SloResponseProfileBuilder.php b/src/LightSaml/Logout/Builder/Profile/WebBrowserSlo/SloResponseProfileBuilder.php new file mode 100644 index 0000000..64abe39 --- /dev/null +++ b/src/LightSaml/Logout/Builder/Profile/WebBrowserSlo/SloResponseProfileBuilder.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the GPL-3 license that is bundled + * with this source code in the file LICENSE. + */ + +namespace LightSaml\Logout\Builder\Profile\WebBrowserSlo; + +use LightSaml\Builder\Profile\AbstractProfileBuilder; +use LightSaml\Context\Profile\ProfileContext; +use LightSaml\Logout\Builder\Action\Profile\SingleLogout\SloResponseActionBuilder; +use LightSaml\Logout\Profile\Profiles; + +class SloResponseProfileBuilder extends AbstractProfileBuilder +{ + /** + * @return string + */ + protected function getProfileId() + { + return Profiles::SLO_RECEIVE_LOGOUT_RESPONSE; + } + + /** + * @return string + */ + protected function getProfileRole() + { + return ProfileContext::ROLE_NONE; + } + + /** + * @return \LightSaml\Builder\Action\ActionBuilderInterface + */ + protected function getActionBuilder() + { + return new SloResponseActionBuilder($this->container); + } +} diff --git a/src/LightSaml/Logout/Profile/Profiles.php b/src/LightSaml/Logout/Profile/Profiles.php index 5583b9e..11fb7c8 100644 --- a/src/LightSaml/Logout/Profile/Profiles.php +++ b/src/LightSaml/Logout/Profile/Profiles.php @@ -14,6 +14,7 @@ class Profiles { const SLO_SEND_LOGOUT_REQUEST = 'slo_send_logout_request'; + const SLO_RECEIVE_LOGOUT_RESPONSE = 'slo_receive_logout_response'; private function __construct() { diff --git a/src/LightSaml/Logout/Resolver/Logout/LogoutSessionResolver.php b/src/LightSaml/Logout/Resolver/Logout/LogoutSessionResolver.php index 05a50b7..8cc7843 100644 --- a/src/LightSaml/Logout/Resolver/Logout/LogoutSessionResolver.php +++ b/src/LightSaml/Logout/Resolver/Logout/LogoutSessionResolver.php @@ -17,7 +17,7 @@ class LogoutSessionResolver implements LogoutSessionResolverInterface { - /** @var SsoStateStoreInterface */ + /** @var SsoStateStoreInterface */ protected $ssoStateStore; /** @@ -47,6 +47,30 @@ public function resolve($ownEntityId) return $result; } + public function terminateSession($entityId, $nameId, $nameIdFormat, $sessionIndex = null) + { + $ssoState = $this->ssoStateStore->get(); + + $count = 0; + + $ssoState->modify(function (SsoSessionState $session) use ($entityId, $nameId, $nameIdFormat, &$count) { + if (($session->getIdpEntityId() == $entityId || $session->getSpEntityId() == $entityId) && + $session->getNameId() == $nameId && + $session->getNameIdFormat() == $nameIdFormat + ) { + ++$count; + + return false; + } + + return true; + }); + + $this->ssoStateStore->set($ssoState); + + return $count; + } + /** * @param SsoState $ssoState * @param string $ownEntityId diff --git a/src/LightSaml/Logout/Resolver/Logout/LogoutSessionResolverInterface.php b/src/LightSaml/Logout/Resolver/Logout/LogoutSessionResolverInterface.php index 54ad3c2..b2175af 100644 --- a/src/LightSaml/Logout/Resolver/Logout/LogoutSessionResolverInterface.php +++ b/src/LightSaml/Logout/Resolver/Logout/LogoutSessionResolverInterface.php @@ -21,4 +21,14 @@ interface LogoutSessionResolverInterface * @return SsoSessionState|null */ public function resolve($ownEntityId); + + /** + * @param string $entityId + * @param string $nameId + * @param string $nameIdFormat + * @param string $sessionIndex + * + * @return int Number of sso sessions terminated for given arguments + */ + public function terminateSession($entityId, $nameId, $nameIdFormat, $sessionIndex = null); }