Passed
Pull Request — master (#4920)
by Nils
06:17
created

processMigratePersonalItemsSubtask()   B

Complexity

Conditions 9
Paths 43

Size

Total Lines 69
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 9
eloc 50
c 1
b 0
f 1
nc 43
nop 2
dl 0
loc 69
rs 7.5353

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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      MigrateUserHandlerTrait.php
23
 * @author    Nils Laumaillé ([email protected])
24
 * @copyright 2009-2025 Teampass.net
25
 * @license   GPL-3.0
26
 * @see       https://www.teampass.net
27
 */
28
29
use TeampassClasses\Language\Language;
30
31
trait MigrateUserHandlerTrait {
32
    use UserHandlerTrait;
33
    abstract protected function completeTask();
34
35
    /**
36
     * Generate user keys
37
     * @param array $taskData Données de la tâche
38
     * @param array $arguments Arguments nécessaires pour la création des clés
39
     * @return void
40
     */
41
    private function migratePersonalItems($arguments) {
42
        // Get all subtasks related to this task
43
        $subtasks = DB::query(
44
            'SELECT * FROM ' . prefixTable('background_subtasks') . ' WHERE task_id = %i AND is_in_progress = 0 ORDER BY `task` ASC',
45
            $this->taskId
46
        );
47
    
48
        if (empty($subtasks)) {
49
            if (LOG_TASKS=== true) $this->logger->log("No subtask was found for task {$this->taskId}");
50
            return;
51
        }
52
    
53
        // Process each subtask
54
        foreach ($subtasks as $subtask) {
55
            if (LOG_TASKS=== true) $this->logger->log("Processing subtask {$subtask['increment_id']} for task {$this->taskId}");
56
            $this->processMigratePersonalItemsSubtask($subtask, $arguments);
57
        }
58
    
59
        // Are all subtasks completed?
60
        $remainingSubtasks = DB::queryFirstField(
61
            'SELECT COUNT(*) FROM ' . prefixTable('background_subtasks') . ' WHERE task_id = %i AND is_in_progress = 0',
62
            $this->taskId
63
        );    
64
        if ($remainingSubtasks == 0) {
65
            $this->completeTask();
66
        }
67
    }
68
    
69
70
    /**
71
     * Process a subtask for generating user keys.
72
     * @param array $subtask The subtask to process.
73
     * @param array $arguments Arguments for the task.
74
     * @return void
75
     */
76
    private function processMigratePersonalItemsSubtask(array $subtask, array $arguments) {
77
        try {
78
            $taskData = json_decode($subtask['task'], true);
79
            
80
            // Mark the subtask as in progress
81
            DB::update(
82
                prefixTable('background_subtasks'),
83
                [
84
                    'is_in_progress' => 1,
85
                    'updated_at' => time(),
86
                    'status' => 'in progress'
87
                ],
88
                'increment_id = %i',
89
                $subtask['increment_id']
90
            );
91
            
92
            if (LOG_TASKS=== true) $this->logger->log("Subtask is in progress: ".$taskData['step'], 'INFO');
93
            switch ($taskData['step'] ?? '') {
94
                case 'user-personal-items-migration-step10':
95
                    $this->migratePersonalItemsStep10($taskData, $arguments);
96
                    break;
97
                case 'user-personal-items-migration-step20':
98
                    $this->migratePersonalItemsStep20($taskData, $arguments);
99
                    break;
100
                case 'user-personal-items-migration-step30':
101
                    $this->migratePersonalItemsStep30($taskData, $arguments);
102
                    break;
103
                case 'user-personal-items-migration-step40':
104
                    $this->migratePersonalItemsStep40($taskData, $arguments);
105
                    break;
106
                case 'user-personal-items-migration-step50':
107
                    $this->migratePersonalItemsStep50($taskData, $arguments);
108
                    break;
109
                case 'user-personal-items-migration-step-final':
110
                    $this->migratePersonalItemsStepFinal($arguments);
111
                    break;
112
                break;
113
                default:
114
                    throw new Exception("Type of subtask unknown: {$this->processType}");
115
            }
116
    
117
            // Mark subtask as completed
118
            DB::update(
119
                prefixTable('background_subtasks'),
120
                [
121
                    'is_in_progress' => -1,
122
                    'finished_at' => time(),
123
                    'status' => 'completed',
124
                ],
125
                'increment_id = %i',
126
                $subtask['increment_id']
127
            );
128
    
129
        } catch (Exception $e) {
130
            // Failure handling
131
            DB::update(
132
                prefixTable('background_subtasks'),
133
                [
134
                    'is_in_progress' => -1,
135
                    'finished_at' => time(),
136
                    'updated_at' => time(),
137
                    'status' => 'failed',
138
                    'error_message' => $e->getMessage(),
139
                ],
140
                'increment_id = %i',
141
                $subtask['increment_id']
142
            );
143
            
144
            $this->logger->log("Subtask {$subtask['increment_id']} failure: " . $e->getMessage(), 'ERROR');
145
        }
146
    }
147
    
148
149
    /**
150
     * Generate new user keys
151
     * @param array $taskData Task data
152
     * @param array $arguments Arguments for the task
153
     */
154
    private function migratePersonalItemsStep10($taskData, $arguments): void
155
    {
156
        // get user private key
157
        $userInfo = $this->getOwnerInfos(
158
            $arguments['user_id'],
159
            $arguments['user_pwd'],
160
            1,
161
            $arguments['user_private_key'] ?? ''
162
        );
163
164
        // get TP_USER private key
165
        $userTP = DB::queryFirstRow(
166
            'SELECT pw, public_key, private_key
167
            FROM ' . prefixTable('users') . '
168
            WHERE id = %i',
169
            TP_USER_ID
170
        );
171
172
        // Start transaction for better performance
173
        DB::startTransaction();
174
175
        // Loop on items
176
        $rows = DB::query(
177
            'SELECT id, pw
178
            FROM ' . prefixTable('items') . '
179
            WHERE perso =  1
180
            AND id_tree IN %li
181
            ORDER BY id ASC
182
            LIMIT %i, %i',
183
            json_decode($arguments['personal_folders_ids']),
184
            $taskData['index'],
185
            $taskData['nb']
186
        );
187
        
188
        foreach ($rows as $item) {
189
            // Get itemKey from current user
190
            $shareKeyForItem = DB::queryFirstRow(
191
                'SELECT share_key, increment_id
192
                FROM ' . prefixTable('sharekeys_items') . '
193
                WHERE object_id = %i AND user_id = %i',
194
                $item['id'],
195
                (int) $arguments['user_id']
196
            );
197
198
            // do we have any input? (#3481)
199
            if ($shareKeyForItem === null || count($shareKeyForItem) === 0) {
200
                continue;
201
            }
202
203
            // Decrypt itemkey with user private key
204
            $itemKey = decryptUserObjectKey($shareKeyForItem['share_key'], $userInfo['private_key']);
205
            
206
            // Now create sharekey for user
207
            $this->createUserShareKey(
208
                'sharekeys_items',
209
                (string) $itemKey,
210
                (string) $userInfo['public_key'],
211
                (int) $item['id'],
212
                (int) $arguments['user_id']
213
            );
214
215
            // Now create sharekey for TP_USER
216
            $this->createUserShareKey(
217
                'sharekeys_items',
218
                (string) $itemKey,
219
                (string) $userTP['public_key'],
220
                (int) $item['id'],
221
                (int) TP_USER_ID
222
            );
223
        }
224
225
        // Commit transaction
226
        DB::commit();
227
    }
228
229
    /**
230
     * Create the sharekey for user
231
     * 
232
     * @param string $table Table name (sharekeys_items, sharekeys_fields, sharekeys_files, sharekeys_suggestions)
233
     * @param string $itemKey Item encryption key in hexadecimal format
234
     * @param string $publicKey User's RSA public key (PEM format)
235
     * @param int $recordId Record ID (object_id in sharekeys table)
236
     * @param int $userId User ID who will receive access to the encrypted key
237
     * @return void
238
     */
239
    private function createUserShareKey(string $table, string $itemKey, string $publicKey, int $recordId, int $userId): void
240
    {
241
        // Prevent to change key if its key is empty
242
        if (empty($itemKey) === true) {
243
            $share_key_for_item = '';
244
        } else {
245
            // Encrypt Item key
246
            $share_key_for_item = encryptUserObjectKey($itemKey, $publicKey);
247
        }
248
        
249
        // Save the new sharekey correctly encrypted in DB
250
        $affected = DB::update(
251
            prefixTable($table),
252
            array(
253
                'object_id' => $recordId,
254
                'user_id' => $userId,
255
                'share_key' => $share_key_for_item,
256
            ),
257
            'object_id = %i AND user_id = %i',
258
            $recordId,
259
            $userId
260
        );
261
        
262
        // If now row was updated, it means the user has no key for this item, so we need to insert it
263
        if ($affected === 0) {
264
            DB::insert(
265
                prefixTable($table),
266
                array(
267
                    'object_id' => $recordId,
268
                    'user_id' => $userId,
269
                    'share_key' => $share_key_for_item,
270
                )
271
            );
272
        }
273
    }
274
275
276
    /**
277
     * Generate new user keys - step 20
278
     * @param array $taskData Task data
279
     * @param array $arguments Arguments for the task
280
     * @return void
281
     */
282
    private function migratePersonalItemsStep20($taskData, $arguments) {
283
        // get user private key
284
        $userInfo = $this->getOwnerInfos(
285
            $arguments['user_id'],
286
            $arguments['user_pwd'],
287
            1,
288
            $arguments['user_private_key'] ?? ''
289
        );
290
291
        // get TP_USER private key
292
        $userTP = DB::queryFirstRow(
293
            'SELECT pw, public_key, private_key
294
            FROM ' . prefixTable('users') . '
295
            WHERE id = %i',
296
            TP_USER_ID
297
        );
298
299
        // Start transaction for better performance
300
        DB::startTransaction();
301
302
        // Loop on logs
303
        $rows = DB::query(
304
            'SELECT increment_id
305
            FROM ' . prefixTable('log_items') . '
306
            WHERE raison LIKE "at_pw :%" AND encryption_type = "teampass_aes"
307
            ORDER BY increment_id ASC
308
            LIMIT ' . $taskData['index'] . ', ' . $taskData['nb']
309
        );
310
        foreach ($rows as $record) {
311
            // Get itemKey from current user
312
            $currentUserKey = DB::queryFirstRow(
313
                'SELECT share_key
314
                FROM ' . prefixTable('sharekeys_logs') . '
315
                WHERE object_id = %i AND user_id = %i',
316
                $record['increment_id'],
317
                $arguments['user_id']
318
            );
319
320
            // do we have any input? (#3481)
321
            if ($currentUserKey === null || count($currentUserKey) === 0) {
322
                continue;
323
            }
324
325
            // Decrypt itemkey with admin key
326
            $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $userInfo['private_key']);
327
            
328
            // Now create sharekey for user
329
            $this->createUserShareKey(
330
                'sharekeys_logs',
331
                (string) $itemKey,
332
                (string) $userInfo['public_key'],
333
                (int) $record['id'],
334
                (int) $arguments['user_id'],
335
            );
336
337
            // Now create sharekey for TP_USER
338
            $this->createUserShareKey(
339
                'sharekeys_logs',
340
                (string) $itemKey,
341
                (string) $userTP['public_key'],
342
                (int) $record['id'],
343
                (int) TP_USER_ID,
344
            );
345
        }
346
347
        // Commit transaction
348
        DB::commit();
349
    }
350
351
352
    /**
353
     * Generate new user keys - step 30
354
     * @param array $taskData Task data
355
     * @param array $arguments Arguments for the task
356
     * @return void
357
     */
358
    private function migratePersonalItemsStep30($taskData, $arguments) {
359
        // get user private key
360
        $userInfo = $this->getOwnerInfos(
361
            $arguments['user_id'],
362
            $arguments['user_pwd'],
363
            1,
364
            $arguments['user_private_key'] ?? ''
365
        );
366
367
        // get TP_USER private key
368
        $userTP = DB::queryFirstRow(
369
            'SELECT pw, public_key, private_key
370
            FROM ' . prefixTable('users') . '
371
            WHERE id = %i',
372
            TP_USER_ID
373
        );
374
375
        // Start transaction for better performance
376
        DB::startTransaction();
377
378
        // Loop on fields
379
        $rows = DB::query(
380
            'SELECT id
381
            FROM ' . prefixTable('categories_items') . '
382
            WHERE encryption_type = "teampass_aes"
383
            ORDER BY id ASC
384
            LIMIT %i, %i',
385
            $taskData['index'],
386
            $taskData['nb']
387
        );
388
        foreach ($rows as $record) {
389
            // Get itemKey from current user
390
            $currentUserKey = DB::queryFirstRow(
391
                'SELECT share_key
392
                FROM ' . prefixTable('sharekeys_fields') . '
393
                WHERE object_id = %i AND user_id = %i',
394
                $record['id'],
395
                $arguments['user_id']
396
            );
397
398
            if (isset($currentUserKey['share_key']) === true) {
399
                // Decrypt itemkey with user key
400
                $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $userInfo['private_key']);
401
402
                // Now create sharekey for user
403
                $this->createUserShareKey(
404
                    'sharekeys_fields',
405
                    (string) $itemKey,
406
                    (string) $userInfo['public_key'],
407
                    (int) $record['id'],
408
                    (int) $arguments['user_id']
409
                );
410
411
                // Now create sharekey for TP_USER
412
                $this->createUserShareKey(
413
                    'sharekeys_fields',
414
                    (string) $itemKey,
415
                    (string) $userTP['public_key'],
416
                    (int) $record['id'],
417
                    (int) TP_USER_ID
418
                );
419
            }
420
        }
421
422
        // Commit transaction
423
        DB::commit();
424
    }
