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 ( 5442c0...516271 )
by François
02:38
created

AccessToken::json()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 1
eloc 7
nc 1
nop 0
1
<?php
2
3
/**
4
 * Copyright (c) 2016, 2017 François Kooman <[email protected]>.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24
25
namespace fkooman\OAuth\Client;
26
27
use DateInterval;
28
use DateTime;
29
use fkooman\OAuth\Client\Exception\AccessTokenException;
30
use fkooman\OAuth\Client\Exception\OAuthServerException;
31
use fkooman\OAuth\Client\Http\Response;
32
33
class AccessToken
34
{
35
    /** @var \DateTime */
36
    private $issuedAt;
37
38
    /** @var string */
39
    private $accessToken;
40
41
    /** @var string */
42
    private $tokenType;
43
44
    /** @var int|null */
45
    private $expiresIn = null;
46
47
    /** @var string|null */
48
    private $refreshToken = null;
49
50
    /** @var string|null */
51
    private $scope = null;
52
53
    /**
54
     * @param array $tokenData
55
     */
56
    private function __construct(array $tokenData)
57
    {
58
        $requiredKeys = ['issued_at', 'access_token', 'token_type'];
59
        foreach ($requiredKeys as $requiredKey) {
60
            if (!array_key_exists($requiredKey, $tokenData)) {
61
                throw new AccessTokenException(sprintf('missing key "%s"', $requiredKey));
62
            }
63
        }
64
65
        // set required keys
66
        $this->setIssuedAt($tokenData['issued_at']);
67
        $this->setAccessToken($tokenData['access_token']);
68
        $this->setTokenType($tokenData['token_type']);
69
70
        // set optional keys
71
        if (array_key_exists('expires_in', $tokenData)) {
72
            $this->setExpiresIn($tokenData['expires_in']);
73
        }
74
        if (array_key_exists('refresh_token', $tokenData)) {
75
            $this->setRefreshToken($tokenData['refresh_token']);
76
        }
77
        if (array_key_exists('scope', $tokenData)) {
78
            $this->setScope($tokenData['scope']);
79
        }
80
    }
81
82
    /**
83
     * @param \DateTime     $dateTime
84
     * @param Http\Response $response
85
     * @param string        $scope
86
     */
87
    public static function fromCodeResponse(DateTime $dateTime, Response $response, $scope)
88
    {
89
        if (!$response->isOkay()) {
90
            throw new OAuthServerException($response);
91
        }
92
        $tokenData = $response->json();
93
        // if the scope was not part of the response, add the request scope,
94
        // because according to the RFC, if the scope is ommitted the requested
95
        // scope was granted!
96
        if (!array_key_exists('scope', $tokenData)) {
97
            $tokenData['scope'] = $scope;
98
        }
99
        // add the current DateTime as well to be able to figure out if the
100
        // token expired
101
        $tokenData['issued_at'] = $dateTime->format('Y-m-d H:i:s');
102
103
        return new self($tokenData);
104
    }
105
106
    /**
107
     * @param \DateTime     $dateTime
108
     * @param Http\Response $response
109
     * @param AccessToken   $accessToken to steal the old scope and refresh_token from!
110
     */
111
    public static function fromRefreshResponse(DateTime $dateTime, Response $response, AccessToken $accessToken)
112
    {
113
        if (!$response->isOkay()) {
114
            throw new OAuthServerException($response);
115
        }
116
117
        $tokenData = $response->json();
118
        // if the scope is not part of the response, add the request scope,
119
        // because according to the RFC, if the scope is ommitted the requested
120
        // scope was granted!
121
        if (!array_key_exists('scope', $tokenData)) {
122
            $tokenData['scope'] = $accessToken->getScope();
123
        }
124
        // if the refresh_token is not part of the response, we wil reuse the
125
        // existing refresh_token for future refresh_token requests
126
        if (!array_key_exists('refresh_token', $tokenData)) {
127
            $tokenData['refresh_token'] = $accessToken->getRefreshToken();
128
        }
129
        // add the current DateTime as well to be able to figure out if the
130
        // token expired
131
        $tokenData['issued_at'] = $dateTime->format('Y-m-d H:i:s');
132
133
        return new self($tokenData);
134
    }
135
136
    /**
137
     * @param string $tokenString a JSON encoded AccessToken
138
     *
139
     * @return AccessToken
140
     */
141
    public static function fromStorage($tokenString)
142
    {
143
        $tokenData = json_decode($tokenString, true);
144 View Code Duplication
        if (is_null($tokenData) && JSON_ERROR_NONE !== json_last_error()) {
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...
145
            $errorMsg = function_exists('json_last_error_msg') ? json_last_error_msg() : json_last_error();
146
            throw new AccessTokenException(sprintf('unable to decode JSON from storage: %s', $errorMsg));
147
        }
148
149
        return new self($tokenData);
150
    }
151
152
    /**
153
     * @return string a JSON encoded AccessToken
154
     */
155
    public function toTokenStorage()
156
    {
157
        return json_encode(
158
            [
159
                'access_token' => $this->getToken(),
160
                'token_type' => $this->getTokenType(),
161
                'expires_in' => $this->getExpiresIn(),
162
                'refresh_token' => $this->getRefreshToken(),
163
                'scope' => $this->getScope(),
164
            ]
165
        );
166
    }
