Completed
Push — master ( 1de13c...bf55ce )
by Alex
33:38
created

AuthCodeGrant   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 242
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 14

Importance

Changes 22
Bugs 7 Features 2
Metric Value
wmc 29
c 22
b 7
f 2
lcom 2
cbo 14
dl 0
loc 242
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
C respondToAccessTokenRequest() 0 70 11
A getIdentifier() 0 4 1
A canRespondToAuthorizationRequest() 0 8 3
B validateAuthorizationRequest() 0 54 8
B completeAuthorizationRequest() 0 53 5
1
<?php
2
/**
3
 * @author      Alex Bilbie <[email protected]>
4
 * @copyright   Copyright (c) Alex Bilbie
5
 * @license     http://mit-license.org/
6
 *
7
 * @link        https://github.com/thephpleague/oauth2-server
8
 */
9
10
namespace League\OAuth2\Server\Grant;
11
12
use DateInterval;
13
use League\OAuth2\Server\Entities\ClientEntityInterface;
14
use League\OAuth2\Server\Entities\ScopeEntityInterface;
15
use League\OAuth2\Server\Entities\UserEntityInterface;
16
use League\OAuth2\Server\Exception\OAuthServerException;
17
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
18
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
19
use League\OAuth2\Server\RequestEvent;
20
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
21
use League\OAuth2\Server\ResponseTypes\RedirectResponse;
22
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
23
use Psr\Http\Message\ServerRequestInterface;
24
25
class AuthCodeGrant extends AbstractAuthorizeGrant
26
{
27
    /**
28
     * @var \DateInterval
29
     */
30
    private $authCodeTTL;
31
32
    /**
33
     * @param \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface     $authCodeRepository
34
     * @param \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface $refreshTokenRepository
35
     * @param \DateInterval                                                      $authCodeTTL
36
     */
37
    public function __construct(
38
        AuthCodeRepositoryInterface $authCodeRepository,
39
        RefreshTokenRepositoryInterface $refreshTokenRepository,
40
        \DateInterval $authCodeTTL
41
    ) {
42
        $this->setAuthCodeRepository($authCodeRepository);
43
        $this->setRefreshTokenRepository($refreshTokenRepository);
44
        $this->authCodeTTL = $authCodeTTL;
45
        $this->refreshTokenTTL = new \DateInterval('P1M');
46
    }
47
48
    /**
49
     * Respond to an access token request.
50
     *
51
     * @param \Psr\Http\Message\ServerRequestInterface                  $request
52
     * @param \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface $responseType
53
     * @param \DateInterval                                             $accessTokenTTL
54
     *
55
     * @throws \League\OAuth2\Server\Exception\OAuthServerException
56
     *
57
     * @return \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface
58
     */
59
    public function respondToAccessTokenRequest(
60
        ServerRequestInterface $request,
61
        ResponseTypeInterface $responseType,
62
        DateInterval $accessTokenTTL
63
    ) {
64
        // Validate request
65
        $client = $this->validateClient($request);
66
        $encryptedAuthCode = $this->getRequestParameter('code', $request, null);
67
68
        if ($encryptedAuthCode === null) {
69
            throw OAuthServerException::invalidRequest('code');
70
        }
71
72
        // Validate the authorization code
73
        try {
74
            $authCodePayload = json_decode($this->decrypt($encryptedAuthCode));
75
            if (time() > $authCodePayload->expire_time) {
76
                throw OAuthServerException::invalidRequest('code', 'Authorization code has expired');
77
            }
78
79
            if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) {
80
                throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked');
81
            }
82
83
            if ($authCodePayload->client_id !== $client->getIdentifier()) {
84
                throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client');
85
            }
86
87
            // The redirect URI is required in this request
88
            $redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
89
            if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) {
90
                throw OAuthServerException::invalidRequest('redirect_uri');
91
            }
92
93
            if ($authCodePayload->redirect_uri !== $redirectUri) {
94
                throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI');
95
            }
96
97
            $scopes = [];
98
            foreach ($authCodePayload->scopes as $scopeId) {
99
                $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeId);
100
101
                if (!$scope instanceof ScopeEntityInterface) {
102
                    // @codeCoverageIgnoreStart
103
                    throw OAuthServerException::invalidScope($scopeId);
104
                    // @codeCoverageIgnoreEnd
105
                }
106
107
                $scopes[] = $scope;
108
            }
109
110
            // Finalize the requested scopes
111
            $scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $authCodePayload->user_id);
112
        } catch (\LogicException  $e) {
113
            throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code');
114
        }
115
116
        // Issue and persist access + refresh tokens
117
        $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
118
        $refreshToken = $this->issueRefreshToken($accessToken);
119
120
        // Inject tokens into response type
121
        $responseType->setAccessToken($accessToken);
122
        $responseType->setRefreshToken($refreshToken);
