SoapBox/SignedRequests

View on GitHub
src/Middlewares/Laravel/VerifySignature.php

Summary

Maintainability
A
25 mins
Test Coverage
<?php

namespace SoapBox\SignedRequests\Middlewares\Laravel;

use Closure;
use Illuminate\Http\Request;
use SoapBox\SignedRequests\Requests\Verifier;
use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Contracts\Config\Repository as Configurations;
use SoapBox\SignedRequests\Exceptions\ExpiredRequestException;
use SoapBox\SignedRequests\Exceptions\InvalidSignatureException;
use SoapBox\SignedRequests\Exceptions\InvalidConfigurationException;

class VerifySignature
{
    /**
     * An instance of the configurations repository.
     *
     * @var \Illuminate\Contracts\Config\Repository
     */
    protected $configurations;

    /**
     * An instance of the cache repository.
     *
     * @var \Illuminate\Contracts\Cache\Repository
     */
    protected $cache;

    /**
     * Expect an instance of the configurations repository so we can lookup
     * where to find our signature, algorithm, and key from.
     *
     * @param \Illuminate\Contracts\Config\Repository $configurations
     *        An instance of the Illuminate configurations repository to lookup
     *        configurations with.
     * @param \Illuminate\Contracts\Cache\Repository $cache
     *        An instance of the Illuminate cache repository for preventing
     *        replay attacks.
     */
    public function __construct(Configurations $configurations, Cache $cache)
    {
        $this->configurations = $configurations;
        $this->cache = $cache;
    }

    /**
     * Applies the middleware to the request before moving onto the next request
     * handler.
     *
     * @throws \SoapBox\SignedRequests\Exceptions\InvalidSignatureException
     *         Thrown when the signature of the request is not valid.
     * @throws \SoapBox\SignedRequests\Exceptions\ExpiredRequestException
     *         Thrown if request replays are disabled and either the request
     *         timestamp is outside the window of tolerance, or the request has
     *         previously been served.
     * @throws \SoapBox\SignedRequests\Exceptions\InvalidConfigurationException
     *         Thrown if the request key is not defined in the config
     *
     * @param  \Illuminate\Http\Request $request
     *         An instance of the request.
     * @param  \Closure $next
     *         A callback function of where to go next.
     * @param  mixed $requestKey
     *
     * @return mixed
     */
    public function handle(Request $request, Closure $next, $requestKey = 'default')
    {
        if (!array_key_exists($requestKey, $this->configurations->get('signed-requests'))) {
            throw new InvalidConfigurationException();
        }

        $signed = new Verifier($request);

        $key = sprintf(
            '%s.%s',
            $this->configurations->get("signed-requests.$requestKey.cache-prefix"),
            $signed->getId()
        );

        $tolerance = $this->configurations->get("signed-requests.$requestKey.request-replay.tolerance");

        if (true !== $this->configurations->get("signed-requests.$requestKey.request-replay.allow")) {
            $isExpired = $signed->isExpired($tolerance);

            if ($isExpired || $this->cache->has($key)) {
                throw new ExpiredRequestException();
            }
        }

        $signed
            ->setSignatureHeader($this->configurations->get("signed-requests.$requestKey.headers.signature"))
            ->setAlgorithmHeader($this->configurations->get("signed-requests.$requestKey.headers.algorithm"));

        if (!$signed->isValid($this->configurations->get("signed-requests.$requestKey.key"))) {
            throw new InvalidSignatureException();
        }

        $this->cache->put($key, $key, $tolerance / 60);

        return $next($request);
    }
}