Source of file OAuthClient.php

Size: 10,781 Bytes - Last Modified: 2019-08-09T18:27:54+00:00

/home/travis/build/zerospam/sdk-framework/src/Client/OAuthClient.php

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
<?php
/**
 * Created by PhpStorm.
 * User: aaflalo
 * Date: 18-06-01
 * Time: 09:42.
 */

namespace ZEROSPAM\Framework\SDK\Client;

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\RequestOptions;
use League\OAuth2\Client\Token\AccessToken;
use Psr\Http\Message\ResponseInterface;
use ZEROSPAM\Framework\SDK\Client\Exception\SDKException;
use ZEROSPAM\Framework\SDK\Client\Exception\TooManyRetriesException;
use ZEROSPAM\Framework\SDK\Client\Middleware\IMiddleware;
use ZEROSPAM\Framework\SDK\Client\Middleware\IPreRequestMiddleware;
use ZEROSPAM\Framework\SDK\Client\Middleware\IRefreshTokenMiddleware;
use ZEROSPAM\Framework\SDK\Config\IOAuthConfiguration;
use ZEROSPAM\Framework\SDK\Exception\Middleware\NoMiddlewareSetException;
use ZEROSPAM\Framework\SDK\Request\Api\IRequest;
use ZEROSPAM\Framework\SDK\Request\Type\RequestType;
use ZEROSPAM\Framework\SDK\Response\Api\BaseResponse;
use ZEROSPAM\Framework\SDK\Response\Api\IRateLimitedResponse;
use ZEROSPAM\Framework\SDK\Response\Api\IResponse;
use ZEROSPAM\Framework\SDK\Response\RateLimit\RateLimitData;
use ZEROSPAM\Framework\SDK\Utils\JSON\JSONParsing;

/**
 * Class OAuthClient
 *
 * Client for OAuth server interaction
 *
 * @package ZEROSPAM\Framework\SDK\Client
 */
class OAuthClient implements IOAuthClient
{
    /**
     * @var IOAuthConfiguration
     */
    private $configuration;

    /**
     * @var RateLimitData
     */
    private $rateLimit;
    /**
     * @var ClientInterface
     */
    private $guzzleClient;
    /**
     * @var AccessToken
     */
    private $token;

    /**
     * @var \ZEROSPAM\Framework\SDK\Client\Middleware\IMiddleware[][]
     */
    private $postRequestMiddlewares = [];
    /**
     * @var IPreRequestMiddleware[]
     */
    private $preRequestMiddlewares = [];

    /**
     * @var IRefreshTokenMiddleware[]
     */
    private $refreshTokenMiddlewares = [];

    /**
     * @var IRequest
     */
    private $lastRequest;

    /**
     * OauthClient constructor.
     *
     * @param IOAuthConfiguration  $configuration
     * @param AccessToken          $token
     * @param ClientInterface|null $guzzleClient Only set if you want to override the default client
     */
    public function __construct(IOAuthConfiguration $configuration, AccessToken $token, ClientInterface $guzzleClient = null)
    {
        $this->configuration = $configuration;
        $this->rateLimit     = new RateLimitData();
        $this->token         = $token;
        $this->guzzleClient  = $guzzleClient;
        if ($this->guzzleClient == null) {
            $this->guzzleClient = new Client(['base_uri' => $this->configuration->getEndPoint()]);
        }
        foreach ($this->configuration->defaultMiddlewares() as $middleware) {
            $this->registerMiddleware($middleware);
        }
        foreach ($this->configuration->defaultPreRequestMiddlewares() as $middleware) {
            $this->registerPreRequestMiddleware($middleware);
        }
    }

    /**
     * Register the given middleware.
     *
     * @param \ZEROSPAM\Framework\SDK\Client\Middleware\IMiddleware $middleware
     *
     * @return IOAuthClient
     */
    public function registerMiddleware(IMiddleware $middleware): IOAuthClient
    {
        $middleware->setClient($this);
        foreach ($middleware::statusCode() as $statusCode) {
            $this->postRequestMiddlewares[$statusCode][] = $middleware;
        }

        return $this;
    }

    /**
     * Register a pre request middleware
     *
     * @param IPreRequestMiddleware $middleware
     *
     * @return IOAuthClient
     */
    public function registerPreRequestMiddleware(IPreRequestMiddleware $middleware): IOAuthClient
    {
        $this->preRequestMiddlewares[get_class($middleware)] = $middleware;

        return $this;
    }

    /**
     * Register a middleware to take care of refresh token
     *
     * @param IRefreshTokenMiddleware $middleware
     *
     * @return IOAuthClient
     */
    public function registerRefreshTokenMiddleware(IRefreshTokenMiddleware $middleware): IOAuthClient
    {
        $middleware->setClient($this);
        $this->refreshTokenMiddlewares[get_class($middleware)] = $middleware;

        return $this;
    }

    /**
     * Unregister the middleware.
     *
     * In fact, all middleware having the same class
     *
     * @param \ZEROSPAM\Framework\SDK\Client\Middleware\IMiddleware $middleware
     *
     * @return IOAuthClient
     */
    public function unregisterMiddleware(IMiddleware $middleware): IOAuthClient
    {
        $middlewareClass = get_class($middleware);
        foreach ($middleware::statusCode() as $statusCode) {
            if (!isset($this->postRequestMiddlewares[$statusCode])) {
                continue;
            }
            $result = array_filter(
                $this->postRequestMiddlewares[$statusCode],
                function (IMiddleware $currMiddleware) use ($middlewareClass) {
                    return get_class($currMiddleware) != $middlewareClass;
                }
            );

            if (empty($result)) {
                unset($this->postRequestMiddlewares[$statusCode]);
            } else {
                $this->postRequestMiddlewares[$statusCode] = $result;
            }
        }

        return $this;
    }