123
124
        // Revoke used auth code
125
        $this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id);
126
127
        return $responseType;
128
    }
129
130
    /**
131
     * Return the grant identifier that can be used in matching up requests.
132
     *
133
     * @return string
134
     */
135
    public function getIdentifier()
136
    {
137
        return 'authorization_code';
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    public function canRespondToAuthorizationRequest(ServerRequestInterface $request)
144
    {
145
        return (
146
            array_key_exists('response_type', $request->getQueryParams())
147
            && $request->getQueryParams()['response_type'] === 'code'
148
            && isset($request->getQueryParams()['client_id'])
149
        );
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155
    public function validateAuthorizationRequest(ServerRequestInterface $request)
156
    {
157
        $clientId = $this->getQueryStringParameter(
158
            'client_id',
159
            $request,
160
            $this->getServerParameter('PHP_AUTH_USER', $request)
161
        );
162
        if (is_null($clientId)) {
163
            throw OAuthServerException::invalidRequest('client_id');
164
        }
165
166
        $client = $this->clientRepository->getClientEntity(
167
            $clientId,
168
            $this->getIdentifier()
169
        );
170
171
        if ($client instanceof ClientEntityInterface === false) {
172
            $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request));
173
            throw OAuthServerException::invalidClient();
174
        }
175
176
        $redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
177
        if ($redirectUri !== null) {
178
            if (
179
                is_string($client->getRedirectUri())
180
                && (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
181
            ) {
182
                $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request));
183
                throw OAuthServerException::invalidClient();
184
            } elseif (
185
                is_array($client->getRedirectUri())
186
                && in_array($redirectUri, $client->getRedirectUri()) === false
187
            ) {
188
                $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request));
189
                throw OAuthServerException::invalidClient();
190
            }
191
        }
192
193
        $scopes = $this->validateScopes(
194
            $this->getQueryStringParameter('scope', $request),
195
            $client->getRedirectUri()
0 ignored issues
show
Bug introduced by
It seems like $client->getRedirectUri() targeting League\OAuth2\Server\Ent...rface::getRedirectUri() can also be of type array; however, League\OAuth2\Server\Gra...Grant::validateScopes() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
196
        );
197
198
        $stateParameter = $this->getQueryStringParameter('state', $request);
199
200
        $authorizationRequest = new AuthorizationRequest();
201
        $authorizationRequest->setGrantTypeId($this->getIdentifier());
202
        $authorizationRequest->setClient($client);
203
        $authorizationRequest->setRedirectUri($redirectUri);
204
        $authorizationRequest->setState($stateParameter);
205
        $authorizationRequest->setScopes($scopes);
206
207
        return $authorizationRequest;
208
    }
209
210
    /**
211
     * {@inheritdoc}
212
     */
213
    public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
214
    {
215
        if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
216
            throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
217
        }
218
219
        $finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
220
            ? is_array($authorizationRequest->getClient()->getRedirectUri())
221
                ? $authorizationRequest->getClient()->getRedirectUri()[0]
222
                : $authorizationRequest->getClient()->getRedirectUri()
223
            : $authorizationRequest->getRedirectUri();
224
225
        // The user approved the client, redirect them back with an auth code
226
        if ($authorizationRequest->isAuthorizationApproved() === true) {
227
            $authCode = $this->issueAuthCode(
228
                $this->authCodeTTL,
229
                $authorizationRequest->getClient(),
230
                $authorizationRequest->getUser()->getIdentifier(),
231
                $authorizationRequest->getRedirectUri(),
232
                $authorizationRequest->getScopes()
233
            );
234
235
            $redirectPayload['code'] = $this->encrypt(
0 ignored issues
show
Coding Style Comprehensibility introduced by
$redirectPayload was never initialized. Although not strictly required by PHP, it is generally a good practice to add $redirectPayload = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
236
                json_encode(
237
                    [
238
                        'client_id'    => $authCode->getClient()->getIdentifier(),
239
                        'redirect_uri' => $authCode->getRedirectUri(),
240
                        'auth_code_id' => $authCode->getIdentifier(),
241
                        'scopes'       => $authCode->getScopes(),
242
                        'user_id'      => $authCode->getUserIdentifier(),
243
                        'expire_time'  => (new \DateTime())->add($this->authCodeTTL)->format('U'),
244
                    ]
245
                )
246
            );
247
            $redirectPayload['state'] = $authorizationRequest->getState();
248
249
            $response = new RedirectResponse();
250
            $response->setRedirectUri(
251
                $this->makeRedirectUri(
252
                    $finalRedirectUri,
253
                    $redirectPayload
254
                )
255
            );
256
257
            return $response;
258
        }
259
260
        // The user denied the client, redirect them back with an error
261
        throw OAuthServerException::accessDenied(
262
            'The user denied the request',
263
            $finalRedirectUri
264
        );
265
    }
266
}
267