Issues (8)

src/Service/Wopi/PutFile.php (1 issue)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace ChampsLibres\WopiBundle\Service\Wopi;
6
7
use ChampsLibres\WopiBundle\Contracts\AuthorizationManagerInterface;
8
use ChampsLibres\WopiBundle\Contracts\UserManagerInterface;
9
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
10
use ChampsLibres\WopiLib\Contract\Service\WopiInterface;
11
use DateTimeImmutable;
12
use DateTimeInterface;
13
use Psr\Http\Message\RequestInterface;
14
use Psr\Http\Message\ResponseFactoryInterface;
15
use Psr\Http\Message\ResponseInterface;
16
use Psr\Http\Message\StreamFactoryInterface;
17
use Psr\Log\LoggerInterface;
18
use RuntimeException;
19
20
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
21
22
use function strlen;
23
24
class PutFile
25
{
26
    private const LOG_PREFIX = '[wopi][wopi/PutFile] ';
27
28
    private AuthorizationManagerInterface $authorizationManager;
29
30
    private DocumentManagerInterface $documentManager;
31
32
    private LoggerInterface $logger;
33
34
    private ResponseFactoryInterface $responseFactory;
35
36
    private StreamFactoryInterface $streamFactory;
37
38
    private UserManagerInterface $userManager;
39
40
    /**
41
     * @var 'version'|'timestamp'
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'version'|'timestamp' at position 0 could not be parsed: Unknown type name ''version'' at position 0 in 'version'|'timestamp'.
Loading history...
42
     */
43
    private string $versionManagement;
44
45
    public function __construct(
46
        AuthorizationManagerInterface $authorizationManager,
47
        DocumentManagerInterface $documentManager,
48
        LoggerInterface $logger,
49
        ParameterBagInterface $parameterBag,
50
        ResponseFactoryInterface $responseFactory,
51
        StreamFactoryInterface $streamFactory,
52
        UserManagerInterface $userManager
53
    ) {
54
        $this->documentManager = $documentManager;
55
        $this->logger = $logger;
56
        $this->responseFactory = $responseFactory;
57
        $this->streamFactory = $streamFactory;
58
        $this->userManager = $userManager;
59
        $this->authorizationManager = $authorizationManager;
60
        $this->versionManagement = $parameterBag->get('wopi')['version_management'];
61
    }
62
63
    public function __invoke(
64
        string $fileId,
65
        string $accessToken,
66
        string $xWopiLock,
67
        string $xWopiEditors,
68
        RequestInterface $request
69
    ): ResponseInterface {
70
        $this->logger->debug(self::LOG_PREFIX . 'put file', ['fileId' => $fileId]);
71
        $document = $this->documentManager->findByDocumentId($fileId);
72
73
        if (null === $document) {
74
            return $this->responseFactory->createResponse(404)
75
                ->withBody($this->streamFactory->createStream((string) json_encode([
76
                    'message' => "Document with id {$fileId} not found",
77
                ])));
78
        }
79
80
        if (!$this->authorizationManager->userCanWrite($accessToken, $document, $request)) {
81
            $userIdentifier = $this->userManager->getUserId($accessToken, $fileId, $request);
82
            $this->logger->info(self::LOG_PREFIX . 'user is not allowed to write document', ['fileId' => $fileId, 'userIdentifier' => $userIdentifier]);
83
84
            return $this->responseFactory->createResponse(401)->withBody($this->streamFactory->createStream((string) json_encode([
85
                'message' => 'user is not allowed to write this document',
86
            ])));
87
        }
88
89
        $version = $this->documentManager->getVersion($document);
90
91
        // File is unlocked
92
        if (false === $this->documentManager->hasLock($document)) {
93
            if (0 !== $this->documentManager->getSize($document)) {
94
                $this->logger->error(self::LOG_PREFIX . 'file unlocked', ['fileId' => $fileId]);
95
96
                return $this
97
                    ->responseFactory
98
                    ->createResponse(409)
99
                    ->withHeader(
100
                        WopiInterface::HEADER_ITEM_VERSION,
101
                        sprintf('v%s', $version)
102
                    );
103
            }
104
        }
105
106
        // File is locked
107
        if (
108
            $this->documentManager->hasLock($document)
109
            && $xWopiLock !== $currentLock = $this->documentManager->getLock($document)
110
        ) {
111
            $this->logger->error(self::LOG_PREFIX . 'file locked and lock does not match', ['fileId' => $fileId]);
112
113
            return $this
114
                ->responseFactory
115
                ->createResponse(409)
116
                ->withHeader(
117
                    WopiInterface::HEADER_LOCK,
118
                    $currentLock
119
                )
120
                ->withHeader(
121
                    WopiInterface::HEADER_ITEM_VERSION,
122
                    sprintf('v%s', $version)
123
                )
124
                ->withBody(
125
                    $this->streamFactory->createStream((string) json_encode([
126
                        'message' => 'File locked',
127
                    ]))
128
                );
129
        }
130
131
        // for collabora online editor, check timestamp if present
132
        if ($request->hasHeader('x-lool-wopi-timestamp')) {
133
            $date = DateTimeImmutable::createFromFormat(
134
                DateTimeImmutable::ATOM,
135
                $request->getHeader('x-lool-wopi-timestamp')[0]
136
            );
137
138
            if (false === $date) {
139
                $errors = DateTimeImmutable::getLastErrors();
140
141
                if (false === $errors) {
142
                    throw new RuntimeException('Could not find error on DateTimeImmutable parsing');
143
                }
144
145
                $e = array_merge($errors['warnings'], $errors['errors']);
146
147
                $this->logger->error(self::LOG_PREFIX . 'Error parsing date', ['fileId' => $fileId,
148
                    'date' => $request->getHeader('x-lool-wopi-timestamp')[0], 'errors' => $e]);
149
150
                throw new RuntimeException('Error parsing date: ' . implode(', ', $e));
151
            }
152
153
            if ($this->documentManager->getLastModifiedDate($document) > $date) {
154
                $this->logger->error(self::LOG_PREFIX . 'File has more recent modified date', ['fileId' => $fileId]);
155
156
                return $this
157
                    ->responseFactory
158
                    ->createResponse(409)
159
                    ->withHeader(
160
                        WopiInterface::HEADER_LOCK,
161
                        $currentLock ?? ''
162
                    )
163
                    ->withHeader(
164
                        WopiInterface::HEADER_ITEM_VERSION,
165
                        sprintf('v%s', $version)
166
                    )
167
                    ->withBody(
168
                        $this->streamFactory->createStream(
169
                            (string) json_encode(
170
                                [
171
                                    'LOOLStatusCode' => 1010,
172
                                    'COOLStatusCode' => 1010,
173
                                ]
174
                            )
175
                        )
176
                    );
177
            }
178
        }
179
180
        $body = (string) $request->getBody();
181
        $this->documentManager->write(
182
            $document,
183
            [
184
                'content' => $body,
185
                'size' => strlen($body),
186
            ]
187
        );
188
        $version = $this->documentManager->getVersion($document);
189
190
        $response = $this
191
            ->responseFactory
192
            ->createResponse()
193
            ->withHeader(
194
                WopiInterface::HEADER_LOCK,
195
                $xWopiLock
196
            )
197
            ->withHeader(
198
                WopiInterface::HEADER_ITEM_VERSION,
199
                sprintf('v%s', $version)
200
            );
201
202
        if ('timestamp' === $this->versionManagement) {
203
            return $response
204
                ->withBody(
205
                    $this->streamFactory->createStream(
206
                        (string) json_encode([
207
                            'LastModifiedTime' => $this->documentManager->getLastModifiedDate($document)->format(DateTimeInterface::ATOM),
208
                        ])
209
                    )
210
                );
211
        }
212
213
        $this->logger->info(self::LOG_PREFIX . 'file saved', ['fileId' => $fileId]);
214
215
        return $response;
216
    }
217
}
218