Passed
Push — master ( e824e6...9b9d31 )
by Nils
05:57
created

migratePersonalItemsStep40()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 68
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 4
eloc 36
c 1
b 0
f 1
nc 3
nop 2
dl 0
loc 68
rs 9.344

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