<?php

/**
 * Class WsFirewallListener
 *
 * 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\Firewall;

use Symfony\Component\HttpFoundation\Request as RequestIn;
use Sso\WebserviceBundle\Security\Model\Request\Request as XmlRequest;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Sso\WebserviceBundle\Security\Api\Manager as ApiManager;
use Sso\WebserviceBundle\Security\Authentication\Token\WsFirewallToken;
use Sso\WebserviceBundle\Security\Exception\FirewallException;

/**
 * Class WsFirewallListener
 *
 * @copyright  2016 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 * @package    Sso\WebserviceBundle\Security\Firewall
 */
class WsFirewallListener implements ListenerInterface
{
    /**
     * @var TokenStorageInterface
     */
    private $tokenStorage;

    /**
     * @var AuthenticationManagerInterface
     */
    private $authenticationManager;

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

    /**
     * @var string
     */
    private $userIdentifier;

    /**
     * @var XmlRequest
     */
    private $requestModel;

    /**
     * @var bool
     */
    private $accessRestricted;

    /**
     * @var \Sso\WebserviceBundle\ErrorHandler\Manager
     */
    private $errorHandler;

    /**
     * @var \Sso\WebserviceBundle\ErrorHandler\ErrorStorage\Factory
     */
    private $errorStorage;

    /**
     * @var string
     */
    private $remoteIp;

    /**
     * @var array
     */
    private $userWsRoles = [];

    /**
     * @var array
     */
    private $userWsServiceTokens = [];

    /**
     * WsFirewallListener constructor.
     * @param TokenStorageInterface $tokenStorage
     * @param AuthenticationManagerInterface $authenticationManager
     * @param ApiManager $apiM
     */
    public function __construct(
        TokenStorageInterface $tokenStorage,
        AuthenticationManagerInterface $authenticationManager,
        ApiManager $apiM
    ) {
        $this->tokenStorage = $tokenStorage;
        $this->authenticationManager = $authenticationManager;
        $this->apiM = $apiM;

        $this->errorHandler = $this->apiM->getErrorHandler();
        $this->errorStorage = $this->errorHandler->errorStorage();
    }

    /**
     *
     * @param GetResponseEvent $event
     * @return mixed
     */
    public function handle(GetResponseEvent $event)
    {
        $exceptionThrown = false;

        try {
            //first set the remote ip
            $this->remoteIp = $this->getRemoteIp($event->getRequest());

            //Firewall Level One
            $this->apiM->firewall()->levelOne()->headerValidation($event->getRequest());

            //Firewall Level Two performance
            $this->apiM->firewall()->levelTwo()->checkXmlAndControllerAction(
                $event->getRequest()->getContent(),
                $this->errorHandler->errorStorage()->errors()->getController(),
                $this->errorHandler->errorStorage()->errors()->getAction()
            );

            //Firewall Level Three
            list($this->requestModel, $this->accessRestricted) = $this->apiM->firewall()->levelThree()
                ->validateRequest($event->getRequest()->getContent());

            //Firewall Level Four
            list($this->userWsRoles, $this->userWsServiceTokens) = $this->apiM->firewall()->levelFour()
                ->userHasApplicationWebserviceConsumer($this->requestModel);

            //trying to log in
            $this->authenticateRequester();

        } catch (FirewallException $exc) {
            //return error view
            $exceptionThrown = true;
        } catch (AuthenticationException $authExc) {
            $this->errorStorage->setErrorFromValues(401, 'fw004', 'hd008', 'InvalidCredentials',
                $authExc->getMessage());
            $exceptionThrown = true;
        } catch (\Exception $exc) {
            $this->errorStorage->setErrorFromValues(500, 'fw000', 'hd009', 'Internal Error', $exc->getMessage());
            $exceptionThrown = true;
        }

        if ($exceptionThrown) {
            return $this->getErrorView($event);
        }

        return true;
    }

    /**
     * @return bool
     */
    private function authenticateRequester()
    {
        $credentials = $this->requestModel->getCredentials();

        // build the dummy token
        $token = new WsFirewallToken(['ROLE_WEBSERVICE']);
        $token->userIdentifier = $credentials->getServiceTrigger()->getUserIdentifier();
        $token->serviceToken = $credentials->getServiceProvider()->getServiceToken();
        $token->userName = $credentials->getServiceTrigger()->getUserName();
        $token->controller = $this->errorHandler->errorStorage()->errors()->getController();
        $token->action = $this->errorHandler->errorStorage()->errors()->getAction();
        $token->remoteIp = $this->remoteIp;
        $token->userWsRoles = $this->userWsRoles;
        $token->userWsServiceTokens = $this->userWsServiceTokens;
        $token->accessRestricted = $this->accessRestricted;
        if ($this->accessRestricted) {
            $token->applicationUsername = $credentials->getApplicationUser()->getUserName();
            $token->applicationUserIdentifier = $credentials->getApplicationUser()->getUserIdentifier();
        }

        //try to authenticate this token
        $authToken = $this->authenticationManager->authenticate($token);
        $this->tokenStorage->setToken($authToken);

        return true;
    }

    /**
     * @param RequestIn $request
     * @return string
     */
    private function getRemoteIp(RequestIn $request)
    {
        // Search for ip behind elastic load balancer (ELB)
        if ($request->headers->has('x-forwarded-for')) {
            $parts = array_map('trim', explode(',', $request->headers->get('x-forwarded-for')));
            $ipAddress = filter_var($parts[0], FILTER_VALIDATE_IP);
        }

        // Not behind a ELB
        if (empty($ipAddress)) {
            $ipAddress = $request->getClientIp();
        }

        $this->apiM->logger()->info('Remote address: '.$ipAddress);

        return $ipAddress;
    }

    /**
     * @param GetResponseEvent $event
     * @return bool
     */
    protected function getErrorView(GetResponseEvent $event)
    {
        // error response as xml
        $response = $this->errorHandler->getSymfonyResponse();
        $response->setStatusCode($this->errorHandler->response()->response()->getErrors()->getCode() > 0 ? $this->errorHandler->response()->response()->getErrors()->getCode() : 200);
        $response->headers->set('Content-type', 'text/xml');
        $response->setContent($this->apiM->serializer()->serialize(
            $this->errorHandler->response()->response()->getErrorResponse(),
            'xml'
        ));

        // Exit with new response set
        $event->setResponse($response);

        return true;
    }
}
