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 ( 994736...366350 )
by François
08:25 queued 05:01
created

ApiModule::deleteDocument()   C

Complexity

Conditions 8
Paths 6

Size

Total Lines 48
Code Lines 26

Duplication

Lines 6
Ratio 12.5 %

Importance

Changes 0
Metric Value
dl 6
loc 48
rs 5.9322
c 0
b 0
f 0
cc 8
eloc 26
nc 6
nop 2
1
<?php
2
3
/**
4
 *  This program is free software: you can redistribute it and/or modify
5
 *  it under the terms of the GNU Lesser General Public License as published by
6
 *  the Free Software Foundation, either version 3 of the License, or
7
 *  (at your option) any later version.
8
 *
9
 *  This program is distributed in the hope that it will be useful,
10
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 *  GNU Lesser General Public License for more details.
13
 *
14
 *  You should have received a copy of the GNU Lesser General Public License
15
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
namespace fkooman\RemoteStorage;
19
20
use fkooman\RemoteStorage\Exception\PathException;
21
use fkooman\RemoteStorage\Http\Exception\HttpException;
22
use fkooman\RemoteStorage\Http\Request;
23
use fkooman\RemoteStorage\Http\Response;
24
use fkooman\RemoteStorage\Http\Service;
25
use fkooman\RemoteStorage\Http\ServiceModuleInterface;
26
use fkooman\RemoteStorage\OAuth\TokenInfo;
27
use fkooman\RemoteStorage\OAuth\TokenStorage;
28
use InvalidArgumentException;
29
30
class ApiModule implements ServiceModuleInterface
31
{
32
    /** @var RemoteStorage */
33
    private $remoteStorage;
34
35
    /** @var \fkooman\RemoteStorage\OAuth\TokenStorage */
36
    private $tokenStorage;
37
38
    /** @var string */
39
    private $serverMode;
40
41
    public function __construct(RemoteStorage $remoteStorage, TokenStorage $tokenStorage, $serverMode)
0 ignored issues
show
Unused Code introduced by
The parameter $serverMode is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
42
    {
43
        $this->remoteStorage = $remoteStorage;
44
        $this->tokenStorage = $tokenStorage;
45
    }
46
47
    public function init(Service $service)
48
    {
49
        // ApiAuth OPTIONAL??
50
        // maybe for "public" files it is optional?
51
        $service->addRoute(
52
            'GET',
53
            '*',
54 View Code Duplication
            function (Request $request, array $hookData) {
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...
55
                $tokenInfo = $hookData['bearer'];
56
57
                $response = $this->getObject($request, $tokenInfo);
58
                $this->addNoCache($response);
59
                $this->addCors($response);
60
61
                return $response;
62
            }
63
        );
64
65
        $service->addRoute(
66
            'HEAD',
67
            '*',
68 View Code Duplication
            function (Request $request, array $hookData) {
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...
69
                $tokenInfo = $hookData['bearer'];
70
71
                $response = $this->getObject($request, $tokenInfo);
72
                $this->addNoCache($response);
73
                $this->addCors($response);
74
75
                return $response;
76
            }
77
        );
78
79
        // put a document
80
        // ApiAuth
81
        $service->put(
82
            '*',
83 View Code Duplication
            function (Request $request, array $hookData) {
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...
84
                $tokenInfo = $hookData['bearer'];
85
86
                $response = $this->putDocument($request, $tokenInfo);
87
                $this->addCors($response);
88
89
                return $response;
90
            }
91
        );
92
93
        // delete a document
94
        // ApiAuth
95
        $service->delete(
96
            '*',
97 View Code Duplication
            function (Request $request, array $hookData) {
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...
98
                $tokenInfo = $hookData['bearer'];
99
100
                $response = $this->deleteDocument($request, $tokenInfo);
101
                $this->addCors($response);
102
103
                return $response;
104
            }
105
        );
106
107
        // options request
108
        // NoAuth
109
        $service->options(
110
            '*',
111
            function (Request $request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
112
                $response = new Response();
113
                $response->addHeader(
114
                    'Access-Control-Allow-Methods',
115
                    'GET, PUT, DELETE, HEAD, OPTIONS'
116
                );
117
                $response->addHeader(
118
                    'Access-Control-Allow-Headers',
119
                    'Authorization, Content-Length, Content-Type, Origin, X-Requested-With, If-Match, If-None-Match'
120
                );
121
                $this->addCors($response);
122
123
                return $response;
124
            }
125
        );
126
    }