167
168
    /**
169
     * @return string
170
     *
171
     * @see https://tools.ietf.org/html/rfc6749#section-5.1
172
     */
173
    public function getToken()
174
    {
175
        return $this->accessToken;
176
    }
177
178
    /**
179
     * @return string
180
     *
181
     * @see https://tools.ietf.org/html/rfc6749#section-7.1
182
     */
183
    public function getTokenType()
184
    {
185
        return $this->tokenType;
186
    }
187
188
    /**
189
     * @return int|null
190
     *
191
     * @see https://tools.ietf.org/html/rfc6749#section-5.1
192
     */
193
    public function getExpiresIn()
194
    {
195
        return $this->expiresIn;
196
    }
197
198
    /**
199
     * @return string|null the refresh token
200
     *
201
     * @see https://tools.ietf.org/html/rfc6749#section-1.5
202
     */
203
    public function getRefreshToken()
204
    {
205
        return $this->refreshToken;
206
    }
207
208
    /**
209
     * @return string|null
210
     *
211
     * @see https://tools.ietf.org/html/rfc6749#section-3.3
212
     */
213
    public function getScope()
214
    {
215
        return $this->scope;
216
    }
217
218
    /**
219
     * @param \DateTime $dateTime
220
     *
221
     * @return bool
222
     */
223
    public function isExpired(DateTime $dateTime)
224
    {
225
        if (is_null($this->getExpiresIn())) {
226
            // if no expiry was indicated, assume it is valid
227
            return false;
228
        }
229
230
        // check to see if issuedAt + expiresIn > provided DateTime
231
        $issuedAt = clone $this->issuedAt; // XXX do we need to clone here?
232
        $expiresAt = date_add($issuedAt, new DateInterval(sprintf('PT%dS', $this->getExpiresIn())));
233
234
        return $dateTime >= $expiresAt;
235
    }
236
237
    /**
238
     * @param string $issuedAt
239
     */
240
    private function setIssuedAt($issuedAt)
241
    {
242
        self::requireString('expires_at', $issuedAt);
243
        if (1 !== preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/', $issuedAt)) {
244
            throw new AccessTokenException('invalid "expires_at"');
245
        }
246
        $this->issuedAt = new DateTime($issuedAt);
247
    }
248
249
    /**
250
     * @param string $accessToken
251
     */
252
    private function setAccessToken($accessToken)
253
    {
254
        self::requireString('access_token', $accessToken);
255
        // access-token = 1*VSCHAR
256
        // VSCHAR       = %x20-7E
257
        if (1 !== preg_match('/^[\x20-\x7E]+$/', $accessToken)) {
258
            throw new AccessTokenException('invalid "access_token"');
259
        }
260
        $this->accessToken = $accessToken;
261
    }
262
263
    /**
264
     * @param string $tokenType
265
     */
266
    private function setTokenType($tokenType)
267
    {
268
        self::requireString('token_type', $tokenType);
269
        if ('bearer' !== $tokenType) {
270
            throw new AccessTokenException('unsupported "token_type"');
271
        }
272
        $this->tokenType = $tokenType;
273
    }
274
275
    /**
276
     * @param int|null $expiresIn
277
     */
278
    private function setExpiresIn($expiresIn)
279
    {
280
        if (!is_null($expiresIn)) {
281
            self::requireInt('expires_in', $expiresIn);
282
            if (0 >= $expiresIn) {
283
                throw new AccessTokenException('invalid "expires_in"');
284
            }
285
        }
286
        $this->expiresIn = $expiresIn;
287
    }
288
289
    /**
290
     * @param string|null $refreshToken
291
     */
292 View Code Duplication
    private function setRefreshToken($refreshToken)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
293
    {
294
        if (!is_null($refreshToken)) {
295
            self::requireString('refresh_token', $refreshToken);
296
            // refresh-token = 1*VSCHAR
297
            // VSCHAR        = %x20-7E
298
            if (1 !== preg_match('/^[\x20-\x7E]+$/', $refreshToken)) {
299
                throw new AccessTokenException('invalid "refresh_token"');
300
            }
301
        }
302
        $this->refreshToken = $refreshToken;
303
    }
304
305
    /**
306
     * @param string|null $scope
307
     */
308 View Code Duplication
    private function setScope($scope)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
309
    {
310
        if (!is_null($scope)) {
311
            self::requireString('scope', $scope);
312
            // scope       = scope-token *( SP scope-token )
313
            // scope-token = 1*NQCHAR
314
            // NQCHAR      = %x21 / %x23-5B / %x5D-7E
315
            foreach (explode(' ', $scope) as $scopeToken) {
316
                if (1 !== preg_match('/^[\x21\x23-\x5B\x5D-\x7E]+$/', $scopeToken)) {
317
                    throw new AccessTokenException('invalid "scope"');
318
                }
319
            }
320
        }
321
        $this->scope = $scope;
322
    }
323
324
    /**
325
     * @param string $k
326
     * @param string $v
327
     */
328
    private static function requireString($k, $v)
329
    {
330
        if (!is_string($v)) {
331
            throw new AccessTokenException(sprintf('"%s" must be string', $k));
332
        }
333
    }
334
335
    /**
336
     * @param string $k
337
     * @param int    $v
338
     */
339
    private static function requireInt($k, $v)
340
    {
341
        if (!is_int($v)) {
342
            throw new AccessTokenException(sprintf('"%s" must be int', $k));
343
        }
344
    }
345
}
346