Wopi::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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

349
                $suggestedTarget = sprintf('%s%s', /** @scrutinizer ignore-type */ $filename, $suggestedTarget);
Loading history...
350
            }
351
352
            $target = $suggestedTarget;
353
        } else {
354
            $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

354
            $document = $this->documentManager->findByDocumentFilename(/** @scrutinizer ignore-type */ $relativeTarget);
Loading history...
355
356
            /**
357
             * If a file with the specified name already exists,
358
             * the host must respond with a 409 Conflict,
359
             * unless the X-WOPI-OverwriteRelativeTarget request header is set to true.
360
             *
361
             * When responding with a 409 Conflict for this reason,
362
             * the host may include an X-WOPI-ValidRelativeTarget specifying a file name that is valid.
363
             *
364
             * If the X-WOPI-OverwriteRelativeTarget request header is set to true
365
             * and a file with the specified name already exists and is locked,
366
             * the host must respond with a 409 Conflict and include an
367
             * X-WOPI-Lock response header containing the value of the current lock on the file.
368
             */
369
            if (null !== $document) {
370
                if (false === $overwriteRelativeTarget) {
371
                    $extension = pathinfo($this->documentManager->getBasename($document), PATHINFO_EXTENSION);
372
373
                    return $this
374
                        ->responseFactory
375
                        ->createResponse(409)
376
                        ->withHeader('Content-Type', 'application/json')
377
                        ->withHeader(
378
                            WopiInterface::HEADER_VALID_RELATIVE_TARGET,
379
                            sprintf('%s.%s', uniqid(), $extension)
380
                        );
381
                }
382
383
                if ($this->documentManager->hasLock($document)) {
384
                    return $this
385
                        ->responseFactory
386
                        ->createResponse(409)
387
                        ->withHeader(WopiInterface::HEADER_LOCK, $this->documentManager->getLock($document));
388
                }
389
            }
390
391
            $target = $relativeTarget;
392
        }
393
394
        /** @var array{filename: string, extension: string} $pathInfo */
395
        $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

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