<?php

/*
 * This file is part of the AuthStack project.
 *
 * (c) Buckhill Ltd <development@buckhill.co.uk>
 *
 * This source file is subject to the Buckhill license that is bundled
 * with this source code in the file LICENSE.
 */

namespace AuthStack\SP;


use LightSaml\Credential\KeyHelper;
use LightSaml\Binding\BindingFactory;
use LightSaml\Model\Protocol\Response;
use AuthStack\SP\Validators\SAMLResponse;
use RobRichards\XMLSecLibs\XMLSecurityKey;
use LightSaml\Context\Profile\MessageContext;
use Symfony\Component\HttpFoundation\Request;
use LightSaml\Error\LightSamlBindingException;
use AuthStack\SP\Erors\DecryptionKeyException;
use LightSaml\Model\Context\DeserializationContext;
use AuthStack\SP\Errors\ConfigurationException;

abstract class AbstractSP
{
    protected $idp;
    protected $response;

    public function __construct(array $config, $idp)
    {
        if(!array_key_exists(sha1($idp), $config['IdP']))
        {
            throw new \Exception(sprintf('IdP %s not present in configuration.', $idp));
        }

        $this->idp = $idp;

        \Buckhill\config()->set('AuthStack', $config);
    }


    /**
     * @param Request|null $request
     *
     * @return AttributeCollection
     */
    abstract public function consume(Request $request = null);


    abstract public function authn();


    /**
     * @param Request|null $request
     *
     * @return Response
     */
    protected function checkRequest(Request $request = null)
    {
        if(is_null($request))
        {
            $request = Request::createFromGlobals();
        }

        $response = $this->getResponse($request);

        $validator = new SAMLResponse($this->idp);

        $validator->validate($response);

        $this->response = $response;

        return $response;
    }


    /**
     * @param Request $request
     * @return \LightSaml\Model\Protocol\Response
     * @throws \LogicException|LightSamlBindingException
     *
     */
    protected function getResponse(Request $request)
    {
        $factory = new BindingFactory();
        $context = new MessageContext();

        $binding = $factory->getBindingByRequest($request);

        $binding->receive($request, $context);

        return $context->getMessage();
    }


    /**
     * @return \RobRichards\XMLSecLibs\XMLSecurityKey
     * @throws DecryptionKeyException
     */
    protected function getDecryptionKey()
    {
        $key = \Buckhill\config('AuthStack.SP.keys.encryption', false);

        if(false === $key)
        {
            throw new DecryptionKeyException("Service Provider wants encrypted assertions, but there's no decryption key available.");
        }

        return KeyHelper::createPrivateKey($key['private'], $key['passphrase'], false, $key['algorithm']);
    }


    /**
     * @param Response $response
     *
     * @return \LightSaml\Model\Assertion\Assertion
     * @throws DecryptionKeyException
     */
    protected function getAssertions(Response $response)
    {
        if($this->needsDecrypting())
        {
            $assertion = $this->decrypt($response, $this->getDecryptionKey());
        }
        else
        {
            $assertion = $response->getFirstAssertion();

            if(empty($assertion))
            {
                throw new \Exception('Unencrypted assertion not found - check your configuration or if the IDP is sending unencrypted assertions.');
            }
        }

        return $assertion;
    }


    /**
     * @param Response       $response
     * @param XMLSecurityKey $key
     *
     * @return \LightSaml\Model\Assertion\Assertion
     */
    protected function decrypt(Response $response, XMLSecurityKey $key)
    {
        $ctx = new DeserializationContext();

        /** @var  $assertion \LightSaml\Model\Assertion\EncryptedAssertionReader  */
        $assertion = $response->getFirstEncryptedAssertion();

        if(empty($assertion))
        {
            throw new ConfigurationException('Encrypted assertion not found - check your configuration or if the IDP is sending encrypted assertions.');
        }

        $decrypted = $assertion->decryptAssertion($key, $ctx);

        return $decrypted;
    }

    /**
     *
     * @return bool
     */
    protected function needsDecrypting()
    {
        $idpSpecificConfiguration = \Buckhill\config(sprintf('AuthStack.IdP.%s', sha1($this->idp)));

        $key = 'sendsEncryptedAssertions';

        if(!empty($idpSpecificConfiguration) && is_array($idpSpecificConfiguration) && array_key_exists($key, $idpSpecificConfiguration))
        {
            return (bool)$idpSpecificConfiguration[$key];
        }

        return \Buckhill\config('AuthStack.SP.wantsEncryptedAssertions', true);
    }
}
