<?php

/**
 * @file classes/security/authorization/internal/ApiCsrfMiddleware.php
 *
 * Copyright (c) 2014-2021 Simon Fraser University
 * Copyright (c) 2000-2021 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class ApiCsrfMiddleware
 *
 * @ingroup security_authorization
 *
 * @brief Slim middleware which requires a CSRF token for POST, PUT and DELETE
 *  operations whenever an API Token is not in use.
 */

namespace PKP\security\authorization\internal;

use APP\core\Application;
use PKP\core\APIResponse;
use PKP\handler\APIHandler;
use Slim\Http\Request as SlimRequest;

class ApiCsrfMiddleware
{
    /** @var APIHandler $handler Reference to api handler */
    protected $_handler = null;

    /**
     * Constructor
     *
     */
    public function __construct(APIHandler $handler)
    {
        $this->_handler = $handler;
    }

    /**
     * Middleware invokable function
     *
     * @param SlimRequest $slimRequest request
     * @param APIResponse $response response
     * @param callable $next Next middleware
     *
     * @return APIResponse
     */
    public function __invoke($slimRequest, $response, $next)
    {
        if ($this->_isCSRFRequired($slimRequest) && !$this->_isCSRFValid($slimRequest)) {
            return $response->withJson([
                'error' => 'form.csrfInvalid',
                'errorMessage' => __('form.csrfInvalid'),
            ], 403);
        }
        $response = $next($slimRequest, $response);
        return $response;
    }

    /**
     * Check if a CSRF token is required
     *
     * @param SlimRequest $slimRequest
     *
     * @return bool
     */
    protected function _isCSRFRequired($slimRequest)
    {
        if ($this->_handler->getApiToken()) {
            return false;
        }
        $server = $slimRequest->getServerParams();
        return !empty($server['REQUEST_METHOD']) && in_array($server['REQUEST_METHOD'], ['POST', 'PUT', 'DELETE']);
    }

    /**
     * Check if the CSRF token is present and valid
     *
     * @param SlimRequest $slimRequest
     *
     * @return bool
     */
    protected function _isCSRFValid($slimRequest)
    {
        $server = $slimRequest->getServerParams();
        if (empty($server['HTTP_X_CSRF_TOKEN'])) {
            return false;
        }
        $session = Application::get()->getRequest()->getSession();
        return $session && $session->getCSRFToken() === $server['HTTP_X_CSRF_TOKEN'];
    }
}

if (!PKP_STRICT_MODE) {
    class_alias('\PKP\security\authorization\internal\ApiCsrfMiddleware', '\ApiCsrfMiddleware');
}
