<?php

/**
 * Class WsFirewallProvider
 *
 * 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  2016 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 */

namespace Sso\WebserviceBundle\Security\Authentication\Provider;

use Sso\WebserviceBundle\Security\Authentication\Token\WsFirewallToken;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Sso\WebserviceBundle\Security\Api\Manager as ApiManager;

/**
 * Class WsFirewallProvider
 *
 * @copyright  2016 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 * @package    Sso\WebserviceBundle\Security\Authentication\Provider
 */
class WsFirewallProvider implements AuthenticationProviderInterface
{

    const EXCEPTION_SERVICETOKEN = "The servicetoken is not valid or not valid for this request";
    const EXCEPTION_SERVICETOKEN_USER = "You are not allowed to use this serviceToken";

    /**
     * @var UserProviderInterface
     */
    private $userProvider;

    /**
     * @var ApiManager
     */
    private $apiM;

    /**
     * WsFirewallProvider constructor.
     * @param UserProviderInterface $userProvider
     * @param ApiManager $apiM
     */
    public function __construct(UserProviderInterface $userProvider, ApiManager $apiM)
    {
        $this->userProvider = $userProvider;
        $this->apiM = $apiM;
    }

    /**
     * @param TokenInterface $token
     * @return WsFirewallToken
     */
    public function authenticate(TokenInterface $token)
    {
        $this->checkServiceToken($token);
        $this->checkUserIdentifier($token);
        $this->checkApplicationUserIdentifier($token);

        $user = new \Sso\BackendSecurityBundle\Entity\User();
        $user->setEmail($token->userName);
        $user->setUsername($token->userName);
        $user->addRole('ROLE_WEBSERVICE');

        if ($user) {
            $authenticatedToken = new WsFirewallToken($user->getRoles());
            //and here comes the funny part add all data...
            $authenticatedToken->setUser($user);
            $authenticatedToken->userIdentifier = $token->userIdentifier;
            $authenticatedToken->serviceToken = $token->serviceToken;
            $authenticatedToken->userName = $token->userName;
            $authenticatedToken->controller = $token->controller;
            $authenticatedToken->action = $token->action;
            $authenticatedToken->remoteIp = $token->remoteIp;
            $authenticatedToken->userWsRoles = $token->userWsRoles;
            $authenticatedToken->accessRestricted = $token->accessRestricted;

            if ($token->accessRestricted) {
                $authenticatedToken->applicationUsername = $token->applicationUsername;
                $authenticatedToken->applicationUserIdentifier = $token->applicationUserIdentifier;
            }

            return $authenticatedToken;
        }

        throw new AuthenticationException('Wrong authentication data', 2);
    }


    /**
     * @param TokenInterface $token
     */
    protected function checkServiceToken(TokenInterface $token)
    {
        $tokenEntity = $this->apiM->database()->serviceProvider()->token()->credentialsGetActiveToken($token->serviceToken);
        if (empty($tokenEntity)) {
            throw new AuthenticationException(static::EXCEPTION_SERVICETOKEN, 2);
        }

        // Check the remote address
        if ($tokenEntity->getServiceToken2Ip() != "*" && $tokenEntity->getServiceToken2Ip() != $token->remoteIp) {
            throw new AuthenticationException(static::EXCEPTION_SERVICETOKEN, 2);
        }

        // Check controller and action
        $tokenControllerAction = $this->apiM->database()->serviceProvider()->service()->credentialsCheckTokenToControllerAndAction($token->serviceToken, $token->controller, $token->action);
        if (empty($tokenControllerAction)) {
            throw new AuthenticationException(static::EXCEPTION_SERVICETOKEN, 2);
        }

        // Check if the user is allowed to use this serviceToken
        if(!in_array($token->serviceToken, $token->getUserWsServiceTokens())){
            throw new AuthenticationException(static::EXCEPTION_SERVICETOKEN_USER, 2);
        }

        // Save the token usage very stupid at the moment
        $this->apiM->database()->serviceProvider()->tokenUsage()->setController($token->controller);
        $this->apiM->database()->serviceProvider()->tokenUsage()->setAction($token->action);
        $this->apiM->database()->serviceProvider()->tokenUsage()->setIp($token->remoteIp);
        $this->apiM->database()->serviceProvider()->tokenUsage()->setToken($tokenEntity);
    }

    /**
     * Check user identifier against IdP
     * @param TokenInterface $token
     */
    private function checkUserIdentifier(TokenInterface $token)
    {
        // Validate user identifier
        if (!$this->apiM->userService()->userIdentifierValidate($token->userName, $token->userIdentifier)) {
            throw new AuthenticationException('Invalid UserIdentifier or Username', 2);
        }
    }

    /**
     * Check application user identifier against IdP if token has restricted access
     * @param TokenInterface $token
     */
    private function checkApplicationUserIdentifier(TokenInterface $token)
    {
        if (
            $token->accessRestricted &&
            !$this->apiM->userService()->userIdentifierValidate($token->applicationUsername, $token->applicationUserIdentifier)
        ) {
            throw new AuthenticationException('Invalid application UserIdentifier or Username', 2);
        }
    }

    /**
     * @param TokenInterface $token
     * @return boolean
     */
    public function supports(TokenInterface $token)
    {
        return $token instanceof WsFirewallToken;
    }
}
