Passed
Push — master ( a8d522...c97e91 )
by Alexandre
02:40
created

AuthorizationCodeFlow   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 172
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 172
rs 10
c 0
b 0
f 0
wmc 21

5 Methods

Rating   Name   Duplication   Size   Complexity  
A base64url_encode() 0 3 1
A base64url_decode() 0 7 2
B handleAuthorizationRequest() 0 31 5
A __construct() 0 4 1
C handleAccessTokenRequest() 0 91 12
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: Alexandre
5
 * Date: 07/03/2018
6
 * Time: 23:43
7
 */
8
9
namespace OAuth2\Extensions\PKCE\Flows;
10
11
12
use OAuth2\Endpoints\AuthorizationEndpoint;
13
use OAuth2\Endpoints\TokenEndpoint;
14
use OAuth2\Exceptions\OAuthException;
15
use OAuth2\Extensions\PKCE\Credentials\CodeChallenge;
16
use OAuth2\Extensions\PKCE\Storages\AuthorizationCodeStorageInterface;
17
use OAuth2\Roles\Clients\PublicClient;
18
19
/**
20
 * Class AuthorizationCodeFlow
21
 * @package OAuth2\Extensions\PKCE\Flows
22
 * @rfc https://tools.ietf.org/html/rfc7636
23
 */