127
128
    public function getObject(Request $request, TokenInfo $tokenInfo)
129
    {
130
        $path = new Path($request->getPathInfo());
131
132
        // allow requests to public files (GET|HEAD) without authentication
133
        if ($path->getIsPublic() && $path->getIsDocument()) {
134
            return $this->getDocument($path, $request, $tokenInfo);
135
        }
136
137
        // past this point we MUST be authenticated
138
        if (null === $tokenInfo) {
139
            throw new HttpException(
140
                'no_token',
141
                401,
142
                ['WWW-Authenticate' => 'Bearer realm="remoteStorage API"']
143
            );
144
        }
145
146
        if ($path->getIsFolder()) {
147
            return $this->getFolder($path, $request, $tokenInfo);
148
        }
149
150
        return $this->getDocument($path, $request, $tokenInfo);
151
    }
152
153
    public function getFolder(Path $path, Request $request, TokenInfo $tokenInfo)
154
    {
155
        if ($path->getUserId() !== $tokenInfo->getUserId()) {
156
            throw new HttpException('path does not match authorized subject', 403);
157
        }
158
        if (!$this->hasReadScope($tokenInfo->getScope(), $path->getModuleName())) {
159
            throw new HttpException('path does not match authorized scope', 403);
160
        }
161
162
        $folderVersion = $this->remoteStorage->getVersion($path);
163
        if (null === $folderVersion) {
164
            // folder does not exist, so we just invent this
165
            // ETag that will be the same for all empty folders
166
            $folderVersion = 'e:404';
167
        }
168
169
        $requestedVersion = $this->stripQuotes(
170
            $request->getHeader('If-None-Match', false, null)
171
        );
172
173 View Code Duplication
        if (null !== $requestedVersion) {
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...
174
            if (in_array($folderVersion, $requestedVersion)) {
175
                //return new RemoteStorageResponse($request, 304, $folderVersion);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
176
                $response = new Response(304, 'application/ld+json');
177
                $response->addHeader('ETag', '"'.$folderVersion.'"');
178
179
                return $response;
180
            }
181
        }
182
183
        $rsr = new Response(200, 'application/ld+json');
184
        $rsr->addHeader('ETag', '"'.$folderVersion.'"');
185
186
        if ('GET' === $request->getRequestMethod()) {
187
            $rsr->setBody(
188
                $this->remoteStorage->getFolder(
189
                    $path,
190
                    $this->stripQuotes(
191
                        $request->getHeader('If-None-Match', false, null)
192
                    )
193
                )
194
            );
195
        }
196
197
        return $rsr;
198
    }
199
200
    public function getDocument(Path $path, Request $request, TokenInfo $tokenInfo = null)
