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 ( c63c04...d9b7b1 )
by François
02:15
created

OAuth2Client::validateTokenResponse()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 33
Code Lines 15

Duplication

Lines 10
Ratio 30.3 %

Importance

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