Passed
Push — master ( dddfaf...d58b96 )
by Nils
05:00
created

performRecuringItemTasks()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 20
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 20
rs 10
1
<?php
2
/**
3
 * Teampass - a collaborative passwords manager.
4
 * ---
5
 * This file is part of the TeamPass project.
6
 * 
7
 * TeamPass is free software: you can redistribute it and/or modify it
8
 * under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation, version 3 of the License.
10
 * 
11
 * TeamPass is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU General Public License for more details.
15
 * 
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18
 * 
19
 * Certain components of this file may be under different licenses. For
20
 * details, see the `licenses` directory or individual file headers.
21
 * ---
22
 * @file      background_tasks___items_handler.php
23
 * @author    Nils Laumaillé ([email protected])
24
 * @copyright 2009-2024 Teampass.net
25
 * @license   GPL-3.0
26
 * @see       https://www.teampass.net
27
 */
28
29
use TeampassClasses\NestedTree\NestedTree;
30
use TeampassClasses\SessionManager\SessionManager;
31
use Symfony\Component\HttpFoundation\Request;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Request. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
32
use Symfony\Component\Process\Process;
33
use TeampassClasses\Language\Language;
34
35
// Load functions
36
require_once __DIR__.'/../sources/main.functions.php';
37
38
// init
39
loadClasses('DB');
40
$session = SessionManager::getSession();
41
$request = Request::createFromGlobals();
42
$lang = new Language();
43
44
// Load config if $SETTINGS not defined
45
try {
46
    include_once __DIR__.'/../includes/config/tp.config.php';
47
} catch (Exception $e) {
48
    throw new Exception("Error file '/includes/config/tp.config.php' not exists", 1);
49
}
50
51
// Define Timezone
52
date_default_timezone_set(isset($SETTINGS['timezone']) === true ? $SETTINGS['timezone'] : 'UTC');
53
54
// Set header properties
55
header('Content-type: text/html; charset=utf-8');
56
header('Cache-Control: no-cache, no-store, must-revalidate');
57
error_reporting(E_ERROR);
58
// increase the maximum amount of time a script is allowed to run
59
set_time_limit($SETTINGS['task_maximum_run_time']);
60
61
// --------------------------------- //
62
63
require_once __DIR__.'/background_tasks___functions.php';
64
65
$tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
66
67
// Get PHP binary
68
$phpBinaryPath = getPHPBinary();
69
70
// log start
71
$logID = doLog('start', 'item_keys', (isset($SETTINGS['enable_tasks_log']) === true ? (int) $SETTINGS['enable_tasks_log'] : 0));
72
73
74
// Manage the tasks in queue.
75
// Task to treat selection is:
76
// 1- take first is_in_progress === 1
77
// 2- take first is_in_progress === 0 and finished_at === null
78
DB::debugmode(false);
79
$process_to_perform = DB::queryfirstrow(
80
    'SELECT *
81
    FROM ' . prefixTable('background_tasks') . '
82
    WHERE is_in_progress = %i AND process_type IN ("item_copy", "new_item", "update_item", "item_update_create_keys")
83
    ORDER BY increment_id ASC',
84
    1
85
);
86
87
if (DB::count() > 0) {
88
    // handle tasks inside this process
89
    if (WIP === true) error_log("Process in progress: ".$process_to_perform['increment_id']);
90
    handleTask(
91
        $process_to_perform['increment_id'],
92
        json_decode($process_to_perform['arguments'], true),
93
        $SETTINGS,
94
    );
95
} else {
96
    // search for next process to handle
97
    $process_to_perform = DB::queryfirstrow(
98
        'SELECT *
99
        FROM ' . prefixTable('background_tasks') . '
100
        WHERE is_in_progress = %i AND (finished_at = "" OR finished_at IS NULL) AND process_type IN ("item_copy", "new_item", "update_item", "item_update_create_keys")
101
        ORDER BY increment_id ASC',
102
        0
103
    );
104
    
105
    if (DB::count() > 0) {
106
        if (WIP === true) error_log("New process ta start: ".$process_to_perform['increment_id']);
107
        // update DB - started_at
108
        DB::update(
109
            prefixTable('background_tasks'),
110
            array(
111
                'started_at' => time(),
112
            ),
113
            'increment_id = %i',
114
            $process_to_perform['increment_id']
115
        );
116
117
        provideLog('[PROCESS][#'. $process_to_perform['increment_id'].'][START]', $SETTINGS);
118
        handleTask(
119
            $process_to_perform['increment_id'],
120
            json_decode($process_to_perform['arguments'], true),
121
            $SETTINGS,
122
            $process_to_perform['item_id']
123
        );
124
    } else {
125
126
    }
127
}
128
129
130
// Do special tasks
131
performRecuringItemTasks($SETTINGS);
132
133
// log end
134
doLog('end', '', (isset($SETTINGS['enable_tasks_log']) === true ? (int) $SETTINGS['enable_tasks_log'] : 0), $logID);
135
136
// launch a new iterative process
137
$process_to_perform = DB::queryfirstrow(
138
    'SELECT *
139
    FROM ' . prefixTable('background_tasks') . '
140
    WHERE is_in_progress = %i AND process_type IN ("item_copy", "new_item", "update_item", "item_update_create_keys")
141
    ORDER BY increment_id DESC',
142
    1
143
);
144
if (DB::count() > 0) {
145
    $process = new Symfony\Component\Process\Process([$phpBinaryPath, __FILE__]);
146
    $process->start();  
147
    $process->wait();
148
}
149
150
151
/**
152
 * Handle the task
153
 *
154
 * @param int   $processId
155
 * @param array $ProcessArguments
156
 * @param array $SETTINGS
157
 *
158
 * @return bool
159
 */
