<?php
/*
 * Your installation or use of this SugarCRM file is subject to the applicable
 * terms available at
 * http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
 * If you do not agree to all of the applicable terms or do not have the
 * authority to bind the entity as an authorized representative, then do not
 * install or use this SugarCRM file.
 *
 * Copyright (C) SugarCRM Inc. All rights reserved.
 */

namespace Sugarcrm\IdentityProvider\Saml2;

use OneLogin\Saml2\Auth;
use OneLogin\Saml2\Constants;
use OneLogin\Saml2\Error;
use OneLogin\Saml2\LogoutRequest;
use OneLogin\Saml2\LogoutResponse;
use OneLogin\Saml2\Utils;
use Sugarcrm\IdentityProvider\Saml2\Builder\RequestBuilder;
use Sugarcrm\IdentityProvider\Saml2\Builder\ResponseBuilder;

/**
 * Class to support SAML POST binding.
 *
 * Class AuthPostBinding
 * @package Sugarcrm\IdentityProvider\Saml2
 */
class AuthPostBinding extends Auth
{
    /**
     * Last AuthNRequest ID or LogoutRequest ID generated by this Service Provider
     *
     * @var string
     */
    private $_lastRequestID;

    /**
     * @inheritDoc
     */
    public function login(
        $returnTo = null,
        array $parameters = [],
        $forceAuthn = false,
        $isPassive = false,
        $stay = false,
        $setNameIdPolicy = true
    ) {
        $authnRequest = $this->getRequestBuilder()->buildLoginRequest($forceAuthn, $isPassive, $setNameIdPolicy);

        $this->_lastRequestID = $authnRequest->getId();

        $samlRequest = $authnRequest->getRequest();
        $parameters['SAMLRequest'] = $samlRequest;

        if (!empty($returnTo)) {
            $parameters['RelayState'] = $returnTo;
        } else {
            $parameters['RelayState'] = $this->getSelfRoutedURLNoQuery();
        }

        $security = $this->getSettings()->getSecurityData();
        if (isset($security['authnRequestsSigned'])
            && $security['authnRequestsSigned']
            && array_key_exists('signatureAlgorithm', $security)
        ) {
            $signature = $this->buildRequestSignature(
                $samlRequest,
                $parameters['RelayState'],
                $security['signatureAlgorithm']
            );
            $parameters['SigAlg'] = $security['signatureAlgorithm'];
            $parameters['Signature'] = $signature;
        }
        return new AuthResult($this->getSSOurl(), 'POST', $parameters);
    }

    /**
     * @inheritDoc
     */
    public function getLastRequestID()
    {
        return $this->_lastRequestID;
    }

    /**
     * @inheritdoc
     *
     * @return AuthResult
     */
    public function logout(
        $returnTo = null,
        array $parameters = [],
        $nameId = null,
        $sessionIndex = null,
        $stay = true,
        $nameIdFormat = null,
        $nameIdNameQualifier = null,
        $nameIdSPNameQualifier = null
    ): AuthResult {
        if ($returnTo) {
            $parameters['RelayState'] = $returnTo;
        }

        $logoutRequest = $this->getRequestBuilder()
                              ->buildLogoutRequest(null, ['nameId' => $nameId, 'sessionIndex' => $sessionIndex]);
        $parameters['SAMLRequest'] = $logoutRequest->getRequest();
        return new AuthResult($this->getSLOurl(), 'POST', $parameters);
    }

    /**
     * Processing service SLO response.
     *
     * @param $response
     * @return LogoutResponse
     * @throws Error
     * @throws \OneLogin\Saml2\ValidationError
     */
    public function processServiceSLO($response)
    {
        $logoutResponse = $this->getResponseBuilder()->buildLogoutResponse($response);

        if (!$logoutResponse) {
            throw new Error('SAML response is not valid');
        }

        if (!$logoutResponse->isValid()) {
            throw new Error($logoutResponse->getError());
        }

        if ($logoutResponse->getStatus() !== Constants::STATUS_SUCCESS) {
            throw new Error('SAML response is not success');
        }

        return $logoutResponse;
    }

    /**
     * Processing IDP initiated SLO request.
     *
     * @param $request
     * @param null $relayState
     * @return AuthResult
     * @throws Error
     */
    public function processIdpSLO($request, $relayState = null)
    {
        $logoutRequest = $this->getRequestBuilder()->buildLogoutRequest($request);

        if (!$logoutRequest || !$logoutRequest->isValid(true)) {
            throw new Error('SAML request is not valid');
        }

        $logoutResponse = $this->getResponseBuilder()->buildLogoutResponse();
        $logoutResponse->build($logoutRequest->id);

        $resultResponse = $logoutResponse->getResponse();
        $parameters = [
            'SAMLResponse' => $resultResponse,
            'nameId' => LogoutRequest::getNameId($logoutRequest->getXML(), $this->getSettings()->getSPkey()),
        ];
        if (!empty($relayState)) {
            $parameters['RelayState'] = $relayState;
        }

        $security = $this->getSettings()->getSecurityData();
        if (!empty($security['logoutResponseSigned']) && !empty($security['signatureAlgorithm'])) {
            $signature = $this->buildResponseSignature(
                $logoutResponse,
                $relayState,
                $security['signatureAlgorithm']
            );
            $parameters['SigAlg'] = $security['signatureAlgorithm'];
            $parameters['Signature'] = $signature;
        }

        return new AuthResult($this->getSLOurl(), 'POST', $parameters);
    }

    /**
     * Should non-existing User be automatically created when comes from SAML login.
     *
     * @return bool
     */
    public function isUserProvisionNeeded()
    {
        $spSettings = $this->getSettings()->getSPData();
        return (array_key_exists('provisionUser', $spSettings)) ? $spSettings['provisionUser'] : false;
    }

    /**
     * Gets request builder instance.
     *
     * @return RequestBuilder
     */
    protected function getRequestBuilder()
    {
        return new RequestBuilder($this);
    }

    /**
     * Gets response builder instance.
     *
     * @return ResponseBuilder
     */
    protected function getResponseBuilder()
    {
        return new ResponseBuilder($this);
    }

    /**
     * Returns the routed URL of the current host + current view.
     *
     * @see Utils::getSelfRoutedURLNoQuery
     * @return string
     */
    protected function getSelfRoutedURLNoQuery()
    {
        return Utils::getSelfRoutedURLNoQuery();
    }

    /**
     * Is needed validation request id.
     *
     * @return bool
     */
    public function isRequestIdValidationNeeded()
    {
        $security = (array)$this->getSettings()->getSecurityData();
        return !empty($security['validateRequestId']);
    }
}
