Passed
Pull Request — master (#36)
by
unknown
26:41 queued 11:34
created

Wopi::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 6
Bugs 1 Features 0
Metric Value
cc 1
eloc 10
c 6
b 1
f 0
nc 1
nop 10
dl 0
loc 22
ccs 0
cts 9
cp 0
crap 2
rs 9.9332

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 */
7
8
declare(strict_types=1);
9
10
namespace ChampsLibres\WopiBundle\Service;
11
12
use ChampsLibres\WopiBundle\Contracts\AuthorizationManagerInterface;
13
use ChampsLibres\WopiBundle\Contracts\UserManagerInterface;
14
use ChampsLibres\WopiBundle\Service\Wopi\PutFile;
15
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
16
use ChampsLibres\WopiLib\Contract\Service\WopiInterface;
17
use DateTimeInterface;
18
19
use Psr\Http\Message\RequestInterface;
20
use Psr\Http\Message\ResponseFactoryInterface;
21
22
use Psr\Http\Message\ResponseInterface;
23
use Psr\Http\Message\StreamFactoryInterface;
24
use Psr\Http\Message\UriFactoryInterface;
25
use Psr\Log\LoggerInterface;
26
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
27
use Symfony\Component\HttpFoundation\Response;
28
29
use Symfony\Component\Routing\RouterInterface;
30
31
use const PATHINFO_EXTENSION;
32
use const PATHINFO_FILENAME;
33
34
final class Wopi implements WopiInterface
35
{
36
    private const LOG_PREFIX = '[wopi][Wopi] ';
37
38
    private AuthorizationManagerInterface $authorizationManager;
39
40
    private DocumentManagerInterface $documentManager;
41
42
    private bool $enableLock;
43
44
    private LoggerInterface $logger;
45
46
    private PutFile $putFileExecutor;
47
48
    private ResponseFactoryInterface $responseFactory;
49
50
    private RouterInterface $router;
51
52
    private StreamFactoryInterface $streamFactory;
53
54
    private UriFactoryInterface $uriFactory;
55
56
    private UserManagerInterface $userManager;
57
58
    public function __construct(
59
        AuthorizationManagerInterface $authorizationManager,
60
        DocumentManagerInterface $documentManager,
61
        LoggerInterface $logger,
62
        ResponseFactoryInterface $responseFactory,
63
        RouterInterface $router,
64
        StreamFactoryInterface $streamFactory,
65
        UriFactoryInterface $uriFactory,
66
        UserManagerInterface $userManager,
67
        PutFile $putFile,
68
        ParameterBagInterface $parameterBag
69
    ) {
70
        $this->authorizationManager = $authorizationManager;
71
        $this->documentManager = $documentManager;
72
        $this->logger = $logger;
73
        $this->responseFactory = $responseFactory;
74
        $this->streamFactory = $streamFactory;
75
        $this->router = $router;
76
        $this->uriFactory = $uriFactory;
77
        $this->userManager = $userManager;
78
        $this->putFileExecutor = $putFile;
79
        $this->enableLock = $parameterBag->get('wopi')['enable_lock'];
80
    }
81
82
    /**
83
     * @param array<string, string|boolean|int|null> $overrideProperties
84
     */
85
    public function checkFileInfo(string $fileId, string $accessToken, RequestInterface $request, array $overrideProperties = []): ResponseInterface
86
    {
87
        $userIdentifier = $this->userManager->getUserId($accessToken, $fileId, $request);
88
89
        if (null === $userIdentifier && false === $this->userManager->isAnonymousUser($accessToken, $fileId, $request)) {
90
            $this->logger->error(self::LOG_PREFIX . 'user not found nor anonymous');
91
92
            return $this->responseFactory
93
                ->createResponse(404)
94
                ->withBody($this->streamFactory->createStream((string) json_encode(['message' => 'user not found nor anonymous'])));
95
        }
96
97
        $document = $this->documentManager->findByDocumentId($fileId);
98
99
        if (null === $document) {
100
            return $this->makeDocumentNotFoundResponse($fileId);
101
        }
102
103
        if (!$this->authorizationManager->userCanRead($accessToken, $document, $request)) {
104
            $this->logger->info(self::LOG_PREFIX . 'user is not allowed to read document', ['fileId' => $fileId, 'userIdentifier' => $userIdentifier]);
105
106
            return $this->responseFactory->createResponse(401)->withBody($this->streamFactory->createStream((string) json_encode([
107
                'message' => 'user is not allowed to see this document',
108
            ])));
109
        }
110
111
        $properties = [
112
            'BaseFileName' => $this->documentManager->getBasename($document),
113
            'OwnerId' => 'Symfony',
114
            'Size' => $this->documentManager->getSize($document),
115
            'UserId' => $userIdentifier,
116
            'ReadOnly' => !$this->authorizationManager->userCanWrite($accessToken, $document, $request),
117
            'RestrictedWebViewOnly' => $this->authorizationManager->isRestrictedWebViewOnly($accessToken, $document, $request),
118
            'UserCanAttend' => $this->authorizationManager->userCanAttend($accessToken, $document, $request),
119
            'UserCanPresent' => $this->authorizationManager->userCanPresent($accessToken, $document, $request),
120
            'UserCanRename' => $this->authorizationManager->userCanRename($accessToken, $document, $request),
121
            'UserCanWrite' => $this->authorizationManager->userCanWrite($accessToken, $document, $request),
122
            'UserCanNotWriteRelative' => $this->authorizationManager->userCannotWriteRelative($accessToken, $document, $request),
123
            'SupportsUserInfo' => false,
124
            'SupportsDeleteFile' => true,
125
            'SupportsLocks' => $this->enableLock,
126
            'SupportsGetLock' => $this->enableLock,
127
            'SupportsExtendedLockLength' => $this->enableLock,
128
            'SupportsUpdate' => true,
129
            'SupportsRename' => true,
130
            'SupportsFolders' => false,
131
            'UserFriendlyName' => $this->userManager->getUserFriendlyName($accessToken, $fileId, $request),
132
            'DisablePrint' => false,
133
            'AllowExternalMarketplace' => false,
134
            'SupportedShareUrlTypes' => [
135
                'ReadOnly',
136
            ],
137
            'SHA256' => $this->documentManager->getSha256($document),
138
            'LastModifiedTime' => $this->documentManager->getLastModifiedDate($document)
139
                ->format(DateTimeInterface::ATOM),
140
        ];
141
142
        return $this
143
            ->responseFactory
144
            ->createResponse()
145
            ->withHeader('Content-Type', 'application/json')
146
            ->withBody($this->streamFactory->createStream((string) json_encode(array_merge($properties, $overrideProperties))));
147
    }
148
149
    public function deleteFile(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
150
    {
151
        $document = $this->documentManager->findByDocumentId($fileId);
152
153
        if (null === $document) {
154
            return $this->makeDocumentNotFoundResponse($fileId);
155
        }
156
157
        if (
158
            false === $this->authorizationManager->userCanDelete($accessToken, $document, $request)
159
            || false === $this->authorizationManager->userCanWrite($accessToken, $document, $request)
160
        ) {
161
            $this->logger->info(
162
                self::LOG_PREFIX . 'user is not authorized to delete file',
163
                ['fileId' => $fileId, 'userId' => $this->userManager->getUserId($accessToken, $fileId, $request)]
164
            );
165
166
            return $this->responseFactory
167
                ->createResponse(401);
168
        }
169
170
        $this->documentManager->remove($document);
171
172
        return $this
173
            ->responseFactory
174
            ->createResponse(200);
175
    }
176
177
    public function enumerateAncestors(
178
        string $fileId,
179
        string $accessToken,
180
        RequestInterface $request
181
    ): ResponseInterface {
182
        return $this
183
            ->responseFactory
184
            ->createResponse(501);
185
    }
186
187
    public function getFile(
188
        string $fileId,
189
        string $accessToken,
190
        RequestInterface $request
191
    ): ResponseInterface {
192
        $document = $this->documentManager->findByDocumentId($fileId);
193
194
        if (null === $document) {
195
            return $this->makeDocumentNotFoundResponse($fileId);
196
        }
197
198
        if (!$this->authorizationManager->userCanRead($accessToken, $document, $request)) {
199
            $userIdentifier = $this->userManager->getUserId($accessToken, $fileId, $request);
200
            $this->logger->info(self::LOG_PREFIX . 'user is not allowed to read document', ['fileId' => $fileId, 'userIdentifier' => $userIdentifier]);
201
202
            return $this->responseFactory->createResponse(401)->withBody($this->streamFactory->createStream((string) json_encode([
203
                'message' => 'user is not allowed to see this document',
204
            ])));
205
        }
206
207
        $revision = $this->documentManager->getVersion($document);
208
        $content = $this->documentManager->read($document);
209
210
        return $this
211
            ->responseFactory
212
            ->createResponse()
213
            ->withHeader(
214
                WopiInterface::HEADER_ITEM_VERSION,
215
                sprintf('v%s', $revision)
216
            )
217
            ->withHeader(
218
                'Content-Type',
219
                'application/octet-stream',
220
            )
221
            ->withHeader(
222
                'Content-Length',
223
                (string) $this->documentManager->getSize($document)
224
            )
225
            ->withHeader(
226
                'Content-Disposition',
227
                sprintf('attachment; filename=%s', $this->documentManager->getBasename($document))
228
            )
229
            ->withBody($content);
230
    }
231
232
    public function getLock(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
233
    {
234
        $document = $this->documentManager->findByDocumentId($fileId);
235
236
        if (null === $document) {
237
            return $this->makeDocumentNotFoundResponse($fileId);
238
        }
239
240
        if (!$this->authorizationManager->isTokenValid($accessToken, $document, $request)) {
241
            $this->logger->info(self::LOG_PREFIX . 'invalid access token', ['fileId' => $fileId]);
242
243
            return $this->responseFactory->createResponse(401)->withBody($this->streamFactory->createStream((string) json_encode([
244
                'message' => 'invalid access token',
245
            ])));
246
        }
247
248
        if ($this->documentManager->hasLock($document)) {
249
            return $this
250
                ->responseFactory
251
                ->createResponse()
252
                ->withHeader(WopiInterface::HEADER_LOCK, $this->documentManager->getLock($document));
253
        }
254
255
        return $this
256
            ->responseFactory
257
            ->createResponse(404)
258
            ->withHeader(WopiInterface::HEADER_LOCK, '');
259
    }
260
261
    public function getShareUrl(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
262
    {
263
        return $this
264
            ->responseFactory
265
            ->createResponse(501);
266
    }
267
268
    public function lock(
269
        string $fileId,
270
        string $accessToken,
271
        string $xWopiLock,
272
        RequestInterface $request
273
    ): ResponseInterface {
274
        $document = $this->documentManager->findByDocumentId($fileId);
275
276
        if (null === $document) {
277
            return $this->makeDocumentNotFoundResponse($fileId);
278
        }
279
280
        if (!$this->authorizationManager->isTokenValid($accessToken, $document, $request)) {
281
            $this->logger->info(self::LOG_PREFIX . 'invalid access token', ['fileId' => $fileId]);
282
283
            return $this->responseFactory->createResponse(401)->withBody($this->streamFactory->createStream((string) json_encode([
284
                'message' => 'invalid access token',
285
            ])));
286
        }
287
288
        $version = $this->documentManager->getVersion($document);
289
290
        if ($this->documentManager->hasLock($document)) {
291
            if ($xWopiLock !== $currentLock = $this->documentManager->getLock($document)) {
292
                return $this
293
                    ->responseFactory
294
                    ->createResponse(409)
295
                    ->withHeader(WopiInterface::HEADER_LOCK, $currentLock)
296
                    ->withHeader(
297
                        WopiInterface::HEADER_ITEM_VERSION,
298
                        sprintf('v%s', $version)
299
                    );
300
            }
301
        }
302
303
        $this->documentManager->lock($document, $xWopiLock);
304
305
        return $this
306
            ->responseFactory
307
            ->createResponse()
308
            ->withHeader(
309
                WopiInterface::HEADER_ITEM_VERSION,
310
                sprintf('v%s', $version)
311
            );
312
    }
313
314
    public function putFile(
315
        string $fileId,
316
        string $accessToken,
317
        string $xWopiLock,
318
        string $xWopiEditors,
319
        RequestInterface $request
320
    ): ResponseInterface {
321
        return ($this->putFileExecutor)($fileId, $accessToken, $xWopiLock, $xWopiEditors, $request);
322
    }
323
324
    public function putRelativeFile(
325
        string $fileId,
326
        string $accessToken,
327
        ?string $suggestedTarget,
328
        ?string $relativeTarget,
329
        bool $overwriteRelativeTarget,
330
        int $size,
331
        RequestInterface $request
332
    ): ResponseInterface {
333
        if ((null === $suggestedTarget) && (null === $relativeTarget)) {
334
            return $this
335
                ->responseFactory
336
                ->createResponse(400)
337
                ->withBody($this->streamFactory->createStream((string) json_encode([
338
                    'message' => 'target is null',
339
                ])));
340
        }
341
342
        if (null !== $suggestedTarget) {
343
            // If it starts with a dot...
344
            if (0 === strpos($suggestedTarget, '.', 0)) {
345
                $document = $this->documentManager->findByDocumentId($fileId);
346
347
                if (null === $document) {
348
                    return $this->makeDocumentNotFoundResponse($fileId);
349
                }
350
                $filename = pathinfo($this->documentManager->getBasename($document), PATHINFO_EXTENSION | PATHINFO_FILENAME);
351
352
                $suggestedTarget = sprintf('%s%s', $filename, $suggestedTarget);
0 ignored issues
show
Bug introduced by
It seems like $filename can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

352
                $suggestedTarget = sprintf('%s%s', /** @scrutinizer ignore-type */ $filename, $suggestedTarget);
Loading history...
353
            }
354
355
            $target = $suggestedTarget;
356
        } else {
357
            $document = $this->documentManager->findByDocumentFilename($relativeTarget);
0 ignored issues
show
Bug introduced by
It seems like $relativeTarget can also be of type null; however, parameter $documentFilename of ChampsLibres\WopiLib\Con...indByDocumentFilename() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

357
            $document = $this->documentManager->findByDocumentFilename(/** @scrutinizer ignore-type */ $relativeTarget);
Loading history...
358
359
            /**
360
             * If a file with the specified name already exists,
361
             * the host must respond with a 409 Conflict,
362
             * unless the X-WOPI-OverwriteRelativeTarget request header is set to true.
363
             *
364
             * When responding with a 409 Conflict for this reason,
365
             * the host may include an X-WOPI-ValidRelativeTarget specifying a file name that is valid.
366
             *
367
             * If the X-WOPI-OverwriteRelativeTarget request header is set to true
368
             * and a file with the specified name already exists and is locked,
369
             * the host must respond with a 409 Conflict and include an
370
             * X-WOPI-Lock response header containing the value of the current lock on the file.
371
             */
372
            if (null !== $document) {
373
                if (false === $overwriteRelativeTarget) {
374
                    $extension = pathinfo($this->documentManager->getBasename($document), PATHINFO_EXTENSION);
375
376
                    return $this
377
                        ->responseFactory
378
                        ->createResponse(409)
379
                        ->withHeader('Content-Type', 'application/json')
380
                        ->withHeader(
381
                            WopiInterface::HEADER_VALID_RELATIVE_TARGET,
382
                            sprintf('%s.%s', uniqid(), $extension)
383
                        );
384
                }
385
386
                if ($this->enableLock && $this->documentManager->hasLock($document)) {
387
                    return $this
388
                        ->responseFactory
389
                        ->createResponse(409)
390
                        ->withHeader(WopiInterface::HEADER_LOCK, $this->documentManager->getLock($document));
391
                }
392
            }
393
394
            $target = $relativeTarget;
395
        }
396
397
        /** @var array{filename: string, extension: string} $pathInfo */
398
        $pathInfo = pathinfo($target, PATHINFO_EXTENSION | PATHINFO_FILENAME);
0 ignored issues
show
Bug introduced by
It seems like $target can also be of type null; however, parameter $path of pathinfo() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

398
        $pathInfo = pathinfo(/** @scrutinizer ignore-type */ $target, PATHINFO_EXTENSION | PATHINFO_FILENAME);
Loading history...
399
400
        $new = $this->documentManager->create([
401
            'basename' => $target,
402
            'name' => $pathInfo['filename'],
403
            'extension' => $pathInfo['extension'],
404
            'content' => (string) $request->getBody(),
405
            'size' => $request->getHeaderLine(WopiInterface::HEADER_SIZE),
406
        ]);
407
408
        $this->documentManager->write($new);
409
410
        $uri = $this
411
            ->uriFactory
412
            ->createUri(
413
                $this
414
                    ->router
415
                    ->generate(
416
                        'checkFileInfo',
417
                        [
418
                            'fileId' => $this->documentManager->getDocumentId($new),
419
                        ],
420
                        RouterInterface::ABSOLUTE_URL
421
                    )
422
            )
423
            ->withQuery(http_build_query([
424
                'access_token' => $accessToken,
425
            ]));
426
427
        $properties = [
428
            'Name' => $this->documentManager->getBasename($new),
429
            'Url' => (string) $uri,
430
            'HostEditUrl' => $this->documentManager->getDocumentId($new),
431
            'HostViewUrl' => $this->documentManager->getDocumentId($new),
432
        ];
433
434
        return $this
435
            ->responseFactory
436
            ->createResponse()
437
            ->withHeader('Content-Type', 'application/json')
438
            ->withBody($this->streamFactory->createStream((string) json_encode($properties)));
439
    }
440
441
    public function putUserInfo(string $fileId, string $accessToken, RequestInterface $request): ResponseInterface
442
    {
443
        $this->logger->warning(self::LOG_PREFIX . 'user info called, but not implemented');
444
445
        return $this->responseFactory->createResponse(501)
446
            ->withBody($this->streamFactory->createStream((string) json_encode([
447
                'message' => 'User info not implemented',
448
            ])));
449
    }
450
451
    public function refreshLock(
452
        string $fileId,
453
        string $accessToken,
454
        string $xWopiLock,
455
        RequestInterface $request
456
    ): ResponseInterface {
457
        // note: the validation of access token is done inside unlock and lock methods
458
        $this->unlock($fileId, $accessToken, $xWopiLock, $request);
459
460
        return $this->lock($fileId, $accessToken, $xWopiLock, $request);
461
    }
462
463
    public function renameFile(
464
        string $fileId,
465
        string $accessToken,
466
        string $xWopiLock,
467
        string $xWopiRequestedName,
468
        RequestInterface $request
469
    ): ResponseInterface {
470
        $document = $this->documentManager->findByDocumentId($fileId);
471
472
        if (null === $document) {
473
            return $this->makeDocumentNotFoundResponse($fileId);
474
        }
475
476
        if (!$this->authorizationManager->userCanRename($accessToken, $document, $request)) {
477
            $userIdentifier = $this->userManager->getUserId($accessToken, $fileId, $request);
478
            $this->logger->info(self::LOG_PREFIX . 'user is not allowed to rename', ['fileId' => $fileId, 'userIdentifier' => $userIdentifier]);
479
480
            return $this->responseFactory->createResponse(401)->withBody($this->streamFactory->createStream((string) json_encode([
481
                'message' => 'user is not allowed to rename',
482
            ])));
483
        }
484
485
        if ($this->enableLock && $this->documentManager->hasLock($document)) {
486
            if ($xWopiLock !== $currentLock = $this->documentManager->getLock($document)) {
487
                return $this
488
                    ->responseFactory
489
                    ->createResponse(409)
490
                    ->withHeader(WopiInterface::HEADER_LOCK, $currentLock);
491
            }
492
        }
493
494
        $this->documentManager->rename($document, $xWopiRequestedName);
495
496
        $data = [
497
            'Name' => $xWopiRequestedName,
498
        ];
499
500
        return $this
501
            ->responseFactory
502
            ->createResponse(200)
503
            ->withHeader('Content-Type', 'application/json')
504
            ->withBody(
505
                $this->streamFactory->createStream((string) json_encode($data))
506
            );
507
    }
508
509
    public function unlock(
510
        string $fileId,
511
        string $accessToken,
512
        string $xWopiLock,
513
        RequestInterface $request
514
    ): ResponseInterface {
515
        $document = $this->documentManager->findByDocumentId($fileId);
516
517
        if (null === $document) {
518
            return $this->makeDocumentNotFoundResponse($fileId);
519
        }
520
521
        if (!$this->authorizationManager->isTokenValid($accessToken, $document, $request)) {
522
            $this->logger->info(self::LOG_PREFIX . 'invalid access token', ['fileId' => $fileId]);
523
524
            return $this->responseFactory->createResponse(401)->withBody($this->streamFactory->createStream((string) json_encode([
525
                'message' => 'invalid access token',
526
            ])));
527
        }
528
529
        $version = $this->documentManager->getVersion($document);
530
531
        if (!$this->documentManager->hasLock($document)) {
532
            return $this
533
                ->responseFactory
534
                ->createResponse(409)
535
                ->withHeader(WopiInterface::HEADER_LOCK, '');
536
        }
537
538
        $currentLock = $this->documentManager->getLock($document);
539
540
        if ($this->enableLock && $currentLock !== $xWopiLock) {
541
            return $this
542
                ->responseFactory
543
                ->createResponse(409)
544
                ->withHeader(WopiInterface::HEADER_LOCK, $currentLock);
545
        }
546
547
        $this->documentManager->deleteLock($document);
548
549
        return $this
550
            ->responseFactory
551
            ->createResponse()
552
            ->withHeader(WopiInterface::HEADER_LOCK, '')
553
            ->withHeader(
554
                WopiInterface::HEADER_ITEM_VERSION,
555
                sprintf('v%s', $version)
556
            );
557
    }
558
559
    public function unlockAndRelock(
560
        string $fileId,
561
        string $accessToken,
562
        string $xWopiLock,
563
        string $xWopiOldLock,
564
        RequestInterface $request
565
    ): ResponseInterface {
566
        $this->unlock($fileId, $accessToken, $xWopiOldLock, $request);
567
568
        return $this->lock($fileId, $accessToken, $xWopiLock, $request);
569
    }
570
571
    private function makeDocumentNotFoundResponse(string $fileId): ResponseInterface
572
    {
573
        $this->logger->error(self::LOG_PREFIX . 'Document not found', ['fileId' => $fileId]);
574
575
        return $this->responseFactory->createResponse(404)
576
            ->withBody($this->streamFactory->createStream((string) json_encode([
577
                'message' => "Document with id {$fileId} not found",
578
            ])));
579
    }
580
}
581