Issues (2037)

plugin/onlyoffice/callback.php (10 issues)

1
<?php
2
/**
3
 *
4
 * (c) Copyright Ascensio System SIA 2023
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 *
18
 */
19
20
require_once __DIR__.'/../../main/inc/global.inc.php';
21
22
use ChamiloSession as Session;
23
use \Firebase\JWT\JWT;
24
use \Firebase\JWT\Key;
25
26
/**
27
 * Status of the document
28
 */
29
const TrackerStatus_Editing = 1;
30
const TrackerStatus_MustSave = 2;
31
const TrackerStatus_Corrupted = 3;
32
const TrackerStatus_Closed = 4;
33
const TrackerStatus_ForceSave = 6;
34
const TrackerStatus_CorruptedForceSave = 7;
35
36
$plugin = OnlyofficePlugin::create();
37
38
if (isset($_GET["hash"]) && !empty($_GET["hash"])) {
39
    $callbackResponseArray = [];
40
    @header( 'Content-Type: application/json; charset==utf-8');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for header(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

40
    /** @scrutinizer ignore-unhandled */ @header( 'Content-Type: application/json; charset==utf-8');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
Are you sure the usage of header('Content-Type: ap.../json; charset==utf-8') is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
41
    @header( 'X-Robots-Tag: noindex' );
0 ignored issues
show
Are you sure the usage of header('X-Robots-Tag: noindex') is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
42
    @header( 'X-Content-Type-Options: nosniff' );
0 ignored issues
show
Are you sure the usage of header('X-Content-Type-Options: nosniff') is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
43
44
    list ($hashData, $error) = Crypt::ReadHash($_GET["hash"]);
45
    if ($hashData === null) {
46
        $callbackResponseArray["status"] = "error";
47
        $callbackResponseArray["error"] = $error;
48
        die(json_encode($callbackResponseArray));
49
    }
50
51
    $type = $hashData->type;
52
    $courseId = $hashData->courseId;
53
    $userId = $hashData->userId;
54
    $docId = $hashData->docId;
55
    $groupId = $hashData->groupId;
56
    $sessionId = $hashData->sessionId;
57
58
    $courseInfo = api_get_course_info_by_id($courseId);
59
    $courseCode = $courseInfo["code"];
60
61
    if (!empty($userId)) {
62
        $userInfo = api_get_user_info($userId);
63
    } else {
64
        $result["error"] = "User not found";
65
        die (json_encode($result));
66
    }
67
68
    if (api_is_anonymous()) {
69
        $loggedUser = [
70
            "user_id" => $userInfo["id"],
71
            "status" => $userInfo["status"],
72
            "uidReset" => true,
73
        ];
74
75
        Session::write("_user", $loggedUser);
76
        Login::init_user($loggedUser["user_id"], true);
77
    } else {
78
        $userId = api_get_user_id();
79
    }
80
81
    switch($type) {
82
        case "track":
83
            $callbackResponseArray = track();
84
            die (json_encode($callbackResponseArray));
85
        case "download":
86
            $callbackResponseArray = download();
0 ignored issues
show
Are you sure the assignment to $callbackResponseArray is correct as download() 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...
87
            die (json_encode($callbackResponseArray));
88
        default:
89
            $callbackResponseArray["status"] = "error";
90
            $callbackResponseArray["error"] = "404 Method not found";
91
            die(json_encode($callbackResponseArray));
92
    }
93
}
94
95
/**
96
 * Handle request from the document server with the document status information
97
 */
98
function track(): array
99
{
100
    $result = [];
101
102
    global $plugin;
103
    global $courseCode;
104
    global $userId;
105
    global $docId;
106
    global $groupId;
107
    global $sessionId;
108
    global $courseInfo;
109
110
    if (($body_stream = file_get_contents("php://input")) === false) {
111
        $result["error"] = "Bad Request";
112
        return $result;
113
    }
114
115
    $data = json_decode($body_stream, true);
116
117
    if ($data === null) {
118
        $result["error"] = "Bad Response";
119
        return $result;
120
    }
121
122
    if (!empty($plugin->getDocumentServerSecret())) {
123
124
        if (!empty($data["token"])) {
125
            try {
126
                $payload = JWT::decode($data["token"], new Key($plugin->getDocumentServerSecret(), "HS256"));
127
            } catch (\UnexpectedValueException $e) {
128
                $result["status"] = "error";
129
                $result["error"] = "403 Access denied";
130
                return $result;
131
            }
132
        } else {
133
            $token = substr(getallheaders()[$plugin->getJwtHeader()], strlen("Bearer "));
134
            try {
135
                $decodeToken = JWT::decode($token, new Key($plugin->getDocumentServerSecret(), "HS256"));
136
                $payload = $decodeToken->payload;
137
            } catch (\UnexpectedValueException $e) {
138
                $result["status"] = "error";
139
                $result["error"] = "403 Access denied";
140
                return $result;
141
            }
142
        }
143
144
        $data["url"] = isset($payload->url) ? $payload->url : null;
145
        $data["status"] = $payload->status;
146
    }
147
148
    $status = $data["status"];
149
150
    $track_result = 1;
151
    switch ($status) {
152
        case TrackerStatus_MustSave:
153
        case TrackerStatus_Corrupted:
154
155
            $downloadUri = $data["url"];
156
            $downloadUri = $plugin->replaceDocumentServerUrlToInternal($downloadUri);
157
158
            if (!empty($docId) && !empty($courseCode)) {
159
                $docInfo = DocumentManager::get_document_data_by_id($docId, $courseCode, false, $sessionId);
160
161
                if ($docInfo === false) {
162
                    $result["error"] = "File not found";
163
                    return $result;
164
                }
165
166
                $filePath = $docInfo["absolute_path"];
167
            } else {
168
                $result["error"] = "Bad Request";
169
                return $result;
170
            }
171
172
            list ($isAllowToEdit, $isMyDir, $isGroupAccess, $isReadonly) = getPermissions($docInfo, $userId, $courseCode, $groupId, $sessionId);
173
174
            if ($isReadonly) {
175
                break;
176
            }
177
178
            if (($new_data = file_get_contents($downloadUri)) === false) {
179
                break;
180
            }
181
182
            if ($isAllowToEdit || $isMyDir || $isGroupAccess) {
183
                $groupInfo = GroupManager::get_group_properties($groupId);
184
185
                if ($fp = @fopen($filePath, "w")) {
186
                    fputs($fp, $new_data);
187
                    fclose($fp);
188
                    api_item_property_update($courseInfo,
189
                                                TOOL_DOCUMENT,
190
                                                $docId,
191
                                                "DocumentUpdated",
192
                                                $userId,
193
                                                $groupInfo,
194
                                                null,
195
                                                null,
196
                                                null,
197
                                                $sessionId);
198
                    update_existing_document($courseInfo,
199
                                                $docId,
200
                                                filesize($filePath),
201
                                                false);
202
                    $track_result = 0;
203
                    break;
204
                }
205
            }
206
207
        case TrackerStatus_Editing:
208
        case TrackerStatus_Closed:
209
210
            $track_result = 0;
211
            break;
212
    }
213
214
    $result["error"] = $track_result;
215
    return $result;
216
}
217
218
/**
219
 * Downloading file by the document service
220
 */
221
function download()
222
{
223
    global $plugin;
224
    global $courseCode;
225
    global $userId;
226
    global $docId;
227
    global $groupId;
228
    global $sessionId;
229
    global $courseInfo;
230
231
    if (!empty($plugin->getDocumentServerSecret())) {
232
        $token = substr(getallheaders()[$plugin->getJwtHeader()], strlen("Bearer "));
233
        try {
234
            $payload = JWT::decode($token, new Key($plugin->getDocumentServerSecret(), "HS256"));
235
236
        } catch (\UnexpectedValueException $e) {
237
            $result["status"] = "error";
0 ignored issues
show
Comprehensibility Best Practice introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.
Loading history...
238
            $result["error"] = "403 Access denied";
239
            return $result;
240
        }
241
    }
242
243
    if (!empty($docId) && !empty($courseCode)) {
244
        $docInfo = DocumentManager::get_document_data_by_id($docId, $courseCode, false, $sessionId);
245
246
        if ($docInfo === false) {
247
            $result["error"] = "File not found";
248
            return $result;
249
        }
250
251
        $filePath = $docInfo["absolute_path"];
252
    } else {
253
        $result["error"] = "File not found";
254
        return $result;
255
    }
256
257
    @header("Content-Type: application/octet-stream");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for header(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

257
    /** @scrutinizer ignore-unhandled */ @header("Content-Type: application/octet-stream");

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
Are you sure the usage of header('Content-Type: application/octet-stream') is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
258
    @header("Content-Disposition: attachment; filename=" . $docInfo["title"]);
0 ignored issues
show
Are you sure the usage of header('Content-Disposit...=' . $docInfo['title']) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
259
260
    readfile($filePath);
261
    exit();
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
262
}
263
264
/**
265
 * Method checks access rights to document and returns permissions
266
 */
267
function getPermissions(array $docInfo, int $userId, string $courseCode, int $groupId = null, int $sessionId = null): array
268
{
269
    $isAllowToEdit = api_is_allowed_to_edit(true, true);
270
    $isMyDir = DocumentManager::is_my_shared_folder($userId, $docInfo["absolute_parent_path"], $sessionId);
271
272
    $isGroupAccess = false;
273
    if (!empty($groupId)) {
274
        $courseInfo = api_get_course_info($courseCode);
275
        Session::write("_real_cid", $courseInfo["real_id"]);
276
        $groupProperties = GroupManager::get_group_properties($groupId);
277
        $docInfoGroup = api_get_item_property_info($courseInfo["real_id"], "document", $docInfo["id"], $sessionId);
278
        $isGroupAccess = GroupManager::allowUploadEditDocument($userId, $courseCode, $groupProperties, $docInfoGroup);
279
    }
280
281
    $isReadonly = $docInfo["readonly"];
282
283
    return [$isAllowToEdit, $isMyDir, $isGroupAccess, $isReadonly];
284
}
285