201
    {
202
        if (null !== $tokenInfo) {
203
            if ($path->getUserId() !== $tokenInfo->getUserId()) {
204
                throw new HttpException('path does not match authorized subject', 403);
205
            }
206
            if (!$this->hasReadScope($tokenInfo->getScope(), $path->getModuleName())) {
207
                throw new HttpException('path does not match authorized scope', 403);
208
            }
209
        }
210
        $documentVersion = $this->remoteStorage->getVersion($path);
211
        if (null === $documentVersion) {
212
            throw new HttpException(
213
                sprintf('document "%s" not found', $path->getPath()),
214
                404
215
            );
216
        }
217
218
        $requestedVersion = $this->stripQuotes(
219
            $request->getHeader('If-None-Match', false, null)
220
        );
221
        $documentContentType = $this->remoteStorage->getContentType($path);
222
223 View Code Duplication
        if (null !== $requestedVersion) {
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...
224
            if (in_array($documentVersion, $requestedVersion)) {
225
                $response = new Response(304, $documentContentType);
226
                $response->addHeader('ETag', '"'.$documentVersion.'"');
227
228
                return $response;
229
            }
230
        }
231
232
        $rsr = new Response(200, $documentContentType);
233
        $rsr->addHeader('ETag', '"'.$documentVersion.'"');
234
235
        if ('development' !== $this->serverMode) {
236
            $rsr->addHeader('Accept-Ranges', 'bytes');
237
        }
238
239
        if ('GET' === $request->getRequestMethod()) {
240
            if ('development' === $this->serverMode) {
241
                // use body
242
                $rsr->setBody(
243
                    file_get_contents(
244
                        $this->remoteStorage->getDocument(
245
                            $path,
246
                            $requestedVersion
247
                        )
248
                    )
249
                );
250
            } else {
251
                // use X-SendFile
252
                $rsr->setFile(
253
                    $this->remoteStorage->getDocument(
254
                        $path,
255
                        $requestedVersion
256
                    )
257
                );
258
            }
259
        }
260
261
        return $rsr;
262
    }
263
264
    public function putDocument(Request $request, TokenInfo $tokenInfo)
265
    {
266
        $path = new Path($request->getPathInfo());
267
268
        if ($path->getUserId() !== $tokenInfo->getUserId()) {
269
            throw new HttpException('path does not match authorized subject', 403);
270
        }
271
        if (!$this->hasWriteScope($tokenInfo->getScope(), $path->getModuleName())) {
272
            throw new HttpException('path does not match authorized scope', 403);
273
        }
274
275
        $ifMatch = $this->stripQuotes(
276
            $request->getHeader('If-Match', false, null)
277
        );
278
        $ifNoneMatch = $this->stripQuotes(
279
            $request->getHeader('If-None-Match', false, null)
280
        );
281
282
        $documentVersion = $this->remoteStorage->getVersion($path);
283 View Code Duplication
        if (null !== $ifMatch && !in_array($documentVersion, $ifMatch)) {
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...
284
            throw new HttpException('version mismatch', 412);
285
        }
286
287
        if (null !== $ifNoneMatch && in_array('*', $ifNoneMatch) && null !== $documentVersion) {
288
            throw new HttpException('document already exists', 412);
289
        }
290
291
        $x = $this->remoteStorage->putDocument(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $x is correct as $this->remoteStorage->pu...$ifMatch, $ifNoneMatch) (which targets fkooman\RemoteStorage\RemoteStorage::putDocument()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
292
            $path,
293
            $request->getHeader('Content-Type'),
294
            $request->getBody(),
295
            $ifMatch,
296
            $ifNoneMatch
297
        );
298
        // we have to get the version again after the PUT
299
        $documentVersion = $this->remoteStorage->getVersion($path);
300
301
        $rsr = new Response();
302
        $rsr->addHeader('ETag', '"'.$documentVersion.'"');
303
        $rsr->setBody($x);
304
305
        return $rsr;
306
    }
307
308
    public function deleteDocument(Request $request, TokenInfo $tokenInfo)