160
function handleTask(int $processId, array $ProcessArguments, array $SETTINGS, int $itemId = null): bool
161
{
162
    provideLog('[PROCESS][#'. $processId.'][START]', $SETTINGS);
163
    $task_to_perform = DB::queryfirstrow(
164
        'SELECT *
165
        FROM ' . prefixTable('background_subtasks') . '
166
        WHERE task_id = %i AND finished_at IS NULL
167
        ORDER BY increment_id ASC',
168
        $processId
169
    );
170
171
    // get the process object
172
    //$processObject = json_decode($ProcessArguments['object_key'], true);
173
    
174
    if (DB::count() > 0) {
175
        // check if a linux process is not currently on going
176
        // if sub_task_in_progress === 1 then exit
177
        if ((int) $task_to_perform['sub_task_in_progress'] === 0) {
178
            // handle next task
179
            $args = json_decode($task_to_perform['task'], true);
180
            provideLog('[TASK][#'. $task_to_perform['increment_id'].'][START]Task '.$args['step'], $SETTINGS);
181
182
            // flag as in progress
183
            DB::update(
184
                prefixTable('background_tasks'),
185
                array(
186
                    'updated_at' => time(),
187
                    'is_in_progress' => 1,
188
                ),
189
                'increment_id = %i',
190
                $processId
191
            );
192
193
            // flag task as on going
194
            if ((int) $args['index'] === 0) {
195
                DB::update(
196
                    prefixTable('background_subtasks'),
197
                    array(
198
                        'is_in_progress' => 1,
199
                    ),
200
                    'increment_id = %i',
201
                    $task_to_perform['increment_id']
202
                );
203
            }
204
205
            // flag sub task in progress as on going
206
            DB::update(
207
                prefixTable('background_subtasks'),
208
                array(
209
                    'sub_task_in_progress' => 1,
210
                ),
211
                'increment_id = %i',
212
                $task_to_perform['increment_id']
213
            );
214
215
            // handle the task step
216
            handleTaskStep($args, $ProcessArguments, $SETTINGS);
217
218
            // update the task status
219
            DB::update(
220
                prefixTable('background_subtasks'),
221
                array(
222
                    'sub_task_in_progress' => 0,    // flag sub task is no more in prgoress
223
                    'task' => json_encode(["status" => "Done"]),
224
                    'is_in_progress' => -1,
225
                    'finished_at' => time(),
226
                    'updated_at' => time(),
227
                ),
228
                'increment_id = %i',
229
                $task_to_perform['increment_id']
230
            );
231
232
            provideLog('[TASK]['.$args['step'].'] starting at '.$args['index'].' is done.', $SETTINGS);
233
234
            // are all tasks done?
235
            DB::query(
236
                'SELECT *
237
                FROM ' . prefixTable('background_subtasks') . '
238
                WHERE task_id = %i AND finished_at IS NULL',
239
                $processId
240
            );
241
            if (DB::count() === 0) {
242
                // all tasks are done
243
                provideLog('[PROCESS]['.$processId.'][FINISHED]', $SETTINGS);
244
                DB::debugmode(false);
245
                DB::update(
246
                    prefixTable('background_tasks'),
247
                    array(
248
                        'finished_at' => time(),
249
                        'is_in_progress' => -1,
250
                        'arguments' => json_encode([
251
                            'new_user_id' => isset($ProcessArguments['new_user_id']) === true ? $ProcessArguments['new_user_id'] : '',
252
                        ])
253
                    ),
254
                    'increment_id = %i',
255
                    $processId
256
                );
257
258
                // if item was being updated then remove the edition lock
259
                if (is_null($itemId) === false) {
260
                    DB::delete(prefixTable('items_edition'), 'item_id = %i', $itemId);
261
                }
262
            }
263
            return false;
264
265
        } else {
266
            // Task is currently being in progress by another server process
267
            provideLog('[TASK][#'. $task_to_perform['increment_id'].'][WARNING] Similar task already being processes', $SETTINGS);
268
            return false;
269
        }
270
    } else {
271
        // no more task to perform
272
        provideLog('[PROCESS]['.$processId.'][FINISHED]', $SETTINGS);
273
        DB::update(
274
            prefixTable('background_tasks'),
275
            array(
276
                'finished_at' => time(),
277
                'is_in_progress' => -1,
278
                'arguments' => json_encode([
279
                    'item_id' => isset($ProcessArguments['item_id']) === true ? $ProcessArguments['item_id'] : '',
280
                ])
281
            ),
282
            'increment_id = %i',
283
            $processId
284
        );
285
286
        // if item was being updated then remove the edition lock
287
        if (is_null($itemId) === false) {
288
            DB::delete(prefixTable('items_edition'), 'item_id = %i', $itemId);
289
        }
290
    }
291
    return false;
292
}
293
294
/**
295
 * Handle the task step
296
 *
297
 * @param array $args
298
 * @param array $ProcessArguments
299
 * @param array $SETTINGS
300
 *
301
 * @return void
302
 */
