<?php
/**
 * Saml response
 *
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is protected by copyright law - it is NOT Freeware.
 *
 * Any unauthorized use of this software without a valid license
 * is a violation of the license agreement and will be prosecuted by
 * civil and criminal law.
 *
 * @copyright  2015 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 */

namespace LifeStyle\Tools\SamlBundle\Saml;

/**
 * Saml response
 */
class Response
{
    /**
     * Saml config
     *
     * @var Configuration
     */
    protected $samlConfig;

    /**
     * The decoded, unprocessed XML assertion provided to the constructor.
     *
     * @var string
     */
    public $assertion;

    /**
     * A DOMDocument class loaded from the $assertion.
     *
     * @var \DOMDocument
     */
    public $document;

    /**
     * Construct the response object.
     *
     * @param Configuration $samlConfig Settings containing the necessary X.509 certificate to decode the XML.
     * @param string $assertion A UUEncoded SAML assertion from the IdP.
     */
    public function __construct(Configuration $samlConfig, $assertion)
    {
        $this->samlConfig = $samlConfig;
        $this->assertion = base64_decode($assertion);
        $this->document = new \DOMDocument();
        $this->document->loadXML($this->assertion);
    }

    /**
     * Determine if the SAML Response is valid using the certificate.
     *
     * @return bool Validate the document
     */
    public function isValid()
    {
        $xmlSec = new XmlSec($this->samlConfig, $this);
        return $xmlSec->isValid();
    }

    /**
     * Get the NameID provided by the SAML response from the IdP.
     *
     * @return string
     */
    public function getNameId()
    {
        $entries = $this->queryAssertion('/saml:Subject/saml:NameID');
        return $entries->item(0)->nodeValue;
    }

    /**
     * Get the SessionNotOnOrAfter attribute, as Unix Epoc, from the
     * AuthnStatement element.
     * Using this attribute, the IdP suggests the local session expiration
     * time.
     *
     * @return integer|null The SessionNotOnOrAfter as unix epoc or NULL if not present
     */
    public function getSessionNotOnOrAfter()
    {
        $entries = $this->queryAssertion('/saml:AuthnStatement[@SessionNotOnOrAfter]');
        if ($entries->length == 0) {
            return NULL;
        }
        $notOnOrAfter = $entries->item(0)->getAttribute('SessionNotOnOrAfter');
        return strtotime($notOnOrAfter);
    }

    /**
     * Get attributes
     *
     * @return array
     */
    public function getAttributes()
    {
        $entries = $this->queryAssertion('/saml:AttributeStatement/saml:Attribute');

        $attributes = array();
        /** @var $entry DOMNode */
        foreach ($entries as $entry) {
            $attributeName = $entry->attributes->getNamedItem('Name')->nodeValue;

            $attributeValues = array();
            foreach ($entry->childNodes as $childNode) {
                if ($childNode->nodeType == XML_ELEMENT_NODE && $childNode->tagName === 'saml:AttributeValue') {
                    $attributeValues[] = $childNode->nodeValue;
                }
            }

            $attributes[$attributeName] = $attributeValues;
        }
        return $attributes;
    }

    /**
     * Query assertion
     *
     * @param string $assertionXpath
     * @return DOMNodeList
     */
    protected function queryAssertion($assertionXpath)
    {
        $xpath = new \DOMXPath($this->document);
        $xpath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:2.0:protocol');
        $xpath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:2.0:assertion');
        $xpath->registerNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#');

        $signatureQuery = '/samlp:Response/saml:Assertion/ds:Signature/ds:SignedInfo/ds:Reference';
        $assertionReferenceNode = $xpath->query($signatureQuery)->item(0);
        if (!$assertionReferenceNode) {
            throw new \Exception('Unable to query assertion, no Signature Reference found?');
        }
        $id = substr($assertionReferenceNode->attributes->getNamedItem('URI')->nodeValue, 1);

        $nameQuery = "/samlp:Response/saml:Assertion[@ID='$id']" . $assertionXpath;
        return $xpath->query($nameQuery);
    }

}
