GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( b6868d...d3b2b1 )
by François
02:04
created

OAuth2Client::parseRequestUri()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 35
Code Lines 19

Duplication

Lines 10
Ratio 28.57 %

Importance

Changes 0
Metric Value
dl 10
loc 35
rs 8.439
c 0
b 0
f 0
cc 5
eloc 19
nc 5
nop 1
1
<?php
2
/**
3
 *  Copyright (C) 2017 François Kooman <[email protected]>.
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU Affero General Public License as
7
 *  published by the Free Software Foundation, either version 3 of the
8
 *  License, or (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU Affero General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU Affero General Public License
16
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
namespace fkooman\OAuth\Client;
20
21
use DateInterval;
22
use DateTime;
23
use fkooman\OAuth\Client\Exception\OAuthException;
24
use fkooman\OAuth\Client\Exception\OAuthServerException;
25
use InvalidArgumentException;
26
27
/**
28
 * OAuth 2.0 Client. Helper class to make it easy to obtain an access token
29
 * from an OAuth 2.0 provider.
30
 */
31
class OAuth2Client
32
{
33
    /** @var Provider */
34
    private $provider;
35
36
    /** @var HttpClientInterface */
37
    private $httpClient;
38
39
    /** @var RandomInterface */
40
    private $random;
41
42
    /** @var \DateTime */
43
    private $dateTime;
44
45
    /**
46
     * Instantiate an OAuth 2.0 Client.
47
     *
48
     * @param Provider            $provider   the OAuth 2.0 provider configuration
49
     * @param HttpClientInterface $httpClient the HTTP client implementation
50
     * @param RandomInterface     $random     the random implementation
51
     */
52
    public function __construct(Provider $provider, HttpClientInterface $httpClient, RandomInterface $random = null, DateTime $dateTime = null)
53
    {
54
        $this->provider = $provider;
55
        $this->httpClient = $httpClient;
56
        if (is_null($random)) {
57
            $random = new Random();
58
        }
59
        $this->random = $random;
60
        if (is_null($dateTime)) {
61
            $dateTime = new DateTime();
62
        }
63
        $this->dateTime = $dateTime;
64
    }
65
66
    /**
67
     * Obtain an authorization request URL to start the authorization process
68
     * at the OAuth provider.
69
     *
70
     * @param string $scope       the space separated scope tokens
71
     * @param string $redirectUri the URL to redirect back to after coming back
72
     *                            from the OAuth provider (callback URL)
73
     *
74
     * @return string the authorization request URL
75
     *
76
     * @see https://tools.ietf.org/html/rfc6749#section-3.3
77
     * @see https://tools.ietf.org/html/rfc6749#section-3.1.2
78
     */
79
    public function getAuthorizationRequestUri($scope, $redirectUri)
80
    {
81
        $queryParams = http_build_query(
82
            [
83
                'client_id' => $this->provider->getId(),
84
                'redirect_uri' => $redirectUri,
85
                'scope' => $scope,
86
                'state' => $this->random->get(16),
87
                'response_type' => 'code',
88
            ],
89
            '&'
90
        );
91
92
        return sprintf(
93
            '%s%s%s',
94
            $this->provider->getAuthorizationEndpoint(),
95
            false === strpos($this->provider->getAuthorizationEndpoint(), '?') ? '?' : '&',
96
            $queryParams
97
        );
98
    }
99
100
    /**
101
     * Obtain the access token from the OAuth provider after returning from the
102
     * OAuth provider on the redirectUri (callback URL).
103
     *
104
     * @param string $requestUri    the original authorization
105
     *                              request URL as obtained by getAuthorzationRequestUri
106
     * @param string $responseCode  the code passed to the 'code'
107
     *                              query parameter on the callback URL
108
     * @param string $responseState the state passed to the 'state'
109
     *                              query parameter on the callback URL
110
     *
111
     * @return AccessToken
112
     */
113
    public function getAccessToken($requestUri, $responseCode, $responseState)
114
    {
115
        $requestParameters = self::parseRequestUri($requestUri);
116
        if ($responseState !== $requestParameters['state']) {
117
            // the OAuth state from the initial request MUST be the same as the
118
            // state used by the response
119
            throw new OAuthException('invalid OAuth state');
120
        }
121
122
        if ($requestParameters['client_id'] !== $this->provider->getId()) {
123
            // the client_id used for the initial request differs from the
124
            // currently configured Provider, the client_id MUST be identical
125
            throw new OAuthException('unexpected client identifier');
126
        }
127
128
        // prepare access_token request
129
        $tokenRequestData = [
130
            'client_id' => $this->provider->getId(),
131
            'grant_type' => 'authorization_code',
132
            'code' => $responseCode,
133
            'redirect_uri' => $requestParameters['redirect_uri'],
134
        ];
135
136
        $responseData = $this->validateTokenResponse(
137
            $this->httpClient->post(
138
                $this->provider,
139
                $tokenRequestData
140
            ),
141
            $requestParameters['scope']
142
        );
143
144
        return new AccessToken(
145
            $responseData['access_token'],
146
            $responseData['token_type'],
147
            $responseData['scope'],
148
            $responseData['expires_at']
149
        );
150
    }
151
152
    private static function parseRequestUri($requestUri)
153
    {
154
        if (!is_string($requestUri)) {
155
            throw new InvalidArgumentException('"requestUri" MUST be string');
156
        }
157
158
        if (false === strpos($requestUri, '?')) {
159
            throw new OAuthException('"requestUri" not valid, no query string');
160
        }
161
162
        parse_str(explode('?', $requestUri)[1], $requestParameters);
163
164
        $requiredParameters = [
165
            'client_id',
166
            'redirect_uri',
167
            'scope',
168
            'state',
169
            'response_type',
170
        ];
171
172
        // all of the above parameters were part of the requestUri, make sure
173
        // they are still there...
174 View Code Duplication
        foreach ($requiredParameters as $requiredParameter) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
175
            if (!array_key_exists($requiredParameter, $requestParameters)) {
176
                throw new OAuthException(
177
                    sprintf(
178
                        'request URI not valid, missing required query parameter "%s"',
179
                        $requiredParameter
180
                    )
181
                );
182
            }
183
        }
184
185
        return $requestParameters;
186
    }
