<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

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;

    /**
     * @param LoggerInterface  $logger
     * @param IdStoreInterface $idStore
     */
    public function __construct(LoggerInterface $logger, IdStoreInterface $idStore)
    {
        parent::__construct($logger);

        $this->idStore = $idStore;
    }

    /**
     * @param AssertionContext $context
     *
     * @return void
     */
    protected function doExecute(AssertionContext $context)
    {
        if ($context->getAssertion()->hasBearerSubject()) {
            $this->validateBearerAssertion($context);
        }
    }

    /**
     * @param AssertionContext $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)
        );
    }

    /**
     * @param AssertionContext $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;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

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;

    /**
     * @param LoggerInterface         $logger
     * @param BindingFactoryInterface $bindingFactory
     */
    public function __construct(LoggerInterface $logger, BindingFactoryInterface $bindingFactory)
    {
        parent::__construct($logger);

        $this->bindingFactory = $bindingFactory;
    }

    /**
     * @param ProfileContext $context
     *
     * @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, array(
                'message' => $outboundContext->getSerializationContext()->getDocument()->saveXML(),
            ))
        );
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Model\Protocol;

use LightSaml\Model\Context\DeserializationContext;
use LightSaml\Model\Context\SerializationContext;
use LightSaml\Model\AbstractSamlModel;
use LightSaml\SamlConstants;

class NameIDPolicy extends AbstractSamlModel
{
    /**
     * @var string|null
     */
    protected $format;

    /**
     * @var bool|null
     */
    protected $allowCreate;

    /**
     * @var string|null
     */
    protected $spNameQualifier;

    /**
     * @param string $format
     * @param bool   $allowCreate
     */
    public function __construct($format = null, $allowCreate = null)
    {
        $this->allowCreate = $allowCreate;
        $this->format = $format;
    }

    /**
     * @param string|bool|null $allowCreate
     *
     * @return NameIDPolicy
     */
    public function setAllowCreate($allowCreate)
    {
        if (null === $allowCreate) {
            $this->allowCreate = null;
        } elseif (is_string($allowCreate) || is_int($allowCreate)) {
            $this->allowCreate = 0 == strcasecmp($allowCreate, 'true') || true === $allowCreate || 1 == $allowCreate;
        } else {
            $this->allowCreate = (bool) $allowCreate;
        }

        return $this;
    }

    /**
     * @return bool|null
     */
    public function getAllowCreate()
    {
        return $this->allowCreate;
    }

    /**
     * @return string|null
     */
    public function getAllowCreateString()
    {
        if (null === $this->allowCreate) {
            return null;
        }

        return $this->allowCreate ? 'true' : 'false';
    }

    /**
     * @param string|null $format
     *
     * @return NameIDPolicy
     */
    public function setFormat($format)
    {
        $this->format = (string) $format;

        return $this;
    }

    /**
     * @return string|null
     */
    public function getFormat()
    {
        return $this->format;
    }

    /**
     * @param string|null $spNameQualifier
     *
     * @return NameIDPolicy
     */
    public function setSPNameQualifier($spNameQualifier)
    {
        $this->spNameQualifier = $spNameQualifier;

        return $this;
    }

    /**
     * @return string|null
     */
    public function getSPNameQualifier()
    {
        return $this->spNameQualifier;
    }

    /**
     * @param \DOMNode             $parent
     * @param SerializationContext $context
     *
     * @return void
     */
    public function serialize(\DOMNode $parent, SerializationContext $context)
    {
        $result = $this->createElement('NameIDPolicy', SamlConstants::NS_PROTOCOL, $parent, $context);

        $this->attributesToXml(array('Format', 'SPNameQualifier', 'AllowCreate'), $result);
    }

    /**
     * @param \DOMNode               $node
     * @param DeserializationContext $context
     */
    public function deserialize(\DOMNode $node, DeserializationContext $context)
    {
        $this->checkXmlNodeName($node, 'NameIDPolicy', SamlConstants::NS_PROTOCOL);

        $this->attributesFromXml($node, array('Format', 'SPNameQualifier', 'AllowCreate'));
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

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
{
    /**
     * @param ProfileContext $context
     *
     * @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)
        );
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Criteria;

class CriteriaSet
{
    /** @var array|CriteriaInterface[] */
    protected $criterions = array();

    /**
     * @param CriteriaInterface[] $criterions
     */
    public function __construct(array $criterions = array())
    {
        foreach ($criterions as $criterion) {
            $this->add($criterion);
        }
    }

    /**
     * @param CriteriaInterface $criteria
     *
     * @return CriteriaSet
     */
    public function add(CriteriaInterface $criteria)
    {
        $this->criterions[] = $criteria;

        return $this;
    }

    /**
     * @param CriteriaInterface $criteria
     *
     * @return CriteriaSet
     */
    public function addIfNone(CriteriaInterface $criteria)
    {
        if (false == $this->has(get_class($criteria))) {
            $this->add($criteria);
        }

        return $this;
    }

    /**
     * @param CriteriaSet $criteriaSet
     *
     * @return CriteriaSet
     */
    public function addAll(CriteriaSet $criteriaSet)
    {
        foreach ($criteriaSet->all() as $criteria) {
            $this->add($criteria);
        }

        return $this;
    }

    /**
     * @param mixed    $condition
     * @param callable $callback
     *
     * @return CriteriaSet
     */
    public function addIf($condition, $callback)
    {
        if ($condition) {
            $criteria = call_user_func($callback);
            if ($criteria) {
                $this->add($criteria);
            }
        }

        return $this;
    }

    /**
     * @return CriteriaInterface[]|array
     */
    public function all()
    {
        return $this->criterions;
    }

    /**
     * @param string $class
     *
     * @return array|CriteriaInterface[]
     */
    public function get($class)
    {
        $result = array();
        foreach ($this->criterions as $criteria) {
            if ($criteria instanceof $class) {
                $result[] = $criteria;
            }
        }

        return $result;
    }

    /**
     * @param string $class
     *
     * @return CriteriaInterface|null
     */
    public function getSingle($class)
    {
        foreach ($this->criterions as $criteria) {
            if ($criteria instanceof $class) {
                return $criteria;
            }
        }

        return null;
    }

    /**
     * @param string $class
     *
     * @return bool
     */
    public function has($class)
    {
        foreach ($this->criterions as $criteria) {
            if ($criteria instanceof $class) {
                return true;
            }
        }

        return false;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Credential;

use LightSaml\Error\LightSamlSecurityException;
use RobRichards\XMLSecLibs\XMLSecurityKey;

class KeyHelper
{
    /**
     * @param string $key        Key content or key filename
     * @param string $passphrase Passphrase for the private key
     * @param bool   $isFile     true if $key is a filename of the key
     * @param string $type
     *
     * @return XMLSecurityKey
     */
    public static function createPrivateKey($key, $passphrase, $isFile = false, $type = XMLSecurityKey::RSA_SHA1)
    {
        $result = new XMLSecurityKey($type, array('type' => 'private'));
        $result->passphrase = $passphrase;
        $result->loadKey($key, $isFile, false);

        return $result;
    }

    /**
     * @param X509Certificate $certificate
     *
     * @return XMLSecurityKey
     */
    public static function createPublicKey(X509Certificate $certificate)
    {
        if (null == $certificate->getSignatureAlgorithm()) {
            throw new LightSamlSecurityException('Unrecognized certificate signature algorithm');
        }
        $key = new XMLSecurityKey($certificate->getSignatureAlgorithm(), array('type' => 'public'));
        $key->loadKey($certificate->toPem(), false, true);

        return $key;
    }

    /**
     * @param XMLSecurityKey $key
     * @param string         $algorithm
     *
     * @throws \LightSaml\Error\LightSamlSecurityException
     * @throws \InvalidArgumentException
     *
     * @return XMLSecurityKey
     */
    public static function castKey(XMLSecurityKey $key, $algorithm)
    {
        if (false == is_string($algorithm)) {
            throw new \InvalidArgumentException('Algorithm must be string');
        }

        // do nothing if algorithm is already the type of the key
        if ($key->type === $algorithm) {
            return $key;
        }

        $keyInfo = openssl_pkey_get_details($key->key);
        if (false === $keyInfo) {
            throw new LightSamlSecurityException('Unable to get key details from XMLSecurityKey.');
        }
        if (false == isset($keyInfo['key'])) {
            throw new LightSamlSecurityException('Missing key in public key details.');
        }

        $newKey = new XMLSecurityKey($algorithm, array('type' => 'public'));
        $newKey->loadKey($keyInfo['key']);

        return $newKey;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Model\Assertion;

use LightSaml\Model\Context\DeserializationContext;
use RobRichards\XMLSecLibs\XMLSecurityKey;

class EncryptedAssertionReader extends EncryptedElementReader
{
    /**
     * @param XMLSecurityKey[]       $inputKeys
     * @param DeserializationContext $deserializationContext
     *
     * @return Assertion
     */
    public function decryptMultiAssertion(array $inputKeys, DeserializationContext $deserializationContext)
    {
        $dom = $this->decryptMulti($inputKeys);

        return $this->getAssertionFromDom($dom, $deserializationContext);
    }

    /**
     * @param XMLSecurityKey         $credential
     * @param DeserializationContext $deserializationContext
     *
     * @return Assertion
     */
    public function decryptAssertion($credential, DeserializationContext $deserializationContext)
    {
        $dom = $this->decrypt($credential);

        return $this->getAssertionFromDom($dom, $deserializationContext);
    }

    /**
     * @param \DOMElement            $dom
     * @param DeserializationContext $deserializationContext
     *
     * @return Assertion
     */
    protected function getAssertionFromDom(\DOMElement $dom, DeserializationContext $deserializationContext)
    {
        $deserializationContext->setDocument($dom->ownerDocument);

        $assertion = new Assertion();
        $assertion->deserialize($dom, $deserializationContext);

        return $assertion;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Validator\Model\Xsd;

use LightSaml\Error\LightSamlXmlException;

class XsdValidator
{
    /**
     * @param string $xml
     *
     * @return XsdError[]
     */
    public function validateProtocol($xml)
    {
        return $this->validate($xml, 'saml-schema-protocol-2.0.xsd');
    }

    /**
     * @param string $xml
     *
     * @return XsdError[]
     */
    public function validateMetadata($xml)
    {
        return $this->validate($xml, 'saml-schema-metadata-2.0.xsd');
    }

    /**
     * @param string $xml
     * @param string $schema
     *
     * @return XsdError[]
     */
    private function validate($xml, $schema)
    {
        $result = [];
        libxml_clear_errors();
        $doc = new \DOMDocument();

        set_error_handler(function ($errno, $errstr, $errfile, $errline, array $errcontext) use (&$result) {
            $error = new XsdError(XsdError::FATAL, $errno, $errstr, 0, 0);
            $result[] = $error;
        });

        $schemaFile = __DIR__.'/../../../../../xsd/'.$schema;
        if (!is_file($schemaFile)) {
            throw new LightSamlXmlException('Invalid schema specified');
        }

        $ok = @$doc->loadXML($xml);
        if (!$ok) {
            restore_error_handler();

            return [
                new XsdError(XsdError::FATAL, 0, 'Invalid XML', 0, 0),
            ];
        }

        @$doc->schemaValidate($schemaFile);

        /** @var \LibXMLError[] $errors */
        $errors = libxml_get_errors();
        foreach ($errors as $error) {
            $err = XsdError::fromLibXMLError($error);
            $result[] = $err;
        }

        restore_error_handler();

        return $result;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml;

final class Helper
{
    const TIME_FORMAT = 'Y-m-d\TH:i:s\Z';

    /**
     * @param string $duration
     */
    public static function validateDurationString($duration)
    {
        if ($duration) {
            try {
                new \DateInterval((string) $duration);
            } catch (\Exception $ex) {
                throw new \InvalidArgumentException(sprintf("Invalid duration '%s' format", $duration), 0, $ex);
            }
        }
    }

    /**
     * @param int $time
     *
     * @return string
     */
    public static function time2string($time)
    {
        return gmdate('Y-m-d\TH:i:s\Z', $time);
    }

    /**
     * @param int|string|\DateTime $value
     *
     * @return int
     *
     * @throws \InvalidArgumentException
     */
    public static function getTimestampFromValue($value)
    {
        if (is_string($value)) {
            return self::parseSAMLTime($value);
        } elseif ($value instanceof \DateTime) {
            return $value->getTimestamp();
        } elseif (is_int($value)) {
            return $value;
        } else {
            throw new \InvalidArgumentException();
        }
    }

    /**
     * @param string $time
     *
     * @return int
     *
     * @throws \InvalidArgumentException
     */
    public static function parseSAMLTime($time)
    {
        $matches = [];
        if (0 == preg_match(
                '/^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)(?:\\.\\d+)?(Z|[+-]\\d\\d:\\d\\d)$/D',
                $time,
                $matches
            )) {
            throw new \InvalidArgumentException('Invalid SAML2 timestamp: '.$time);
        }

        return strtotime($time);
    }

    /**
     * @param int $length
     *
     * @return string
     *
     * @throws \InvalidArgumentException
     */
    public static function generateRandomBytes($length)
    {
        $length = intval($length);
        if ($length <= 0) {
            throw new \InvalidArgumentException();
        }

        if (function_exists('openssl_random_pseudo_bytes')) {
            return openssl_random_pseudo_bytes($length);
        }

        $data = '';
        for ($i = 0; $i < $length; ++$i) {
            $data .= chr(mt_rand(0, 255));
        }

        return $data;
    }

    /**
     * @param string $bytes
     *
     * @return string
     */
    public static function stringToHex($bytes)
    {
        $result = '';
        $len = strlen($bytes);
        for ($i = 0; $i < $len; ++$i) {
            $result .= sprintf('%02x', ord($bytes[$i]));
        }

        return $result;
    }

    /**
     * @return string
     */
    public static function generateID()
    {
        return '_'.self::stringToHex(self::generateRandomBytes(21));
    }

    /**
     * Is ID element at least 128 bits in length (SAML2.0 standard section 1.3.4).
     *
     * @param string $id
     *
     * @return bool
     */
    public static function validateIdString($id)
    {
        return is_string($id) && strlen(trim($id)) >= 16;
    }

    /**
     * @param string $value
     *
     * @return bool
     */
    public static function validateRequiredString($value)
    {
        return is_string($value) && strlen(trim($value)) > 0;
    }

    /**
     * @param string $value
     *
     * @return bool
     */
    public static function validateOptionalString($value)
    {
        return null === $value || self::validateRequiredString($value);
    }

    /**
     * @param string $value
     *
     * @return bool
     */
    public static function validateWellFormedUriString($value)
    {
        $value = trim($value);
        if ('' == $value || strlen($value) > 65520) {
            return false;
        }

        if (preg_match('|\s|', $value)) {
            return false;
        }

        $parts = parse_url($value);
        if (isset($parts['scheme'])) {
            if ($parts['scheme'] != rawurlencode($parts['scheme'])) {
                return false;
            }
        } else {
            return false;
        }

        return true;
    }

    /**
     * @param int $notBefore
     * @param int $now
     * @param int $allowedSecondsSkew
     *
     * @return bool
     */
    public static function validateNotBefore($notBefore, $now, $allowedSecondsSkew)
    {
        return null == $notBefore || (($notBefore - $allowedSecondsSkew) < $now);
    }

    /**
     * @param int $notOnOrAfter
     * @param int $now
     * @param int $allowedSecondsSkew
     *
     * @return bool
     */
    public static function validateNotOnOrAfter($notOnOrAfter, $now, $allowedSecondsSkew)
    {
        return null == $notOnOrAfter || ($now < ($notOnOrAfter + $allowedSecondsSkew));
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Model\Metadata;

use LightSaml\Model\Context\DeserializationContext;
use LightSaml\Model\Context\SerializationContext;
use LightSaml\Model\Assertion\Attribute;
use LightSaml\SamlConstants;

class IdpSsoDescriptor extends SSODescriptor
{
    /** @var bool|null */
    protected $wantAuthnRequestsSigned;

    /** @var SingleSignOnService[]|null */
    protected $singleSignOnServices;

    /** @var Attribute[]|null */
    protected $attributes;

    /**
     * @param bool|null $wantAuthnRequestsSigned
     *
     * @return IdpSsoDescriptor
     */
    public function setWantAuthnRequestsSigned($wantAuthnRequestsSigned)
    {
        $this->wantAuthnRequestsSigned = filter_var($wantAuthnRequestsSigned, FILTER_VALIDATE_BOOLEAN, ['flags' => FILTER_NULL_ON_FAILURE]);

        return $this;
    }

    /**
     * @return bool|null
     */
    public function getWantAuthnRequestsSigned()
    {
        return $this->wantAuthnRequestsSigned;
    }

    /**
     * @param SingleSignOnService $singleSignOnService
     *
     * @return IdpSsoDescriptor
     */
    public function addSingleSignOnService(SingleSignOnService $singleSignOnService)
    {
        if (false == is_array($this->singleSignOnServices)) {
            $this->singleSignOnServices = array();
        }
        $this->singleSignOnServices[] = $singleSignOnService;

        return $this;
    }

    /**
     * @return SingleSignOnService[]|null
     */
    public function getAllSingleSignOnServices()
    {
        return $this->singleSignOnServices;
    }

    /**
     * @param string $url
     *
     * @return SingleSignOnService[]
     */
    public function getAllSingleSignOnServicesByUrl($url)
    {
        $result = array();
        foreach ($this->getAllSingleSignOnServices() as $svc) {
            if ($svc->getLocation() == $url) {
                $result[] = $svc;
            }
        }

        return $result;
    }

    /**
     * @param string $binding
     *
     * @return SingleSignOnService[]
     */
    public function getAllSingleSignOnServicesByBinding($binding)
    {
        $result = array();
        foreach ($this->getAllSingleSignOnServices() as $svc) {
            if ($svc->getBinding() == $binding) {
                $result[] = $svc;
            }
        }

        return $result;
    }

    /**
     * @param string|null $binding
     *
     * @return SingleSignOnService|null
     */
    public function getFirstSingleSignOnService($binding = null)
    {
        foreach ($this->getAllSingleSignOnServices() as $svc) {
            if (null == $binding || $svc->getBinding() == $binding) {
                return $svc;
            }
        }

        return null;
    }

    /**
     * @param \LightSaml\Model\Assertion\Attribute $attribute
     *
     * @return IdpSsoDescriptor
     */
    public function addAttribute(Attribute $attribute)
    {
        if (false == is_array($this->attributes)) {
            $this->attributes = array();
        }
        $this->attributes[] = $attribute;

        return $this;
    }

    /**
     * @return \LightSaml\Model\Assertion\Attribute[]|null
     */
    public function getAllAttributes()
    {
        return $this->attributes;
    }

    /**
     * @param \DOMNode             $parent
     * @param SerializationContext $context
     */
    public function serialize(\DOMNode $parent, SerializationContext $context)
    {
        $result = $this->createElement('IDPSSODescriptor', SamlConstants::NS_METADATA, $parent, $context);

        parent::serialize($result, $context);

        $this->attributesToXml(array('WantAuthnRequestsSigned'), $result);

        if ($this->getAllSingleSignOnServices()) {
            foreach ($this->getAllSingleSignOnServices() as $object) {
                $object->serialize($result, $context);
            }
        }
        if ($this->getAllAttributes()) {
            foreach ($this->getAllAttributes() as $object) {
                $object->serialize($result, $context);
            }
        }
    }

    /**
     * @param \DOMNode               $node
     * @param DeserializationContext $context
     */
    public function deserialize(\DOMNode $node, DeserializationContext $context)
    {
        $this->checkXmlNodeName($node, 'IDPSSODescriptor', SamlConstants::NS_METADATA);

        parent::deserialize($node, $context);

        $this->attributesFromXml($node, array('WantAuthnRequestsSigned'));

        $this->singleSignOnServices = array();
        $this->manyElementsFromXml(
            $node,
            $context,
            'SingleSignOnService',
            'md',
            'LightSaml\Model\Metadata\SingleSignOnService',
            'addSingleSignOnService'
        );

        $this->attributes = array();
        $this->manyElementsFromXml(
            $node,
            $context,
            'Attribute',
            'saml',
            'LightSaml\Model\Assertion\Attribute',
            'addAttribute'
        );
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Resolver\Credential;

use LightSaml\Criteria\CriteriaSet;
use LightSaml\Credential\CredentialInterface;
use LightSaml\Credential\X509CredentialInterface;
use LightSaml\Credential\Criteria\X509CredentialCriteria;

class X509CredentialResolver extends AbstractQueryableResolver
{
    /**
     * @param CriteriaSet           $criteriaSet
     * @param CredentialInterface[] $arrCredentials
     *
     * @return CredentialInterface[]
     */
    public function resolve(CriteriaSet $criteriaSet, array $arrCredentials = array())
    {
        if (false == $criteriaSet->has(X509CredentialCriteria::class)) {
            return $arrCredentials;
        }

        $result = array();
        foreach ($arrCredentials as $credential) {
            if ($credential instanceof X509CredentialInterface) {
                $result[] = $credential;
            }
        }

        return $result;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

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 ProfileContext $context
     * @param string         $location
     *
     * @return CriteriaSet
     */
    protected function getCriteriaSet(ProfileContext $context, $location)
    {
        $result = parent::getCriteriaSet($context, $location);

        $result->add(new ServiceTypeCriteria(AssertionConsumerService::class));

        return $result;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\State\Request;

use LightSaml\Meta\ParameterBag;

class RequestState implements \Serializable
{
    /** @var string */
    private $id;

    /** @var ParameterBag */
    private $parameters;

    /**
     * @param string $id
     * @param mixed  $nonce
     */
    public function __construct($id = null, $nonce = null)
    {
        $this->id = $id;
        $this->parameters = new ParameterBag();
        if ($nonce) {
            $this->parameters->set('nonce', $nonce);
        }
    }

    /**
     * @param string $id
     *
     * @return RequestState
     */
    public function setId($id)
    {
        $this->id = $id;

        return $this;
    }

    /**
     * @return string
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return ParameterBag
     */
    public function getParameters()
    {
        return $this->parameters;
    }

    /**
     * @deprecated Since 1.2, to be removed in 2.0. Use getParameters() instead
     *
     * @param mixed $nonce
     *
     * @return RequestState
     */
    public function setNonce($nonce)
    {
        $this->parameters->set('nonce', $nonce);

        return $this;
    }

    /**
     * @deprecated Since 1.2, to be removed in 2.0. Use getParameters() instead
     *
     * @return mixed
     */
    public function getNonce()
    {
        return $this->parameters->get('nonce');
    }

    /**
     * (PHP 5 >= 5.1.0)<br/>
     * String representation of object.
     *
     * @see http://php.net/manual/en/serializable.serialize.php
     *
     * @return string the string representation of the object or null
     */
    public function serialize()
    {
        $nonce = $this->getNonce();

        return serialize(array($this->id, $nonce, $this->parameters->serialize()));
    }

    /**
     * @param string $serialized The string representation of the object
     *
     * @return void
     */
    public function unserialize($serialized)
    {
        $nonce = null;
        $this->parameters = new ParameterBag();
        list($this->id, $nonce, $parameters) = unserialize($serialized);
        $this->parameters->unserialize($parameters);
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Action;

use LightSaml\Context\ContextInterface;

abstract class WrappedAction implements ActionInterface
{
    /**
     * @var ActionInterface
     */
    protected $action;

    /**
     * @param ActionInterface $action
     */
    public function __construct(ActionInterface $action)
    {
        $this->action = $action;
    }

    /**
     * @param ContextInterface $context
     *
     * @return void
     */
    public function execute(ContextInterface $context)
    {
        $this->beforeAction($context);
        $this->action->execute($context);
        $this->afterAction($context);
    }

    /**
     * @param ContextInterface $context
     */
    abstract protected function beforeAction(ContextInterface $context);

    /**
     * @param ContextInterface $context
     */
    abstract protected function afterAction(ContextInterface $context);
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Resolver\Endpoint;

use LightSaml\Criteria\CriteriaSet;
use LightSaml\Model\Metadata\EndpointReference;

class CompositeEndpointResolver implements EndpointResolverInterface
{
    /** @var EndpointResolverInterface[] */
    protected $resolvers = array();

    /**
     * @param EndpointResolverInterface[] $builders
     */
    public function __construct(array $builders = array())
    {
        $this->resolvers = $builders;
    }

    /**
     * @param EndpointResolverInterface $builder
     *
     * @return CompositeEndpointResolver
     */
    public function add(EndpointResolverInterface $builder)
    {
        $this->resolvers[] = $builder;

        return $this;
    }

    /**
     * @param CriteriaSet         $criteriaSet
     * @param EndpointReference[] $candidates
     *
     * @return EndpointReference[]
     */
    public function resolve(CriteriaSet $criteriaSet, array $candidates)
    {
        $result = $candidates;

        foreach ($this->resolvers as $resolver) {
            $result = $resolver->resolve($criteriaSet, $result);
        }

        return $result;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Store\EntityDescriptor;

use LightSaml\Error\LightSamlXmlException;
use LightSaml\Model\Metadata\EntitiesDescriptor;
use LightSaml\Model\Metadata\EntityDescriptor;

class FileEntityDescriptorStore implements EntityDescriptorStoreInterface
{
    private $filename;

    /** @var EntityDescriptor|EntitiesDescriptor */
    private $object;

    /**
     * @param string $filename
     */
    public function __construct($filename)
    {
        $this->filename = $filename;
    }

    /**
     * @param string $entityId
     *
     * @return EntityDescriptor|null
     */
    public function get($entityId)
    {
        if (null == $this->object) {
            $this->load();
        }

        if ($this->object instanceof EntityDescriptor) {
            if ($this->object->getEntityID() == $entityId) {
                return $this->object;
            } else {
                return null;
            }
        } else {
            return $this->object->getByEntityId($entityId);
        }
    }

    /**
     * @param string $entityId
     *
     * @return bool
     */
    public function has($entityId)
    {
        return null != $this->get($entityId);
    }

    /**
     * @return array|EntityDescriptor[]
     */
    public function all()
    {
        if (null == $this->object) {
            $this->load();
        }

        if ($this->object instanceof EntityDescriptor) {
            return [$this->object];
        } else {
            return $this->object->getAllEntityDescriptors();
        }
    }

    private function load()
    {
        try {
            $this->object = EntityDescriptor::load($this->filename);
        } catch (LightSamlXmlException $ex) {
            $this->object = EntitiesDescriptor::load($this->filename);
        }
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

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;

    /**
     * @param LoggerInterface $logger
     * @param ActionInterface $assertionAction
     */
    public function __construct(LoggerInterface $logger, ActionInterface $assertionAction)
    {
        parent::__construct($logger);

        $this->assertionAction = $assertionAction;
    }

    /**
     * @param ProfileContext $context
     */
    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 = array();
        if ($this->assertionAction instanceof DebugPrintTreeActionInterface) {
            $arr = array_merge($arr, $this->assertionAction->debugPrintTree());
        } else {
            $arr[get_class($this->assertionAction)] = array();
        }

        $result = array(
            static::class => $arr,
        );

        return $result;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Validator\Model\Assertion;

use LightSaml\Error\LightSamlValidationException;
use LightSaml\Helper;
use LightSaml\Model\Assertion\Assertion;

class AssertionTimeValidator implements AssertionTimeValidatorInterface
{
    /**
     * @param Assertion $assertion
     * @param int       $now
     * @param int       $allowedSecondsSkew
     *
     * @throws \LightSaml\Error\LightSamlValidationException
     *
     * @return void
     */
    public function validateTimeRestrictions(Assertion $assertion, $now, $allowedSecondsSkew)
    {
        if ($allowedSecondsSkew < 0) {
            $allowedSecondsSkew = -1 * $allowedSecondsSkew;
        }

        $this->validateConditions($assertion, $now, $allowedSecondsSkew);
        $this->validateAuthnStatements($assertion, $now, $allowedSecondsSkew);
        $this->validateSubject($assertion, $now, $allowedSecondsSkew);
    }

    /**
     * @param Assertion $assertion
     * @param int       $now
     * @param int       $allowedSecondsSkew
     */
    protected function validateConditions(Assertion $assertion, $now, $allowedSecondsSkew)
    {
        if (false == $assertion->getConditions()) {
            return;
        }

        if (false == Helper::validateNotBefore($assertion->getConditions()->getNotBeforeTimestamp(), $now, $allowedSecondsSkew)) {
            throw new LightSamlValidationException('Conditions.NotBefore must not be in the future');
        }

        if (false == Helper::validateNotOnOrAfter($assertion->getConditions()->getNotOnOrAfterTimestamp(), $now, $allowedSecondsSkew)) {
            throw new LightSamlValidationException('Conditions.NotOnOrAfter must not be in the past');
        }
    }

    /**
     * @param Assertion $assertion
     * @param int       $now
     * @param int       $allowedSecondsSkew
     */
    protected function validateAuthnStatements(Assertion $assertion, $now, $allowedSecondsSkew)
    {
        if (false == $assertion->getAllAuthnStatements()) {
            return;
        }

        foreach ($assertion->getAllAuthnStatements() as $authnStatement) {
            if (false == Helper::validateNotOnOrAfter($authnStatement->getSessionNotOnOrAfterTimestamp(), $now, $allowedSecondsSkew)) {
                throw new LightSamlValidationException('AuthnStatement attribute SessionNotOnOrAfter MUST be in the future');
            }
            // TODO: Consider validating that authnStatement.AuthnInstant is in the past
        }
    }

    /**
     * @param Assertion $assertion
     * @param int       $now
     * @param int       $allowedSecondsSkew
     */
    protected function validateSubject(Assertion $assertion, $now, $allowedSecondsSkew)
    {
        if (false == $assertion->getSubject()) {
            return;
        }

        foreach ($assertion->getSubject()->getAllSubjectConfirmations() as $subjectConfirmation) {
            if ($subjectConfirmation->getSubjectConfirmationData()) {
                if (false == Helper::validateNotBefore($subjectConfirmation->getSubjectConfirmationData()->getNotBeforeTimestamp(), $now, $allowedSecondsSkew)) {
                    throw new LightSamlValidationException('SubjectConfirmationData.NotBefore must not be in the future');
                }
                if (false == Helper::validateNotOnOrAfter($subjectConfirmation->getSubjectConfirmationData()->getNotOnOrAfterTimestamp(), $now, $allowedSecondsSkew)) {
                    throw new LightSamlValidationException('SubjectConfirmationData.NotOnOrAfter must not be in the past');
                }
            }
        }
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Resolver\Credential;

use LightSaml\Credential\CredentialInterface;
use LightSaml\Credential\Criteria\PublicKeyThumbprintCriteria;
use LightSaml\Criteria\CriteriaSet;

class PublicKeyThumbprintResolver extends AbstractQueryableResolver
{
    /**
     * @param CriteriaSet           $criteriaSet
     * @param CredentialInterface[] $arrCredentials
     *
     * @return CredentialInterface[]
     */
    public function resolve(CriteriaSet $criteriaSet, array $arrCredentials = array())
    {
        if (false == $criteriaSet->has(PublicKeyThumbprintCriteria::class)) {
            return $arrCredentials;
        }

        $result = array();
        /** @var PublicKeyThumbprintCriteria $criteria */
        foreach ($criteriaSet->get(PublicKeyThumbprintCriteria::class) as $criteria) {
            foreach ($arrCredentials as $credential) {
                if ($credential->getPublicKey() && $credential->getPublicKey()->getX509Thumbprint() == $criteria->getThumbprint()) {
                    $result[] = $credential;
                }
            }
        }

        return $result;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Binding;

use LightSaml\Error\LightSamlBindingException;
use LightSaml\SamlConstants;
use Symfony\Component\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;
    }

    /**
     * @param null|EventDispatcherInterface $eventDispatcher
     *
     * @return BindingFactoryInterface
     */
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher = null)
    {
        $this->eventDispatcher = $eventDispatcher;

        return $this;
    }

    /**
     * @param Request $request
     *
     * @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));
    }

    /**
     * @param Request $request
     *
     * @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;
    }

    /**
     * @param Request $request
     *
     * @return null|string
     */
    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;
    }

    /**
     * @param Request $request
     *
     * @return null|string
     */
    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;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Store\TrustOptions;

use LightSaml\Meta\TrustOptions\TrustOptions;

class StaticTrustOptionsStore implements TrustOptionsStoreInterface
{
    /** @var TrustOptions[] */
    protected $options = array();

    /**
     * @param string       $entityId
     * @param TrustOptions $options
     *
     * @return StaticTrustOptionsStore
     */
    public function add($entityId, TrustOptions $options)
    {
        $this->options[$entityId] = $options;

        return $this;
    }

    /**
     * @param string $entityId
     *
     * @return TrustOptions|null
     */
    public function get($entityId)
    {
        return isset($this->options[$entityId]) ? $this->options[$entityId] : null;
    }

    /**
     * @param string $entityId
     *
     * @return bool
     */
    public function has($entityId)
    {
        return isset($this->options[$entityId]);
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

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 MessageContext $context
     * @param null|string    $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 = array($type => $msgStr);
        if ($message->getRelayState()) {
            $data['RelayState'] = $message->getRelayState();
        }

        $result = new SamlPostResponse($destination, $data);
        $result->renderContent();

        return $result;
    }

    /**
     * @param Request        $request
     * @param MessageContext $context
     */
    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);
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

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;

    /**
     * @param LoggerInterface            $logger
     * @param RequestStateStoreInterface $requestStore
     */
    public function __construct(LoggerInterface $logger, RequestStateStoreInterface $requestStore)
    {
        parent::__construct($logger);

        $this->requestStore = $requestStore;
    }

    /**
     * @param AssertionContext $context
     */
    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
     * @param AssertionContext $context
     *
     * @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;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Store\Credential;

use LightSaml\Credential\CredentialInterface;

class StaticCredentialStore implements CredentialStoreInterface
{
    /**
     * entityID => CredentialInterface[].
     *
     * @var array
     */
    protected $credentials = array();

    /**
     * @param string $entityId
     *
     * @return CredentialInterface[]
     */
    public function getByEntityId($entityId)
    {
        $this->checkEntityIdExistence($entityId);

        return $this->credentials[$entityId];
    }

    /**
     * @param string $entityId
     *
     * @return bool
     */
    public function has($entityId)
    {
        return array_key_exists($entityId, $this->credentials);
    }

    /**
     * @param CredentialInterface $credential
     *
     * @return StaticCredentialStore
     */
    public function add(CredentialInterface $credential)
    {
        $this->checkEntityIdExistence($credential->getEntityId());

        $this->credentials[$credential->getEntityId()][] = $credential;

        return $this;
    }

    /**
     * @param string $entityId
     */
    private function checkEntityIdExistence($entityId)
    {
        if (false == $this->has($entityId)) {
            $this->credentials[$entityId] = array();
        }
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Resolver\Credential;

use LightSaml\Model\Metadata\IdpSsoDescriptor;
use LightSaml\Model\Metadata\SpSsoDescriptor;
use LightSaml\Credential\Context\MetadataCredentialContext;
use LightSaml\Credential\CredentialInterface;
use LightSaml\Criteria\CriteriaSet;
use LightSaml\Credential\Criteria\MetadataCriteria;

class MetadataFilterResolver extends AbstractQueryableResolver
{
    /**
     * @param CriteriaSet           $criteriaSet
     * @param CredentialInterface[] $arrCredentials
     *
     * @return CredentialInterface[]
     */
    public function resolve(CriteriaSet $criteriaSet, array $arrCredentials = array())
    {
        if (false == $criteriaSet->has(MetadataCriteria::class)) {
            return $arrCredentials;
        }

        $result = array();
        foreach ($criteriaSet->get(MetadataCriteria::class) as $criteria) {
            /* @var MetadataCriteria $criteria */
            foreach ($arrCredentials as $credential) {
                /** @var MetadataCredentialContext $metadataContext */
                $metadataContext = $credential->getCredentialContext()->get(MetadataCredentialContext::class);
                if (false == $metadataContext ||
                    MetadataCriteria::TYPE_IDP == $criteria->getMetadataType() && $metadataContext->getRoleDescriptor() instanceof IdpSsoDescriptor ||
                    MetadataCriteria::TYPE_SP == $criteria->getMetadataType() && $metadataContext->getRoleDescriptor() instanceof SpSsoDescriptor
                ) {
                    $result[] = $credential;
                }
            }
        }

        return $result;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Model\Assertion;

use LightSaml\Model\Context\DeserializationContext;
use LightSaml\Model\Context\SerializationContext;
use LightSaml\SamlConstants;

class ProxyRestriction extends AbstractCondition
{
    /**
     * @var int|null
     */
    protected $count;

    /**
     * @var string[]|null
     */
    protected $audience;

    /**
     * @param int      $count
     * @param string[] $audience
     */
    public function __construct($count = null, array $audience = null)
    {
        $this->count = $count;
        $this->audience = $audience;
    }

    /**
     * @param string $audience
     *
     * @return ProxyRestriction
     */
    public function addAudience($audience)
    {
        if (false == is_array($this->audience)) {
            $this->audience = array();
        }
        $this->audience[] = (string) $audience;

        return $this;
    }

    /**
     * @return null|\string[]
     */
    public function getAllAudience()
    {
        return $this->audience;
    }

    /**
     * @param int|null $count
     *
     * @return ProxyRestriction
     */
    public function setCount($count)
    {
        $this->count = null !== $count ? intval($count) : null;

        return $this;
    }

    /**
     * @return int|null
     */
    public function getCount()
    {
        return $this->count;
    }

    /**
     * @param \DOMNode             $parent
     * @param SerializationContext $context
     *
     * @return void
     */
    public function serialize(\DOMNode $parent, SerializationContext $context)
    {
        $result = $this->createElement('ProxyRestriction', SamlConstants::NS_ASSERTION, $parent, $context);

        $this->attributesToXml(array('count'), $result);

        $this->manyElementsToXml($this->getAllAudience(), $result, $context, 'Audience');
    }

    /**
     * @param \DOMNode               $node
     * @param DeserializationContext $context
     */
    public function deserialize(\DOMNode $node, DeserializationContext $context)
    {
        $this->checkXmlNodeName($node, 'ProxyRestriction', SamlConstants::NS_ASSERTION);

        $this->attributesFromXml($node, array('count'));

        $this->manyElementsFromXml($node, $context, 'Audience', 'saml', null, 'addAudience');
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\State\Sso;

use LightSaml\Meta\ParameterBag;

class SsoState implements \Serializable
{
    /** @var string */
    private $localSessionId;

    /** @var ParameterBag */
    private $parameters;

    /** @var SsoSessionState[] */
    private $ssoSessions = array();

    public function __construct()
    {
        $this->parameters = new ParameterBag();
    }

    /**
     * @return string
     */
    public function getLocalSessionId()
    {
        return $this->localSessionId;
    }

    /**
     * @param string $localSessionId
     *
     * @return SsoState
     */
    public function setLocalSessionId($localSessionId)
    {
        $this->localSessionId = $localSessionId;

        return $this;
    }

    /**
     * @return ParameterBag
     */
    public function getParameters()
    {
        return $this->parameters;
    }

    /**
     * @deprecated Since 1.2, to be removed in 2.0. Use getParameters() instead
     *
     * @return array
     */
    public function getOptions()
    {
        return $this->parameters->all();
    }

    /**
     * @deprecated Since 1.2, to be removed in 2.0. Use getParameters() instead
     *
     * @param string $name
     * @param mixed  $value
     *
     * @return SsoState
     */
    public function addOption($name, $value)
    {
        $this->parameters->set($name, $value);

        return $this;
    }

    /**
     * @deprecated Since 1.2, to be removed in 2.0. Use getParameters() instead
     *
     * @param string $name
     *
     * @return SsoState
     */
    public function removeOption($name)
    {
        $this->parameters->remove($name);

        return $this;
    }

    /**
     * @deprecated Since 1.2, to be removed in 2.0. Use getParameters() instead
     *
     * @param string $name
     *
     * @return bool
     */
    public function hasOption($name)
    {
        return $this->parameters->has($name);
    }

    /**
     * @return SsoSessionState[]
     */
    public function getSsoSessions()
    {
        return $this->ssoSessions;
    }

    /**
     * @param SsoSessionState[] $ssoSessions
     *
     * @return SsoState
     */
    public function setSsoSessions(array $ssoSessions)
    {
        $this->ssoSessions = array();
        foreach ($ssoSessions as $ssoSession) {
            $this->addSsoSession($ssoSession);
        }

        return $this;
    }

    /**
     * @param SsoSessionState $ssoSessionState
     *
     * @return SsoState
     */
    public function addSsoSession(SsoSessionState $ssoSessionState)
    {
        $this->ssoSessions[] = $ssoSessionState;

        return $this;
    }

    /**
     * @param $idpEntityId
     * @param $spEntityId
     * @param $nameId
     * @param $nameIdFormat
     * @param $sessionIndex
     *
     * @return SsoSessionState[]
     */
    public function filter($idpEntityId, $spEntityId, $nameId, $nameIdFormat, $sessionIndex)
    {
        $result = array();

        foreach ($this->ssoSessions as $ssoSession) {
            if ((!$idpEntityId || $ssoSession->getIdpEntityId() === $idpEntityId) &&
                (!$spEntityId || $ssoSession->getSpEntityId() === $spEntityId) &&
                (!$nameId || $ssoSession->getNameId() === $nameId) &&
                (!$nameIdFormat || $ssoSession->getNameIdFormat() === $nameIdFormat) &&
                (!$sessionIndex || $ssoSession->getSessionIndex() === $sessionIndex)
            ) {
                $result[] = $ssoSession;
            }
        }

        return $result;
    }

    /**
     * @param callable $callback
     *
     * @return SsoState
     */
    public function modify($callback)
    {
        $this->ssoSessions = array_values(array_filter($this->ssoSessions, $callback));

        return $this;
    }

    /**
     * @return string the string representation of the object or null
     */
    public function serialize()
    {
        return serialize(array(
            $this->localSessionId,
            $this->ssoSessions,
            [],
            $this->parameters,
        ));
    }

    /**
     * @param string $serialized
     *
     * @return void
     */
    public function unserialize($serialized)
    {
        $data = unserialize($serialized);

        // add a few extra elements in the array to ensure that we have enough keys when unserializing
        // older data which does not include all properties.
        $data = array_merge($data, array_fill(0, 5, null));
        $oldOptions = null;

        list(
            $this->localSessionId,
            $this->ssoSessions,
            $oldOptions, // old deprecated options
            $this->parameters) = $data;

        // in case it was serialized in old way, copy old options to parameters
        if ($oldOptions && 0 == $this->parameters->count()) {
            $this->parameters->add($oldOptions);
        }
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Action;

use LightSaml\Context\ContextInterface;

class CompositeAction implements ActionInterface, DebugPrintTreeActionInterface, CompositeActionInterface
{
    /** @var ActionInterface[] */
    protected $children = array();

    /**
     * @param ActionInterface[] $children
     */
    public function __construct(array $children = array())
    {
        foreach ($children as $action) {
            $this->add($action);
        }
    }

    /**
     * @return ActionInterface[]
     */
    public function getChildren()
    {
        return $this->children;
    }

    /**
     * @param ActionInterface $action
     *
     * @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;
            }
        }
    }

    /**
     * @param ContextInterface $context
     *
     * @return void
     */
    public function execute(ContextInterface $context)
    {
        foreach ($this->children as $action) {
            $action->execute($context);
        }
    }

    /**
     * @return array
     */
    public function debugPrintTree()
    {
        $arr = array();
        foreach ($this->children as $childAction) {
            if ($childAction instanceof DebugPrintTreeActionInterface) {
                $arr = array_merge($arr, $childAction->debugPrintTree());
            } else {
                $arr = array_merge($arr, array(get_class($childAction) => array()));
            }
        }

        $result = array(
            static::class => $arr,
        );

        return $result;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return json_encode($this->debugPrintTree(), JSON_PRETTY_PRINT);
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Builder\Action;

use LightSaml\Action\ActionInterface;
use LightSaml\Action\CompositeAction;

class CompositeActionBuilder implements ActionBuilderInterface
{
    /**
     * int priority => ActionInterface[].
     *
     * @var array
     */
    private $actions = array();

    /** @var int */
    protected $increaseStep = 5;

    /** @var int */
    private $biggestPriority = 0;

    /**
     * @param ActionInterface $action
     * @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] = array();
        }
        $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;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Action;

use LightSaml\Context\ContextInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;

class DispatchEventAction implements ActionInterface
{
    /** @var EventDispatcherInterface */
    protected $eventDispatcher;

    /** @var string */
    protected $event;

    /**
     * @param EventDispatcherInterface $eventDispatcher
     * @param string                   $event
     */
    public function __construct(EventDispatcherInterface $eventDispatcher, $event)
    {
        $this->eventDispatcher = $eventDispatcher;
        $this->event = $event;
    }

    /**
     * @param ContextInterface $context
     *
     * @return void
     */
    public function execute(ContextInterface $context)
    {
        $this->eventDispatcher->dispatch($this->event, new GenericEvent($context));
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Model\Metadata;

use LightSaml\Helper;
use LightSaml\Model\Context\DeserializationContext;
use LightSaml\Model\Context\SerializationContext;
use LightSaml\Model\XmlDSig\Signature;
use LightSaml\SamlConstants;

class EntitiesDescriptor extends Metadata
{
    /** @var int */
    protected $validUntil;

    /** @var string */
    protected $cacheDuration;

    /** @var string */
    protected $id;

    /** @var string */
    protected $name;

    /** @var Signature */
    protected $signature;

    /** @var EntitiesDescriptor[]|EntityDescriptor[] */
    protected $items = array();

    /**
     * @param string $filename
     *
     * @return EntitiesDescriptor
     */
    public static function load($filename)
    {
        return self::loadXml(file_get_contents($filename));
    }

    /**
     * @param string $xml
     *
     * @return EntitiesDescriptor
     */
    public static function loadXml($xml)
    {
        $context = new DeserializationContext();
        $context->getDocument()->loadXML($xml);
        $ed = new self();
        $ed->deserialize($context->getDocument(), $context);

        return $ed;
    }

    /**
     * @param string $cacheDuration
     *
     * @return EntitiesDescriptor
     *
     * @throws \InvalidArgumentException
     */
    public function setCacheDuration($cacheDuration)
    {
        Helper::validateDurationString($cacheDuration);

        $this->cacheDuration = $cacheDuration;

        return $this;
    }

    /**
     * @return string
     */
    public function getCacheDuration()
    {
        return $this->cacheDuration;
    }

    /**
     * @param string $id
     *
     * @return EntitiesDescriptor
     */
    public function setID($id)
    {
        $this->id = (string) $id;

        return $this;
    }

    /**
     * @return string
     */
    public function getID()
    {
        return $this->id;
    }

    /**
     * @param string $name
     *
     * @return EntitiesDescriptor
     */
    public function setName($name)
    {
        $this->name = (string) $name;

        return $this;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param \LightSaml\Model\XmlDSig\Signature $signature
     *
     * @return EntitiesDescriptor
     */
    public function setSignature(Signature $signature)
    {
        $this->signature = $signature;

        return $this;
    }

    /**
     * @return \LightSaml\Model\XmlDSig\Signature
     */
    public function getSignature()
    {
        return $this->signature;
    }

    /**
     * @param int|string $validUntil
     *
     * @return EntitiesDescriptor
     *
     * @throws \InvalidArgumentException
     */
    public function setValidUntil($validUntil)
    {
        $value = Helper::getTimestampFromValue($validUntil);
        if ($value < 0) {
            throw new \InvalidArgumentException('Invalid validUntil');
        }
        $this->validUntil = $value;

        return $this;
    }

    /**
     * @return string
     */
    public function getValidUntilString()
    {
        if ($this->validUntil) {
            return Helper::time2string($this->validUntil);
        }

        return null;
    }

    /**
     * @return int
     */
    public function getValidUntilTimestamp()
    {
        return $this->validUntil;
    }

    /**
     * @return \DateTime|null
     */
    public function getValidUntilDateTime()
    {
        if ($this->validUntil) {
            return new \DateTime('@'.$this->validUntil);
        }

        return null;
    }

    /**
     * @param EntitiesDescriptor|EntityDescriptor $item
     *
     * @return EntitiesDescriptor
     *
     * @throws \InvalidArgumentException
     */
    public function addItem($item)
    {
        if (false == $item instanceof self && false == $item instanceof EntityDescriptor) {
            throw new \InvalidArgumentException('Expected EntitiesDescriptor or EntityDescriptor');
        }
        if ($item === $this) {
            throw new \InvalidArgumentException('Circular reference detected');
        }
        if ($item instanceof self) {
            if ($item->containsItem($this)) {
                throw new \InvalidArgumentException('Circular reference detected');
            }
        }
        $this->items[] = $item;

        return $this;
    }

    /**
     * @param EntitiesDescriptor|EntityDescriptor $item
     *
     * @return bool
     *
     * @throws \InvalidArgumentException
     */
    public function containsItem($item)
    {
        if (false == $item instanceof self && false == $item instanceof EntityDescriptor) {
            throw new \InvalidArgumentException('Expected EntitiesDescriptor or EntityDescriptor');
        }
        foreach ($this->items as $i) {
            if ($i === $item) {
                return true;
            }
            if ($i instanceof self) {
                if ($i->containsItem($item)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * @return EntitiesDescriptor[]|EntityDescriptor[]
     */
    public function getAllItems()
    {
        return $this->items;
    }

    /**
     * @return EntityDescriptor[]
     */
    public function getAllEntityDescriptors()
    {
        $result = array();
        foreach ($this->items as $item) {
            if ($item instanceof self) {
                $result = array_merge($result, $item->getAllEntityDescriptors());
            } else {
                $result[] = $item;
            }
        }

        return $result;
    }

    /**
     * @param string $entityId
     *
     * @return EntityDescriptor|null
     */
    public function getByEntityId($entityId)
    {
        foreach ($this->getAllEntityDescriptors() as $entityDescriptor) {
            if ($entityDescriptor->getEntityID() == $entityId) {
                return $entityDescriptor;
            }
        }

        return null;
    }

    /**
     * @param \DOMNode             $parent
     * @param SerializationContext $context
     *
     * @return void
     */
    public function serialize(\DOMNode $parent, SerializationContext $context)
    {
        $result = $this->createElement('EntitiesDescriptor', SamlConstants::NS_METADATA, $parent, $context);

        $this->attributesToXml(array('validUntil', 'cacheDuration', 'ID', 'Name'), $result);

        $this->singleElementsToXml(array('Signature'), $result, $context);

        $this->manyElementsToXml($this->getAllItems(), $result, $context);
    }

    /**
     * @param \DOMNode               $node
     * @param DeserializationContext $context
     */
    public function deserialize(\DOMNode $node, DeserializationContext $context)
    {
        $this->checkXmlNodeName($node, 'EntitiesDescriptor', SamlConstants::NS_METADATA);

        $this->attributesFromXml($node, array('validUntil', 'cacheDuration', 'ID', 'Name'));

        $this->singleElementsFromXml($node, $context, array(
            'Signature' => array('ds', 'LightSaml\Model\XmlDSig\SignatureXmlReader'),
        ));

        $this->manyElementsFromXml(
            $node,
            $context,
            'EntityDescriptor',
            'md',
            'LightSaml\Model\Metadata\EntityDescriptor',
            'addItem'
        );
        $this->manyElementsFromXml(
            $node,
            $context,
            'EntitiesDescriptor',
            'md',
            'LightSaml\Model\Metadata\EntitiesDescriptor',
            'addItem'
        );
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

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 LoggerInterface             $logger
     * @param SignatureValidatorInterface $signatureValidator
     * @param bool                        $requireSignature
     */
    public function __construct(LoggerInterface $logger, SignatureValidatorInterface $signatureValidator, $requireSignature = true)
    {
        parent::__construct($logger);

        $this->signatureValidator = $signatureValidator;
        $this->requireSignature = $requireSignature;
    }

    /**
     * @param AssertionContext $context
     *
     * @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, array(
                        '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);
        }
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Resolver\Credential;

use LightSaml\Credential\CredentialInterface;
use LightSaml\Criteria\CriteriaSet;
use LightSaml\Credential\Criteria\PrivateKeyCriteria;

class PrivateKeyResolver extends AbstractQueryableResolver
{
    /**
     * @param CriteriaSet           $criteriaSet
     * @param CredentialInterface[] $arrCredentials
     *
     * @return CredentialInterface[]
     */
    public function resolve(CriteriaSet $criteriaSet, array $arrCredentials = array())
    {
        if (false == $criteriaSet->has(PrivateKeyCriteria::class)) {
            return $arrCredentials;
        }

        $result = array();
        foreach ($arrCredentials as $credential) {
            if ($credential->getPrivateKey()) {
                $result[] = $credential;
            }
        }

        return $result;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Context;

abstract class AbstractContext implements ContextInterface
{
    /** @var ContextInterface|null */
    private $parent;

    /** @var ContextInterface[] */
    private $subContexts = array();

    /**
     * @return ContextInterface|null
     */
    public function getParent()
    {
        return $this->parent;
    }

    /**
     * @return ContextInterface
     */
    public function getTopParent()
    {
        if ($this->getParent()) {
            return $this->getParent()->getTopParent();
        }

        return $this;
    }

    /**
     * @param ContextInterface|null $parent
     *
     * @return ContextInterface
     */
    public function setParent(ContextInterface $parent = null)
    {
        $this->parent = $parent;

        return $this;
    }

    /**
     * @param string      $name
     * @param null|string $class
     *
     * @return ContextInterface|null
     */
    public function getSubContext($name, $class = null)
    {
        if (isset($this->subContexts[$name])) {
            return $this->subContexts[$name];
        }

        if ($class) {
            $result = $this->createSubContext($class);
            $this->addSubContext($name, $result);

            return $result;
        }

        return null;
    }

    /**
     * @param string $class
     * @param bool   $autoCreate
     *
     * @return ContextInterface|null
     */
    public function getSubContextByClass($class, $autoCreate)
    {
        return $this->getSubContext($class, $autoCreate ? $class : null);
    }

    /**
     * @param string                  $name
     * @param object|ContextInterface $subContext
     *
     * @return AbstractContext
     */
    public function addSubContext($name, $subContext)
    {
        if (false === is_object($subContext)) {
            throw new \InvalidArgumentException('Expected object or ContextInterface');
        }

        $existing = isset($this->subContexts[$name]) ? $this->subContexts[$name] : null;
        if ($existing === $subContext) {
            return $this;
        }

        $this->subContexts[$name] = $subContext;
        if ($subContext instanceof ContextInterface) {
            $subContext->setParent($this);
        }

        if ($existing instanceof ContextInterface) {
            $existing->setParent(null);
        }

        return $this;
    }

    /**
     * @param string $name
     *
     * @return ContextInterface
     */
    public function removeSubContext($name)
    {
        $subContext = $this->getSubContext($name, false);

        if ($subContext) {
            $subContext->setParent(null);
            unset($this->subContexts[$name]);
        }

        return $this;
    }

    /**
     * @param string $name
     *
     * @return bool
     */
    public function containsSubContext($name)
    {
        return isset($this->subContexts[$name]);
    }

    /**
     * @return ContextInterface
     */
    public function clearSubContexts()
    {
        foreach ($this->subContexts as $subContext) {
            $subContext->setParent(null);
        }
        $this->subContexts = array();

        return $this;
    }

    /**
     * @return \ArrayIterator
     */
    public function getIterator()
    {
        return new \ArrayIterator($this->subContexts);
    }

    /**
     * @param string $ownName
     *
     * @return array
     */
    public function debugPrintTree($ownName = 'root')
    {
        $result = array(
            $ownName => static::class,
        );

        if ($this->subContexts) {
            $arr = array();
            foreach ($this->subContexts as $name => $subContext) {
                if ($subContext instanceof ContextInterface) {
                    $arr = array_merge($arr, $subContext->debugPrintTree($name));
                } else {
                    $arr = array_merge($arr, array($name => get_class($subContext)));
                }
            }
            $result[$ownName.'__children'] = $arr;
        }

        return $result;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return json_encode($this->debugPrintTree(), JSON_PRETTY_PRINT);
    }

    /**
     * @param string $path
     *
     * @return ContextInterface
     */
    public function getPath($path)
    {
        if (is_string($path)) {
            $path = explode('/', $path);
        } elseif (false === is_array($path)) {
            throw new \InvalidArgumentException('Expected string or array');
        }

        $name = array_shift($path);
        $subContext = $this->getSubContext($name);
        if (null == $subContext) {
            return null;
        }

        if (empty($path)) {
            return $subContext;
        } else {
            return $subContext->getPath($path);
        }
    }

    /**
     * @param string $class
     *
     * @return ContextInterface
     */
    protected function createSubContext($class)
    {
        $result = new $class();

        return $result;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Context\Profile\Helper;

use LightSaml\Context\Profile\AssertionContext;
use LightSaml\Error\LightSamlContextException;
use LightSaml\Model\Assertion\EncryptedAssertionReader;

abstract class AssertionContextHelper
{
    /**
     * @param AssertionContext $context
     *
     * @return EncryptedAssertionReader
     */
    public static function getEncryptedAssertionReader(AssertionContext $context)
    {
        $result = $context->getEncryptedAssertion();
        if ($result instanceof EncryptedAssertionReader) {
            return $result;
        }

        throw new LightSamlContextException($context, 'Expected EncryptedAssertionReader');
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

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;

    /**
     * @param LoggerInterface                $logger
     * @param EntityDescriptorStoreInterface $idpEntityDescriptorProvider
     */
    public function __construct(LoggerInterface $logger, EntityDescriptorStoreInterface $idpEntityDescriptorProvider)
    {
        parent::__construct($logger);

        $this->idpEntityDescriptorProvider = $idpEntityDescriptorProvider;
    }

    /**
     * @param AssertionContext $context
     *
     * @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)
        );
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace LightSaml\Resolver\Endpoint;

use LightSaml\Criteria\CriteriaSet;
use LightSaml\Model\Metadata\EndpointReference;
use LightSaml\Resolver\Endpoint\Criteria\DescriptorTypeCriteria;

/**
 * Filters out candidate endpoints which RoleDescriptor does not match with type specified
 * in the DescriptorTypeCriteria. If criteria set does not have DescriptorTypeCriteria
 * it will return all endpoint candidates.
 */
class DescriptorTypeEndpointResolver implements EndpointResolverInterface
{
    /**
     * @param CriteriaSet         $criteriaSet
     * @param EndpointReference[] $candidates
     *
     * @return EndpointReference[]
     */
    public function resolve(CriteriaSet $criteriaSet, array $candidates)
    {
        if (false === $criteriaSet->has(DescriptorTypeCriteria::class)) {
            return $candidates;
        }

        $result = array();
        /** @var DescriptorTypeCriteria $descriptorTypeCriteria */
        foreach ($criteriaSet->get(DescriptorTypeCriteria::class) as $descriptorTypeCriteria) {
            foreach ($candidates as $endpointReference) {
                $type = $descriptorTypeCriteria->getDescriptorType();
                if ($endpointReference->getDescriptor() instanceof $type) {
                    $result[] = $endpointReference;
                }
            }
        }

        return $result;
    }
}


<?php

/*
 * This file is part of the LightSAML-Core package.
 *
 * (c) Milos Tomic <tmilos@lightsaml.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

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;

    /**
     * @param LoggerInterface           $logger
     * @param EndpointResolverInterface $endpointResolver
     */
    public function __construct(LoggerInterface $logger, EndpointResolverInterface $endpointResolver)
    {
        parent::__construct($logger);

        $this->endpointResolver = $endpointResolver;
    }

    /**
     * @param ProfileContext $context
     *
     * @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 ProfileContext $context
     * @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;
    }
}