309
    {
310
        $path = new Path($request->getPathInfo());
311
312
        if ($path->getUserId() !== $tokenInfo->getUserId()) {
313
            throw new HttpException('path does not match authorized subject', 403);
314
        }
315
        if (!$this->hasWriteScope($tokenInfo->getScope(), $path->getModuleName())) {
316
            throw new HttpException('path does not match authorized scope', 403);
317
        }
318
319
        // need to get the version before the delete
320
        $documentVersion = $this->remoteStorage->getVersion($path);
321
322
        $ifMatch = $this->stripQuotes(
323
            $request->getHeader('If-Match', false, null)
324
        );
325
326
        // if document does not exist, and we have If-Match header set we should
327
        // return a 412 instead of a 404
328 View Code Duplication
        if (null !== $ifMatch && !in_array($documentVersion, $ifMatch)) {
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...
329
            throw new HttpException('version mismatch', 412);
330
        }
331
332
        if (null === $documentVersion) {
333
            throw new HttpException(
334
                sprintf('document "%s" not found', $path->getPath()),
335
                404
336
            );
337
        }
338
339
        $ifMatch = $this->stripQuotes(
340
            $request->getHeader('If-Match', false, null)
341
        );
342 View Code Duplication
        if (null !== $ifMatch && !in_array($documentVersion, $ifMatch)) {
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...
343
            throw new HttpException('version mismatch', 412);
344
        }
345
346
        $x = $this->remoteStorage->deleteDocument(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $x is correct as $this->remoteStorage->de...cument($path, $ifMatch) (which targets fkooman\RemoteStorage\Re...orage::deleteDocument()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
347
            $path,
348
            $ifMatch
349
        );
350
        $rsr = new Response();
351
        $rsr->addHeader('ETag', '"'.$documentVersion.'"');
352
        $rsr->setBody($x);
353
354
        return $rsr;
355
    }
356
357
    /**
358
     * ETag/If-Match/If-None-Match are always quoted, this method removes
359
     * the quotes.
360
     */
361
    public function stripQuotes($versionHeader)
362
    {
363
        if (null === $versionHeader) {
364
            return;
365
        }
366
367
        $versions = [];
368
369
        if ('*' === $versionHeader) {
370
            return ['*'];
371
        }
372
373
        foreach (explode(',', $versionHeader) as $v) {
374
            $v = trim($v);
375
            $startQuote = strpos($v, '"');
376
            $endQuote = strrpos($v, '"');
377
            $length = strlen($v);
378
379
            if (0 !== $startQuote || $length - 1 !== $endQuote) {
380
                throw new HttpException('version header must start and end with a double quote', 400);
381
            }
382
            $versions[] = substr($v, 1, $length - 2);
383
        }
384
385
        return $versions;
386
    }
387
388
//    public function run(Request $request = null)
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
389
//    {
390
//        if (null === $request) {
391
//            throw new InvalidArgumentException('must provide Request object');
392
//        }
393
394
//        $response = null;
0 ignored issues
show
Unused Code Comprehensibility introduced by
49% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
395
//        try {
396
//            $response = parent::run($request);
397
//        } catch (PathException $e) {
398
//            $e = new BadRequestException($e->getMessage());
399
//            $response = $e->getJsonResponse();
400
//        }
401
402
//        // if error, add CORS
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
403
//        $statusCode = $response->getStatusCode();
404
//        if (400 <= $statusCode && 500 > $statusCode) {
405
//            $this->addCors($response);
406
//            $this->addNoCache($response);
407
//        }
408
409
//        return $response;
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
410
//    }
411
412
    private function hasReadScope($scope, $moduleName)
413
    {
414
        $obtainedScopes = explode(' ', $scope);
415
        $requiredScopes = [
416
            '*:r',
417
            '*:rw',
418
            sprintf('%s:%s', $moduleName, 'r'),
419
            sprintf('%s:%s', $moduleName, 'rw'),
420
        ];
421
422
        foreach ($requiredScopes as $requiredScope) {
423
            if (in_array($requiredScope, $obtainedScopes)) {
424
                return true;
425
            }
426
        }
427
428
        return false;
429
    }
430
431
    private function hasWriteScope($scope, $moduleName)
432
    {
433
        $obtainedScopes = explode(' ', $scope);
434
        $requiredScopes = [
435
            '*:rw',
436
            sprintf('%s:%s', $moduleName, 'rw'),
437
        ];
438
439
        foreach ($requiredScopes as $requiredScope) {
440
            if (in_array($requiredScope, $obtainedScopes)) {
441
                return true;
442
            }
443
        }
444
445
        return false;
446
    }
447
448
    private function addCors(Response &$response)
449
    {
450
        $response->addHeader('Access-Control-Allow-Origin', '*');
451
        $response->addHeader(
452
            'Access-Control-Expose-Headers',
453
            'ETag, Content-Length'
454
        );
455
    }
456
457
    private function addNoCache(Response &$response)
458
    {
459
        $response->addHeader('Expires', '0');
460
        $response->addHeader('Cache-Control', 'no-cache');
461
    }
462
}
463