Viel neues

This commit is contained in:
Sven Steinert
2026-04-30 12:06:00 +02:00
parent 118809bfae
commit fce31ebcd7
1274 changed files with 181255 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
{
"name": "litesaml/lightsaml",
"license": "MIT",
"type": "library",
"description": "SAML 2.0 PHP library",
"keywords": ["SAML 2.0", "PHP", "library", "lightSAML", "Single SignOn", "Single Logout"],
"authors": [
{
"name": "William",
"email": "work@suppo.fr"
},
{
"name": "Milos Tomic",
"email": "tmilos@gmail.com",
"homepage": "https://github.com/tmilos/",
"role": "Developer"
}
],
"support": {
"issues": "https://github.com/litesaml/lightsaml/issues",
"source": "https://github.com/litesaml/lightsaml",
"docs": "https://docs.litesaml.com"
},
"autoload": {
"psr-4": {
"LightSaml\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"LightSaml\\Tests\\": "tests/"
}
},
"require": {
"php": ">=7.4",
"robrichards/xmlseclibs": "~2.0|~3.0|~4.0",
"symfony/http-foundation": "~5.0|~6.0|~7.0",
"psr/event-dispatcher": "^1.0"
},
"require-dev": {
"symfony/dom-crawler": "~5.0|~6.0|~7.0",
"symfony/css-selector": "~5.0|~6.0|~7.0",
"pimple/pimple": "~3.0",
"phpunit/phpunit": "~8.4|~9.5",
"monolog/monolog": "^2.0|^3.0",
"squizlabs/php_codesniffer": "^3.6",
"litesaml/schemas": "~1.0.0",
"phpstan/phpstan": "^1.8",
"marcocesarato/php-conventional-changelog": "^1.15"
},
"prefer-stable": true,
"minimum-stability": "stable",
"scripts": {
"test": "vendor/bin/phpunit",
"phpcs": "vendor/bin/phpcs --standard=PSR12 --exclude=Generic.Files.LineLength ./src",
"phpstan": "vendor/bin/phpstan analyse --memory-limit 512M --ansi",
"tag": "vendor/bin/conventional-changelog --commit"
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace LightSaml\Action;
use LightSaml\Context\ContextInterface;
interface ActionInterface
{
public function execute(ContextInterface $context);
}

View File

@@ -0,0 +1,26 @@
<?php
namespace LightSaml\Action;
use Psr\Log\LoggerInterface;
class ActionLogWrapper implements ActionWrapperInterface
{
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* @return ActionInterface
*/
public function wrap(ActionInterface $action)
{
return new LoggableAction($action, $this->logger);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace LightSaml\Action;
interface ActionWrapperInterface
{
/**
* @return ActionInterface
*/
public function wrap(ActionInterface $action);
}

View File

@@ -0,0 +1,31 @@
<?php
namespace LightSaml\Action\Assertion;
use LightSaml\Action\ActionInterface;
use LightSaml\Context\ContextInterface;
use LightSaml\Context\Profile\AssertionContext;
use LightSaml\Error\LightSamlContextException;
use Psr\Log\LoggerInterface;
abstract class AbstractAssertionAction implements ActionInterface
{
/** @var LoggerInterface */
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function execute(ContextInterface $context)
{
if ($context instanceof AssertionContext) {
$this->doExecute($context);
} else {
throw new LightSamlContextException($context, 'Expected AssertionContext');
}
}
abstract protected function doExecute(AssertionContext $context);
}

View File

@@ -0,0 +1,51 @@
<?php
namespace LightSaml\Action\Assertion\Inbound;
use LightSaml\Action\Assertion\AbstractAssertionAction;
use LightSaml\Context\Profile\AssertionContext;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Error\LightSamlContextException;
use LightSaml\SamlConstants;
use Psr\Log\LoggerInterface;
class AssertionIssuerFormatValidatorAction extends AbstractAssertionAction
{
/** @var string */
private $expectedIssuerFormat = SamlConstants::NAME_ID_FORMAT_ENTITY;
/**
* @param string $expectedIssuerFormat
*/
public function __construct(LoggerInterface $logger, $expectedIssuerFormat)
{
parent::__construct($logger);
$this->expectedIssuerFormat = $expectedIssuerFormat;
}
protected function doExecute(AssertionContext $context)
{
if (null == $context->getAssertion()->getIssuer()) {
$message = 'Assertion element must have an issuer element';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
if (
$context->getAssertion()->getIssuer()->getFormat() &&
$context->getAssertion()->getIssuer()->getFormat() != $this->expectedIssuerFormat
) {
$message = sprintf(
"Response Issuer Format if set must have value '%s' but it was '%s'",
$this->expectedIssuerFormat,
$context->getAssertion()->getIssuer()->getFormat()
);
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this, [
'actualFormat' => $context->getAssertion()->getIssuer()->getFormat(),
'expectedFormat' => $this->expectedIssuerFormat,
]));
throw new LightSamlContextException($context, $message);
}
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace LightSaml\Action\Assertion\Inbound;
use LightSaml\Action\Assertion\AbstractAssertionAction;
use LightSaml\Context\Profile\AssertionContext;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Credential\Criteria\MetadataCriteria;
use LightSaml\Error\LightSamlContextException;
use LightSaml\Error\LightSamlModelException;
use LightSaml\Model\XmlDSig\AbstractSignatureReader;
use LightSaml\Validator\Model\Signature\SignatureValidatorInterface;
use Psr\Log\LoggerInterface;
class AssertionSignatureValidatorAction extends AbstractAssertionAction
{
/** @var SignatureValidatorInterface */
protected $signatureValidator;
/** @var bool */
protected $requireSignature;
/**
* @param bool $requireSignature
*/
public function __construct(LoggerInterface $logger, SignatureValidatorInterface $signatureValidator, $requireSignature = true)
{
parent::__construct($logger);
$this->signatureValidator = $signatureValidator;
$this->requireSignature = $requireSignature;
}
/**
* @return void
*/
protected function doExecute(AssertionContext $context)
{
$signature = $context->getAssertion()->getSignature();
if (null === $signature) {
if ($this->requireSignature) {
$message = 'Assertions must be signed';
$this->logger->critical($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
} else {
$this->logger->debug('Assertion is not signed', LogHelper::getActionContext($context, $this));
return;
}
}
if ($signature instanceof AbstractSignatureReader) {
$metadataType = ProfileContext::ROLE_IDP === $context->getProfileContext()->getOwnRole() ? MetadataCriteria::TYPE_SP : MetadataCriteria::TYPE_IDP;
$credential = $this->signatureValidator->validate($signature, $context->getAssertion()->getIssuer()->getValue(), $metadataType);
if ($credential) {
$keyNames = $credential->getKeyNames();
$this->logger->debug(
sprintf('Assertion signature validated with key "%s"', implode(', ', $keyNames)),
LogHelper::getActionContext($context, $this, [
'credential' => $credential,
])
);
} else {
$this->logger->warning(
'Assertion signature verification was not performed',
LogHelper::getActionContext($context, $this)
);
}
} else {
$message = 'Expected AbstractSignatureReader';
$this->logger->critical($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlModelException($message);
}
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace LightSaml\Action\Assertion\Inbound;
use LightSaml\Action\Assertion\AbstractAssertionAction;
use LightSaml\Context\Profile\AssertionContext;
use LightSaml\Validator\Model\Assertion\AssertionValidatorInterface;
use Psr\Log\LoggerInterface;
class AssertionValidatorAction extends AbstractAssertionAction
{
/** @var AssertionValidatorInterface */
protected $assertionValidator;
public function __construct(LoggerInterface $logger, AssertionValidatorInterface $assertionValidator)
{
parent::__construct($logger);
$this->assertionValidator = $assertionValidator;
}
protected function doExecute(AssertionContext $context)
{
$this->assertionValidator->validateAssertion($context->getAssertion());
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace LightSaml\Action\Assertion\Inbound;
use LightSaml\Action\Assertion\AbstractAssertionAction;
use LightSaml\Context\Profile\AssertionContext;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\ProfileContexts;
use LightSaml\Context\Profile\RequestStateContext;
use LightSaml\Error\LightSamlContextException;
use LightSaml\Store\Request\RequestStateStoreInterface;
use Psr\Log\LoggerInterface;
class InResponseToValidatorAction extends AbstractAssertionAction
{
/** @var RequestStateStoreInterface */
protected $requestStore;
public function __construct(LoggerInterface $logger, RequestStateStoreInterface $requestStore)
{
parent::__construct($logger);
$this->requestStore = $requestStore;
}
protected function doExecute(AssertionContext $context)
{
if (null === $context->getAssertion()->getSubject()) {
return;
}
foreach ($context->getAssertion()->getSubject()->getAllSubjectConfirmations() as $subjectConfirmation) {
if (
$subjectConfirmation->getSubjectConfirmationData() &&
$subjectConfirmation->getSubjectConfirmationData()->getInResponseTo()
) {
$requestState = $this->validateInResponseTo(
$subjectConfirmation->getSubjectConfirmationData()->getInResponseTo(),
$context
);
/** @var RequestStateContext $requestStateContext */
$requestStateContext = $context->getSubContext(ProfileContexts::REQUEST_STATE, RequestStateContext::class);
$requestStateContext->setRequestState($requestState);
}
}
}
/**
* @param string $inResponseTo
*
* @return \LightSaml\State\Request\RequestState
*/
protected function validateInResponseTo($inResponseTo, AssertionContext $context)
{
$requestState = $this->requestStore->get($inResponseTo);
if (null == $requestState) {
$message = sprintf("Unknown InResponseTo '%s'", $inResponseTo);
$this->logger->emergency($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
return $requestState;
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace LightSaml\Action\Assertion\Inbound;
use LightSaml\Action\Assertion\AbstractAssertionAction;
use LightSaml\Context\Profile\AssertionContext;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Error\LightSamlContextException;
use LightSaml\Store\EntityDescriptor\EntityDescriptorStoreInterface;
use Psr\Log\LoggerInterface;
class KnownAssertionIssuerAction extends AbstractAssertionAction
{
/** @var EntityDescriptorStoreInterface */
private $idpEntityDescriptorProvider;
public function __construct(LoggerInterface $logger, EntityDescriptorStoreInterface $idpEntityDescriptorProvider)
{
parent::__construct($logger);
$this->idpEntityDescriptorProvider = $idpEntityDescriptorProvider;
}
/**
* @return void
*/
protected function doExecute(AssertionContext $context)
{
if (null === $context->getAssertion()->getIssuer()) {
$message = 'Assertion element must have an issuer element';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
if (false == $this->idpEntityDescriptorProvider->has($context->getAssertion()->getIssuer()->getValue())) {
$message = sprintf("Unknown issuer '%s'", $context->getAssertion()->getIssuer()->getValue());
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this, [
'messageIssuer' => $context->getAssertion()->getIssuer()->getValue(),
]));
throw new LightSamlContextException($context, $message);
}
$this->logger->debug(
sprintf('Known assertion issuer: "%s"', $context->getAssertion()->getIssuer()->getValue()),
LogHelper::getActionContext($context, $this)
);
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace LightSaml\Action\Assertion\Inbound;
use LightSaml\Action\Assertion\AbstractAssertionAction;
use LightSaml\Context\Profile\AssertionContext;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Criteria\CriteriaSet;
use LightSaml\Error\LightSamlContextException;
use LightSaml\Model\Assertion\SubjectConfirmation;
use LightSaml\Model\Metadata\AssertionConsumerService;
use LightSaml\Model\Metadata\SpSsoDescriptor;
use LightSaml\Resolver\Endpoint\Criteria\DescriptorTypeCriteria;
use LightSaml\Resolver\Endpoint\Criteria\LocationCriteria;
use LightSaml\Resolver\Endpoint\Criteria\ServiceTypeCriteria;
use LightSaml\Resolver\Endpoint\EndpointResolverInterface;
use Psr\Log\LoggerInterface;
class RecipientValidatorAction extends AbstractAssertionAction
{
/** @var EndpointResolverInterface */
private $endpointResolver;
public function __construct(LoggerInterface $logger, EndpointResolverInterface $endpointResolver)
{
parent::__construct($logger);
$this->endpointResolver = $endpointResolver;
}
/**
* @return void
*/
protected function doExecute(AssertionContext $context)
{
if ($context->getAssertion()->getAllAuthnStatements() && $context->getAssertion()->hasBearerSubject()) {
$this->validateBearerAssertion($context);
}
}
protected function validateBearerAssertion(AssertionContext $context)
{
foreach ($context->getAssertion()->getSubject()->getBearerConfirmations() as $subjectConfirmation) {
$this->validateSubjectConfirmation($context, $subjectConfirmation);
}
}
protected function validateSubjectConfirmation(AssertionContext $context, SubjectConfirmation $subjectConfirmation)
{
$recipient = $subjectConfirmation->getSubjectConfirmationData()->getRecipient();
if (null == $recipient) {
$message = 'Bearer SubjectConfirmation must contain Recipient attribute';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
$criteriaSet = new CriteriaSet([
new DescriptorTypeCriteria(SpSsoDescriptor::class),
new ServiceTypeCriteria(AssertionConsumerService::class),
new LocationCriteria($recipient),
]);
$ownEntityDescriptor = $context->getProfileContext()->getOwnEntityDescriptor();
$arrEndpoints = $this->endpointResolver->resolve($criteriaSet, $ownEntityDescriptor->getAllEndpoints());
if (empty($arrEndpoints)) {
$message = sprintf("Recipient '%s' does not match SP descriptor", $recipient);
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this, [
'recipient' => $recipient,
]));
throw new LightSamlContextException($context, $message);
}
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace LightSaml\Action\Assertion\Inbound;
use LightSaml\Action\Assertion\AbstractAssertionAction;
use LightSaml\Context\Profile\AssertionContext;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Error\LightSamlContextException;
use LightSaml\Store\Id\IdStoreInterface;
use Psr\Log\LoggerInterface;
/**
* 4.1.4.5 POST-Specific Processing Rules
* The service provider MUST ensure that bearer assertions are not replayed, by maintaining the set of used
* ID values for the length of time for which the assertion would be considered valid based on the
* NotOnOrAfter attribute in the <SubjectConfirmationData>.
*/
class RepeatedIdValidatorAction extends AbstractAssertionAction
{
/** @var IdStoreInterface */
protected $idStore;
public function __construct(LoggerInterface $logger, IdStoreInterface $idStore)
{
parent::__construct($logger);
$this->idStore = $idStore;
}
/**
* @return void
*/
protected function doExecute(AssertionContext $context)
{
if ($context->getAssertion()->hasBearerSubject()) {
$this->validateBearerAssertion($context);
}
}
/**
* @throws \LightSaml\Error\LightSamlContextException
*/
protected function validateBearerAssertion(AssertionContext $context)
{
if (null == $context->getAssertion()->getId()) {
$message = 'Bearer Assertion must have ID attribute';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
if (null == $context->getAssertion()->getIssuer()) {
$message = 'Bearer Assertion must have Issuer element';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
if ($this->idStore->has($context->getAssertion()->getIssuer()->getValue(), $context->getAssertion()->getId())) {
$message = sprintf(
"Repeated assertion id '%s' of issuer '%s'",
$context->getAssertion()->getId(),
$context->getAssertion()->getIssuer()->getValue()
);
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this, [
'id' => $context->getAssertion()->getId(),
'issuer' => $context->getAssertion()->getIssuer()->getValue(),
]));
throw new LightSamlContextException($context, $message);
}
$this->idStore->set(
$context->getAssertion()->getIssuer()->getValue(),
$context->getAssertion()->getId(),
$this->getIdExpiryTime($context)
);
}
/**
* @throws \LogicException
* @throws \LightSaml\Error\LightSamlValidationException
*
* @return \DateTime
*/
protected function getIdExpiryTime(AssertionContext $context)
{
/** @var \DateTime $result */
$result = null;
$bearerConfirmations = $context->getAssertion()->getSubject()->getBearerConfirmations();
if (null == $bearerConfirmations) {
throw new \LogicException('Bearer assertion must have bearer subject confirmations');
}
foreach ($bearerConfirmations as $subjectConfirmation) {
if (null == $subjectConfirmation->getSubjectConfirmationData()) {
$message = 'Bearer SubjectConfirmation must have SubjectConfirmationData element';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
$dt = $subjectConfirmation->getSubjectConfirmationData()->getNotOnOrAfterDateTime();
if (null == $dt) {
$message = 'Bearer SubjectConfirmation must have NotOnOrAfter attribute';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
if (null == $result || $result->getTimestamp() < $dt->getTimestamp()) {
$result = $dt;
}
}
if (null == $result) {
$message = 'Unable to find NotOnOrAfter attribute in bearer assertion';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
return $result;
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace LightSaml\Action\Assertion\Inbound;
use LightSaml\Action\Assertion\AbstractAssertionAction;
use LightSaml\Context\Profile\AssertionContext;
use LightSaml\Provider\TimeProvider\TimeProviderInterface;
use LightSaml\Validator\Model\Assertion\AssertionTimeValidatorInterface;
use Psr\Log\LoggerInterface;
class TimeValidatorAction extends AbstractAssertionAction
{
/** @var AssertionTimeValidatorInterface */
protected $assertionTimeValidator;
/** @var TimeProviderInterface */
protected $timeProvider;
/** @var int */
protected $allowedSecondsSkew;
/**
* @param int $allowedSecondsSkew
*/
public function __construct(
LoggerInterface $logger,
AssertionTimeValidatorInterface $assertionTimeValidator,
TimeProviderInterface $timeProvider,
$allowedSecondsSkew = 120
) {
parent::__construct($logger);
$this->assertionTimeValidator = $assertionTimeValidator;
$this->timeProvider = $timeProvider;
$this->allowedSecondsSkew = $allowedSecondsSkew;
}
/**
* @return void
*/
protected function doExecute(AssertionContext $context)
{
$this->assertionTimeValidator->validateTimeRestrictions(
$context->getAssertion(),
$this->timeProvider->getTimestamp(),
$this->allowedSecondsSkew
);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace LightSaml\Action;
use LightSaml\Context\ContextInterface;
use LightSaml\Context\Profile\ExceptionContext;
use LightSaml\Context\Profile\ProfileContexts;
class CatchableErrorAction implements ActionInterface
{
/** @var ActionInterface */
protected $mainAction;
/** @var ActionInterface */
protected $errorAction;
public function __construct(ActionInterface $mainAction, ActionInterface $errorAction)
{
$this->mainAction = $mainAction;
$this->errorAction = $errorAction;
}
/**
* @return void
*/
public function execute(ContextInterface $context)
{
try {
$this->mainAction->execute($context);
} catch (\Exception $ex) {
/** @var ExceptionContext $exceptionContext */
$exceptionContext = $context->getSubContext(ProfileContexts::EXCEPTION, ExceptionContext::class);
$exceptionContext->addException($ex);
$this->errorAction->execute($context);
}
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace LightSaml\Action;
use LightSaml\Context\ContextInterface;
class CompositeAction implements ActionInterface, DebugPrintTreeActionInterface, CompositeActionInterface
{
/** @var ActionInterface[] */
protected $children = [];
/**
* @param ActionInterface[] $children
*/
public function __construct(array $children = [])
{
foreach ($children as $action) {
$this->add($action);
}
}
/**
* @return ActionInterface[]
*/
public function getChildren()
{
return $this->children;
}
/**
* @return CompositeAction
*/
public function add(ActionInterface $action)
{
$this->children[] = $action;
return $this;
}
/**
* @param callable $callable
*
* @return ActionInterface|null
*/
public function map($callable)
{
foreach ($this->children as $k => $action) {
$newAction = call_user_func($callable, $action);
if ($newAction) {
$this->children[$k] = $newAction;
}
}
}
/**
* @return void
*/
public function execute(ContextInterface $context)
{
foreach ($this->children as $action) {
$action->execute($context);
}
}
/**
* @return array
*/
public function debugPrintTree()
{
$arr = [];
foreach ($this->children as $childAction) {
if ($childAction instanceof DebugPrintTreeActionInterface) {
$arr = array_merge($arr, $childAction->debugPrintTree());
} else {
$arr = array_merge($arr, [get_class($childAction) => []]);
}
}
$result = [
static::class => $arr,
];
return $result;
}
/**
* @return string
*/
public function __toString()
{
return json_encode($this->debugPrintTree(), JSON_PRETTY_PRINT);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace LightSaml\Action;
interface CompositeActionInterface extends ActionInterface
{
/**
* @return CompositeActionInterface
*/
public function add(ActionInterface $action);
/**
* @param callable $callable
*
* @return ActionInterface|null
*/
public function map($callable);
}

View File

@@ -0,0 +1,11 @@
<?php
namespace LightSaml\Action;
interface DebugPrintTreeActionInterface
{
/**
* @return array
*/
public function debugPrintTree();
}

View File

@@ -0,0 +1,26 @@
<?php
namespace LightSaml\Action;
use LightSaml\Context\ContextInterface;
use LightSaml\Event\ActionOccurred;
use Psr\EventDispatcher\EventDispatcherInterface;
class DispatchEventAction implements ActionInterface
{
/** @var EventDispatcherInterface */
protected $eventDispatcher;
public function __construct(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
/**
* @return void
*/
public function execute(ContextInterface $context)
{
$this->eventDispatcher->dispatch(new ActionOccurred($context));
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace LightSaml\Action;
use LightSaml\Context\ContextInterface;
use Psr\Log\LoggerInterface;
class LoggableAction extends WrappedAction
{
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(ActionInterface $action, LoggerInterface $logger)
{
parent::__construct($action);
$this->logger = $logger;
}
protected function beforeAction(ContextInterface $context)
{
$this->logger->debug(sprintf('Executing action "%s"', get_class($this->action)), [
'context' => $context,
'action' => $this->action,
]);
}
protected function afterAction(ContextInterface $context)
{
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace LightSaml\Action;
use LightSaml\Context\ContextInterface;
class NullAction implements ActionInterface
{
/**
* @return void
*/
public function execute(ContextInterface $context)
{
// null
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace LightSaml\Action\Profile;
use LightSaml\Action\ActionInterface;
use LightSaml\Context\ContextInterface;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Error\LightSamlContextException;
use Psr\Log\LoggerInterface;
abstract class AbstractProfileAction implements ActionInterface
{
/** @var LoggerInterface */
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* @return void
*/
public function execute(ContextInterface $context)
{
if ($context instanceof ProfileContext) {
$this->doExecute($context);
} else {
$message = sprintf('Expected ProfileContext but got %s', get_class($context));
$this->logger->emergency($message, ['context' => $context]);
throw new LightSamlContextException($context, $message);
}
}
abstract protected function doExecute(ProfileContext $context);
}

View File

@@ -0,0 +1,43 @@
<?php
namespace LightSaml\Action\Profile\Entity;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Context\Profile\ProfileContexts;
use LightSaml\Model\Context\SerializationContext;
use Symfony\Component\HttpFoundation\Response;
class SerializeOwnEntityAction extends AbstractProfileAction
{
/** @var string[] */
protected $supportedContextTypes = ['application/samlmetadata+xml', 'application/xml', 'text/xml'];
protected function doExecute(ProfileContext $context)
{
$ownEntityDescriptor = $context->getOwnEntityDescriptor();
/** @var SerializationContext $serializationContext */
$serializationContext = $context->getSubContext(ProfileContexts::SERIALIZATION, SerializationContext::class);
$serializationContext->getDocument()->formatOutput = true;
$ownEntityDescriptor->serialize($serializationContext->getDocument(), $serializationContext);
$xml = $serializationContext->getDocument()->saveXML();
$response = new Response($xml);
$contentType = 'text/xml';
$acceptableContentTypes = array_flip($context->getHttpRequest()->getAcceptableContentTypes());
foreach ($this->supportedContextTypes as $supportedContentType) {
if (isset($acceptableContentTypes[$supportedContentType])) {
$contentType = $supportedContentType;
break;
}
}
$response->headers->replace(['Content-Type' => $contentType]);
$context->getHttpResponseContext()->setResponse($response);
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace LightSaml\Action\Profile;
use LightSaml\Context\ContextInterface;
use LightSaml\Context\Profile\AssertionContext;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Context\Profile\ProfileContexts;
use LightSaml\Context\Profile\RequestStateContext;
use LightSaml\Store\Request\RequestStateStoreInterface;
use Psr\Log\LoggerInterface;
class FlushRequestStatesAction extends AbstractProfileAction
{
/** @var RequestStateStoreInterface */
protected $requestStore;
public function __construct(LoggerInterface $logger, RequestStateStoreInterface $requestStore)
{
parent::__construct($logger);
$this->requestStore = $requestStore;
}
/**
* @return void
*/
protected function doExecute(ProfileContext $context)
{
$this->flush($context->getInboundContext()->getSubContext(ProfileContexts::REQUEST_STATE, null));
foreach ($context as $child) {
if ($child instanceof AssertionContext) {
$this->flush($child->getSubContext(ProfileContexts::REQUEST_STATE, null));
}
}
}
/**
* @param ContextInterface|null $requestStateContext
*/
protected function flush($requestStateContext = null)
{
if (
$requestStateContext instanceof RequestStateContext &&
$requestStateContext->getRequestState() &&
$requestStateContext->getRequestState()->getId()
) {
$existed = $this->requestStore->remove($requestStateContext->getRequestState()->getId());
if ($existed) {
$this->logger->debug(
sprintf('Removed request state "%s"', $requestStateContext->getRequestState()->getId()),
LogHelper::getActionContext($requestStateContext, $this)
);
} else {
$this->logger->warning(
sprintf('Request state "%s" does not exist', $requestStateContext->getRequestState()->getId()),
LogHelper::getActionContext($requestStateContext, $this)
);
}
}
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Criteria\CriteriaSet;
use LightSaml\Error\LightSamlContextException;
use LightSaml\Model\Metadata\IdpSsoDescriptor;
use LightSaml\Model\Metadata\SpSsoDescriptor;
use LightSaml\Resolver\Endpoint\Criteria\DescriptorTypeCriteria;
use LightSaml\Resolver\Endpoint\Criteria\LocationCriteria;
use LightSaml\Resolver\Endpoint\EndpointResolverInterface;
use Psr\Log\LoggerInterface;
abstract class AbstractDestinationValidatorAction extends AbstractProfileAction
{
/** @var EndpointResolverInterface */
protected $endpointResolver;
public function __construct(LoggerInterface $logger, EndpointResolverInterface $endpointResolver)
{
parent::__construct($logger);
$this->endpointResolver = $endpointResolver;
}
/**
* @return void
*/
protected function doExecute(ProfileContext $context)
{
$message = MessageContextHelper::asSamlMessage($context->getInboundContext());
$destination = $message->getDestination();
if (null == $destination) {
return;
}
$criteriaSet = $this->getCriteriaSet($context, $destination);
$endpoints = $this->endpointResolver->resolve($criteriaSet, $context->getOwnEntityDescriptor()->getAllEndpoints());
if ($endpoints) {
return;
}
$message = sprintf('Invalid inbound message destination "%s"', $destination);
$this->logger->emergency($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
/**
* @param string $location
*
* @return CriteriaSet
*/
protected function getCriteriaSet(ProfileContext $context, $location)
{
$criteriaSet = new CriteriaSet([
new DescriptorTypeCriteria(
ProfileContext::ROLE_IDP === $context->getOwnRole()
? IdpSsoDescriptor::class
: SpSsoDescriptor::class
),
new LocationCriteria($location),
]);
return $criteriaSet;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Error\LightSamlContextException;
use Psr\Log\LoggerInterface;
class AssertBindingTypeAction extends AbstractProfileAction
{
/** @var string[] */
protected $expectedBindingTypes;
/**
* @param string[] $expectedBindingTypes
*/
public function __construct(LoggerInterface $logger, array $expectedBindingTypes)
{
parent::__construct($logger);
$this->expectedBindingTypes = $expectedBindingTypes;
}
protected function doExecute(ProfileContext $context)
{
if (false === in_array($context->getInboundContext()->getBindingType(), $this->expectedBindingTypes)) {
$message = sprintf(
'Unexpected binding type "%s" - expected binding types are: %s',
$context->getInboundContext()->getBindingType(),
implode(' ', $this->expectedBindingTypes)
);
$this->logger->critical($message, LogHelper::getActionErrorContext($context, $this, [
'actualBindingType' => $context->getInboundContext()->getBindingType(),
'expectedBindingTypes' => $this->expectedBindingTypes,
]));
throw new LightSamlContextException($context, $message);
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Message;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Criteria\CriteriaSet;
use LightSaml\Model\Metadata\SingleSignOnService;
use LightSaml\Resolver\Endpoint\Criteria\ServiceTypeCriteria;
class DestinationValidatorAuthnRequestAction extends AbstractDestinationValidatorAction
{
/**
* @param string $location
*
* @return CriteriaSet
*/
protected function getCriteriaSet(ProfileContext $context, $location)
{
$result = parent::getCriteriaSet($context, $location);
$result->add(new ServiceTypeCriteria(SingleSignOnService::class));
return $result;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Message;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Criteria\CriteriaSet;
use LightSaml\Model\Metadata\AssertionConsumerService;
use LightSaml\Resolver\Endpoint\Criteria\ServiceTypeCriteria;
class DestinationValidatorResponseAction extends AbstractDestinationValidatorAction
{
/**
* @param string $location
*
* @return CriteriaSet
*/
protected function getCriteriaSet(ProfileContext $context, $location)
{
$result = parent::getCriteriaSet($context, $location);
$result->add(new ServiceTypeCriteria(AssertionConsumerService::class));
return $result;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Error\LightSamlContextException;
class EntityIdFromMessageIssuerAction extends AbstractProfileAction
{
protected function doExecute(ProfileContext $context)
{
$message = MessageContextHelper::asSamlMessage($context->getInboundContext());
if (null == $message->getIssuer()) {
throw new LightSamlContextException($context, 'Inbound messages does not have Issuer');
}
$context->getPartyEntityContext()->setEntityId($message->getIssuer()->getValue());
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Message;
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\Error\LightSamlValidationException;
use LightSaml\Validator\Model\NameId\NameIdValidatorInterface;
use Psr\Log\LoggerInterface;
class IssuerValidatorAction extends AbstractProfileAction
{
/** @var NameIdValidatorInterface */
protected $nameIdValidator;
/** @var string */
protected $allowedFormat;
/**
* @param string $allowedFormat
*/
public function __construct(LoggerInterface $logger, NameIdValidatorInterface $nameIdValidator, $allowedFormat)
{
parent::__construct($logger);
$this->nameIdValidator = $nameIdValidator;
$this->allowedFormat = $allowedFormat;
}
/**
* @return void
*/
protected function doExecute(ProfileContext $context)
{
$message = MessageContextHelper::asSamlMessage($context->getInboundContext());
if (false == $message->getIssuer()) {
$message = 'Inbound message must have Issuer element';
$this->logger->emergency($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
if (
$this->allowedFormat &&
$message->getIssuer()->getValue() &&
$message->getIssuer()->getFormat() &&
$message->getIssuer()->getFormat() != $this->allowedFormat
) {
$message = sprintf(
"Response Issuer Format if set must have value '%s' but it was '%s'",
$this->allowedFormat,
$message->getIssuer()->getFormat()
);
$this->logger->emergency($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
try {
$this->nameIdValidator->validateNameId($message->getIssuer());
} catch (LightSamlValidationException $ex) {
throw new LightSamlContextException($context, $ex->getMessage(), 0, $ex);
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Credential\Criteria\MetadataCriteria;
use LightSaml\Error\LightSamlModelException;
use LightSaml\Model\XmlDSig\AbstractSignatureReader;
use LightSaml\Validator\Model\Signature\SignatureValidatorInterface;
use Psr\Log\LoggerInterface;
/**
* Validates the signature, if any, of the inbound message.
*/
class MessageSignatureValidatorAction extends AbstractProfileAction
{
/** @var SignatureValidatorInterface */
protected $signatureValidator;
public function __construct(LoggerInterface $logger, SignatureValidatorInterface $signatureValidator)
{
parent::__construct($logger);
$this->signatureValidator = $signatureValidator;
}
/**
* @return void
*/
protected function doExecute(ProfileContext $context)
{
$message = MessageContextHelper::asSamlMessage($context->getInboundContext());
$signature = $message->getSignature();
if (null === $signature) {
$this->logger->debug('Message is not signed', LogHelper::getActionContext($context, $this));
return;
}
if ($signature instanceof AbstractSignatureReader) {
$metadataType = ProfileContext::ROLE_IDP === $context->getOwnRole() ? MetadataCriteria::TYPE_SP : MetadataCriteria::TYPE_IDP;
$credential = $this->signatureValidator->validate($signature, $message->getIssuer()->getValue(), $metadataType);
if ($credential) {
$keyNames = $credential->getKeyNames();
$this->logger->debug(
sprintf('Message signature validated with key "%s"', implode(', ', $keyNames)),
LogHelper::getActionContext($context, $this, [
'credential' => $credential,
])
);
} else {
$this->logger->warning(
'Signature verification was not performed',
LogHelper::getActionContext($context, $this)
);
}
} else {
$message = 'Expected AbstractSignatureReader';
$this->logger->critical($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlModelException($message);
}
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Binding\BindingFactoryInterface;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Error\LightSamlBindingException;
use Psr\Log\LoggerInterface;
/**
* Receives message from HTTP Request into inbound context,
* optionally enforces biding type to the one specified in the inbound context.
*/
class ReceiveMessageAction extends AbstractProfileAction
{
/** @var BindingFactoryInterface */
protected $bindingFactory;
public function __construct(LoggerInterface $logger, BindingFactoryInterface $bindingFactory)
{
parent::__construct($logger);
$this->bindingFactory = $bindingFactory;
}
/**
* @return void
*/
protected function doExecute(ProfileContext $context)
{
$bindingType = $this->bindingFactory->detectBindingType($context->getHttpRequest());
if (null == $bindingType) {
$message = 'Unable to resolve binding type, invalid or unsupported http request';
$this->logger->critical($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlBindingException($message);
}
$this->logger->debug(sprintf('Detected binding type: %s', $bindingType), LogHelper::getActionContext($context, $this));
$binding = $this->bindingFactory->create($bindingType);
$binding->receive($context->getHttpRequest(), $context->getInboundContext());
$context->getInboundContext()->setBindingType($bindingType);
$this->logger->info(
'Received message',
LogHelper::getActionContext($context, $this, [
'message' => $context->getInboundContext()->getDeserializationContext()->getDocument()->saveXML(),
])
);
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Error\LightSamlContextException;
use LightSaml\Meta\TrustOptions\TrustOptions;
use LightSaml\Store\EntityDescriptor\EntityDescriptorStoreInterface;
use LightSaml\Store\TrustOptions\TrustOptionsStoreInterface;
use Psr\Log\LoggerInterface;
/**
* Looks up inbound message Issuer in entity descriptor providers and sets it to the party context.
*/
class ResolvePartyEntityIdAction extends AbstractProfileAction
{
/** @var EntityDescriptorStoreInterface */
private $spEntityDescriptorProvider;
/** @var EntityDescriptorStoreInterface */
private $idpEntityDescriptorProvider;
/** @var TrustOptionsStoreInterface */
protected $trustOptionsProvider;
public function __construct(
LoggerInterface $logger,
EntityDescriptorStoreInterface $spEntityDescriptorProvider,
EntityDescriptorStoreInterface $idpEntityDescriptorProvider,
TrustOptionsStoreInterface $trustOptionsProvider
) {
parent::__construct($logger);
$this->spEntityDescriptorProvider = $spEntityDescriptorProvider;
$this->idpEntityDescriptorProvider = $idpEntityDescriptorProvider;
$this->trustOptionsProvider = $trustOptionsProvider;
}
protected function doExecute(ProfileContext $context)
{
$partyContext = $context->getPartyEntityContext();
if ($partyContext->getEntityDescriptor() && $partyContext->getTrustOptions()) {
$this->logger->debug(
sprintf('Party EntityDescriptor and TrustOptions already set for "%s"', $partyContext->getEntityDescriptor()->getEntityID()),
LogHelper::getActionContext($context, $this, [
'partyEntityId' => $partyContext->getEntityDescriptor()->getEntityID(),
])
);
return;
}
$entityId = $partyContext->getEntityDescriptor() ? $partyContext->getEntityDescriptor()->getEntityID() : null;
$entityId = $entityId ? $entityId : $partyContext->getEntityId();
if (null == $entityId) {
$message = 'EntityID is not set in the party context';
$this->logger->critical($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
if (null == $partyContext->getEntityDescriptor()) {
$partyEntityDescriptor = $this->getPartyEntityDescriptor(
$context,
ProfileContext::ROLE_IDP === $context->getOwnRole()
? $this->spEntityDescriptorProvider
: $this->idpEntityDescriptorProvider,
$context->getPartyEntityContext()->getEntityId()
);
$partyContext->setEntityDescriptor($partyEntityDescriptor);
$this->logger->debug(
sprintf('Known issuer resolved: "%s"', $partyEntityDescriptor->getEntityID()),
LogHelper::getActionContext($context, $this, [
'partyEntityId' => $partyEntityDescriptor->getEntityID(),
])
);
}
if (null == $partyContext->getTrustOptions()) {
$trustOptions = $this->trustOptionsProvider->get($partyContext->getEntityDescriptor()->getEntityID());
if (null === $trustOptions) {
$trustOptions = new TrustOptions();
}
$partyContext->setTrustOptions($trustOptions);
}
}
/**
* @param string $entityId
*
* @return \LightSaml\Model\Metadata\EntityDescriptor
*/
protected function getPartyEntityDescriptor(
ProfileContext $context,
EntityDescriptorStoreInterface $entityDescriptorProvider,
$entityId
) {
$partyEntityDescriptor = $entityDescriptorProvider->get($entityId);
if (null === $partyEntityDescriptor) {
$message = sprintf("Unknown issuer '%s'", $entityId);
$this->logger->emergency($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
return $partyEntityDescriptor;
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Response;
use LightSaml\Action\ActionInterface;
use LightSaml\Action\DebugPrintTreeActionInterface;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\AssertionContext;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use Psr\Log\LoggerInterface;
class AssertionAction extends AbstractProfileAction implements DebugPrintTreeActionInterface
{
/** @var ActionInterface */
private $assertionAction;
public function __construct(LoggerInterface $logger, ActionInterface $assertionAction)
{
parent::__construct($logger);
$this->assertionAction = $assertionAction;
}
protected function doExecute(ProfileContext $context)
{
$response = MessageContextHelper::asResponse($context->getInboundContext());
foreach ($response->getAllAssertions() as $index => $assertion) {
$name = sprintf('assertion_%s', $index);
/** @var AssertionContext $assertionContext */
$assertionContext = $context->getSubContext($name, AssertionContext::class);
$assertionContext
->setAssertion($assertion)
->setId($name)
;
$this->assertionAction->execute($assertionContext);
}
}
/**
* @param int $depth
*
* @return array
*/
public function debugPrintTree($depth = 0)
{
$arr = [];
if ($this->assertionAction instanceof DebugPrintTreeActionInterface) {
$arr = array_merge($arr, $this->assertionAction->debugPrintTree());
} else {
$arr[get_class($this->assertionAction)] = [];
}
$result = [
static::class => $arr,
];
return $result;
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Response;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Credential\CredentialInterface;
use LightSaml\Credential\Criteria\EntityIdCriteria;
use LightSaml\Credential\Criteria\MetadataCriteria;
use LightSaml\Credential\Criteria\UsageCriteria;
use LightSaml\Credential\UsageType;
use LightSaml\Error\LightSamlContextException;
use LightSaml\Model\Assertion\EncryptedAssertionReader;
use LightSaml\Model\Context\DeserializationContext;
use LightSaml\Resolver\Credential\CredentialResolverInterface;
use LightSaml\SamlConstants;
use Psr\Log\LoggerInterface;
class DecryptAssertionsAction extends AbstractProfileAction
{
/** @var CredentialResolverInterface */
protected $credentialResolver;
public function __construct(LoggerInterface $logger, CredentialResolverInterface $credentialResolver)
{
parent::__construct($logger);
$this->credentialResolver = $credentialResolver;
}
protected function doExecute(ProfileContext $context)
{
$response = MessageContextHelper::asResponse($context->getInboundContext());
if (0 === count($response->getAllEncryptedAssertions())) {
$this->logger->debug('Response has no encrypted assertions', LogHelper::getActionContext($context, $this));
return;
}
$ownEntityDescriptor = $context->getOwnEntityDescriptor();
$query = $this->credentialResolver->query();
$query
->add(new EntityIdCriteria($ownEntityDescriptor->getEntityID()))
->add(new MetadataCriteria(
ProfileContext::ROLE_IDP === $context->getOwnRole()
? MetadataCriteria::TYPE_IDP
: MetadataCriteria::TYPE_SP,
SamlConstants::PROTOCOL_SAML2
))
->add(new UsageCriteria(UsageType::ENCRYPTION))
;
$query->resolve();
$privateKeys = $query->getPrivateKeys();
if (empty($privateKeys)) {
$message = 'No credentials resolved for assertion decryption';
$this->logger->emergency($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
$this->logger->info('Trusted decryption candidates', LogHelper::getActionContext($context, $this, [
'credentials' => array_map(function (CredentialInterface $credential) {
return sprintf(
"Entity: '%s'; PK X509 Thumb: '%s'",
$credential->getEntityId(),
$credential->getPublicKey() ? $credential->getPublicKey()->getX509Thumbprint() : ''
);
}, $privateKeys),
]));
foreach ($response->getAllEncryptedAssertions() as $index => $encryptedAssertion) {
if ($encryptedAssertion instanceof EncryptedAssertionReader) {
$name = sprintf('assertion_encrypted_%s', $index);
/** @var DeserializationContext $deserializationContext */
$deserializationContext = $context->getInboundContext()->getSubContext($name, DeserializationContext::class);
$assertion = $encryptedAssertion->decryptMultiAssertion($privateKeys, $deserializationContext);
$response->addAssertion($assertion);
$this->logger->info(
'Assertion decrypted',
LogHelper::getActionContext($context, $this, [
'assertion' => $deserializationContext->getDocument()->saveXML(),
])
);
}
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Response;
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;
class HasAssertionsValidatorAction extends AbstractProfileAction
{
protected function doExecute(ProfileContext $context)
{
$response = MessageContextHelper::asResponse($context->getInboundContext());
if ($response->getAllAssertions()) {
return;
}
$message = 'Response must contain at least one assertion';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Response;
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;
class HasAuthnStatementValidatorAction extends AbstractProfileAction
{
protected function doExecute(ProfileContext $context)
{
$response = MessageContextHelper::asResponse($context->getInboundContext());
foreach ($response->getAllAssertions() as $assertion) {
if ($assertion->getAllAuthnStatements()) {
return;
}
}
$message = 'Response must have at least one Assertion containing AuthnStatement element';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Response;
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;
class HasBearerAssertionsValidatorAction extends AbstractProfileAction
{
protected function doExecute(ProfileContext $context)
{
$response = MessageContextHelper::asResponse($context->getInboundContext());
if ($response->getBearerAssertions()) {
return;
}
$message = 'Response must contain at least one bearer assertion';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace LightSaml\Action\Profile\Inbound\Response;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Resolver\Session\SessionProcessorInterface;
use Psr\Log\LoggerInterface;
class SpSsoStateAction extends AbstractProfileAction
{
/** @var SessionProcessorInterface */
private $sessionProcessor;
public function __construct(LoggerInterface $logger, SessionProcessorInterface $sessionProcessor)
{
parent::__construct($logger);
$this->sessionProcessor = $sessionProcessor;
}
protected function doExecute(ProfileContext $context)
{
$response = MessageContextHelper::asResponse($context->getInboundContext());
$this->sessionProcessor->processAssertions(
$response->getAllAssertions(),
$context->getOwnEntityDescriptor()->getEntityID(),
$context->getPartyEntityDescriptor()->getEntityID()
);
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace LightSaml\Action\Profile\Inbound\StatusResponse;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Context\Profile\ProfileContexts;
use LightSaml\Context\Profile\RequestStateContext;
use LightSaml\Error\LightSamlContextException;
use LightSaml\State\Request\RequestStateParameters;
use LightSaml\Store\Request\RequestStateStoreInterface;
use Psr\Log\LoggerInterface;
class InResponseToValidatorAction extends AbstractProfileAction
{
/** @var RequestStateStoreInterface */
protected $requestStore;
public function __construct(LoggerInterface $logger, RequestStateStoreInterface $requestStore)
{
parent::__construct($logger);
$this->requestStore = $requestStore;
}
protected function doExecute(ProfileContext $context)
{
$response = MessageContextHelper::asStatusResponse($context->getInboundContext());
$inResponseTo = $response->getInResponseTo();
if ($inResponseTo) {
$requestState = $this->requestStore->get($inResponseTo);
if (null == $requestState) {
$message = sprintf("Unknown InResponseTo '%s'", $inResponseTo);
$this->logger->critical($message, LogHelper::getActionErrorContext($context, $this, [
'in_response_to' => $inResponseTo,
]));
throw new LightSamlContextException($context, $message);
}
$sentToParty = $requestState->getParameters()->get(RequestStateParameters::PARTY);
if ($sentToParty && $response->getIssuer() && $response->getIssuer()->getValue() != $sentToParty) {
$message = sprintf('AuthnRequest with id "%s" sent to party "%s" but StatusResponse for that request issued by party "%s"', $inResponseTo, $sentToParty, $response->getIssuer()->getValue());
$this->logger->critical($message, LogHelper::getActionErrorContext($context, $this, [
'sent_to' => $sentToParty,
'received_from' => $response->getIssuer()->getValue(),
]));
throw new LightSamlContextException($context, $message);
}
/** @var RequestStateContext $requestStateContext */
$requestStateContext = $context->getInboundContext()->getSubContext(ProfileContexts::REQUEST_STATE, RequestStateContext::class);
$requestStateContext->setRequestState($requestState);
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace LightSaml\Action\Profile\Inbound\StatusResponse;
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\LightSamlAuthenticationException;
use LightSaml\Error\LightSamlContextException;
/**
* Throws LightSamlAuthenticationException if status of inbound message is not successful.
*/
class StatusAction extends AbstractProfileAction
{
protected function doExecute(ProfileContext $context)
{
$statusResponse = MessageContextHelper::asStatusResponse($context->getInboundContext());
if ($statusResponse->getStatus() && $statusResponse->getStatus()->isSuccess()) {
return;
}
if (null == $statusResponse->getStatus()) {
$message = 'Status response does not have Status set';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
$status = $statusResponse->getStatus()->getStatusCode()->getValue();
$status .= "\n" . $statusResponse->getStatus()->getStatusMessage();
if ($statusResponse->getStatus()->getStatusCode()->getStatusCode()) {
$status .= "\n" . $statusResponse->getStatus()->getStatusCode()->getStatusCode()->getValue();
}
$message = 'Unsuccessful SAML response: ' . $status;
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this, ['status' => $status]));
throw new LightSamlAuthenticationException($statusResponse, $message);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace LightSaml\Action\Profile\Outbound\AuthnRequest;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Criteria\CriteriaSet;
use LightSaml\Error\LightSamlContextException;
use LightSaml\Model\Metadata\AssertionConsumerService;
use LightSaml\Model\Metadata\SpSsoDescriptor;
use LightSaml\Resolver\Endpoint\Criteria\BindingCriteria;
use LightSaml\Resolver\Endpoint\Criteria\DescriptorTypeCriteria;
use LightSaml\Resolver\Endpoint\Criteria\ServiceTypeCriteria;
use LightSaml\Resolver\Endpoint\EndpointResolverInterface;
use LightSaml\SamlConstants;
use Psr\Log\LoggerInterface;
// TODO ACSUrlAction not used in profile builder, has to be added
class ACSUrlAction extends AbstractProfileAction
{
/** @var EndpointResolverInterface */
private $endpointResolver;
public function __construct(LoggerInterface $logger, EndpointResolverInterface $endpointResolver)
{
parent::__construct($logger);
$this->endpointResolver = $endpointResolver;
}
protected function doExecute(ProfileContext $context)
{
$ownEntityDescriptor = $context->getOwnEntityDescriptor();
$criteriaSet = new CriteriaSet([
new DescriptorTypeCriteria(SpSsoDescriptor::class),
new ServiceTypeCriteria(AssertionConsumerService::class),
new BindingCriteria([SamlConstants::BINDING_SAML2_HTTP_POST]),
]);
$endpoints = $this->endpointResolver->resolve($criteriaSet, $ownEntityDescriptor->getAllEndpoints());
if (empty($endpoints)) {
$message = 'Missing ACS Service with HTTP POST binding in own SP SSO Descriptor';
$this->logger->error($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
MessageContextHelper::asAuthnRequest($context->getOutboundContext())
->setAssertionConsumerServiceURL($endpoints[0]->getEndpoint()->getLocation());
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace LightSaml\Action\Profile\Outbound\AuthnRequest;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Model\Protocol\AuthnRequest;
/**
* Creates empty AuthnRequest in outbound context.
*/
class CreateAuthnRequestAction extends AbstractProfileAction
{
protected function doExecute(ProfileContext $context)
{
$context->getOutboundContext()->setMessage(new AuthnRequest());
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Model\Assertion\Issuer;
use LightSaml\SamlConstants;
/**
* Sets the Issuer of the outbound message to the value of own entityID.
*/
class CreateMessageIssuerAction extends AbstractProfileAction
{
/**
* @return void
*/
protected function doExecute(ProfileContext $context)
{
$ownEntityDescriptor = $context->getOwnEntityDescriptor();
$issuer = new Issuer($ownEntityDescriptor->getEntityID());
$issuer->setFormat(SamlConstants::NAME_ID_FORMAT_ENTITY);
MessageContextHelper::asSamlMessage($context->getOutboundContext())
->setIssuer($issuer);
$this->logger->debug(
sprintf('Issuer set to "%s"', $ownEntityDescriptor->getEntityID()),
LogHelper::getActionContext($context, $this)
);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
/**
* Sets destination of the outbound message to the value of location of endpoint from the context.
*/
class DestinationAction extends AbstractProfileAction
{
/**
* @return void
*/
protected function doExecute(ProfileContext $context)
{
$endpoint = $context->getEndpoint();
MessageContextHelper::asSamlMessage($context->getOutboundContext())
->setDestination($endpoint->getLocation());
$this->logger->debug(
sprintf('Destination set to "%s"', $endpoint->getLocation()),
LogHelper::getActionContext($context, $this)
);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\ProfileContext;
class ForwardRelayStateAction extends AbstractProfileAction
{
protected function doExecute(ProfileContext $context)
{
if (null == $context->getInboundContext()->getMessage()) {
return;
}
if ($context->getInboundMessage()->getRelayState()) {
$this->logger->debug(sprintf('Forwarding relay state from inbound message: "%s"', $context->getInboundMessage()->getRelayState()));
$context->getOutboundMessage()->setRelayState(
$context->getInboundMessage()->getRelayState()
);
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Helper;
/**
* Sets the ID of the message in the outbound context.
*/
class MessageIdAction extends AbstractProfileAction
{
protected function doExecute(ProfileContext $context)
{
$id = Helper::generateID();
MessageContextHelper::asSamlMessage($context->getOutboundContext())
->setId($id);
$this->logger->info(
sprintf('Message ID set to "%s"', $id),
LogHelper::getActionContext($context, $this, ['message_id' => $id])
);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Provider\TimeProvider\TimeProviderInterface;
use Psr\Log\LoggerInterface;
/**
* Sets outbound message IssueInstant to the value provided by given time provider.
*/
class MessageIssueInstantAction extends AbstractProfileAction
{
/** @var TimeProviderInterface */
protected $timeProvider;
public function __construct(LoggerInterface $logger, TimeProviderInterface $timeProvider)
{
parent::__construct($logger);
$this->timeProvider = $timeProvider;
}
/**
* @return void
*/
protected function doExecute(ProfileContext $context)
{
MessageContextHelper::asSamlMessage($context->getOutboundContext())
->setIssueInstant($this->timeProvider->getTimestamp());
$this->logger->info(
sprintf('Message IssueInstant set to "%s"', MessageContextHelper::asSamlMessage($context->getOutboundContext())->getIssueInstantString()),
LogHelper::getActionContext($context, $this)
);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use Psr\Log\LoggerInterface;
/**
* Sets the Version of the outbound message to the given value.
*/
class MessageVersionAction extends AbstractProfileAction
{
/** @var string */
private $version;
/**
* @param string $version
*/
public function __construct(LoggerInterface $logger, $version)
{
parent::__construct($logger);
$this->version = $version;
}
/**
* @return void
*/
protected function doExecute(ProfileContext $context)
{
MessageContextHelper::asSamlMessage($context->getOutboundContext())
->setVersion($this->version);
$this->logger->debug(
sprintf('Message Version set to "%s"', $this->version),
LogHelper::getActionContext($context, $this)
);
}
}

View File

@@ -0,0 +1,149 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Criteria\CriteriaSet;
use LightSaml\Error\LightSamlContextException;
use LightSaml\Model\Metadata\EndpointReference;
use LightSaml\Model\Metadata\IdpSsoDescriptor;
use LightSaml\Model\Metadata\SpSsoDescriptor;
use LightSaml\Model\Protocol\AuthnRequest;
use LightSaml\Resolver\Endpoint\Criteria\BindingCriteria;
use LightSaml\Resolver\Endpoint\Criteria\DescriptorTypeCriteria;
use LightSaml\Resolver\Endpoint\Criteria\IndexCriteria;
use LightSaml\Resolver\Endpoint\Criteria\LocationCriteria;
use LightSaml\Resolver\Endpoint\Criteria\ServiceTypeCriteria;
use LightSaml\Resolver\Endpoint\EndpointResolverInterface;
use LightSaml\SamlConstants;
use Psr\Log\LoggerInterface;
/**
* Determines to which endpoint outbound message will be sent.
*/
abstract class ResolveEndpointBaseAction extends AbstractProfileAction
{
/** @var EndpointResolverInterface */
protected $endpointResolver;
public function __construct(LoggerInterface $logger, EndpointResolverInterface $endpointResolver)
{
parent::__construct($logger);
$this->endpointResolver = $endpointResolver;
}
protected function doExecute(ProfileContext $context)
{
if ($context->getEndpointContext()->getEndpoint()) {
$this->logger->debug(
sprintf(
'Endpoint already set with location "%s" and binding "%s"',
$context->getEndpoint()->getLocation(),
$context->getEndpoint()->getBinding()
),
LogHelper::getActionContext($context, $this, [
'endpointLocation' => $context->getEndpoint()->getLocation(),
'endpointBinding' => $context->getEndpoint()->getBinding(),
])
);
return;
}
$criteriaSet = $this->getCriteriaSet($context);
$message = $context->getInboundContext()->getMessage();
if ($message instanceof AuthnRequest) {
if (null !== $message->getAssertionConsumerServiceIndex()) {
$criteriaSet->add(new IndexCriteria($message->getAssertionConsumerServiceIndex()));
}
if (null !== $message->getAssertionConsumerServiceURL()) {
$criteriaSet->add(new LocationCriteria($message->getAssertionConsumerServiceURL()));
}
}
$candidates = $this->endpointResolver->resolve($criteriaSet, $context->getPartyEntityDescriptor()->getAllEndpoints());
/** @var EndpointReference $endpointReference */
$endpointReference = array_shift($candidates);
if (null == $endpointReference) {
$message = sprintf(
"Unable to determine endpoint for entity '%s'",
$context->getPartyEntityDescriptor()->getEntityID()
);
$this->logger->emergency($message, LogHelper::getActionErrorContext($context, $this));
throw new LightSamlContextException($context, $message);
}
$this->logger->debug(
sprintf(
'Endpoint resolved to location "%s" and binding "%s"',
$endpointReference->getEndpoint()->getLocation(),
$endpointReference->getEndpoint()->getBinding()
),
LogHelper::getActionContext($context, $this, [
'endpointLocation' => $endpointReference->getEndpoint()->getLocation(),
'endpointBinding' => $endpointReference->getEndpoint()->getBinding(),
])
);
$context->getEndpointContext()->setEndpoint($endpointReference->getEndpoint());
}
/**
* @return CriteriaSet
*/
protected function getCriteriaSet(ProfileContext $context)
{
$criteriaSet = new CriteriaSet();
$bindings = $this->getBindings($context);
if ($bindings) {
$criteriaSet->add(new BindingCriteria($bindings));
}
$descriptorType = $this->getDescriptorType($context);
if ($descriptorType) {
$criteriaSet->add(new DescriptorTypeCriteria($descriptorType));
}
$serviceType = $this->getServiceType($context);
if ($serviceType) {
$criteriaSet->add(new ServiceTypeCriteria($serviceType));
}
return $criteriaSet;
}
/**
* @return string[]
*/
protected function getBindings(ProfileContext $context)
{
return [
SamlConstants::BINDING_SAML2_HTTP_POST,
SamlConstants::BINDING_SAML2_HTTP_REDIRECT,
];
}
/**
* @return string|null
*/
protected function getDescriptorType(ProfileContext $context)
{
return ProfileContext::ROLE_IDP == $context->getOwnRole()
? SpSsoDescriptor::class
: IdpSsoDescriptor::class;
}
/**
* @return string|null
*/
protected function getServiceType(ProfileContext $context)
{
return;
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Model\Metadata\SingleSignOnService;
class ResolveEndpointIdpSsoAction extends ResolveEndpointBaseAction
{
protected function getServiceType(ProfileContext $context)
{
return SingleSignOnService::class;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Error\LightSamlContextException;
use LightSaml\Model\Metadata\IdpSsoDescriptor;
use LightSaml\Model\Metadata\SingleLogoutService;
use LightSaml\Model\Metadata\SpSsoDescriptor;
class ResolveEndpointSloAction extends ResolveEndpointBaseAction
{
protected function getServiceType(ProfileContext $context)
{
return SingleLogoutService::class;
}
protected function getDescriptorType(ProfileContext $context)
{
$ssoSessionState = $context->getLogoutSsoSessionState();
$ownEntityId = $context->getOwnEntityDescriptor()->getEntityID();
if ($ssoSessionState->getIdpEntityId() == $ownEntityId) {
return SpSsoDescriptor::class;
} elseif ($ssoSessionState->getSpEntityId() == $ownEntityId) {
return IdpSsoDescriptor::class;
} else {
throw new LightSamlContextException($context, 'Unable to resolve logout target descriptor type');
}
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Model\Metadata\AssertionConsumerService;
class ResolveEndpointSpAcsAction extends ResolveEndpointBaseAction
{
protected function getServiceType(ProfileContext $context)
{
return AssertionConsumerService::class;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Model\Protocol\LogoutRequest;
use LightSaml\State\Request\RequestState;
use LightSaml\State\Request\RequestStateParameters;
use LightSaml\Store\Request\RequestStateStoreInterface;
use Psr\Log\LoggerInterface;
class SaveRequestStateAction extends AbstractProfileAction
{
/** @var RequestStateStoreInterface */
protected $requestStore;
public function __construct(LoggerInterface $logger, RequestStateStoreInterface $requestStore)
{
parent::__construct($logger);
$this->requestStore = $requestStore;
}
protected function doExecute(ProfileContext $context)
{
$message = MessageContextHelper::asSamlMessage($context->getOutboundContext());
$state = new RequestState();
$state->setId($message->getID());
$partyEntityId = $context->getPartyEntityContext() ? $context->getPartyEntityContext()->getEntityId() : '';
if ($context->getPartyEntityContext() && $context->getPartyEntityContext()->getEntityDescriptor()) {
$partyEntityId = $context->getPartyEntityContext()->getEntityDescriptor()->getEntityID();
}
$state->getParameters()->add([
RequestStateParameters::ID => $message->getID(),
RequestStateParameters::TYPE => get_class($message),
RequestStateParameters::TIMESTAMP => $message->getIssueInstantTimestamp(),
RequestStateParameters::PARTY => $partyEntityId,
RequestStateParameters::RELAY_STATE => $message->getRelayState(),
]);
if ($message instanceof LogoutRequest) {
$state->getParameters()->add([
RequestStateParameters::NAME_ID => $message->getNameID()->getValue(),
RequestStateParameters::NAME_ID_FORMAT => $message->getNameID()->getFormat(),
RequestStateParameters::SESSION_INDEX => $message->getSessionIndex(),
]);
}
$this->requestStore->set($state);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Binding\BindingFactoryInterface;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\ProfileContext;
use Psr\Log\LoggerInterface;
class SendMessageAction extends AbstractProfileAction
{
/** @var BindingFactoryInterface */
protected $bindingFactory;
public function __construct(LoggerInterface $logger, BindingFactoryInterface $bindingFactory)
{
parent::__construct($logger);
$this->bindingFactory = $bindingFactory;
}
/**
* @return void
*/
public function doExecute(ProfileContext $context)
{
$binding = $this->bindingFactory->create($context->getEndpoint()->getBinding());
$outboundContext = $context->getOutboundContext();
$context->getHttpResponseContext()->setResponse(
$binding->send($outboundContext)
);
$this->logger->info(
'Sending message',
LogHelper::getActionContext($context, $this, [
'message' => $outboundContext->getSerializationContext()->getDocument()->saveXML(),
])
);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
class SetRelayStateAction extends AbstractProfileAction
{
protected function doExecute(ProfileContext $context)
{
if ($context->getRelayState()) {
$this->logger->debug(
sprintf('RelayState from context set to outbound message: "%s"', $context->getRelayState()),
LogHelper::getActionContext($context, $this)
);
MessageContextHelper::asSamlMessage($context->getOutboundContext())
->setRelayState($context->getRelayState());
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace LightSaml\Action\Profile\Outbound\Message;
use LightSaml\Action\Profile\AbstractProfileAction;
use LightSaml\Context\Profile\Helper\LogHelper;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Model\Protocol\AuthnRequest;
use LightSaml\Model\Protocol\LogoutRequest;
use LightSaml\Model\Protocol\Response;
use LightSaml\Resolver\Signature\SignatureResolverInterface;
use Psr\Log\LoggerInterface;
/**
* Signs the outbound message, according to TrustOptions settings.
*/
class SignMessageAction extends AbstractProfileAction
{
/** @var SignatureResolverInterface */
protected $signatureResolver;
public function __construct(LoggerInterface $logger, SignatureResolverInterface $signatureResolver)
{
parent::__construct($logger);
$this->signatureResolver = $signatureResolver;
}
protected function doExecute(ProfileContext $context)
{
$shouldSign = $this->shouldSignMessage($context);
if ($shouldSign) {
$signature = $this->signatureResolver->getSignature($context);
if ($signature) {
MessageContextHelper::asSamlMessage($context->getOutboundContext())
->setSignature($signature)
;
$this->logger->debug(
sprintf('Message signed with fingerprint "%s"', $signature->getCertificate()->getFingerprint()),
LogHelper::getActionContext($context, $this, [
'certificate' => $signature->getCertificate()->getInfo(),
])
);
} else {
$this->logger->critical(
'No signature resolved, although signing enabled',
LogHelper::getActionErrorContext($context, $this, [])
);
}
} else {
$this->logger->debug('Signing disabled', LogHelper::getActionContext($context, $this));
}
}
/**
* @return bool
*/
private function shouldSignMessage(ProfileContext $context)
{
$message = $context->getOutboundMessage();
if ($message instanceof LogoutRequest) {
return true;
}
$trustOptions = $context->getTrustOptions();
if ($message instanceof AuthnRequest) {
return $trustOptions->getSignAuthnRequest();
} elseif ($message instanceof Response) {
return $trustOptions->getSignResponse();
}
throw new \LogicException(sprintf('Unexpected message type "%s"', get_class($message)));
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace LightSaml\Action;
use LightSaml\Context\ContextInterface;
abstract class WrappedAction implements ActionInterface
{
/**
* @var ActionInterface
*/
protected $action;
public function __construct(ActionInterface $action)
{
$this->action = $action;
}
/**
* @return void
*/
public function execute(ContextInterface $context)
{
$this->beforeAction($context);
$this->action->execute($context);
$this->afterAction($context);
}
abstract protected function beforeAction(ContextInterface $context);
abstract protected function afterAction(ContextInterface $context);
}

View File

@@ -0,0 +1,62 @@
<?php
namespace LightSaml\Binding;
use LightSaml\Context\Profile\MessageContext;
use LightSaml\Event\MessageReceived;
use LightSaml\Event\MessageSent;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
abstract class AbstractBinding
{
/** @var EventDispatcherInterface|null */
protected $eventDispatcher;
/**
* @return AbstractBinding
*/
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher = null)
{
$this->eventDispatcher = $eventDispatcher;
return $this;
}
/**
* @return EventDispatcherInterface|null
*/
public function getEventDispatcher()
{
return $this->eventDispatcher;
}
/**
* @param string $messageString
*/
protected function dispatchReceive($messageString)
{
if ($this->eventDispatcher) {
$this->eventDispatcher->dispatch(new MessageReceived($messageString));
}
}
/**
* @param string $messageString
*/
protected function dispatchSend($messageString)
{
if ($this->eventDispatcher) {
$this->eventDispatcher->dispatch(new MessageSent($messageString));
}
}
/**
* @param string|null $destination
*
* @return \Symfony\Component\HttpFoundation\Response
*/
abstract public function send(MessageContext $context, $destination = null);
abstract public function receive(Request $request, MessageContext $context);
}

View File

@@ -0,0 +1,133 @@
<?php
namespace LightSaml\Binding;
use LightSaml\Error\LightSamlBindingException;
use LightSaml\SamlConstants;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
class BindingFactory implements BindingFactoryInterface
{
/** @var EventDispatcherInterface|null */
protected $eventDispatcher;
/**
* @param EventDispatcherInterface $eventDispatcher
*/
public function __construct(EventDispatcherInterface $eventDispatcher = null)
{
$this->eventDispatcher = $eventDispatcher;
}
/**
* @return BindingFactoryInterface
*/
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher = null)
{
$this->eventDispatcher = $eventDispatcher;
return $this;
}
/**
* @return AbstractBinding
*/
public function getBindingByRequest(Request $request)
{
$bindingType = $this->detectBindingType($request);
return $this->create($bindingType);
}
/**
* @param string $bindingType
*
* @throws \LogicException
* @throws \LightSaml\Error\LightSamlBindingException
*
* @return AbstractBinding
*/
public function create($bindingType)
{
$result = null;
switch ($bindingType) {
case SamlConstants::BINDING_SAML2_HTTP_REDIRECT:
$result = new HttpRedirectBinding();
break;
case SamlConstants::BINDING_SAML2_HTTP_POST:
$result = new HttpPostBinding();
break;
case SamlConstants::BINDING_SAML2_HTTP_ARTIFACT:
throw new \LogicException('Artifact binding not implemented');
case SamlConstants::BINDING_SAML2_SOAP:
throw new \LogicException('SOAP binding not implemented');
}
if ($result) {
$result->setEventDispatcher($this->eventDispatcher);
return $result;
}
throw new LightSamlBindingException(sprintf("Unknown binding type '%s'", $bindingType));
}
/**
* @return string|null
*/
public function detectBindingType(Request $request)
{
$requestMethod = trim(strtoupper($request->getMethod()));
if ('GET' == $requestMethod) {
return $this->processGET($request);
} elseif ('POST' == $requestMethod) {
return $this->processPOST($request);
}
return null;
}
/**
* @return string|null
*/
protected function processGET(Request $request)
{
$get = $request->query->all();
if (array_key_exists('SAMLRequest', $get) || array_key_exists('SAMLResponse', $get)) {
return SamlConstants::BINDING_SAML2_HTTP_REDIRECT;
} elseif (array_key_exists('SAMLart', $get)) {
return SamlConstants::BINDING_SAML2_HTTP_ARTIFACT;
}
return null;
}
/**
* @return string|null
*/
protected function processPOST(Request $request)
{
$post = $request->request->all();
if (array_key_exists('SAMLRequest', $post) || array_key_exists('SAMLResponse', $post)) {
return SamlConstants::BINDING_SAML2_HTTP_POST;
} elseif (array_key_exists('SAMLart', $post)) {
return SamlConstants::BINDING_SAML2_HTTP_ARTIFACT;
} else {
if ($contentType = $request->headers->get('CONTENT_TYPE')) {
// Remove charset
if (false !== $pos = strpos($contentType, ';')) {
$contentType = substr($contentType, 0, $pos);
}
if ('text/xml' === $contentType) {
return SamlConstants::BINDING_SAML2_SOAP;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace LightSaml\Binding;
use Symfony\Component\HttpFoundation\Request;
interface BindingFactoryInterface
{
/**
* @return AbstractBinding
*/
public function getBindingByRequest(Request $request);
/**
* @param string $bindingType
*
* @throws \LightSaml\Error\LightSamlBindingException
*
* @return AbstractBinding
*/
public function create($bindingType);
/**
* @return string|null
*/
public function detectBindingType(Request $request);
}

View File

@@ -0,0 +1,69 @@
<?php
namespace LightSaml\Binding;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\MessageContext;
use LightSaml\Error\LightSamlBindingException;
use LightSaml\Model\Protocol\AbstractRequest;
use LightSaml\Model\Protocol\SamlMessage;
use Symfony\Component\HttpFoundation\Request;
class HttpPostBinding extends AbstractBinding
{
/**
* @param string|null $destination
*
* @return SamlPostResponse
*/
public function send(MessageContext $context, $destination = null)
{
$message = MessageContextHelper::asSamlMessage($context);
$destination = $message->getDestination() ? $message->getDestination() : $destination;
$serializationContext = $context->getSerializationContext();
$message->serialize($serializationContext->getDocument(), $serializationContext);
$msgStr = $serializationContext->getDocument()->saveXML();
$this->dispatchSend($msgStr);
$msgStr = base64_encode($msgStr);
$type = $message instanceof AbstractRequest ? 'SAMLRequest' : 'SAMLResponse';
$data = [$type => $msgStr];
if ($message->getRelayState()) {
$data['RelayState'] = $message->getRelayState();
}
$result = new SamlPostResponse($destination, $data);
$result->renderContent();
return $result;
}
public function receive(Request $request, MessageContext $context)
{
$post = $request->request->all();
if (array_key_exists('SAMLRequest', $post)) {
$msg = $post['SAMLRequest'];
} elseif (array_key_exists('SAMLResponse', $post)) {
$msg = $post['SAMLResponse'];
} else {
throw new LightSamlBindingException('Missing SAMLRequest or SAMLResponse parameter');
}
$msg = base64_decode($msg);
$this->dispatchReceive($msg);
$deserializationContext = $context->getDeserializationContext();
$result = SamlMessage::fromXML($msg, $deserializationContext);
if (array_key_exists('RelayState', $post)) {
$result->setRelayState($post['RelayState']);
}
$context->setMessage($result);
}
}

View File

@@ -0,0 +1,277 @@
<?php
namespace LightSaml\Binding;
use LightSaml\Context\Profile\Helper\MessageContextHelper;
use LightSaml\Context\Profile\MessageContext;
use LightSaml\Error\LightSamlBindingException;
use LightSaml\Model\Protocol\AbstractRequest;
use LightSaml\Model\Protocol\SamlMessage;
use LightSaml\Model\XmlDSig\SignatureStringReader;
use LightSaml\Model\XmlDSig\SignatureWriter;
use LightSaml\SamlConstants;
use RobRichards\XMLSecLibs\XMLSecurityKey;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
class HttpRedirectBinding extends AbstractBinding
{
/**
* @param string|null $destination
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function send(MessageContext $context, $destination = null)
{
$destination = $context->getMessage()->getDestination() ? $context->getMessage()->getDestination() : $destination;
$url = $this->getRedirectURL($context, $destination);
return new RedirectResponse($url);
}
public function receive(Request $request, MessageContext $context)
{
$data = $this->parseQuery($request);
$this->processData($data, $context);
}
/**
* @throws \Exception
*/
protected function processData(array $data, MessageContext $context)
{
$msg = $this->getMessageStringFromData($data);
$encoding = $this->getEncodingFromData($data);
$msg = $this->decodeMessageString($msg, $encoding);
$this->dispatchReceive($msg);
$deserializationContext = $context->getDeserializationContext();
$message = SamlMessage::fromXML($msg, $deserializationContext);
$this->loadRelayState($message, $data);
$this->loadSignature($message, $data);
$context->setMessage($message);
}
/**
* @return string
*
* @throws LightSamlBindingException
*/
protected function getMessageStringFromData(array $data)
{
if (array_key_exists('SAMLRequest', $data)) {
return $data['SAMLRequest'];
} elseif (array_key_exists('SAMLResponse', $data)) {
return $data['SAMLResponse'];
} else {
throw new LightSamlBindingException('Missing SAMLRequest or SAMLResponse parameter');
}
}
/**
* @return string
*/
protected function getEncodingFromData(array $data)
{
if (array_key_exists('SAMLEncoding', $data)) {
return $data['SAMLEncoding'];
} else {
return SamlConstants::ENCODING_DEFLATE;
}
}
/**
* @param string $msg
* @param string $encoding
*
* @throws \LightSaml\Error\LightSamlBindingException
*
* @return string
*/
protected function decodeMessageString($msg, $encoding)
{
$msg = base64_decode($msg);
switch ($encoding) {
case SamlConstants::ENCODING_DEFLATE:
return gzinflate($msg);
break;
default:
throw new LightSamlBindingException(sprintf("Unknown encoding '%s'", $encoding));
}
}
protected function loadRelayState(SamlMessage $message, array $data)
{
if (array_key_exists('RelayState', $data)) {
$message->setRelayState($data['RelayState']);
}
}
protected function loadSignature(SamlMessage $message, array $data)
{
if (array_key_exists('Signature', $data)) {
if (false == array_key_exists('SigAlg', $data)) {
throw new LightSamlBindingException('Missing signature algorithm');
}
$message->setSignature(
new SignatureStringReader($data['Signature'], $data['SigAlg'], $data['SignedQuery'])
);
}
}
/**
* @param string|null $destination
*
* @return string
*/
protected function getRedirectURL(MessageContext $context, $destination)
{
$message = MessageContextHelper::asSamlMessage($context);
$signature = $message->getSignature();
if ($signature && false == $signature instanceof SignatureWriter) {
throw new LightSamlBindingException('Signature must be SignatureWriter');
}
$xml = $this->getMessageEncodedXml($message, $context);
$msg = $this->addMessageToUrl($message, $xml);
$this->addRelayStateToUrl($msg, $message);
$this->addSignatureToUrl($msg, $signature);
return $this->getDestinationUrl($msg, $message, $destination);
}
/**
* @return string
*/
protected function getMessageEncodedXml(SamlMessage $message, MessageContext $context)
{
$message->setSignature(null);
$serializationContext = $context->getSerializationContext();
$message->serialize($serializationContext->getDocument(), $serializationContext);
$xml = $serializationContext->getDocument()->saveXML();
$this->dispatchSend($xml);
$xml = gzdeflate($xml);
$xml = base64_encode($xml);
return $xml;
}
/**
* @param string $xml
*
* @return string
*/
protected function addMessageToUrl(SamlMessage $message, $xml)
{
if ($message instanceof AbstractRequest) {
$msg = 'SAMLRequest=';
} else {
$msg = 'SAMLResponse=';
}
$msg .= urlencode($xml);
return $msg;
}
/**
* @param string $msg
*/
protected function addRelayStateToUrl(&$msg, SamlMessage $message)
{
if (null !== $message->getRelayState()) {
$msg .= '&RelayState=' . urlencode($message->getRelayState());
}
}
/**
* @param string $msg
*/
protected function addSignatureToUrl(&$msg, SignatureWriter $signature = null)
{
/** @var $key XMLSecurityKey */
$key = $signature ? $signature->getXmlSecurityKey() : null;
if (null != $key) {
$msg .= '&SigAlg=' . urlencode($key->type);
$signature = $key->signData($msg);
$msg .= '&Signature=' . urlencode(base64_encode($signature));
}
}
/**
* @param string $msg
* @param string|null $destination
*
* @return string
*/
protected function getDestinationUrl($msg, SamlMessage $message, $destination)
{
$destination = $message->getDestination() ? $message->getDestination() : $destination;
if (false === strpos($destination, '?')) {
$destination .= '?' . $msg;
} else {
$destination .= '&' . $msg;
}
return $destination;
}
/**
* @return array
*/
protected function parseQuery(Request $request)
{
/*
* Parse the query string. We need to do this ourself, so that we get access
* to the raw (urlencoded) values. This is required because different software
* can urlencode to different values.
*/
$sigQuery = $relayState = $sigAlg = '';
$data = $this->parseQueryString($request->server->get('QUERY_STRING'));
$result = [];
foreach ($data as $name => $value) {
$result[$name] = urldecode($value);
switch ($name) {
case 'SAMLRequest':
case 'SAMLResponse':
$sigQuery = $name . '=' . $value;
break;
case 'RelayState':
$relayState = '&RelayState=' . $value;
break;
case 'SigAlg':
$sigAlg = '&SigAlg=' . $value;
break;
}
}
$result['SignedQuery'] = $sigQuery . $relayState . $sigAlg;
return $result;
}
/**
* @param string $queryString
* @return array
*/
protected function parseQueryString($queryString)
{
$result = [];
foreach (explode('&', $queryString ?: '') as $e) {
$tmp = explode('=', $e, 2);
$name = $tmp[0];
$value = 2 === count($tmp) ? $tmp[1] : '';
$name = urldecode($name);
$result[$name] = $value;
}
return $result;
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace LightSaml\Binding;
use Symfony\Component\HttpFoundation\Response;
class SamlPostResponse extends Response
{
/** @var string */
protected $destination;
/** @var array */
protected $data;
/**
* @param string $destination
* @param int $status
* @param array $headers
*/
public function __construct($destination, array $data, $status = 200, $headers = [])
{
parent::__construct('', $status, $headers);
$this->destination = $destination;
$this->data = $data;
}
/**
* @return array
*/
public function getData()
{
return $this->data;
}
/**
* @return string
*/
public function getDestination()
{
return $this->destination;
}
public function renderContent()
{
$content = <<<'EOT'
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>POST data</title>
</head>
<body onload="document.getElementById('a-very-unique-input-id#lightSAML').click();">
<noscript>
<p><strong>Note:</strong> Since your browser does not support JavaScript, you must press the button below once to proceed.</p>
</noscript>
<form method="post" action="%s">
<input id="a-very-unique-input-id#lightSAML" type="submit" style="display:none;"/>
%s
<noscript>
<input type="submit" value="Submit" />
</noscript>
</form>
</body>
</html>
EOT;
$fields = '';
foreach ($this->data as $name => $value) {
$fields .= sprintf(
'<input type="hidden" name="%s" value="%s" />',
htmlspecialchars($name),
htmlspecialchars($value)
);
}
$content = sprintf($content, htmlspecialchars($this->destination ?? ''), $fields);
$this->setContent($content);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace LightSaml\Bridge\Pimple\Container;
use Pimple\Container;
abstract class AbstractPimpleContainer
{
/** @var Container */
protected $pimple;
public function __construct(Container $pimple)
{
$this->pimple = $pimple;
}
/**
* @return Container
*/
public function getPimple()
{
return $this->pimple;
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace LightSaml\Bridge\Pimple\Container;
use LightSaml\Build\Container\BuildContainerInterface;
use LightSaml\Build\Container\CredentialContainerInterface;
use LightSaml\Build\Container\OwnContainerInterface;
use LightSaml\Build\Container\PartyContainerInterface;
use LightSaml\Build\Container\ProviderContainerInterface;
use LightSaml\Build\Container\ServiceContainerInterface;
use LightSaml\Build\Container\StoreContainerInterface;
use LightSaml\Build\Container\SystemContainerInterface;
class BuildContainer extends AbstractPimpleContainer implements BuildContainerInterface
{
/** @var SystemContainerInterface */
private $systemContainer;
/** @var PartyContainerInterface */
private $partyContainer;
/** @var StoreContainerInterface */
private $storeContainer;
/** @var ProviderContainerInterface */
private $providerContainer;
/** @var CredentialContainerInterface */
private $credentialContainer;
/** @var ServiceContainerInterface */
private $serviceContainer;
/** @var OwnContainerInterface */
private $ownContainer;
/**
* @return SystemContainerInterface
*/
public function getSystemContainer()
{
if (null == $this->systemContainer) {
$this->systemContainer = new SystemContainer($this->pimple);
}
return $this->systemContainer;
}
/**
* @return PartyContainerInterface
*/
public function getPartyContainer()
{
if (null == $this->partyContainer) {
$this->partyContainer = new PartyContainer($this->pimple);
}
return $this->partyContainer;
}
/**
* @return StoreContainerInterface
*/
public function getStoreContainer()
{
if (null == $this->storeContainer) {
$this->storeContainer = new StoreContainer($this->pimple);
}
return $this->storeContainer;
}
/**
* @return ProviderContainerInterface
*/
public function getProviderContainer()
{
if (null == $this->providerContainer) {
$this->providerContainer = new ProviderContainer($this->pimple);
}
return $this->providerContainer;
}
/**
* @return CredentialContainerInterface
*/
public function getCredentialContainer()
{
if (null == $this->credentialContainer) {
$this->credentialContainer = new CredentialContainer($this->pimple);
}
return $this->credentialContainer;
}
/**
* @return ServiceContainerInterface
*/
public function getServiceContainer()
{
if (null == $this->serviceContainer) {
$this->serviceContainer = new ServiceContainer($this->pimple);
}
return $this->serviceContainer;
}
/**
* @return OwnContainerInterface
*/
public function getOwnContainer()
{
if (null == $this->ownContainer) {
$this->ownContainer = new OwnContainer($this->pimple);
}
return $this->ownContainer;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace LightSaml\Bridge\Pimple\Container;
use LightSaml\Build\Container\CredentialContainerInterface;
use LightSaml\Store\Credential\CredentialStoreInterface;
class CredentialContainer extends AbstractPimpleContainer implements CredentialContainerInterface
{
public const CREDENTIAL_STORE = 'lightsaml.container.credential_store';
/**
* @return CredentialStoreInterface
*/
public function getCredentialStore()
{
return $this->pimple[self::CREDENTIAL_STORE];
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace LightSaml\Bridge\Pimple\Container\Factory;
use LightSaml\Bridge\Pimple\Container\CredentialContainer;
use LightSaml\Build\Container\OwnContainerInterface;
use LightSaml\Build\Container\PartyContainerInterface;
use LightSaml\Credential\CredentialInterface;
use LightSaml\Error\LightSamlBuildException;
use LightSaml\Store\Credential\Factory\CredentialFactory;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
class CredentialContainerProvider implements ServiceProviderInterface
{
/** @var PartyContainerInterface */
private $partyContainer;
/** @var OwnContainerInterface */
private $ownContainer;
/** @var CredentialInterface[] */
private $extraCredentials = [];
public function __construct(PartyContainerInterface $partyContainer, OwnContainerInterface $ownContainer)
{
$this->ownContainer = $ownContainer;
$this->partyContainer = $partyContainer;
}
/**
* @return CredentialContainerProvider
*/
public function addExtraCredential(CredentialInterface $credential)
{
if (null === $credential->getEntityId()) {
throw new \InvalidArgumentException('Extra credential must have entityID');
}
$this->extraCredentials[] = $credential;
return $this;
}
/**
* @param Container $pimple A container instance
*/
public function register(Container $pimple)
{
$ownCredentials = $this->ownContainer->getOwnCredentials();
if (empty($ownCredentials)) {
throw new LightSamlBuildException('There are no own credentials');
}
$pimple[CredentialContainer::CREDENTIAL_STORE] = function () {
$factory = new CredentialFactory();
return $factory->build(
$this->partyContainer->getIdpEntityDescriptorStore(),
$this->partyContainer->getSpEntityDescriptorStore(),
$this->ownContainer->getOwnCredentials(),
$this->extraCredentials
);
};
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace LightSaml\Bridge\Pimple\Container\Factory;
use LightSaml\Bridge\Pimple\Container\OwnContainer;
use LightSaml\Credential\CredentialInterface;
use LightSaml\Error\LightSamlBuildException;
use LightSaml\Provider\EntityDescriptor\EntityDescriptorProviderInterface;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
class OwnContainerProvider implements ServiceProviderInterface
{
/** @var CredentialInterface[] */
private $ownCredentials = [];
/** @var EntityDescriptorProviderInterface */
private $ownEntityDescriptorProvider;
/**
* @param CredentialInterface[] $ownCredentials
*/
public function __construct(EntityDescriptorProviderInterface $ownEntityDescriptorProvider, array $ownCredentials = null)
{
$this->ownEntityDescriptorProvider = $ownEntityDescriptorProvider;
if ($ownCredentials) {
foreach ($ownCredentials as $credential) {
$this->addOwnCredential($credential);
}
}
}
/**
* @return OwnContainerProvider
*/
public function addOwnCredential(CredentialInterface $credential)
{
if (null == $credential->getPrivateKey()) {
throw new LightSamlBuildException('Own credential must have private key');
}
$this->ownCredentials[] = $credential;
return $this;
}
/**
* @param Container $pimple A container instance
*/
public function register(Container $pimple)
{
$pimple[OwnContainer::OWN_CREDENTIALS] = function () {
return $this->ownCredentials;
};
$pimple[OwnContainer::OWN_ENTITY_DESCRIPTOR_PROVIDER] = function () {
return $this->ownEntityDescriptorProvider;
};
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace LightSaml\Bridge\Pimple\Container\Factory;
use LightSaml\Bridge\Pimple\Container\PartyContainer;
use LightSaml\Meta\TrustOptions\TrustOptions;
use LightSaml\Store\EntityDescriptor\FixedEntityDescriptorStore;
use LightSaml\Store\TrustOptions\FixedTrustOptionsStore;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
class PartyContainerProvider implements ServiceProviderInterface
{
/**
* @param Container $pimple A container instance
*/
public function register(Container $pimple)
{
$pimple[PartyContainer::IDP_ENTITY_DESCRIPTOR] = function () {
return new FixedEntityDescriptorStore();
};
$pimple[PartyContainer::SP_ENTITY_DESCRIPTOR] = function () {
return new FixedEntityDescriptorStore();
};
$pimple[PartyContainer::TRUST_OPTIONS_STORE] = function () {
return new FixedTrustOptionsStore(new TrustOptions());
};
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace LightSaml\Bridge\Pimple\Container\Factory;
use LightSaml\Bridge\Pimple\Container\ProviderContainer;
use LightSaml\Error\LightSamlBuildException;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
class ProviderContainerProvider implements ServiceProviderInterface
{
/**
* @param Container $pimple A container instance
*/
public function register(Container $pimple)
{
$pimple[ProviderContainer::ATTRIBUTE_VALUE_PROVIDER] = function () {
throw new LightSamlBuildException('Attribute value provider not set');
};
$pimple[ProviderContainer::SESSION_INFO_PROVIDER] = function () {
throw new LightSamlBuildException('Session info provider not set');
};
$pimple[ProviderContainer::NAME_ID_PROVIDER] = function () {
throw new LightSamlBuildException('Name ID provider not set');
};
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace LightSaml\Bridge\Pimple\Container\Factory;
use LightSaml\Binding\BindingFactory;
use LightSaml\Bridge\Pimple\Container\ServiceContainer;
use LightSaml\Build\Container\CredentialContainerInterface;
use LightSaml\Build\Container\StoreContainerInterface;
use LightSaml\Build\Container\SystemContainerInterface;
use LightSaml\Resolver\Credential\Factory\CredentialResolverFactory;
use LightSaml\Resolver\Endpoint\BindingEndpointResolver;
use LightSaml\Resolver\Endpoint\CompositeEndpointResolver;
use LightSaml\Resolver\Endpoint\DescriptorTypeEndpointResolver;
use LightSaml\Resolver\Endpoint\IndexEndpointResolver;
use LightSaml\Resolver\Endpoint\LocationEndpointResolver;
use LightSaml\Resolver\Endpoint\ServiceTypeEndpointResolver;
use LightSaml\Resolver\Session\SessionProcessor;
use LightSaml\Resolver\Signature\OwnSignatureResolver;
use LightSaml\Validator\Model\Assertion\AssertionTimeValidator;
use LightSaml\Validator\Model\Assertion\AssertionValidator;
use LightSaml\Validator\Model\NameId\NameIdValidator;
use LightSaml\Validator\Model\Signature\SignatureValidator;
use LightSaml\Validator\Model\Statement\StatementValidator;
use LightSaml\Validator\Model\Subject\SubjectValidator;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
class ServiceContainerProvider implements ServiceProviderInterface
{
/** @var CredentialContainerInterface */
private $credentialContainer;
/** @var SystemContainerInterface */
private $systemContainer;
/** @var StoreContainerInterface */
private $storeContainer;
public function __construct(
CredentialContainerInterface $credentialContainer,
StoreContainerInterface $storeContainer,
SystemContainerInterface $systemContainer
) {
$this->credentialContainer = $credentialContainer;
$this->storeContainer = $storeContainer;
$this->systemContainer = $systemContainer;
}
/**
* @param Container $pimple A container instance
*/
public function register(Container $pimple)
{
$pimple[ServiceContainer::NAME_ID_VALIDATOR] = function () {
return new NameIdValidator();
};
$pimple[ServiceContainer::ASSERTION_TIME_VALIDATOR] = function () {
return new AssertionTimeValidator();
};
$pimple[ServiceContainer::ASSERTION_VALIDATOR] = function (Container $c) {
$nameIdValidator = $c[ServiceContainer::NAME_ID_VALIDATOR];
return new AssertionValidator(
$nameIdValidator,
new SubjectValidator($nameIdValidator),
new StatementValidator()
);
};
$pimple[ServiceContainer::ENDPOINT_RESOLVER] = function () {
return new CompositeEndpointResolver([
new BindingEndpointResolver(),
new DescriptorTypeEndpointResolver(),
new ServiceTypeEndpointResolver(),
new IndexEndpointResolver(),
new LocationEndpointResolver(),
]);
};
$pimple[ServiceContainer::BINDING_FACTORY] = function () {
return new BindingFactory($this->systemContainer->getEventDispatcher());
};
$pimple[ServiceContainer::CREDENTIAL_RESOLVER] = function () {
$factory = new CredentialResolverFactory($this->credentialContainer->getCredentialStore());
return $factory->build();
};
$pimple[ServiceContainer::SIGNATURE_RESOLVER] = function (Container $c) {
$credentialResolver = $c[ServiceContainer::CREDENTIAL_RESOLVER];
return new OwnSignatureResolver($credentialResolver);
};
$pimple[ServiceContainer::SIGNATURE_VALIDATOR] = function (Container $c) {
$credentialResolver = $c[ServiceContainer::CREDENTIAL_RESOLVER];
return new SignatureValidator($credentialResolver);
};
$pimple[ServiceContainer::SESSION_PROCESSOR] = function () {
return new SessionProcessor($this->storeContainer->getSsoStateStore(), $this->systemContainer->getTimeProvider());
};
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace LightSaml\Bridge\Pimple\Container\Factory;
use LightSaml\Bridge\Pimple\Container\StoreContainer;
use LightSaml\Build\Container\SystemContainerInterface;
use LightSaml\Store\Id\NullIdStore;
use LightSaml\Store\Request\RequestStateSessionStore;
use LightSaml\Store\Sso\SsoStateSessionStore;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
class StoreContainerProvider implements ServiceProviderInterface
{
/** @var SystemContainerInterface */
private $systemContainer;
public function __construct(SystemContainerInterface $systemContainer)
{
$this->systemContainer = $systemContainer;
}
/**
* @param Container $pimple A container instance
*/
public function register(Container $pimple)
{
$pimple[StoreContainer::REQUEST_STATE_STORE] = function () {
return new RequestStateSessionStore($this->systemContainer->getSession(), 'main');
};
$pimple[StoreContainer::ID_STATE_STORE] = function () {
return new NullIdStore();
};
$pimple[StoreContainer::SSO_STATE_STORE] = function () {
return new SsoStateSessionStore($this->systemContainer->getSession(), 'samlsso');
};
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace LightSaml\Bridge\Pimple\Container\Factory;
use LightSaml\Bridge\Pimple\Container\SystemContainer;
use LightSaml\Provider\TimeProvider\SystemTimeProvider;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\NullLogger;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
class SystemContainerProvider implements ServiceProviderInterface
{
/** @var bool */
private $mockSession;
/** @var EventDispatcherInterface|null */
private $eventDispatcher;
public function __construct($mockSession = false, EventDispatcherInterface $eventDispatcher = null)
{
$this->mockSession = $mockSession;
$this->eventDispatcher = $eventDispatcher;
}
/**
* @param Container $pimple A container instance
*/
public function register(Container $pimple)
{
$pimple[SystemContainer::REQUEST] = function () {
return Request::createFromGlobals();
};
$pimple[SystemContainer::SESSION] = function () {
if ($this->mockSession) {
$session = new Session(new MockArraySessionStorage());
} else {
$session = new Session();
}
$session->setName(sprintf('SID%s', mt_rand(1000, 9999)));
$session->start();
return $session;
};
$pimple[SystemContainer::TIME_PROVIDER] = function () {
return new SystemTimeProvider();
};
$pimple[SystemContainer::EVENT_DISPATCHER] = function () {
return $this->eventDispatcher;
};
$pimple[SystemContainer::LOGGER] = function () {
return new NullLogger();
};
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace LightSaml\Bridge\Pimple\Container;
use LightSaml\Build\Container\OwnContainerInterface;
use LightSaml\Credential\CredentialInterface;
use LightSaml\Provider\EntityDescriptor\EntityDescriptorProviderInterface;
class OwnContainer extends AbstractPimpleContainer implements OwnContainerInterface
{
public const OWN_ENTITY_DESCRIPTOR_PROVIDER = 'lightsaml.container.own_entity_descriptor_provider';
public const OWN_CREDENTIALS = 'lightsaml.container.own_credentials';
/**
* @return EntityDescriptorProviderInterface
*/
public function getOwnEntityDescriptorProvider()
{
return $this->pimple[self::OWN_ENTITY_DESCRIPTOR_PROVIDER];
}
/**
* @return CredentialInterface[]
*/
public function getOwnCredentials()
{
return $this->pimple[self::OWN_CREDENTIALS];
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace LightSaml\Bridge\Pimple\Container;
use LightSaml\Build\Container\PartyContainerInterface;
use LightSaml\Store\EntityDescriptor\EntityDescriptorStoreInterface;
use LightSaml\Store\TrustOptions\TrustOptionsStoreInterface;
class PartyContainer extends AbstractPimpleContainer implements PartyContainerInterface
{
public const IDP_ENTITY_DESCRIPTOR = 'lightsaml.container.idp_entity_descriptor';
public const SP_ENTITY_DESCRIPTOR = 'lightsaml.container.sp_entity_descriptor';
public const TRUST_OPTIONS_STORE = 'lightsaml.container.trust_options_store';
/**
* @return EntityDescriptorStoreInterface
*/
public function getIdpEntityDescriptorStore()
{
return $this->pimple[self::IDP_ENTITY_DESCRIPTOR];
}
/**
* @return EntityDescriptorStoreInterface
*/
public function getSpEntityDescriptorStore()
{
return $this->pimple[self::SP_ENTITY_DESCRIPTOR];
}
/**
* @return TrustOptionsStoreInterface
*/
public function getTrustOptionsStore()
{
return $this->pimple[self::TRUST_OPTIONS_STORE];
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace LightSaml\Bridge\Pimple\Container;
use LightSaml\Build\Container\ProviderContainerInterface;
use LightSaml\Error\LightSamlBuildException;
use LightSaml\Provider\Attribute\AttributeValueProviderInterface;
use LightSaml\Provider\NameID\NameIdProviderInterface;
use LightSaml\Provider\Session\SessionInfoProviderInterface;
class ProviderContainer extends AbstractPimpleContainer implements ProviderContainerInterface
{
public const ATTRIBUTE_VALUE_PROVIDER = 'lightsaml.container.attribute_value_provider';
public const SESSION_INFO_PROVIDER = 'lightsaml.container.session_info_provider';
public const NAME_ID_PROVIDER = 'lightsaml.container.name_id_provider';
/**
* @return AttributeValueProviderInterface
*/
public function getAttributeValueProvider()
{
if (isset($this->pimple[self::ATTRIBUTE_VALUE_PROVIDER])) {
return $this->pimple[self::ATTRIBUTE_VALUE_PROVIDER];
}
throw new LightSamlBuildException('Attribute value provider not set');
}
/**
* @return SessionInfoProviderInterface
*/
public function getSessionInfoProvider()
{
if (isset($this->pimple[self::SESSION_INFO_PROVIDER])) {
return $this->pimple[self::SESSION_INFO_PROVIDER];
}
throw new LightSamlBuildException('Session info provider not set');
}
/**
* @return NameIdProviderInterface
*/
public function getNameIdProvider()
{
if (isset($this->pimple[self::NAME_ID_PROVIDER])) {
return $this->pimple[self::NAME_ID_PROVIDER];
}
throw new LightSamlBuildException('Name ID provider not set');
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace LightSaml\Bridge\Pimple\Container;
use LightSaml\Binding\BindingFactoryInterface;
use LightSaml\Build\Container\ServiceContainerInterface;
use LightSaml\Logout\Resolver\Logout\LogoutSessionResolverInterface;
use LightSaml\Resolver\Credential\CredentialResolverInterface;
use LightSaml\Resolver\Endpoint\EndpointResolverInterface;
use LightSaml\Resolver\Session\SessionProcessorInterface;
use LightSaml\Resolver\Signature\SignatureResolverInterface;
use LightSaml\Validator\Model\Assertion\AssertionTimeValidator;
use LightSaml\Validator\Model\Assertion\AssertionValidatorInterface;
use LightSaml\Validator\Model\NameId\NameIdValidatorInterface;
use LightSaml\Validator\Model\Signature\SignatureValidatorInterface;
class ServiceContainer extends AbstractPimpleContainer implements ServiceContainerInterface
{
public const ASSERTION_VALIDATOR = 'lightsaml.container.assertion_validator';
public const ASSERTION_TIME_VALIDATOR = 'lightsaml.container.assertion_time_validator';
public const SIGNATURE_RESOLVER = 'lightsaml.container.signature_resolver';
public const ENDPOINT_RESOLVER = 'lightsaml.container.endpoint_resolver';
public const NAME_ID_VALIDATOR = 'lightsaml.container.name_id_validator';
public const BINDING_FACTORY = 'lightsaml.container.binding_factory';
public const SIGNATURE_VALIDATOR = 'lightsaml.container.signature_validator';
public const CREDENTIAL_RESOLVER = 'lightsaml.container.credential_resolver';
public const LOGOUT_SESSION_RESOLVER = 'lightsaml.container.logout_session_resolver';
public const SESSION_PROCESSOR = 'lightsaml.container.session_processor';
/**
* @return AssertionValidatorInterface
*/
public function getAssertionValidator()
{
return $this->pimple[self::ASSERTION_VALIDATOR];
}
/**
* @return AssertionTimeValidator
*/
public function getAssertionTimeValidator()
{
return $this->pimple[self::ASSERTION_TIME_VALIDATOR];
}
/**
* @return SignatureResolverInterface
*/
public function getSignatureResolver()
{
return $this->pimple[self::SIGNATURE_RESOLVER];
}
/**
* @return EndpointResolverInterface
*/
public function getEndpointResolver()
{
return $this->pimple[self::ENDPOINT_RESOLVER];
}
/**
* @return NameIdValidatorInterface
*/
public function getNameIdValidator()
{
return $this->pimple[self::NAME_ID_VALIDATOR];
}
/**
* @return BindingFactoryInterface
*/
public function getBindingFactory()
{
return $this->pimple[self::BINDING_FACTORY];
}
/**
* @return SignatureValidatorInterface
*/
public function getSignatureValidator()
{
return $this->pimple[self::SIGNATURE_VALIDATOR];
}
/**
* @return CredentialResolverInterface
*/
public function getCredentialResolver()
{
return $this->pimple[self::CREDENTIAL_RESOLVER];
}
/**
* @return LogoutSessionResolverInterface
*/
public function getLogoutSessionResolver()
{
return $this->pimple[self::LOGOUT_SESSION_RESOLVER];
}
/**
* @return SessionProcessorInterface
*/
public function getSessionProcessor()
{
return $this->pimple[self::SESSION_PROCESSOR];
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace LightSaml\Bridge\Pimple\Container;
use LightSaml\Build\Container\StoreContainerInterface;
use LightSaml\Store\Id\IdStoreInterface;
use LightSaml\Store\Request\RequestStateStoreInterface;
use LightSaml\Store\Sso\SsoStateStoreInterface;
class StoreContainer extends AbstractPimpleContainer implements StoreContainerInterface
{
public const REQUEST_STATE_STORE = 'lightsaml.container.request_state_store';
public const ID_STATE_STORE = 'lightsaml.container.id_state_store';
public const SSO_STATE_STORE = 'lightsaml.container.sso_state_store';
/**
* @return RequestStateStoreInterface
*/
public function getRequestStateStore()
{
return $this->pimple[self::REQUEST_STATE_STORE];
}
/**
* @return IdStoreInterface
*/
public function getIdStateStore()
{
return $this->pimple[self::ID_STATE_STORE];
}
/**
* @return SsoStateStoreInterface
*/
public function getSsoStateStore()
{
return $this->pimple[self::SSO_STATE_STORE];
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace LightSaml\Bridge\Pimple\Container;
use LightSaml\Build\Container\SystemContainerInterface;
use LightSaml\Provider\TimeProvider\TimeProviderInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class SystemContainer extends AbstractPimpleContainer implements SystemContainerInterface
{
public const REQUEST = 'lightsaml.container.request';
public const SESSION = 'lightsaml.container.session';
public const TIME_PROVIDER = 'lightsaml.container.time_provider';
public const EVENT_DISPATCHER = 'lightsaml.container.event_dispatcher';
public const LOGGER = 'lightsaml.container.logger';
/**
* @return Request
*/
public function getRequest()
{
return $this->pimple[self::REQUEST];
}
/**
* @return SessionInterface
*/
public function getSession()
{
return $this->pimple[self::SESSION];
}
/**
* @return TimeProviderInterface
*/
public function getTimeProvider()
{
return $this->pimple[self::TIME_PROVIDER];
}
/**
* @return EventDispatcherInterface
*/
public function getEventDispatcher()
{
return $this->pimple[self::EVENT_DISPATCHER];
}
/**
* @return LoggerInterface
*/
public function getLogger()
{
return $this->pimple[self::LOGGER];
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace LightSaml\Build\Container;
interface BuildContainerInterface
{
/**
* @return SystemContainerInterface
*/
public function getSystemContainer();
/**
* @return PartyContainerInterface
*/
public function getPartyContainer();
/**
* @return StoreContainerInterface
*/
public function getStoreContainer();
/**
* @return ProviderContainerInterface
*/
public function getProviderContainer();
/**
* @return CredentialContainerInterface
*/
public function getCredentialContainer();
/**
* @return ServiceContainerInterface
*/
public function getServiceContainer();
/**
* @return OwnContainerInterface
*/
public function getOwnContainer();
}

View File

@@ -0,0 +1,13 @@
<?php
namespace LightSaml\Build\Container;
use LightSaml\Store\Credential\CredentialStoreInterface;
interface CredentialContainerInterface
{
/**
* @return CredentialStoreInterface
*/
public function getCredentialStore();
}

View File

@@ -0,0 +1,19 @@
<?php
namespace LightSaml\Build\Container;
use LightSaml\Credential\CredentialInterface;
use LightSaml\Provider\EntityDescriptor\EntityDescriptorProviderInterface;
interface OwnContainerInterface
{
/**
* @return EntityDescriptorProviderInterface
*/
public function getOwnEntityDescriptorProvider();
/**
* @return CredentialInterface[]
*/
public function getOwnCredentials();
}

View File

@@ -0,0 +1,24 @@
<?php
namespace LightSaml\Build\Container;
use LightSaml\Store\EntityDescriptor\EntityDescriptorStoreInterface;
use LightSaml\Store\TrustOptions\TrustOptionsStoreInterface;
interface PartyContainerInterface
{
/**
* @return EntityDescriptorStoreInterface
*/
public function getIdpEntityDescriptorStore();
/**
* @return EntityDescriptorStoreInterface
*/
public function getSpEntityDescriptorStore();
/**
* @return TrustOptionsStoreInterface
*/
public function getTrustOptionsStore();
}

View File

@@ -0,0 +1,25 @@
<?php
namespace LightSaml\Build\Container;
use LightSaml\Provider\Attribute\AttributeValueProviderInterface;
use LightSaml\Provider\NameID\NameIdProviderInterface;
use LightSaml\Provider\Session\SessionInfoProviderInterface;
interface ProviderContainerInterface
{
/**
* @return AttributeValueProviderInterface
*/
public function getAttributeValueProvider();
/**
* @return SessionInfoProviderInterface
*/
public function getSessionInfoProvider();
/**
* @return NameIdProviderInterface
*/
public function getNameIdProvider();
}

View File

@@ -0,0 +1,67 @@
<?php
namespace LightSaml\Build\Container;
use LightSaml\Binding\BindingFactoryInterface;
use LightSaml\Logout\Resolver\Logout\LogoutSessionResolverInterface;
use LightSaml\Resolver\Credential\CredentialResolverInterface;
use LightSaml\Resolver\Endpoint\EndpointResolverInterface;
use LightSaml\Resolver\Session\SessionProcessorInterface;
use LightSaml\Resolver\Signature\SignatureResolverInterface;
use LightSaml\Validator\Model\Assertion\AssertionTimeValidator;
use LightSaml\Validator\Model\Assertion\AssertionValidatorInterface;
use LightSaml\Validator\Model\NameId\NameIdValidatorInterface;
use LightSaml\Validator\Model\Signature\SignatureValidatorInterface;
interface ServiceContainerInterface
{
/**
* @return AssertionValidatorInterface
*/
public function getAssertionValidator();
/**
* @return AssertionTimeValidator
*/
public function getAssertionTimeValidator();
/**
* @return SignatureResolverInterface
*/
public function getSignatureResolver();
/**
* @return EndpointResolverInterface
*/
public function getEndpointResolver();
/**
* @return NameIdValidatorInterface
*/
public function getNameIdValidator();
/**
* @return BindingFactoryInterface
*/
public function getBindingFactory();
/**
* @return SignatureValidatorInterface
*/
public function getSignatureValidator();
/**
* @return CredentialResolverInterface
*/
public function getCredentialResolver();
/**
* @return LogoutSessionResolverInterface
*/
public function getLogoutSessionResolver();
/**
* @return SessionProcessorInterface
*/
public function getSessionProcessor();
}

View File

@@ -0,0 +1,25 @@
<?php
namespace LightSaml\Build\Container;
use LightSaml\Store\Id\IdStoreInterface;
use LightSaml\Store\Request\RequestStateStoreInterface;
use LightSaml\Store\Sso\SsoStateStoreInterface;
interface StoreContainerInterface
{
/**
* @return RequestStateStoreInterface
*/
public function getRequestStateStore();
/**
* @return IdStoreInterface
*/
public function getIdStateStore();
/**
* @return SsoStateStoreInterface
*/
public function getSsoStateStore();
}

View File

@@ -0,0 +1,37 @@
<?php
namespace LightSaml\Build\Container;
use LightSaml\Provider\TimeProvider\TimeProviderInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
interface SystemContainerInterface
{
/**
* @return Request
*/
public function getRequest();
/**
* @return SessionInterface
*/
public function getSession();
/**
* @return TimeProviderInterface
*/
public function getTimeProvider();
/**
* @return EventDispatcherInterface
*/
public function getEventDispatcher();
/**
* @return LoggerInterface
*/
public function getLogger();
}

View File

@@ -0,0 +1,13 @@
<?php
namespace LightSaml\Builder\Action;
use LightSaml\Action\ActionInterface;
interface ActionBuilderInterface
{
/**
* @return ActionInterface
*/
public function build();
}

View File

@@ -0,0 +1,64 @@
<?php
namespace LightSaml\Builder\Action;
use LightSaml\Action\ActionInterface;
use LightSaml\Action\CompositeAction;
class CompositeActionBuilder implements ActionBuilderInterface
{
/**
* int priority => ActionInterface[].
*
* @var array
*/
private $actions = [];
/** @var int */
protected $increaseStep = 5;
/** @var int */
private $biggestPriority = 0;
/**
* @param int|bool $priority
*
* @return CompositeActionBuilder
*/
public function add(ActionInterface $action, $priority = false)
{
if (false === $priority) {
++$this->biggestPriority;
$priority = $this->biggestPriority;
} elseif (false === is_int($priority)) {
throw new \InvalidArgumentException('Expected integer value for priority');
} elseif ($priority > $this->biggestPriority) {
$this->biggestPriority = $priority;
}
if (false === isset($this->actions[$priority])) {
$this->actions[$priority] = [];
}
$this->actions[$priority][] = $action;
return $this;
}
/**
* @return CompositeAction
*/
public function build()
{
$actions = $this->actions;
ksort($actions);
$result = new CompositeAction();
foreach ($actions as $arr) {
foreach ($arr as $action) {
$result->add($action);
}
}
return $result;
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace LightSaml\Builder\Action\Profile;
use LightSaml\Build\Container\BuildContainerInterface;
use LightSaml\Builder\Action\CompositeActionBuilder;
use LightSaml\Error\LightSamlBuildException;
abstract class AbstractProfileActionBuilder extends CompositeActionBuilder
{
/** @var BuildContainerInterface */
protected $buildContainer;
/** @var bool */
private $initialized = false;
public function __construct(BuildContainerInterface $buildContainer)
{
$this->buildContainer = $buildContainer;
}
/**
* @return void
*/
public function init()
{
if ($this->initialized) {
throw new LightSamlBuildException('Already initialized');
}
$this->doInitialize();
$this->initialized = true;
}
/**
* @return void
*/
abstract protected function doInitialize();
/**
* @return \LightSaml\Action\ActionInterface
*/
public function build()
{
if (false === $this->initialized) {
$this->init();
}
return parent::build();
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace LightSaml\Builder\Action\Profile\Metadata;
use LightSaml\Action\Profile\Entity\SerializeOwnEntityAction;
use LightSaml\Builder\Action\Profile\AbstractProfileActionBuilder;
class MetadataActionBuilder extends AbstractProfileActionBuilder
{
/**
* @return void
*/
protected function doInitialize()
{
$this->add(new SerializeOwnEntityAction($this->buildContainer->getSystemContainer()->getLogger()), 100);
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace LightSaml\Builder\Action\Profile\SingleSignOn\Sp;
use LightSaml\Action\Profile\FlushRequestStatesAction;
use LightSaml\Action\Profile\Inbound\Message\AssertBindingTypeAction;
use LightSaml\Action\Profile\Inbound\Message\DestinationValidatorResponseAction;
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\Response\AssertionAction;
use LightSaml\Action\Profile\Inbound\Response\DecryptAssertionsAction;
use LightSaml\Action\Profile\Inbound\Response\HasAssertionsValidatorAction;
use LightSaml\Action\Profile\Inbound\Response\HasAuthnStatementValidatorAction;
use LightSaml\Action\Profile\Inbound\Response\HasBearerAssertionsValidatorAction;
use LightSaml\Action\Profile\Inbound\Response\SpSsoStateAction;
use LightSaml\Action\Profile\Inbound\StatusResponse\InResponseToValidatorAction;
use LightSaml\Action\Profile\Inbound\StatusResponse\StatusAction;
use LightSaml\Build\Container\BuildContainerInterface;
use LightSaml\Builder\Action\ActionBuilderInterface;
use LightSaml\Builder\Action\Profile\AbstractProfileActionBuilder;
use LightSaml\SamlConstants;
class SsoSpReceiveResponseActionBuilder extends AbstractProfileActionBuilder
{
/** @var ActionBuilderInterface */
private $assertionActionBuilder;
public function __construct(BuildContainerInterface $buildContainer, ActionBuilderInterface $assertionActionBuilder)
{
parent::__construct($buildContainer);
$this->assertionActionBuilder = $assertionActionBuilder;
}
/**
* @return void
*/
protected function doInitialize()
{
// Receive
$this->add(new ReceiveMessageAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getServiceContainer()->getBindingFactory()
), 100);
$this->add(new AssertBindingTypeAction(
$this->buildContainer->getSystemContainer()->getLogger(),
[SamlConstants::BINDING_SAML2_HTTP_POST]
));
// 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 DestinationValidatorResponseAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getServiceContainer()->getEndpointResolver()
));
$this->add(new MessageSignatureValidatorAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getServiceContainer()->getSignatureValidator()
));
$this->add(new DecryptAssertionsAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getServiceContainer()->getCredentialResolver()
));
$this->add(new HasAssertionsValidatorAction(
$this->buildContainer->getSystemContainer()->getLogger()
));
$this->add(new HasAuthnStatementValidatorAction(
$this->buildContainer->getSystemContainer()->getLogger()
));
$this->add(new HasBearerAssertionsValidatorAction(
$this->buildContainer->getSystemContainer()->getLogger()
));
$this->add(new AssertionAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->assertionActionBuilder->build()
));
$this->add(new FlushRequestStatesAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getStoreContainer()->getRequestStateStore()
));
$this->add(new SpSsoStateAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getServiceContainer()->getSessionProcessor()
));
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace LightSaml\Builder\Action\Profile\SingleSignOn\Sp;
use LightSaml\Action\DispatchEventAction;
use LightSaml\Action\Profile\Outbound\AuthnRequest\CreateAuthnRequestAction;
use LightSaml\Action\Profile\Outbound\Message\CreateMessageIssuerAction;
use LightSaml\Action\Profile\Outbound\Message\DestinationAction;
use LightSaml\Action\Profile\Outbound\Message\MessageIdAction;
use LightSaml\Action\Profile\Outbound\Message\MessageIssueInstantAction;
use LightSaml\Action\Profile\Outbound\Message\MessageVersionAction;
use LightSaml\Action\Profile\Outbound\Message\ResolveEndpointIdpSsoAction;
use LightSaml\Action\Profile\Outbound\Message\SaveRequestStateAction;
use LightSaml\Action\Profile\Outbound\Message\SendMessageAction;
use LightSaml\Action\Profile\Outbound\Message\SetRelayStateAction;
use LightSaml\Action\Profile\Outbound\Message\SignMessageAction;
use LightSaml\Builder\Action\Profile\AbstractProfileActionBuilder;
use LightSaml\SamlConstants;
class SsoSpSendAuthnRequestActionBuilder extends AbstractProfileActionBuilder
{
/**
* @return void
*/
protected function doInitialize()
{
// Create AuthnRequest
$this->add(new ResolveEndpointIdpSsoAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getServiceContainer()->getEndpointResolver()
), 100);
$this->add(new CreateAuthnRequestAction(
$this->buildContainer->getSystemContainer()->getLogger()
));
$this->add(new SetRelayStateAction(
$this->buildContainer->getSystemContainer()->getLogger()
));
$this->add(new MessageIdAction(
$this->buildContainer->getSystemContainer()->getLogger()
));
$this->add(new MessageVersionAction(
$this->buildContainer->getSystemContainer()->getLogger(),
SamlConstants::VERSION_20
));
$this->add(new MessageIssueInstantAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getSystemContainer()->getTimeProvider()
));
$this->add(new DestinationAction(
$this->buildContainer->getSystemContainer()->getLogger()
));
$this->add(new CreateMessageIssuerAction(
$this->buildContainer->getSystemContainer()->getLogger()
));
$this->add(new SaveRequestStateAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getStoreContainer()->getRequestStateStore()
));
$this->add(new DispatchEventAction(
$this->buildContainer->getSystemContainer()->getEventDispatcher()
));
$this->add(new SignMessageAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getServiceContainer()->getSignatureResolver()
));
// Send
$this->add(new SendMessageAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getServiceContainer()->getBindingFactory()
), 400);
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace LightSaml\Builder\Action\Profile\SingleSignOn\Sp;
use LightSaml\Action\Assertion\Inbound\AssertionIssuerFormatValidatorAction;
use LightSaml\Action\Assertion\Inbound\AssertionSignatureValidatorAction;
use LightSaml\Action\Assertion\Inbound\AssertionValidatorAction;
use LightSaml\Action\Assertion\Inbound\InResponseToValidatorAction;
use LightSaml\Action\Assertion\Inbound\KnownAssertionIssuerAction;
use LightSaml\Action\Assertion\Inbound\RecipientValidatorAction;
use LightSaml\Action\Assertion\Inbound\RepeatedIdValidatorAction;
use LightSaml\Action\Assertion\Inbound\TimeValidatorAction;
use LightSaml\Builder\Action\Profile\AbstractProfileActionBuilder;
use LightSaml\SamlConstants;
class SsoSpValidateAssertionActionBuilder extends AbstractProfileActionBuilder
{
/**
* @return void
*/
protected function doInitialize()
{
$this->add(new AssertionValidatorAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getServiceContainer()->getAssertionValidator()
), 100);
$this->add(new AssertionIssuerFormatValidatorAction(
$this->buildContainer->getSystemContainer()->getLogger(),
SamlConstants::NAME_ID_FORMAT_ENTITY
));
$this->add(new InResponseToValidatorAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getStoreContainer()->getRequestStateStore()
));
$this->add(new KnownAssertionIssuerAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getPartyContainer()->getIdpEntityDescriptorStore()
));
$this->add(new RecipientValidatorAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getServiceContainer()->getEndpointResolver()
));
$this->add(new RepeatedIdValidatorAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getStoreContainer()->getIdStateStore()
));
$this->add(new TimeValidatorAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getServiceContainer()->getAssertionTimeValidator(),
$this->buildContainer->getSystemContainer()->getTimeProvider(),
120
));
$this->add(new AssertionSignatureValidatorAction(
$this->buildContainer->getSystemContainer()->getLogger(),
$this->buildContainer->getServiceContainer()->getSignatureValidator(),
true
));
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace LightSaml\Builder\Context;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Error\LightSamlBuildException;
use LightSaml\Provider\EntityDescriptor\EntityDescriptorProviderInterface;
use Symfony\Component\HttpFoundation\Request;
class ProfileContextBuilder
{
/** @var Request */
private $request;
/** @var EntityDescriptorProviderInterface */
private $ownEntityDescriptorProvider;
/** @var int */
private $profileId;
/** @var string */
private $profileRole;
/**
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* @return ProfileContextBuilder
*/
public function setRequest(Request $request)
{
$this->request = $request;
return $this;
}
/**
* @return EntityDescriptorProviderInterface
*/
public function getOwnEntityDescriptorProvider()
{
return $this->ownEntityDescriptorProvider;
}
/**
* @return ProfileContextBuilder
*/
public function setOwnEntityDescriptorProvider(EntityDescriptorProviderInterface $ownEntityDescriptorProvider)
{
$this->ownEntityDescriptorProvider = $ownEntityDescriptorProvider;
return $this;
}
/**
* @return int
*/
public function getProfileId()
{
return $this->profileId;
}
/**
* @param int $profileId
*
* @return ProfileContextBuilder
*/
public function setProfileId($profileId)
{
$this->profileId = $profileId;
return $this;
}
/**
* @return string
*/
public function getProfileRole()
{
return $this->profileRole;
}
/**
* @param string $profileRole
*
* @return ProfileContextBuilder
*/
public function setProfileRole($profileRole)
{
$this->profileRole = $profileRole;
return $this;
}
/**
* @return ProfileContext
*/
public function build()
{
if (null === $this->request) {
throw new LightSamlBuildException('HTTP Request not set');
}
if (null === $this->ownEntityDescriptorProvider) {
throw new LightSamlBuildException('Own EntityDescriptor not set');
}
if (null === $this->profileId) {
throw new LightSamlBuildException('ProfileID not set');
}
if (null === $this->profileRole) {
throw new LightSamlBuildException('Profile role not set');
}
$result = new ProfileContext($this->profileId, $this->profileRole);
$result->getHttpRequestContext()->setRequest($this->request);
$result->getOwnEntityContext()->setEntityDescriptor($this->ownEntityDescriptorProvider->get());
return $result;
}
}

View File

@@ -0,0 +1,167 @@
<?php
namespace LightSaml\Builder\EntityDescriptor;
use LightSaml\Credential\X509Certificate;
use LightSaml\Model\Metadata\AssertionConsumerService;
use LightSaml\Model\Metadata\EntityDescriptor;
use LightSaml\Model\Metadata\IdpSsoDescriptor;
use LightSaml\Model\Metadata\KeyDescriptor;
use LightSaml\Model\Metadata\RoleDescriptor;
use LightSaml\Model\Metadata\SingleSignOnService;
use LightSaml\Model\Metadata\SpSsoDescriptor;
use LightSaml\Provider\EntityDescriptor\EntityDescriptorProviderInterface;
use LightSaml\SamlConstants;
class SimpleEntityDescriptorBuilder implements EntityDescriptorProviderInterface
{
/** @var string */
protected $entityId;
/** @var string */
protected $acsUrl;
/** @var string[] */
protected $acsBindings;
/** @var string */
protected $ssoUrl;
/** @var string[] */
protected $ssoBindings;
/** @var string[]|null */
protected $use;
/** @var X509Certificate */
protected $ownCertificate;
/** @var EntityDescriptor */
private $entityDescriptor;
/**
* @param string $entityId
* @param string $acsUrl
* @param string $ssoUrl
* @param string[] $acsBindings
* @param string[] $ssoBindings
* @param string[]|null $use
*/
public function __construct(
$entityId,
$acsUrl,
$ssoUrl,
X509Certificate $ownCertificate,
array $acsBindings = [SamlConstants::BINDING_SAML2_HTTP_POST],
array $ssoBindings = [SamlConstants::BINDING_SAML2_HTTP_POST, SamlConstants::BINDING_SAML2_HTTP_REDIRECT],
$use = [KeyDescriptor::USE_ENCRYPTION, KeyDescriptor::USE_SIGNING]
) {
$this->entityId = $entityId;
$this->acsUrl = $acsUrl;
$this->ssoUrl = $ssoUrl;
$this->ownCertificate = $ownCertificate;
$this->acsBindings = $acsBindings;
$this->ssoBindings = $ssoBindings;
$this->use = $use;
}
/**
* @return EntityDescriptor
*/
public function get()
{
if (null === $this->entityDescriptor) {
$this->entityDescriptor = $this->getEntityDescriptor();
if (false === $this->entityDescriptor instanceof EntityDescriptor) {
throw new \LogicException('Expected EntityDescriptor');
}
}
return $this->entityDescriptor;
}
/**
* @return EntityDescriptor
*/
protected function getEntityDescriptor()
{
$entityDescriptor = new EntityDescriptor();
$entityDescriptor->setEntityID($this->entityId);
$spSsoDescriptor = $this->getSpSsoDescriptor();
if ($spSsoDescriptor) {
$entityDescriptor->addItem($spSsoDescriptor);
}
$idpSsoDescriptor = $this->getIdpSsoDescriptor();
if ($idpSsoDescriptor) {
$entityDescriptor->addItem($idpSsoDescriptor);
}
return $entityDescriptor;
}
/**
* @return SpSsoDescriptor|null
*/
protected function getSpSsoDescriptor()
{
if (null === $this->acsUrl) {
return null;
}
$spSso = new SpSsoDescriptor();
foreach ($this->acsBindings as $index => $biding) {
$acs = new AssertionConsumerService();
$acs->setIndex($index)->setLocation($this->acsUrl)->setBinding($biding);
$spSso->addAssertionConsumerService($acs);
}
$this->addKeyDescriptors($spSso);
return $spSso;
}
/**
* @return IdpSsoDescriptor
*/
protected function getIdpSsoDescriptor()
{
if (null === $this->ssoUrl) {
return null;
}
$idpSso = new IdpSsoDescriptor();
foreach ($this->ssoBindings as $index => $binding) {
$sso = new SingleSignOnService();
$sso
->setLocation($this->ssoUrl)
->setBinding($binding);
$idpSso->addSingleSignOnService($sso);
}
$this->addKeyDescriptors($idpSso);
return $idpSso;
}
protected function addKeyDescriptors(RoleDescriptor $descriptor)
{
if ($this->use) {
foreach ($this->use as $use) {
$kd = new KeyDescriptor();
$kd->setUse($use);
$kd->setCertificate($this->ownCertificate);
$descriptor->addKeyDescriptor($kd);
}
} else {
$kd = new KeyDescriptor();
$kd->setCertificate($this->ownCertificate);
$descriptor->addKeyDescriptor($kd);
}
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace LightSaml\Builder\Profile;
use LightSaml\Build\Container\BuildContainerInterface;
use LightSaml\Builder\Context\ProfileContextBuilder;
abstract class AbstractProfileBuilder implements ProfileBuilderInterface
{
/** @var BuildContainerInterface */
protected $container;
public function __construct(BuildContainerInterface $buildContainer)
{
$this->container = $buildContainer;
}
/**
* @return \LightSaml\Action\CompositeAction
*/
public function buildAction()
{
return $this->getActionBuilder()->build();
}
/**
* @return \LightSaml\Context\Profile\ProfileContext
*/
public function buildContext()
{
$builder = new ProfileContextBuilder();
$builder
->setProfileId($this->getProfileId())
->setRequest($this->container->getSystemContainer()->getRequest())
->setProfileRole($this->getProfileRole())
->setOwnEntityDescriptorProvider($this->container->getOwnContainer()->getOwnEntityDescriptorProvider())
;
return $builder->build();
}
/**
* @return string
*/
abstract protected function getProfileId();
/**
* @return string
*/
abstract protected function getProfileRole();
/**
* @return \LightSaml\Builder\Action\ActionBuilderInterface
*/
abstract protected function getActionBuilder();
}

View File

@@ -0,0 +1,35 @@
<?php
namespace LightSaml\Builder\Profile\Metadata;
use LightSaml\Builder\Action\Profile\Metadata\MetadataActionBuilder;
use LightSaml\Builder\Profile\AbstractProfileBuilder;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Profile\Profiles;
class MetadataProfileBuilder extends AbstractProfileBuilder
{
/**
* @return string
*/
protected function getProfileId()
{
return Profiles::METADATA;
}
/**
* @return string
*/
protected function getProfileRole()
{
return ProfileContext::ROLE_NONE;
}
/**
* @return \LightSaml\Builder\Action\ActionBuilderInterface
*/
protected function getActionBuilder()
{
return new MetadataActionBuilder($this->container);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace LightSaml\Builder\Profile;
interface ProfileBuilderInterface
{
/**
* @return \LightSaml\Action\CompositeAction
*/
public function buildAction();
/**
* @return \LightSaml\Context\Profile\ProfileContext
*/
public function buildContext();
}

View File

@@ -0,0 +1,41 @@
<?php
namespace LightSaml\Builder\Profile\WebBrowserSso\Sp;
use LightSaml\Builder\Action\Profile\SingleSignOn\Sp\SsoSpReceiveResponseActionBuilder;
use LightSaml\Builder\Action\Profile\SingleSignOn\Sp\SsoSpValidateAssertionActionBuilder;
use LightSaml\Builder\Profile\AbstractProfileBuilder;
use LightSaml\Context\Profile\ProfileContext;
use LightSaml\Profile\Profiles;
class SsoSpReceiveResponseProfileBuilder extends AbstractProfileBuilder
{
/**
* @return string
*/
protected function getProfileId()
{
return Profiles::SSO_SP_RECEIVE_RESPONSE;
}
/**
* @return string
*/
protected function getProfileRole()
{
return ProfileContext::ROLE_SP;
}
/**
* @return \LightSaml\Builder\Action\ActionBuilderInterface
*/
protected function getActionBuilder()
{
$result = new SsoSpReceiveResponseActionBuilder(
$this->container,
new SsoSpValidateAssertionActionBuilder($this->container)
);
return $result;
}
}

Some files were not shown because too many files have changed in this diff Show More