24
class AuthorizationCodeFlow extends \OAuth2\Flows\AuthorizationCodeFlow
25
{
26
    /**
27
     * @var AuthorizationCodeStorageInterface
28
     */
29
    protected $authorizationCodeStorage;
30
31
    /**
32
     * AuthorizationCodeFlow constructor.
33
     * @param AuthorizationCodeStorageInterface $authorizationCodeStorage
34
     */
35
    public function __construct(AuthorizationCodeStorageInterface $authorizationCodeStorage)
36
    {
37
        parent::__construct($authorizationCodeStorage);
38
        $this->authorizationCodeStorage = $authorizationCodeStorage;
39
    }
40
41
    /**
42
     * @param AuthorizationEndpoint $authorizationEndpoint
43
     * @param array                 $requestData
44
     * @return array
45
     * @throws OAuthException
46
     */
47
    function handleAuthorizationRequest(AuthorizationEndpoint $authorizationEndpoint, array $requestData): array
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
48
    {
49
        $authorizationCode = $this->authorizationCodeStorage->create(
50
            implode(' ', $authorizationEndpoint->getScopes()),
51
            $authorizationEndpoint->getClient()->getIdentifier(),
52
            $authorizationEndpoint->getResourceOwner()->getIdentifier(),
53
            $requestData['scope'] ?? null,
54
            $requestData['redirect_uri'] ?? null
55
        );
56
57
        if (empty($requestData['code_challenge'])) {
58
            if ($authorizationEndpoint->getClient() instanceof PublicClient) {
59
                throw new OAuthException('invalid_request',
60
                    'The request is missing the required parameter code_challenge for public clients',
61
                    'https://tools.ietf.org/html/rfc7636#section-4.4');
62
            }
63
        } else {
64
            $codeChallengeMethod = empty($requestData['code_challenge_method']) ? 'plain' : $requestData['code_challenge_method'];
65
            if (!in_array($codeChallengeMethod, ['plain', 'S256'])) {
66
                throw new OAuthException('invalid_request',
67
                    'The request includes the invalid parameter code_challenge_method. Supported : plain, S256',
68
                    'https://tools.ietf.org/html/rfc7636#section-4');
69
            }
70
71
            $codeChallenge = new CodeChallenge($requestData['code_challenge'], $codeChallengeMethod);
72
            $this->authorizationCodeStorage->associate($codeChallenge, $authorizationCode);
73
        }
74
75
        $this->authorizationCodeStorage->save($authorizationCode);
76
77
        return ['code' => $authorizationCode->getCode()];
78
    }
79
80
    public function handleAccessTokenRequest(TokenEndpoint $tokenEndpoint, array $requestData): array
81
    {
82
        if (empty($requestData['code'])) {
83
            throw new OAuthException('invalid_request',
84
                'The request is missing the required parameter code',
85
                'https://tools.ietf.org/html/rfc7636#section-4.4');
86
        }
87
        $code = $requestData['code'];
88
89
        $authorizationCode = $this->authorizationCodeStorage->find($code);
90
91
        /**
92
         * ensure that the authorization code was issued to the authenticated
93
         * confidential client, or if the client is public, ensure that the
94
         * code was issued to "client_id" in the request,
95
         */
96
        if (!$authorizationCode || $authorizationCode->getClientIdentifier() !== $tokenEndpoint->getClient()->getIdentifier()) {
97
            throw new OAuthException('invalid_grant',
98
                'The request includes the invalid parameter code',
99
                'https://tools.ietf.org/html/rfc7636#section-4.4');
100
        }
101
102
        $this->authorizationCodeStorage->revoke($code);
103
104
        /**
105
         * verify that the authorization code is valid
106
         */
107
        if ($authorizationCode->isExpired()) {
108
            throw new OAuthException('invalid_grant',
109
                'The request includes the invalid parameter code. The code has expired',
110
                'https://tools.ietf.org/html/rfc7636#section-4.4');
111
        }
112
113
        /**
114
         * ensure that the "redirect_uri" parameter is present if the
115
         * "redirect_uri" parameter was included in the initial authorization
116
         * request as described in Section 4.1.1, and if included ensure that
117
         * their values are identical.
118
         */
119
        if ($authorizationCode->getRedirectUri()) {
120
            if (empty($requestData['redirect_uri'])) {
121
                throw new OAuthException('invalid_request',
122
                    'The request is missing the required parameter redirect_uri',
123
                    'https://tools.ietf.org/html/rfc7636#section-4.1');
124
            }
125
            if ($requestData['redirect_uri'] !== $authorizationCode->getRedirectUri()) {
126
                throw new OAuthException('invalid_request',
127
                    'The request includes the invalid parameter redirect_uri',
128
                    'https://tools.ietf.org/html/rfc7636#section-4.1');
129
            }
130
        }
131
132
        $codeChallenge = $this->authorizationCodeStorage->getCodeChallenge($authorizationCode);
133
134
        if ($codeChallenge) {
135
            if (empty($requestData['code_verifier'])) {
136
                throw new OAuthException('invalid_request',
137
                    'The request is missing the required parameter code_verifier',
138
                    'https://tools.ietf.org/html/rfc7636#section-4.4');
139
            }
140
141
            if ($codeChallenge->getCodeChallengeMethod() === 'S256') {
142
                /**
143
                 * If the "code_challenge_method" from Section 4.3 was "S256", the
144
                 * received "code_verifier" is hashed by SHA-256, base64url-encoded, and
145
                 * then compared to the "code_challenge", i.e.:
146
                 */
147
                $hashedCodeVerifier = self::base64url_encode(hash('sha256', $requestData['code_verifier']));
148
            } else {
149
                /**
150
                 * If the "code_challenge_method" from Section 4.3 was "plain", they are
151
                 * compared directly, i.e.:
152
                 */
153
                $hashedCodeVerifier = $requestData['code_verifier'];
154
            }
155
156
            /**
157
             * If the values are equal, the token endpoint MUST continue processing
158
             * as normal (as defined by OAuth 2.0 [RFC6749]).  If the values are not
159
             * equal, an error response indicating "invalid_grant" as described in
160
             * Section 5.2 of [RFC6749] MUST be returned.
161
             */
162
            if ($hashedCodeVerifier !== $codeChallenge->getCodeChallenge()) {
163
                throw new OAuthException('invalid_grant',
164
                    'The request includes the invalid parameter code_verifier',
165
                    'https://tools.ietf.org/html/rfc7636#section-4.4');
166
            }
167
        }
168
169
        return $tokenEndpoint->issueTokens($authorizationCode->getScope(),
170
            $authorizationCode->getResourceOwnerIdentifier(), $authorizationCode->getCode());
171
    }
172
173
    /**
174
     * @param $data
175
     * @return string
176
     * @src https://gist.github.com/nathggns/6652997
177
     */
178
    public static function base64url_encode($data)
179
    {
180
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
181
    }
182
183
    /**
184
     * @param      $data
185
     * @param bool $pad
186
     * @return bool|string
187
     * @src https://gist.github.com/nathggns/6652997
188
     */
189
    public static function base64url_decode($data, $pad = false)
190
    {
191
        $data = strtr($data, '-_', '+/');
192
        if ($pad) {
193
            $data = str_pad($data, strlen($data) + (4 - strlen($data) % 4) % 4);
194
        }
195
        return base64_decode($data);
196
    }
197
198
}