303
function handleTaskStep(
304
    array $args,
305
    array $ProcessArguments,
306
    array $SETTINGS
307
)
308
{
309
    // perform the task step "create_users_files_key"
310
    if ($args['step'] === 'create_users_files_key') {
311
        // Loop on all files for this item
312
        // and encrypt them for each user
313
        if (WIP === true) provideLog('[DEBUG] '.print_r($args['files_keys'], true), $SETTINGS);
314
        foreach($args['files_keys'] as $file) {
315
            storeUsersShareKey(
316
                prefixTable('sharekeys_items'),
317
                0,
318
                -1,
319
                (int) $file['object_id'],
320
                (string) $file['object_key'],
321
                false,
322
                false,
323
                [],
324
                array_key_exists('all_users_except_id', $ProcessArguments) === true ? $ProcessArguments['all_users_except_id'] : -1,
325
            );
326
        }
327
    } elseif ($args['step'] === 'create_users_fields_key') {
328
        // Loop on all encrypted fields for this item
329
        // and encrypt them for each user
330
        if (WIP === true) provideLog('[DEBUG] '.print_r($args, true), $SETTINGS);
331
        foreach($args['fields_keys'] as $field) {
332
            storeUsersShareKey(
333
                prefixTable('sharekeys_fields'),
334
                0,
335
                -1,
336
                (int) $field['object_id'],
337
                (string) $field['object_key'],
338
                false,
339
                false,
340
                [],
341
                array_key_exists('all_users_except_id', $ProcessArguments) === true ? $ProcessArguments['all_users_except_id'] : -1,
342
            );
343
        }
344
    } elseif ($args['step'] === 'create_users_pwd_key') {
345
        storeUsersShareKey(
346
            prefixTable('sharekeys_items'),
347
            0,
348
            -1,
349
            (int) $ProcessArguments['item_id'],
350
            (string) array_key_exists('pwd', $ProcessArguments) === true ? $ProcessArguments['pwd'] : (array_key_exists('object_key', $ProcessArguments) === true ? $ProcessArguments['object_key'] : ''),
0 ignored issues
show
introduced by
The condition (string)array_key_exists...cessArguments) === true is always false.
Loading history...
351
            false,
352
            false,
353
            [],
354
            array_key_exists('all_users_except_id', $ProcessArguments) === true ? $ProcessArguments['all_users_except_id'] : -1
355
        );
356
    }
357
}
358
359
/**
360
 * Perform recuring tasks
361
 * 
362
 * @param array $SETTINGS
363
 * 
364
 * @return void
365
 */
366
function performRecuringItemTasks($SETTINGS): void
367
{
368
    // Clean multeple items edition
369
    DB::query(
370
        'DELETE i1 FROM '.prefixTable('items_edition').' i1
371
        JOIN (
372
            SELECT user_id, item_id, MIN(timestamp) AS oldest_timestamp
373
            FROM '.prefixTable('items_edition').'
374
            GROUP BY user_id, item_id
375
        ) i2 ON i1.user_id = i2.user_id AND i1.item_id = i2.item_id
376
        WHERE i1.timestamp > i2.oldest_timestamp'
377
    );
378
379
    // Handle item tokens expiration
380
    // Delete entry if token has expired
381
    // Based upon SETTINGS['delay_item_edition'] or EDITION_LOCK_PERIOD (1 day by default)
382
    DB::query(
383
        'DELETE FROM '.prefixTable('items_edition').'
384
        WHERE timestamp < %i',
385
        ($SETTINGS['delay_item_edition'] > 0) ? time() - ($SETTINGS['delay_item_edition']*60) : time() - EDITION_LOCK_PERIOD
386
    );
387
}