425
426
427
    /**
428
     * Generate new user keys - step 40
429
     * @param array $taskData Task data
430
     * @param array $arguments Arguments for the task
431
     * @return void
432
     */
433
    private function migratePersonalItemsStep40($taskData, $arguments) {
434
        // get user private key
435
        $userInfo = $this->getOwnerInfos(
436
            $arguments['user_id'],
437
            $arguments['user_pwd'],
438
            1,
439
            $arguments['user_private_key'] ?? ''
440
        );
441
442
        // get TP_USER private key
443
        $userTP = DB::queryFirstRow(
444
            'SELECT pw, public_key, private_key
445
            FROM ' . prefixTable('users') . '
446
            WHERE id = %i',
447
            TP_USER_ID
448
        );
449
450
        // Start transaction for better performance
451
        DB::startTransaction();
452
453
        // Loop on suggestions
454
        $rows = DB::query(
455
            'SELECT id
456
            FROM ' . prefixTable('suggestion') . '
457
            ORDER BY id ASC
458
            LIMIT %i, %i',
459
            $taskData['index'],
460
            $taskData['nb']
461
        );
462
        foreach ($rows as $record) {
463
            // Get itemKey from current user
464
            $currentUserKey = DB::queryFirstRow(
465
                'SELECT share_key
466
                FROM ' . prefixTable('sharekeys_suggestions') . '
467
                WHERE object_id = %i AND user_id = %i',
468
                $record['id'],
469
                $arguments['user_id']
470
            );
471
472
            // do we have any input? (#3481)
473
            if ($currentUserKey === null || count($currentUserKey) === 0) {
474
                continue;
475
            }
476
477
            // Decrypt itemkey with user key
478
            $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $userInfo['private_key']);
479
480
            // Now create sharekey for user
481
            $this->createUserShareKey(
482
                'sharekeys_suggestions',
483
                (string) $itemKey,
484
                (string) $userInfo['public_key'],
485
                (int) $record['id'],
486
                (int) $arguments['user_id'],
487
            );
488
489
            // Now create sharekey for TP_USER
490
            $this->createUserShareKey(
491
                'sharekeys_fields',
492
                (string) $itemKey,
493
                (string) $userTP['public_key'],
494
                (int) $record['id'],
495
                (int) TP_USER_ID,
496
            );
497
        }
