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 ( e2e651...049a47 )
by François
02:04
created

OAuth2Client::getAccessToken()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 21
nc 3
nop 3
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 fkooman\OAuth\Client\Exception\OAuthException;
22
use fkooman\OAuth\Client\Exception\OAuthServerException;
23
use InvalidArgumentException;
24
25
/**
26
 * OAuth 2.0 Client. Helper class to make it easy to obtain an access token
27
 * from an OAuth 2.0 provider.
28
 */
29
class OAuth2Client
30
{
31
    /** @var Provider */
32
    private $provider;
33
34
    /** @var HttpClientInterface */
35
    private $httpClient;
36
37
    /** @var RandomInterface */
38
    private $random;
39
40
    /**
41
     * Instantiate an OAuth 2.0 Client.
42
     *
43
     * @param Provider            $provider   the OAuth 2.0 provider configuration
44
     * @param HttpClientInterface $httpClient the HTTP client implementation
45
     * @param RandomInterface     $random     the random implementation
46
     */
47
    public function __construct(Provider $provider, HttpClientInterface $httpClient, RandomInterface $random = null)
48
    {
49
        $this->provider = $provider;
50
        $this->httpClient = $httpClient;
51
        if (is_null($random)) {
52
            $random = new Random();
53
        }
54
        $this->random = $random;
55
    }
56
57
    /**
58
     * Obtain an authorization request URL to start the authorization process
59
     * at the OAuth provider.
60
     *
61
     * @param string $scope       the space separated scope tokens
62
     * @param string $redirectUri the URL to redirect back to after coming back
63
     *                            from the OAuth provider (callback URL)
64
     *
65
     * @return string the authorization request URL
66
     *
67
     * @see https://tools.ietf.org/html/rfc6749#section-3.3
68
     * @see https://tools.ietf.org/html/rfc6749#section-3.1.2
69
     */
70
    public function getAuthorizationRequestUri($scope, $redirectUri)
71
    {
72
        $queryParams = http_build_query(
73
            [
74
                'client_id' => $this->provider->getId(),
75
                'redirect_uri' => $redirectUri,
76
                'scope' => $scope,
77
                'state' => $this->random->get(16),
78
                'response_type' => 'code',
79
            ],
80
            '&'
81
        );
82
83
        return sprintf(
84
            '%s%s%s',
85
            $this->provider->getAuthorizationEndpoint(),
86
            false === strpos($this->provider->getAuthorizationEndpoint(), '?') ? '?' : '&',
87
            $queryParams
88
        );
89
    }
90
91
    /**
92
     * Obtain the access token from the OAuth provider after returning from the
93
     * OAuth provider on the redirectUri (callback URL).
94
     *
95
     * @param string $requestUri    the original authorization
96
     *                              request URL as obtained by getAuthorzationRequestUri
97
     * @param string $responseCode  the code passed to the 'code'
98
     *                              query parameter on the callback URL
99
     * @param string $responseState the state passed to the 'state'
100
     *                              query parameter on the callback URL
101
     *
102
     * @return AccessToken
103
     */
104
    public function getAccessToken($requestUri, $responseCode, $responseState)
105
    {
106
        $requestParameters = self::parseRequestUri($requestUri);
107
        if ($responseState !== $requestParameters['state']) {
108
            // the OAuth state from the initial request MUST be the same as the
109
            // state used by the response
110
            throw new OAuthException('invalid OAuth state');
111
        }
112
113
        if ($requestParameters['client_id'] !== $this->provider->getId()) {
114
            // the client_id used for the initial request differs from the
115
            // currently configured Provider, the client_id MUST be identical
116
            throw new OAuthException('unexpected client identifier');
117
        }
118
119
        // prepare access_token request
120
        $tokenRequestData = [
121
            'client_id' => $this->provider->getId(),
122
            'grant_type' => 'authorization_code',
123
            'code' => $responseCode,
124
            'redirect_uri' => $requestParameters['redirect_uri'],
125
        ];
126
127
        $responseData = self::validateTokenResponse(
128
            $this->httpClient->post(
129
                $this->provider,
130
                $tokenRequestData
131
            ),
132
            $requestParameters['scope']
133
        );
134
135
        return new AccessToken(
136
            $responseData['access_token'],
137
            $responseData['token_type'],
138
            $responseData['scope'],
139
            $responseData['expires_in']
140
        );
141
    }
142
143
    private static function parseRequestUri($requestUri)
144
    {
145
        if (!is_string($requestUri)) {
146
            throw new InvalidArgumentException('"requestUri" MUST be string');
147
        }
148
149
        if (false === strpos($requestUri, '?')) {
150
            throw new OAuthException('"requestUri" not valid, no query string');
151
        }
152
153
        parse_str(explode('?', $requestUri)[1], $requestParameters);
154
155
        $requiredParameters = [
156
            'client_id',
157
            'redirect_uri',
158
            'scope',
159
            'state',
160
            'response_type',
161
        ];
162
163
        // all of the above parameters were part of the requestUri, make sure
164
        // they are still there...
165 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...
166
            if (!array_key_exists($requiredParameter, $requestParameters)) {
167
                throw new OAuthException(
168
                    sprintf(
169
                        'request URI not valid, missing required query parameter "%s"',
170
                        $requiredParameter
171
                    )
172
                );
173
            }
174
        }
175
176
        return $requestParameters;
177
    }
178
179
    private static function validateTokenResponse(array $tokenResponse, $requestScope)
180
    {
181
        // check if an error occurred
182
        if (array_key_exists('error', $tokenResponse)) {
183
            if (array_key_exists('error_description', $tokenResponse)) {
184
                throw new OAuthServerException(sprintf('%s: %s', $tokenResponse['error'], $tokenResponse['error_description']));
185
            }
186
187
            throw new OAuthServerException($tokenResponse['error']);
188
        }
189
190
        $requiredParameters = [
191
            'access_token',
192
            'token_type',
193
        ];
194
195 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...
196
            if (!array_key_exists($requiredParameter, $tokenResponse)) {
197
                throw new OAuthException(
198
                    sprintf(
199
                        'token response not valid, missing required parameter "%s"',
200
                        $requiredParameter
201
                    )
202
                );
203
            }
204
        }
205
206
        if (!array_key_exists('scope', $tokenResponse)) {
207
            // if the token endpoint does not return a 'scope' value, the
208
            // specification says the requested scope was granted
209
            $tokenResponse['scope'] = $requestScope;
210
        }
211
212
        if (!array_key_exists('expires_in', $tokenResponse)) {
213
            // if the 'expires_in' field is not available, we make it null
214
            // here, the client will just have to try to see if the token is
215
            // still valid...
216
            $tokenResponse['expires_in'] = null;
217
        }
218
219
        return $tokenResponse;
220
    }
221
}
222