    /**
     * UnRegister a pre request middleware
     *
     * @param IPreRequestMiddleware $middleware
     *
     * @return IOAuthClient
     */
    public function unregisterPreRequestMiddleware(IPreRequestMiddleware $middleware): IOAuthClient
    {
        unset($this->preRequestMiddlewares[get_class($middleware)]);

        return $this;
    }

    /**
     * UnRegister a refreshToken middleware
     *
     * @param IRefreshTokenMiddleware $middleware
     *
     * @return IOAuthClient
     */
    public function unregisterRefreshTokenMiddleware(IRefreshTokenMiddleware $middleware): IOAuthClient
    {
        unset($this->refreshTokenMiddlewares[get_class($middleware)]);

        return $this;
    }

    /**
     * Refresh token.
     *
     * @throws NoMiddlewareSetException
     */
    public function refreshToken(): AccessToken
    {
        if (empty($this->refreshTokenMiddlewares)) {
            throw new NoMiddlewareSetException('No refresh token middleware present.');
        }
        $token = $this->token;
        foreach ($this->refreshTokenMiddlewares as $middleware) {
            $token = $middleware->handleRefreshToken($token, $this->lastRequest->tries());
        }

        return $this->token = $token;
    }

    /**
     * Currently used access token.
     *
     * @return AccessToken
     */
    public function getToken(): AccessToken
    {
        return $this->token;
    }

    /**
     * Set the AccessToken
     *
     * @param AccessToken $token
     */
    public function setToken(AccessToken $token): void
    {
        $this->token = $token;
    }

    /**
     * Get linked configuration
     *
     * @return IOAuthConfiguration
     */
    public function getConfiguration(): IOAuthConfiguration
    {
        return $this->configuration;
    }


    /**
     * Process the given request and return an array containing the results.
     *
     * @param IRequest $request
     *
     * @return IResponse
     */
    public function processRequest(IRequest $request): IResponse
    {
        $this->lastRequest = $request;
        foreach ($this->preRequestMiddlewares as $middleware) {
            $middleware->handle($request);
        }
        $request->incrementTries();

        $headers = $this->configuration->getProvider()->getHeaders($this->token);

        $options = $request->requestOptions();
        if (isset($options[RequestOptions::HEADERS])) {
            $options[RequestOptions::HEADERS] = array_merge($options[RequestOptions::HEADERS], $headers);
        } else {
            $options[RequestOptions::HEADERS] = $headers;
        }

        try {
            $response   = $this->guzzleClient->request($request->httpType()->getValue(), $request->toUri(), $options);
            $parsedData = JSONParsing::responseToJson($response);

            if (isset($this->postRequestMiddlewares[$response->getStatusCode()])) {
                foreach ($this->postRequestMiddlewares[$response->getStatusCode()] as $middleware) {
                    $parsedData = $middleware->handle($request, $response, $parsedData);
                }
            }
        } catch (BadResponseException $e) {
            /**
             * Check status code.
             */
            $response = $e->getResponse();
            $this->processThrottleData($response);

            if (!isset($this->postRequestMiddlewares[$response->getStatusCode()])) {
                throw new SDKException($e->getMessage(), $e->getCode(), $e);
            }
            $parsedData = JSONParsing::responseToJson($response);

            foreach ($this->postRequestMiddlewares[$response->getStatusCode()] as $middleware) {
                $parsedData = $middleware->handle($request, $response, $parsedData);
            }
        } catch (RequestException $e) {
            if ($request->requestType()->is(RequestType::HTTP_GET())
                && $request->tries() < 3) {
                return $this->processRequest($request);
            }

            throw new TooManyRetriesException($e->getMessage(), $e->getCode(), $e);
        } catch (\GuzzleHttp\Exception\GuzzleException $e) {
            throw new SDKException($e->getMessage(), $e->getCode(), $e);
        }

        /**
         * @var $data BaseResponse
         */
        $data = $request->processResponse($parsedData);

        if ($data instanceof BaseResponse) {
            $data->setRawData($parsedData);
        }

        if ($data instanceof IRateLimitedResponse) {
            $this->processThrottleData($response);
            $data->setRateLimit($this->rateLimit);
        }

        $request->setResponse($data);

        return $data;
    }

    /**
     * Process the rate limit.
     *
     * @param ResponseInterface $response
     */
    private function processThrottleData(ResponseInterface $response)
    {
        if (empty($rateLimit = $response->getHeader('X-RateLimit-Limit'))) {
            return;
        }

        if (empty($remaining = $response->getHeader('X-RateLimit-Remaining'))) {
            return;
        }

        if (!empty($reset = $response->getHeader('X-RateLimit-Reset'))) {
            $this->rateLimit->setEndOfThrottle(intval($reset[0]));
        } else {
            $this->rateLimit->setEndOfThrottle(null);
        }

        $this->rateLimit->setMaxPerMinute(intval($rateLimit[0]))->setRemaining(intval($remaining[0]));
    }
}