498
499
        // Commit transaction
500
        DB::commit();
501
    }
502
503
504
    /**
505
     * Generate new user keys - step 50
506
     * @param array $taskData Task data
507
     * @param array $arguments Arguments for the task
508
     * @return void
509
     */
510
    private function migratePersonalItemsStep50($taskData, $arguments) {
511
        // get user private key
512
        $userInfo = $this->getOwnerInfos(
513
            $arguments['user_id'],
514
            $arguments['user_pwd'],
515
            1,
516
            $arguments['user_private_key'] ?? ''
517
        );
518
519
        // get TP_USER private key
520
        $userTP = DB::queryFirstRow(
521
            'SELECT pw, public_key, private_key
522
            FROM ' . prefixTable('users') . '
523
            WHERE id = %i',
524
            TP_USER_ID
525
        );
526
527
        // Start transaction for better performance
528
        DB::startTransaction();
529
530
        // Loop on files
531
        $rows = DB::query(
532
            'SELECT f.id AS id, i.perso AS perso
533
            FROM ' . prefixTable('files') . ' AS f
534
            INNER JOIN ' . prefixTable('items') . ' AS i ON i.id = f.id_item
535
            WHERE f.status = "' . TP_ENCRYPTION_NAME . '"
536
            LIMIT %i, %i',
537
            $taskData['index'],
538
            $taskData['nb']
539
        ); //aes_encryption
540
        foreach ($rows as $record) {
541
            // Get itemKey from current user
542
            $currentUserKey = DB::queryFirstRow(
543
                'SELECT share_key, increment_id
544
                FROM ' . prefixTable('sharekeys_files') . '
545
                WHERE object_id = %i AND user_id = %i',
546
                $record['id'],
547
                (int) $arguments['user_id']
548
            );
549
550
            // do we have any input? (#3481)
551
            if ($currentUserKey === null || count($currentUserKey) === 0) {
552
                continue;
553
            }
554
555
            // Decrypt itemkey with user key
556
            $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $userInfo['private_key']);
557
558
            // Now create sharekey for user
559
            $this->createUserShareKey(
560
                'sharekeys_suggestions',
561
                (string) $itemKey,
562
                (string) $userInfo['public_key'],
563
                (int) $record['id'],
564
                (int) $arguments['user_id'],
565
            );
566
567
            // Now create sharekey for TP_USER
568
            $this->createUserShareKey(
569
                'sharekeys_fields',
570
                (string) $itemKey,
571
                (string) $userTP['public_key'],
572
                (int) $record['id'],
573
                (int) TP_USER_ID,
574
            );
575
        }
576
577
        // Commit transaction
578
        DB::commit();
579
    }
580
581
582
    /**
583
     * Generate new user keys - step final
584
     * @param array $arguments Arguments for the task
585
     */
586
    private function migratePersonalItemsStepFinal($arguments) {
587
        $lang = new Language('english');
0 ignored issues
show
Unused Code introduced by
The assignment to $lang is dead and can be removed.
Loading history...
588
        
589
        // update LOG
590
        logEvents(
591
            $this->settings,
592
            'user_mngt',
593
            'at_user_new_keys',
594
            TP_USER_ID,
595
            "",
596
            (string) $arguments['user_id']
597
        );
598
599
        // Set user as ready for usage
600
        DB::update(
601
            prefixTable('users'),
602
            array(
603
                'ongoing_process_id' => NULL,
604
                'updated_at' => time(),
605
                'personal_items_migrated' => 1,
606
                'is_ready_for_usage' => 1,
607
            ),
608
            'id = %i',
609
            $arguments['user_id']
610
        );
611
    }    
612
}
613