<?php

/**
 * Lifestyle Webconsulting GmbH
 *
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is private 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
 * @link       http://www.life-style.de
 */

namespace Sso\WebserviceBundle\Api\Credentials;

use Sso\WebserviceBundle\Api;
use Sso\WebserviceBundle\Api\Credentials\Exception as CredentialException;
use Sso\WebserviceBundle\Api\Credentials\Error as CredentialError;
use Sso\WebserviceBundle\Api\Validator\Xml as XmlValidator;

/**
 * Class Index
 * @package Sso\WebserviceBundle\Api\Credentials
 */
class Index
{

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

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

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

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

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

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

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

    /**
     * @var Api\Manager
     */
    protected $apiM;

    /**
     * @var Api\Error\Storage
     */
    protected $errors;

    /**
     * Index constructor.
     * @param Api\Manager $apiM
     */
    public function __construct(Api\Manager $apiM)
    {
        $this->apiM = $apiM;
    }

    /**
     * Validate credentials
     *
     * @param \SimpleXMLElement $simpleXmlCredentials
     * @param string $controllerName
     * @param string $actionName
     * @param string $remoteAddress
     * @return boolean
     */
    public function validateCredentials(
        \SimpleXMLElement $simpleXmlCredentials,
        $controllerName,
        $actionName,
        $remoteAddress
    ) {
        $this->_validateXml($simpleXmlCredentials);
        $this->_validateParameters($controllerName, $actionName, $remoteAddress);
        $this->_checkServiceToken();
        $this->_checkUserIdentifier();
        $this->_checkUserRoles();

        // Everything looks fine
        return true;
    }

    /**
     * Exit with error
     *
     * @param string $actionName
     * @param Api\Error\Type\Base $error
     * @throws CredentialException
     */
    private function _exception(Api\Error\Type\Base $error = null)
    {
        if (null !== $error) {
            $this->errors()->addError($error);
        }
        $exception = new CredentialException();
        $exception->setErrors($this->errors()->getErrors());
        throw $exception;
    }

    /**
     * Validate xml for mandatory nodes
     *
     * @param \DOMElement $xml
     */
    private function _validateXml($xml)
    {
        $validator = new XmlValidator();
        if (!$validator->validate($xml, $validator->strToXml($this->_xmlAllowedElements()))) {
            $this->errors()->addError(new CredentialError('cr11', 'CredentialsXml'));
            $this->errors()->addErrors($validator->errors()->getErrors());
            $this->_exception();
        }
        $this->ServiceToken = (string)$xml->ServiceProvider->ServiceToken;
        $this->Username = isset($xml->ServiceTrigger->Username) ? (string)$xml->ServiceTrigger->Username : '';
        $this->UserIdentifier = (string)$xml->ServiceTrigger->UserIdentifier;
    }

    /**
     * Xml structure for credentials
     *
     * @return string
     */
    private function _xmlAllowedElements()
    {
        return '<ServiceProvider required="true">'
            .'<ServiceToken required="true" />'
            .'<ServiceName required="true" />'
            .'</ServiceProvider>'
            .'<ServiceTrigger required="true">'
            .'<Username required="true" />'
            .'<UserIdentifier required="true" />'
            .'</ServiceTrigger>'
            .'<ServiceCountry required="true">'
            .'<Code required="true" />'
            .'</ServiceCountry>';
    }

    /**
     * Validate function parameters controller-name, action-name, remote-address
     *
     * @param string $controllerName
     * @param string $actionName
     * @param string $remoteAddress
     */
    private function _validateParameters($controllerName, $actionName, $remoteAddress)
    {
        if (empty($controllerName)) {
            $this->errors()->addError(new CredentialError('cr21', 'CredentialsController'));
        } else {
            $this->controllerName = (string)$controllerName;
        }

        if (empty($remoteAddress)) {
            $this->errors()->addError(new CredentialError('cr22', 'CredentialsRemoteAddress'));
        } else {
            $this->remoteAddress = (string)$remoteAddress;
        }

        if (empty($actionName)) {
            $this->errors()->addError(new CredentialError('cr23', 'CredentialsAction'));
        } else {
            $this->actionName = $actionName;
        }

        if ($this->errors()->hasErrors()) {
            $this->_exception();
        }
    }

    /**
     * Check service token
     */
    private function _checkServiceToken()
    {
        $db = $this->apiM->database()->serviceProvider();

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

        // Now check the remote Adress
        if ($TokenData->getServiceToken2Ip() != "*" && $TokenData->getServiceToken2Ip() != $this->remoteAddress) {
            $this->_exception(new CredentialError('cr32', 'CredentialsIp'));
        }

        // Check token allowed for controller and action
        $TokenToCandAData = $db->service()->credentialsCheckTokenToControllerAndAction($this->ServiceToken,
            $this->controllerName, $this->actionName);
        if (empty($TokenToCandAData)) {
            $this->_exception(new CredentialError('cr33', 'CredentialsPermissions'));
        }

        // As we have a valid service token, log usage
        $db->tokenUsage()->setToken($TokenData);
    }

    /**
     * Check user identifier against IdP
     */
    private function _checkUserIdentifier()
    {
        $idpValidator = new Api\IdentityProvider\Validator();

        // Validate hash
        if (!$idpValidator->validateHash($this->UserIdentifier)) {
            $this->_exception(new CredentialError('cr41', 'CredentialsUser'));
        }

        // Old validation using identify-service
        if (!$idpValidator->validateUsername($this->Username)) {
            $this->_exception(new CredentialError('cr42', 'CredentialsUser'));
        }

        if (!$this->apiM->userService()->userIdentifierValidate($this->Username, $this->UserIdentifier)) {
            $this->_exception(new CredentialError('cr43', 'CredentialsUser'));
        }
    }

    /**
     * Check user permissions
     */
    private function _checkUserRoles()
    {

    }

    /**
     * Error storage
     * @return Api\Error\Storage
     */
    public function errors()
    {
        return null !== $this->errors ? $this->errors : ($this->errors = new Api\Error\Storage());
    }
}
