<?php

/**
 * 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  2018 Lifestyle Webconsulting GmbH
 * @version    $Id:$
 * @link       http://www.life-style.de
 */

namespace LifeStyle\Tools\CredentialsBundle\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use LifeStyle\Tools\WebserviceBundle\Event\FilterCredentialsEvent;
use LifeStyle\Tools\WebserviceBundle\Event\Credentials as CredentialsEvents;
use LifeStyle\Tools\WebserviceBundle\Exception\InvalidCredentialsException;
use LifeStyle\Tools\CredentialsBundle\Api\Manager as ApiManager;
use LifeStyle\Tools\WebserviceBundle\Error\Type\External as ExternalError;
use LifeStyle\Tools\WebserviceBundle\Validator\Xml as XmlValidator;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Class ValidateTokenListener
 * @package LifeStyle\Tools\CredentialsBundle\EventListener
 */
class ValidateTokenListener implements EventSubscriberInterface
{

    /**
     * Api manager
     *
     * @var ApiManager
     */
    protected $apiM;

    /**
     *
     * @var RequestStack
     */
    protected $requestStack;

    /**
     * Constructor
     *
     * @param ApiManager $apiM
     */
    public function __construct(ApiManager $apiM, RequestStack $requestStack)
    {
        $this->apiM = $apiM;
        $this->requestStack = $requestStack;
    }

    public function onValidateServiceProvider(FilterCredentialsEvent $event)
    {
        if (null === ($xml = $event->getServiceProvider())) {
            $this->exception('cr11', 'CredentialsXml');
        }
        if (!$this->validateXml($xml)) {
            $this->exception('cr12', 'CredentialsXml');
        }

        $db = $this->apiM->database();

        // First check the Token
        $TokenData = $db->token()->credentialsGetActiveToken($xml->ServiceToken);
        if (empty($TokenData)) {
            $this->exception('cr31', 'CredentialsToken');
        }

        // Now check the remote Adress
        if ($TokenData->getServiceToken2Ip() != "*" && $TokenData->getServiceToken2Ip() != $event->getRemoteIp()) {
            $this->exception('cr32', 'CredentialsIp');
        }

        // Check token allowed for controller and action
        $TokenToCandAData = $db->service()->credentialsCheckTokenToControllerAndAction($xml->ServiceToken,
            $event->getControllerName(), $event->getActionName());
        if (empty($TokenToCandAData)) {
            $this->exception('cr33', 'CredentialsPermissions');
        }

        // As we have a valid service token, log usage
        $db->tokenUsage()->setIp($event->getRemoteIp());
        $db->tokenUsage()->setController($event->getControllerName());
        $db->tokenUsage()->setAction($event->getActionName());

        $db->tokenUsage()->setToken($TokenData);
    }

    /**
     * Validate xml for mandatory nodes
     *
     * @param \DOMElement $xml
     */
    private function validateXml($xml)
    {
        $validator = new XmlValidator();
        if ($validator->validate($xml, $validator->strToXml($this->xmlAllowedElements()))) {
            return true;
        }
        $this->apiM->errors()->addErrors($validator->errors()->getErrors());
        return false;
    }

    /**
     * Xml structure for credentials
     *
     * @return string
     */
    private function xmlAllowedElements()
    {
        return '<ServiceToken required="true" />'
            . '<ServiceName required="true" />';
    }

    /**
     * Add error and throw exception
     *
     * @param string $errorCode
     * @param string $shortMessage
     * @throws InvalidCredentialsException
     */
    private function exception($errorCode, $shortMessage)
    {
        $this->apiM->errors()->addError(new ExternalError($errorCode, $shortMessage));
        throw new InvalidCredentialsException();
    }

    /**
     * Get list of subscribed events
     *
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return array(CredentialsEvents::VALIDATE_SERVICEPROVIDER => 'onValidateServiceProvider');
    }

}
