Completed
Push — master ( 704578...e88511 )
by Alex
32:19
created

AuthCodeGrant::canRespondToAuthorizationRequest()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 3
eloc 5
nc 3
nop 1
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(
112
                $scopes,
113
                $this->getIdentifier(),
114
                $client,
115
                $authCodePayload->user_id
116
            );
117
        } catch (\LogicException  $e) {
118
            throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code');
119
        }
120
121
        // Issue and persist access + refresh tokens
122
        $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
123
        $refreshToken = $this->issueRefreshToken($accessToken);
124
125
        // Inject tokens into response type
126
        $responseType->setAccessToken($accessToken);
127
        $responseType->setRefreshToken($refreshToken);
128
129
        // Revoke used auth code
130
        $this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id);
131
132
        return $responseType;
133
    }
134
135
    /**
136
     * Return the grant identifier that can be used in matching up requests.
137
     *
138
     * @return string
139
     */
140
    public function getIdentifier()
141
    {
142
        return 'authorization_code';
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function canRespondToAuthorizationRequest(ServerRequestInterface $request)
149
    {
150
        return (
151
            array_key_exists('response_type', $request->getQueryParams())
152
            && $request->getQueryParams()['response_type'] === 'code'
153
            && isset($request->getQueryParams()['client_id'])
154
        );
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160
    public function validateAuthorizationRequest(ServerRequestInterface $request)
161
    {
162
        $clientId = $this->getQueryStringParameter(
163
            'client_id',
164
            $request,
165
            $this->getServerParameter('PHP_AUTH_USER', $request)
166
        );
167
        if (is_null($clientId)) {
168
            throw OAuthServerException::invalidRequest('client_id');
169
        }
170
171
        $client = $this->clientRepository->getClientEntity(
172
            $clientId,
173
            $this->getIdentifier(),
174
            null,
175
            false
176
        );
177
178
        if ($client instanceof ClientEntityInterface === false) {
179
            $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request));
180
            throw OAuthServerException::invalidClient();
181
        }
182
183
        $redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
184
        if ($redirectUri !== null) {
185
            if (
186
                is_string($client->getRedirectUri())
187
                && (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
188
            ) {
189
                $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request));
190
                throw OAuthServerException::invalidClient();
191
            } elseif (
192
                is_array($client->getRedirectUri())
193
                && in_array($redirectUri, $client->getRedirectUri()) === false
194
            ) {
195
                $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request));
196
                throw OAuthServerException::invalidClient();
197
            }
198
        }
199
200
        $scopes = $this->validateScopes(
201
            $this->getQueryStringParameter('scope', $request),
202
            is_array($client->getRedirectUri())
203
                ? $client->getRedirectUri()[0]
204
                : $client->getRedirectUri()
205
        );
206
207
        $stateParameter = $this->getQueryStringParameter('state', $request);
208
209
        $authorizationRequest = new AuthorizationRequest();
210
        $authorizationRequest->setGrantTypeId($this->getIdentifier());
211
        $authorizationRequest->setClient($client);
212
        $authorizationRequest->setRedirectUri($redirectUri);
213
        $authorizationRequest->setState($stateParameter);
214
        $authorizationRequest->setScopes($scopes);
215
216
        return $authorizationRequest;
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222
    public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
223
    {
224
        if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
225
            throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
226
        }
227
228
        $finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
229
            ? is_array($authorizationRequest->getClient()->getRedirectUri())
230
                ? $authorizationRequest->getClient()->getRedirectUri()[0]
231
                : $authorizationRequest->getClient()->getRedirectUri()
232
            : $authorizationRequest->getRedirectUri();
233
234
        // The user approved the client, redirect them back with an auth code
235
        if ($authorizationRequest->isAuthorizationApproved() === true) {
236
            $authCode = $this->issueAuthCode(
237
                $this->authCodeTTL,
238
                $authorizationRequest->getClient(),
239
                $authorizationRequest->getUser()->getIdentifier(),
240
                $authorizationRequest->getRedirectUri(),
241
                $authorizationRequest->getScopes()
242
            );
243
244
            $response = new RedirectResponse();
245
            $response->setRedirectUri(
246
                $this->makeRedirectUri(
247
                    $finalRedirectUri,
248
                    [
249
                        'code'  => $this->encrypt(
250
                            json_encode(
251
                                [
252
                                    'client_id'    => $authCode->getClient()->getIdentifier(),
253
                                    'redirect_uri' => $authCode->getRedirectUri(),
254
                                    'auth_code_id' => $authCode->getIdentifier(),
255
                                    'scopes'       => $authCode->getScopes(),
256
                                    'user_id'      => $authCode->getUserIdentifier(),
257
                                    'expire_time'  => (new \DateTime())->add($this->authCodeTTL)->format('U'),
258
                                ]
259
                            )
260
                        ),
261
                        'state' => $authorizationRequest->getState(),
262
                    ]
263
                )
264
            );
265
266
            return $response;
267
        }
268
269
        // The user denied the client, redirect them back with an error
270
        throw OAuthServerException::accessDenied(
271
            'The user denied the request',
272
            $finalRedirectUri
273
        );
274
    }
275
}
276