Completed
Push — master ( d6a044...3ed8d8 )
by Thomas Mauro
01:14
created

OAuth2Plugin::setLogger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Facile\OAuth2\HttpClient;
6
7
use Facile\OAuth2\HttpClient\Authorization\AuthorizationProvider;
8
use Facile\OAuth2\HttpClient\Authorization\NullProvider;
9
use Facile\OAuth2\HttpClient\Exception\RuntimeException;
10
use Facile\OAuth2\HttpClient\Request\OAuth2Request;
11
use Facile\OAuth2\HttpClient\Request\OAuth2RequestInterface;
12
use Facile\OpenIDClient\Client\ClientInterface;
13
use Facile\OpenIDClient\Service\AuthorizationService;
14
use Facile\OpenIDClient\Token\TokenSetInterface;
15
use Http\Client\Common\Plugin;
16
use Http\Promise\Promise;
17
use function in_array;
18
use Psr\Http\Message\RequestInterface;
19
use Psr\Http\Message\ResponseInterface;
20
use Psr\Log\LoggerInterface;
21
use Psr\Log\NullLogger;
22
23
final class OAuth2Plugin implements Plugin
24
{
25
    /** @var AuthorizationService */
26
    private $authorizationService;
27
28
    /** @var ClientInterface */
29
    private $client;
30
31
    /** @var bool */
32
    private $authenticateFirst;
33
34
    /** @var AuthorizationProvider */
35
    private $authorizationProvider;
36
37
    /** @var array<string, mixed> */
38
    private $grantParams;
39
40
    /** @var array<string, mixed> */
41
    private $defaultParams = [
42
        'grant_type' => 'client_credentials',
43
    ];
44
45
    /** @var LoggerInterface */
46
    private $logger;
47
48
    /**
49
     * @param AuthorizationService $authorizationService Authorization Service
50
     * @param ClientInterface $client OAuth2 client
51
     * @param AuthorizationProvider|null $tokenProvider Token provider
52
     * @param array<string, mixed> $grantParams Grant parameters to use
53
     * @param bool $authorizationFirst Whether to authenticate before a request instead to try without authorization
54
     */
55 9
    public function __construct(
56
        AuthorizationService $authorizationService,
57
        ClientInterface $client,
58
        ?AuthorizationProvider $tokenProvider = null,
59
        array $grantParams = [],
60
        bool $authorizationFirst = true
61
    ) {
62 9
        $this->authorizationService = $authorizationService;
63 9
        $this->client = $client;
64 9
        $this->authorizationProvider = $tokenProvider ?? new NullProvider();
65 9
        $this->grantParams = array_merge(
66 9
            $this->defaultParams,
67
            $grantParams
68
        );
69 9
        $this->authenticateFirst = $authorizationFirst;
70 9
        $this->logger = new NullLogger();
71 9
    }
72
73
    /**
74
     * @param LoggerInterface $logger
75
     */
76
    public function setLogger(LoggerInterface $logger): void
77
    {
78
        $this->logger = $logger;
79
    }
80
81 9
    public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
82
    {
83 9
        $request = $this->prepareRequest($request);
84
85
        /** @var Promise $promise */
86 9
        $promise = $next($request);
87
88
        return $promise->then(function (ResponseInterface $response) use ($next, $request): ResponseInterface {
89 9
            return $this->postRequest($request, $response, $next);
90 9
        });
91
    }
92
93 9
    private function postRequest(OAuth2RequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface
94
    {
95 9
        if (in_array($response->getStatusCode(), [401, 403], true)) {
96
            // try to authenticate or re-authenticate for a new token
97 3
            $this->logger->debug('Received status code {status_code} from resource server', ['status_code' => $response->getStatusCode()]);
98
99 3
            $request = $this->authorize($request);
100
101 3
            return $next($request)->wait();
102
        }
103
104 6
        return $response;
105
    }
106
107 9
    private function prepareRequest(RequestInterface $request): OAuth2RequestInterface
108
    {
109 9
        if (! $request instanceof OAuth2RequestInterface) {
110 4
            $request = new OAuth2Request($request);
111
        }
112
113
        // Merge request grant params
114 9
        $request = $request->withGrantParams(array_merge($this->grantParams, $request->getGrantParams()));
115
116 9
        if ($request->hasHeader('Authorization')) {
117 1
            return $request;
118
        }
119
120 8
        $authorization = $this->authorizationProvider->getAuthorization($this->client, $request);
121
122 8
        if (null !== $authorization) {
123
            // if we have a provided access token, we use it for the first request
124 2
            return $this->createRequest($request, $authorization);
125
        }
126
127 6
        if ($this->authenticateFirst) {
128
            // if we need to authenticate before a request, we trigger the authentication
129 2
            return $this->authorize($request);
130
        }
131
132 4
        return $request;
133
    }
134
135 6
    private function createRequest(OAuth2RequestInterface $request, string $authorization): OAuth2RequestInterface
136
    {
137 6
        return $request->withoutHeader('Authorization')
138 6
            ->withHeader('Authorization', $authorization);
139
    }
140
141 5
    private function authorize(OAuth2RequestInterface $request): OAuth2RequestInterface
142
    {
143 5
        $startTime = microtime(true);
144 5
        $this->logger->debug('Starting authorization');
145
146 5
        $tokenSet = $this->authorizationService->grant($this->client, $request->getGrantParams());
147
148 5
        $this->logger->debug('Authorization received', ['duration' => microtime(true) - $startTime]);
149
150 5
        $accessToken = $this->parseAccessToken($tokenSet);
151
152 5
        $this->logger->debug('Received access token', ['access_token' => $accessToken]);
153
154 5
        $authorization = 'Bearer ' . $accessToken;
155 5
        $this->authorizationProvider->saveAuthorization($this->client, $request, $authorization, $tokenSet->getExpiresIn());
156
157 5
        return $this->createRequest($request, $authorization);
158
    }
159
160 5
    private function parseAccessToken(TokenSetInterface $tokenSet): string
161
    {
162 5
        $accessToken = $tokenSet->getAccessToken();
163
164 5
        if (null === $accessToken) {
165
            throw new RuntimeException('Unable to get a valid access token');
166
        }
167
168 5
        $tokenType = is_string($tokenSet->getTokenType()) ? strtolower($tokenSet->getTokenType()) : null;
169 5
        if (null !== $tokenType && 'bearer' !== $tokenType) {
170
            throw new RuntimeException(sprintf('Only Bearer token are supported, provided "%s" is not supported', $tokenType));
171
        }
172
173 5
        return $accessToken;
174
    }
175
}
176