<?php
/**
 * Saml response handler
 *
 * 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;

use Symfony\Bridge\Monolog\Logger;
use LifeStyle\Tools\SamlBundle\Exception\InvalidSamlResponseException;

/**
 * Saml response handler
 */
class Handler
{

    /**
     * Symfony logger
     *
     * @var Logger
     */
    protected $logger;

    /**
     * Login url
     *
     * @var string
     */
    protected $loginUrl = null;

    /**
     * Last saml response
     *
     * @var Response
     */
    protected $samlResponse = null;

    /**
     * Saml config
     *
     * @var Configuration
     */
    protected $samlConfig;

    /**
     * Constructor
     *
     * @param Logger $logger
     * @param Configuration $samlConfig SAML configuration
     */
    public function __construct(Logger $logger, Configuration $samlConfig)
    {
        $this->logger = $logger;
        $this->samlConfig = $samlConfig;
    }

    /**
     * Login user
     *
     * @param string $response
     * @return bool True on success
     */
    public function login($response)
    {
        if (false === ($samlResponse = $this->parseSamlResponse($response))) {
            $this->logger->addWarning('LOGIN: Received invalid response from identity provider. Cannot log in user.');
            return false;
        }
        $this->samlResponse = new Response($this->samlConfig, $samlResponse);
        $loggedIn = $this->samlResponse->isValid();
        $this->logger->addDebug('LOGIN: Received SAML response. User ' . ($loggedIn ? 'has been successfully authenticated.' : 'could not log in.'));
        return $loggedIn;
    }

    /**
     * Logout current user
     *
     * @param string $response
     */
    public function logout($response)
    {

    }

    /**
     * Check current user logged in
     *
     * @param string $response
     * @param string $username
     * @return bool
     */
    public function check($response, $username)
    {
        $this->loginUrl = false;
        $this->samlResponse = null;

        // Received redirect response
        if (preg_match('#Location: (.*)#', $response, $matches)) {
            $this->loginUrl = trim($matches[1]);
            $this->logger->addDebug(sprintf('CHECK: Received login redirect response to url "%s". User "%s" not logged in.', $matches[1], $username));
            return false;
        }

        // Received SAML response
        if (false !== ($samlResponse = $this->parseSamlResponse($response))) {
            $this->samlResponse = new Response($this->samlConfig, $samlResponse);

            $loggedIn = $this->samlResponse->isValid();
            $this->logger->addDebug(sprintf('CHECK: Received SAML response. User "%s" ' . ($loggedIn ? 'already logged in.' : ' not logged in yet.'), $username));

            if (0 !== strcasecmp($username, $this->getAttribute('username'))) {
                $this->logger->addWarning('CHECK: Usernames from login and saml-response does not match. This may cause some problems.');
            }

            return $loggedIn;
        }

        // Received an unknown response
        $this->logger->addWarning('CHECK: Received unknown response from identity provider.');
        return false;
    }

    /**
     * Get parameter from saml response
     *
     * @param string $name
     * @return mixed
     */
    public function getAttribute($name)
    {
        if (!$this->samlResponse || (!$attributes = $this->samlResponse->getAttributes()) || !isset($attributes[$name])) {
            return null;
        }
        if (1 < count($attributes[$name])) {
            return $attributes[$name];
        }
        return array_shift($attributes[$name]);
    }

    /**
     * Get login url from last SAML response
     *
     * @return string|null
     */
    public function getLoginUrl()
    {
        return $this->loginUrl;
    }

    /**
     * Get SAML response object
     *
     * @return Response|null
     */
    public function getSamlResponse()
    {
        return $this->samlResponse;
    }

    /**
     * Parse saml response
     *
     * @param string $response
     * @return boolean|array False on error, saml array on success
     * @throws \Exception
     */
    protected function parseSamlResponse($response)
    {

        $document = new \DOMDocument();
        if (empty($response) || true !== $document->loadHTML(substr($response, stripos($response, '<!DOCTYPE')))) {
            throw new InvalidSamlResponseException('Response from IdP is not a valid DOM-document!');
        }

        // Search for SAMLResponse
        $samlResponses = array();
        $formDatas = array();
        foreach ($document->getElementsByTagName('form') as $key => $form) {
            $formDatas[$key] = array();
            foreach ($form->attributes as $attribute) {
                $formDatas[$key][$attribute->name] = $attribute->value;
            }
            foreach ($form->getElementsByTagName('input') as $input) {
                if ($input->getAttribute('name') == 'SAMLResponse') {
                    $samlResponses[$key] = $input->getAttribute('value');
                }
            }
        }

        // No saml found
        if (empty($samlResponses)) {
            $this->logger->addDebug('Received empty saml response.');
            return false;
        }

        // Be sure to find the right form
        $samlResponse = false;
        $formData = false;
        foreach (array_reverse(array_keys($samlResponses)) as $key) {
            if (isset($formDatas[$key])) {
                $samlResponse = $samlResponses[$key];
                $formData = $formDatas[$key];
                break;
            }
        }

        // No saml found
        if (empty($samlResponse) || empty($formData)) {
            $this->logger->addDebug('Received empty saml response or missing form data.');
            return false;
        }

        return $samlResponse;
    }

}
