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 ( 7f0d16...8ce98c )
by François
02:09
created

AccessToken::getProviderId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
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
31
class AccessToken
32
{
33
    /** @var string */
34
    private $providerId;
35
36
    /** @var \DateTime */
37
    private $issuedAt;
38
39
    /** @var string */
40
    private $accessToken;
41
42
    /** @var string */
43
    private $tokenType;
44
45
    /** @var int|null */
46
    private $expiresIn = null;
47
48
    /** @var string|null */
49
    private $refreshToken = null;
50
51
    /** @var string|null */
52
    private $scope = null;
53
54
    /**
55
     * @param array $tokenData
56
     */
57
    private function __construct(array $tokenData)
58
    {
59
        $requiredKeys = ['provider_id', 'issued_at', 'access_token', 'token_type'];
60
        foreach ($requiredKeys as $requiredKey) {
61
            if (!array_key_exists($requiredKey, $tokenData)) {
62
                throw new AccessTokenException(sprintf('missing key "%s"', $requiredKey));
63
            }
64
        }
65
66
        // set required keys
67
        $this->setProviderId($tokenData['provider_id']);
68
        $this->setIssuedAt($tokenData['issued_at']);
69
        $this->setAccessToken($tokenData['access_token']);
70
        $this->setTokenType($tokenData['token_type']);
71
72
        // set optional keys
73
        if (array_key_exists('expires_in', $tokenData)) {
74
            $this->setExpiresIn($tokenData['expires_in']);
75
        }
76
        if (array_key_exists('refresh_token', $tokenData)) {
77
            $this->setRefreshToken($tokenData['refresh_token']);
78
        }
79
        if (array_key_exists('scope', $tokenData)) {
80
            $this->setScope($tokenData['scope']);
81
        }
82
    }
83
84
    /**
85
     * @param Provider  $provider
86
     * @param \DateTime $dateTime
87
     * @param array     $tokenData
88
     * @param string    $scope
89
     */
90
    public static function fromCodeResponse(Provider $provider, DateTime $dateTime, array $tokenData, $scope)
91
    {
92
        $tokenData['provider_id'] = $provider->getProviderId();
93
94
        // if the scope was not part of the response, add the request scope,
95
        // because according to the RFC, if the scope is ommitted the requested
96
        // scope was granted!
97
        if (!array_key_exists('scope', $tokenData)) {
98
            $tokenData['scope'] = $scope;
99
        }
100
        // add the current DateTime as well to be able to figure out if the
101
        // token expired
102
        $tokenData['issued_at'] = $dateTime->format('Y-m-d H:i:s');
103
104
        return new self($tokenData);
105
    }
106
107
    /**
108
     * @param Provider    $provider
109
     * @param \DateTime   $dateTime
110
     * @param array       $tokenData
111
     * @param AccessToken $accessToken to steal the old scope and refresh_token from!
112
     */
113
    public static function fromRefreshResponse(Provider $provider, DateTime $dateTime, array $tokenData, AccessToken $accessToken)
114
    {
115
        $tokenData['provider_id'] = $provider->getProviderId();
116
117
        // if the scope is not part of the response, add the request scope,
118
        // because according to the RFC, if the scope is ommitted the requested
119
        // scope was granted!
120
        if (!array_key_exists('scope', $tokenData)) {
121
            $tokenData['scope'] = $accessToken->getScope();
122
        }
123
        // if the refresh_token is not part of the response, we wil reuse the
124
        // existing refresh_token for future refresh_token requests
125
        if (!array_key_exists('refresh_token', $tokenData)) {
126
            $tokenData['refresh_token'] = $accessToken->getRefreshToken();
127
        }
128
        // add the current DateTime as well to be able to figure out if the
129
        // token expired
130
        $tokenData['issued_at'] = $dateTime->format('Y-m-d H:i:s');
131
132
        return new self($tokenData);
133
    }
134
135
    /**
136
     * @param string $tokenString a JSON encoded AccessToken
137
     *
138
     * @return AccessToken
139
     */
140
    public static function fromStorage($tokenString)
141
    {
142
        $tokenData = json_decode($tokenString, true);
143 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...
144
            $errorMsg = function_exists('json_last_error_msg') ? json_last_error_msg() : json_last_error();
145
            throw new AccessTokenException(sprintf('unable to decode JSON from storage: %s', $errorMsg));
146
        }
147
148
        return new self($tokenData);
149
    }
150
151
    /**
152
     * @return string a JSON encoded AccessToken
153
     */
154
    public function toStorage()
155
    {
156
        return json_encode(
157
            [
158
                'provider_id' => $this->getProviderId(),
159
                'issued_at' => $this->issuedAt->format('Y-m-d H:i:s'),
160
                'access_token' => $this->getToken(),
161
                'token_type' => $this->getTokenType(),
162
                'expires_in' => $this->getExpiresIn(),
163
                'refresh_token' => $this->getRefreshToken(),
164
                'scope' => $this->getScope(),
165
            ]
166
        );
167
    }
168
169
    public function getProviderId()
170
    {
171
        return $this->providerId;
172
    }
173
174
    /**
175
     * @return string
176
     *
177
     * @see https://tools.ietf.org/html/rfc6749#section-5.1
178
     */
179
    public function getToken()
180
    {
181
        return $this->accessToken;
182
    }
