Completed
Push — develop ( a4838f...d9bf78 )
by Abdelrahman
01:37
created

AuthCodeGrant::validateAuthorizationCode()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 30
rs 8.1954
c 0
b 0
f 0
cc 8
nc 7
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Rinvex\OAuth\Grants;
6
7
use stdClass;
8
use DateInterval;
9
use LogicException;
10
use League\OAuth2\Server\RequestEvent;
11
use Psr\Http\Message\ServerRequestInterface;
12
use League\OAuth2\Server\Entities\ClientEntityInterface;
13
use League\OAuth2\Server\Exception\OAuthServerException;
14
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
15
use League\OAuth2\Server\Grant\AuthCodeGrant as BaseAuthCodeGrant;
16
use League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface;
17
18
class AuthCodeGrant extends BaseAuthCodeGrant
19
{
20
    /**
21
     * @var CodeChallengeVerifierInterface[]
22
     */
23
    private $codeChallengeVerifiers = [];
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
24
25
    /**
26
     * Respond to an access token request.
27
     *
28
     * @param ServerRequestInterface $request
29
     * @param ResponseTypeInterface  $responseType
30
     * @param DateInterval           $accessTokenTTL
31
     *
32
     * @throws OAuthServerException
33
     *
34
     * @return ResponseTypeInterface
35
     */
36
    public function respondToAccessTokenRequest(ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL)
37
    {
38
        [$clientId] = $this->getClientCredentials($request);
0 ignored issues
show
Bug introduced by
The variable $clientId does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
39
        $client = $this->getClientEntityOrFail($clientId, $request);
40
41
        // Only validate the client if it is confidential
42
        if ($client->isConfidential()) {
43
            $this->validateClient($request);
44
        }
45
46
        $encryptedAuthCode = $this->getRequestParameter('code', $request, null);
47
48
        if ($encryptedAuthCode === null) {
49
            throw OAuthServerException::invalidRequest('code');
50
        }
51
52
        try {
53
            $authCodePayload = \json_decode($this->decrypt($encryptedAuthCode));
54
55
            $this->validateAuthorizationCode($authCodePayload, $client, $request);
56
57
            $scopes = $this->scopeRepository->finalizeScopes(
58
                $this->validateScopes($authCodePayload->scopes),
59
                $this->getIdentifier(),
60
                $client,
61
                $authCodePayload->user_id
62
            );
63
64
        } catch (LogicException $e) {
65
            throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code', $e);
66
        }
67
68
        // Validate code challenge
69
        if (!empty($authCodePayload->code_challenge)) {
70
            $codeVerifier = $this->getRequestParameter('code_verifier', $request, null);
71
72
            if ($codeVerifier === null) {
73
                throw OAuthServerException::invalidRequest('code_verifier');
74
            }
75
76
            // Validate code_verifier according to RFC-7636
77
            // @see: https://tools.ietf.org/html/rfc7636#section-4.1
78
            if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeVerifier) !== 1) {
79
                throw OAuthServerException::invalidRequest(
80
                    'code_verifier',
81
                    'Code Verifier must follow the specifications of RFC-7636.'
82
                );
83
            }
84
85
            if (property_exists($authCodePayload, 'code_challenge_method')) {
86
                if (isset($this->codeChallengeVerifiers[$authCodePayload->code_challenge_method])) {
87
                    $codeChallengeVerifier = $this->codeChallengeVerifiers[$authCodePayload->code_challenge_method];
88
89
                    if ($codeChallengeVerifier->verifyCodeChallenge($codeVerifier, $authCodePayload->code_challenge) === false) {
90
                        throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
91
                    }
92
                } else {
93
                    throw OAuthServerException::serverError(
94
                        sprintf(
95
                            'Unsupported code challenge method `%s`',
96
                            $authCodePayload->code_challenge_method
97
                        )
98
                    );
99
                }
100
            }
101
        }
102
103
        // Issue and persist new access token
104
        $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
105
        $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
106
        $responseType->setAccessToken($accessToken);
0 ignored issues
show
Bug introduced by
It seems like $accessToken defined by $this->issueAccessToken(...load->user_id, $scopes) on line 104 can be null; however, League\OAuth2\Server\Res...rface::setAccessToken() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
107
108
        // Issue and persist new refresh token if given
109
        $refreshToken = $this->issueRefreshToken($accessToken);
0 ignored issues
show
Bug introduced by
It seems like $accessToken defined by $this->issueAccessToken(...load->user_id, $scopes) on line 104 can be null; however, League\OAuth2\Server\Gra...nt::issueRefreshToken() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
110
111
        if ($refreshToken !== null) {
112
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
113
            $responseType->setRefreshToken($refreshToken);
114
        }
115
116
        // Revoke used auth code
117
        $this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id);
118
119
        return $responseType;
120
    }
121
122
    /**
123
     * Validate the authorization code.
124
     *
125
     * @param stdClass               $authCodePayload
126
     * @param ClientEntityInterface  $client
127
     * @param ServerRequestInterface $request
128
     */
129
    private function validateAuthorizationCode($authCodePayload, ClientEntityInterface $client, ServerRequestInterface $request)
0 ignored issues
show
Bug introduced by
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
130
    {
131
        if (! property_exists($authCodePayload, 'auth_code_id')) {
132
            throw OAuthServerException::invalidRequest('code', 'Authorization code malformed');
133
        }
134
135
        if (time() > $authCodePayload->expire_time) {
136
            throw OAuthServerException::invalidRequest('code', 'Authorization code has expired');
137
        }
138
139
        if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) {
140
            throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked');
141
        }
142
143
        if ($authCodePayload->client_id !== $client->getIdentifier()) {
144
            throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client');
145
        }
146
147
        $this->validateUser($authCodePayload);
148
149
        // The redirect URI is required in this request
150
        $redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
151
        if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) {
152
            throw OAuthServerException::invalidRequest('redirect_uri');
153
        }
154
155
        if ($authCodePayload->redirect_uri !== $redirectUri) {
156
            throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI');
157
        }
158
    }
159
160
    /**
161
     * Validate the authorization code user.
162
     *
163
     * @param stdClass               $authCodePayload
164
     */
165
    protected function validateUser(stdClass $authCodePayload)
166
    {
167
        [$userType, $userId] = explode(':', $authCodePayload->user_id);
0 ignored issues
show
Bug introduced by
The variable $userId does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $userType does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
168
169
        if ($userType !== request()->user()->getMorphClass() || $userId !== request()->user()->getRouteKey()) {
170
            throw OAuthServerException::invalidRequest('user_id', 'Authorization code was not issued to this user');
171
        }
172
    }
173
}
174