187
188
    private function validateTokenResponse(array $tokenResponse, $requestScope)
189
    {
190
        // check if an error occurred
191
        if (array_key_exists('error', $tokenResponse)) {
192
            if (array_key_exists('error_description', $tokenResponse)) {
193
                throw new OAuthServerException(sprintf('%s: %s', $tokenResponse['error'], $tokenResponse['error_description']));
194
            }
195
196
            throw new OAuthServerException($tokenResponse['error']);
197
        }
198
199
        $requiredParameters = [
200
            'access_token',
201
            'token_type',
202
        ];
203
204 View Code Duplication
        foreach ($requiredParameters as $requiredParameter) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
205
            if (!array_key_exists($requiredParameter, $tokenResponse)) {
206
                throw new OAuthException(
207
                    sprintf(
208
                        'token response not valid, missing required parameter "%s"',
209
                        $requiredParameter
210
                    )
211
                );
212
            }
213
        }
214
215
        if (!array_key_exists('scope', $tokenResponse)) {
216
            // if the token endpoint does not return a 'scope' value, the
217
            // specification says the requested scope was granted
218
            $tokenResponse['scope'] = $requestScope;
219
        }
220
221
        $tokenResponse['expires_at'] = $this->calculateExpiresAt($tokenResponse);
222
223
        return $tokenResponse;
224
    }
225
226
    private function calculateExpiresAt(array $tokenResponse)
227
    {
228
        $dateTime = clone $this->dateTime;
229
        if (array_key_exists('expires_in', $tokenResponse)) {
230
            return date_add($dateTime, new DateInterval(sprintf('PT%dS', $tokenResponse['expires_in'])));
231
        }
232
233
        // if the 'expires_in' field is not available, we default to 1 year
234
        return date_add($dateTime, new DateInterval('P1Y'));
235
    }
236
}
237