183
184
    /**
185
     * @return string
186
     *
187
     * @see https://tools.ietf.org/html/rfc6749#section-7.1
188
     */
189
    public function getTokenType()
190
    {
191
        return $this->tokenType;
192
    }
193
194
    /**
195
     * @return int|null
196
     *
197
     * @see https://tools.ietf.org/html/rfc6749#section-5.1
198
     */
199
    public function getExpiresIn()
200
    {
201
        return $this->expiresIn;
202
    }
203
204
    /**
205
     * @return string|null the refresh token
206
     *
207
     * @see https://tools.ietf.org/html/rfc6749#section-1.5
208
     */
209
    public function getRefreshToken()
210
    {
211
        return $this->refreshToken;
212
    }
213
214
    /**
215
     * @return string|null
216
     *
217
     * @see https://tools.ietf.org/html/rfc6749#section-3.3
218
     */
219
    public function getScope()
220
    {
221
        return $this->scope;
222
    }
223
224
    /**
225
     * @param \DateTime $dateTime
226
     *
227
     * @return bool
228
     */
229
    public function isExpired(DateTime $dateTime)
230
    {
231
        if (is_null($this->getExpiresIn())) {
232
            // if no expiry was indicated, assume it is valid
233
            return false;
234
        }
235
236
        // check to see if issuedAt + expiresIn > provided DateTime
237
        $issuedAt = clone $this->issuedAt; // XXX do we need to clone here?
238
        $expiresAt = date_add($issuedAt, new DateInterval(sprintf('PT%dS', $this->getExpiresIn())));
239
240
        return $dateTime >= $expiresAt;
241
    }
242
243
    /**
244
     * @param string $providerId
245
     */
246
    private function setProviderId($providerId)
247
    {
248
        $this->providerId = $providerId;
249
    }
250
251
    /**
252
     * @param string $issuedAt
253
     */
254
    private function setIssuedAt($issuedAt)
255
    {
256
        self::requireString('expires_at', $issuedAt);
257
        if (1 !== preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/', $issuedAt)) {
258
            throw new AccessTokenException('invalid "expires_at"');
259
        }
260
        $this->issuedAt = new DateTime($issuedAt);
261
    }
262
263
    /**
264
     * @param string $accessToken
265
     */
266
    private function setAccessToken($accessToken)
267
    {
268
        self::requireString('access_token', $accessToken);
269
        // access-token = 1*VSCHAR
270
        // VSCHAR       = %x20-7E
271
        if (1 !== preg_match('/^[\x20-\x7E]+$/', $accessToken)) {
272
            throw new AccessTokenException('invalid "access_token"');
273
        }
274
        $this->accessToken = $accessToken;
275
    }
276
277
    /**
278
     * @param string $tokenType
279
     */
280
    private function setTokenType($tokenType)
281
    {
282
        self::requireString('token_type', $tokenType);
283
        if ('bearer' !== $tokenType) {
284
            throw new AccessTokenException('unsupported "token_type"');
285
        }
286
        $this->tokenType = $tokenType;
287
    }
288
289
    /**
290
     * @param int|null $expiresIn
291
     */
292
    private function setExpiresIn($expiresIn)
293
    {
294
        if (!is_null($expiresIn)) {
295
            self::requireInt('expires_in', $expiresIn);
296
            if (0 >= $expiresIn) {
297
                throw new AccessTokenException('invalid "expires_in"');
298
            }
299
        }
300
        $this->expiresIn = $expiresIn;
301
    }
302
303
    /**
304
     * @param string|null $refreshToken
305
     */
306 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...
307
    {
308
        if (!is_null($refreshToken)) {
309
            self::requireString('refresh_token', $refreshToken);
310
            // refresh-token = 1*VSCHAR
311
            // VSCHAR        = %x20-7E
312
            if (1 !== preg_match('/^[\x20-\x7E]+$/', $refreshToken)) {
313
                throw new AccessTokenException('invalid "refresh_token"');
314
            }
315
        }
316
        $this->refreshToken = $refreshToken;
317
    }
318
319
    /**
320
     * @param string|null $scope
321
     */
322 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...
323
    {
324
        if (!is_null($scope)) {
325
            self::requireString('scope', $scope);
326
            // scope       = scope-token *( SP scope-token )
327
            // scope-token = 1*NQCHAR
328
            // NQCHAR      = %x21 / %x23-5B / %x5D-7E
329
            foreach (explode(' ', $scope) as $scopeToken) {
330
                if (1 !== preg_match('/^[\x21\x23-\x5B\x5D-\x7E]+$/', $scopeToken)) {
331
                    throw new AccessTokenException('invalid "scope"');
332
                }
333
            }
334
        }
335
        $this->scope = $scope;
336
    }
337
338
    /**
339
     * @param string $k
340
     * @param string $v
341
     */
342
    private static function requireString($k, $v)
343
    {
344
        if (!is_string($v)) {
345
            throw new AccessTokenException(sprintf('"%s" must be string', $k));
346
        }
347
    }
348
349
    /**
350
     * @param string $k
351
     * @param int    $v
352
     */
353
    private static function requireInt($k, $v)
354
    {
355
        if (!is_int($v)) {
356
            throw new AccessTokenException(sprintf('"%s" must be int', $k));
357
        }
358
    }
359
}
360