Passed
Pull Request — master (#4676)
by Nils
05:47
created

UserHandlerTrait::processGenerateUserKeysSubtask()   B

Complexity

Conditions 9
Paths 43

Size

Total Lines 68
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 49
c 1
b 0
f 0
nc 43
nop 2
dl 0
loc 68
rs 7.5571

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      UserHandlerTrait.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 UserHandlerTrait {
32
33
    /**
34
     * Handle user build cache tree
35
     * @param array $arguments Useful arguments for the task
36
     * @return void
37
     */
38
    private function handleUserBuildCacheTree($arguments) {
39
        performVisibleFoldersHtmlUpdate($arguments['user_id']);
40
    }
41
42
43
    /**
44
     * Generate user keys
45
     * @param array $taskData Données de la tâche
46
     * @param array $arguments Arguments nécessaires pour la création des clés
47
     * @return void
48
     */
49
    private function generateUserKeys($arguments) {
50
        // Get all subtasks related to this task
51
        $subtasks = DB::query(
52
            'SELECT * FROM ' . prefixTable('background_subtasks') . ' WHERE task_id = %i AND is_in_progress = 0 ORDER BY `task` ASC',
53
            $this->taskId
54
        );
55
    
56
        if (empty($subtasks)) {
57
            if (LOG_TASKS=== true) $this->logger->log("No subtask was found for task {$this->taskId}");
1 ignored issue
show
introduced by
The condition LOG_TASKS === true is always true.
Loading history...
58
            return;
59
        }
60
    
61
        // Process each subtask
62
        foreach ($subtasks as $subtask) {
63
            if (LOG_TASKS=== true) $this->logger->log("Processing subtask {$subtask['increment_id']} for task {$this->taskId}");
64
            $this->processGenerateUserKeysSubtask($subtask, $arguments);
65
        }
66
    
67
        // Are all subtasks completed?
68
        $remainingSubtasks = DB::queryFirstField(
69
            'SELECT COUNT(*) FROM ' . prefixTable('background_subtasks') . ' WHERE task_id = %i AND is_in_progress = 0',
70
            $this->taskId
71
        );    
72
        if ($remainingSubtasks == 0) {
73
            $this->completeTask();
0 ignored issues
show
Bug introduced by
It seems like completeTask() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

73
            $this->/** @scrutinizer ignore-call */ 
74
                   completeTask();
Loading history...
74
        }
75
    }
76
    
77
78
    /**
79
     * Process a subtask for generating user keys.
80
     * @param array $subtask The subtask to process.
81
     * @param array $arguments Arguments for the task.
82
     * @return void
83
     */
84
    private function processGenerateUserKeysSubtask(array $subtask, array $arguments) {
85
        try {
86
            $taskData = json_decode($subtask['task'], true);
87
            
88
            // Mark the subtask as in progress
89
            DB::update(
90
                prefixTable('background_subtasks'),
91
                [
92
                    'is_in_progress' => 1,
93
                    'updated_at' => time(),
94
                    'status' => 'in progress'
95
                ],
96
                'increment_id = %i',
97
                $subtask['increment_id']
98
            );
99
            
100
            if (LOG_TASKS=== true) $this->logger->log("Subtask is in progress: ".$taskData['step'], 'INFO');
1 ignored issue
show
introduced by
The condition LOG_TASKS === true is always true.
Loading history...
101
            switch ($taskData['step'] ?? '') {
102
                case 'step0':
103
                    $this->generateNewUserStep0($arguments);
104
                    break;
105
                case 'step20':
106
                    $this->generateNewUserStep20($taskData, $arguments, $subtask['task_id']);
107
                    break;
108
                case 'step30':
109
                    $this->generateNewUserStep30($taskData, $arguments, $subtask['task_id']);
110
                    break;
111
                case 'step40':
112
                    $this->generateNewUserStep40($taskData, $arguments, $subtask['task_id']);
113
                    break;
114
                case 'step50':
115
                    $this->generateNewUserStep50($taskData, $arguments, $subtask['task_id']);
116
                    break;
117
                case 'step60':
118
                    $this->generateNewUserStep60($arguments);
119
                    break;
120
                default:
121
                    throw new Exception("Type of subtask unknown: {$this->processType}");
122
            }
123
    
124
            // Mark subtask as completed
125
            DB::update(
126
                prefixTable('background_subtasks'),
127
                [
128
                    'is_in_progress' => -1,
129
                    'finished_at' => time(),
130
                    'status' => 'completed',
131
                ],
132
                'increment_id = %i',
133
                $subtask['increment_id']
134
            );
135
    
136
        } catch (Exception $e) {
137
            // Failure handling
138
            DB::update(
139
                prefixTable('background_subtasks'),
140
                [
141
                    'is_in_progress' => -1,
142
                    'finished_at' => time(),
143
                    'updated_at' => time(),
144
                    'status' => 'failed',
145
                    'error_message' => $e->getMessage(),
146
                ],
147
                'increment_id = %i',
148
                $subtask['increment_id']
149
            );
150
            
151
            $this->logger->log("Subtask {$subtask['increment_id']} failure: " . $e->getMessage(), 'ERROR');
152
        }
153
    }
154
    
155
156
    /**
157
     * Generate new user keys - step 0
158
     * @param array $arguments Arguments for the task
159
     * @return void
160
     */
161
    private function generateNewUserStep0($arguments) {
162
        // CLear old sharekeys
163
        if ($arguments['user_self_change'] === 0) {
164
            deleteUserObjetsKeys($arguments['new_user_id'], $this->settings);
165
        }
166
    }
167
168
169
    /**
170
     * Generate new user keys
171
     * @param array $taskData Task data
172
     * @param array $arguments Arguments for the task
173
     * @param int $taskId Task ID
174
     */
175
    private function generateNewUserStep20($taskData, $arguments, $taskId) {
0 ignored issues
show
Unused Code introduced by
The parameter $taskId is not used and could be removed. ( Ignorable by Annotation )

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

175
    private function generateNewUserStep20($taskData, $arguments, /** @scrutinizer ignore-unused */ $taskId) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
176
        // get user private key
177
        $ownerInfo = $this->getOwnerInfos($arguments['owner_id'], $arguments['creator_pwd']);
178
        $userInfo = $this->getOwnerInfos($arguments['new_user_id'], $arguments['new_user_pwd']);
179
        
180
        // Start transaction for better performance
181
        DB::startTransaction();
182
183
        // Loop on items
184
        $rows = DB::query(
185
            'SELECT id, pw, perso
186
            FROM ' . prefixTable('items') . '
187
            WHERE perso =  %i
188
            ORDER BY id ASC
189
            LIMIT ' . $taskData['index'] . ', ' . $taskData['nb'],
190
            ($arguments['only_personal_items'] ?? 0) === 1 ? 1 : 0
191
        );
192
193
        foreach ($rows as $record) {
194
            // Get itemKey from current user
195
            $currentUserKey = DB::queryFirstRow(
196
                'SELECT share_key, increment_id
197
                FROM ' . prefixTable('sharekeys_items') . '
198
                WHERE object_id = %i AND user_id = %i',
199
                $record['id'],
200
                (int) $record['perso'] === 0 ? $arguments['owner_id'] : $arguments['new_user_id']
201
            );
202
203
            // do we have any input? (#3481)
204
            if ($currentUserKey === null || count($currentUserKey) === 0) {
205
                continue;
206
            }
207
208
            // Decrypt itemkey with admin key
209
            $itemKey = decryptUserObjectKey(
210
                $currentUserKey['share_key'],
211
                (int) $record['perso'] === 0 ? $ownerInfo['private_key'] : $userInfo['private_key']
212
            );
213
            
214
            // Prevent to change key if its key is empty
215
            if (empty($itemKey) === true) {
216
                $share_key_for_item = '';
217
            } else {
218
                // Encrypt Item key
219
                $share_key_for_item = encryptUserObjectKey($itemKey, $userInfo['public_key']);
220
            }
221
            
222
            $currentUserKey = DB::queryFirstRow(
223
                'SELECT increment_id
224
                FROM ' . prefixTable('sharekeys_items') . '
225
                WHERE object_id = %i AND user_id = %i',
226
                $record['id'],
227
                $arguments['new_user_id']
228
            );
229
230
            if ($currentUserKey) {
231
                // NOw update
232
                DB::update(
233
                    prefixTable('sharekeys_items'),
234
                    array(
235
                        'share_key' => $share_key_for_item,
236
                    ),
237
                    'increment_id = %i',
238
                    $currentUserKey['increment_id']
239
                );
240
            } else {
241
                DB::insert(
242
                    prefixTable('sharekeys_items'),
243
                    array(
244
                        'object_id' => (int) $record['id'],
245
                        'user_id' => (int) $arguments['new_user_id'],
246
                        'share_key' => $share_key_for_item,
247
                    )
248
                );
249
            }
250
        }
251
252
        // Commit transaction
253
        DB::commit();
254
    }
255
256
257
    /**
258
     * Generate new user keys - step 30
259
     * @param array $taskData Task data
260
     * @param array $arguments Arguments for the task
261
     * @param int $taskId Task ID
262
     * @return void
263
     */
264
    private function generateNewUserStep30($taskData, $arguments, $taskId) {
0 ignored issues
show
Unused Code introduced by
The parameter $taskId is not used and could be removed. ( Ignorable by Annotation )

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

264
    private function generateNewUserStep30($taskData, $arguments, /** @scrutinizer ignore-unused */ $taskId) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
265
        // get user private key
266
        $ownerInfo = $this->getOwnerInfos($arguments['owner_id'], $arguments['creator_pwd']);
267
        $userInfo = $this->getOwnerInfos($arguments['new_user_id'], $arguments['new_user_pwd']);
268
269
        // Start transaction for better performance
270
        DB::startTransaction();
271
272
        // Loop on logs
273
        $rows = DB::query(
274
            'SELECT increment_id
275
            FROM ' . prefixTable('log_items') . '
276
            WHERE raison LIKE "at_pw :%" AND encryption_type = "teampass_aes"
277
            ORDER BY increment_id ASC
278
            LIMIT ' . $taskData['index'] . ', ' . $taskData['nb']
279
        );
280
        foreach ($rows as $record) {
281
            // Get itemKey from current user
282
            $currentUserKey = DB::queryFirstRow(
283
                'SELECT share_key
284
                FROM ' . prefixTable('sharekeys_logs') . '
285
                WHERE object_id = %i AND user_id = %i',
286
                $record['increment_id'],
287
                $arguments['owner_id']
288
            );
289
290
            // do we have any input? (#3481)
291
            if ($currentUserKey === null || count($currentUserKey) === 0) {
292
                continue;
293
            }
294
295
            // Decrypt itemkey with admin key
296
            $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $ownerInfo['private_key']);
297
298
            // Encrypt Item key
299
            $share_key_for_item = encryptUserObjectKey($itemKey, $userInfo['public_key']);
300
301
            // Save the key in DB
302
            if ($arguments['user_self_change'] === false) {
303
                DB::insert(
304
                    prefixTable('sharekeys_logs'),
305
                    array(
306
                        'object_id' => (int) $record['increment_id'],
307
                        'user_id' => (int) $arguments['new_user_id'],
308
                        'share_key' => $share_key_for_item,
309
                    )
310
                );
311
            } else {
312
                // Get itemIncrement from selected user
313
                if ((int) $arguments['new_user_id'] !== (int) $arguments['owner_id']) {
314
                    $currentUserKey = DB::queryFirstRow(
315
                        'SELECT increment_id
316
                        FROM ' . prefixTable('sharekeys_items') . '
317
                        WHERE object_id = %i AND user_id = %i',
318
                        $record['id'],
319
                        $arguments['new_user_id']
320
                    );
321
                }
322
323
                // NOw update
324
                DB::update(
325
                    prefixTable('sharekeys_logs'),
326
                    array(
327
                        'share_key' => $share_key_for_item,
328
                    ),
329
                    'increment_id = %i',
330
                    $currentUserKey['increment_id']
331
                );
332
            }
333
        }
334
335
        // Commit transaction
336
        DB::commit();
337
    }
338
339
340
    /**
341
     * Generate new user keys - step 40
342
     * @param array $taskData Task data
343
     * @param array $arguments Arguments for the task
344
     * @param int $taskId Task ID
345
     * @return void
346
     */
347
    private function generateNewUserStep40($taskData, $arguments, $taskId) {
0 ignored issues
show
Unused Code introduced by
The parameter $taskId is not used and could be removed. ( Ignorable by Annotation )

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

347
    private function generateNewUserStep40($taskData, $arguments, /** @scrutinizer ignore-unused */ $taskId) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
348
        // get user private key
349
        $ownerInfo = $this->getOwnerInfos($arguments['owner_id'], $arguments['creator_pwd']);
350
        $userInfo = $this->getOwnerInfos($arguments['new_user_id'], $arguments['new_user_pwd']);
351
352
        // Start transaction for better performance
353
        DB::startTransaction();
354
355
        // Loop on fields
356
        $rows = DB::query(
357
            'SELECT id
358
            FROM ' . prefixTable('categories_items') . '
359
            WHERE encryption_type = "teampass_aes"
360
            ORDER BY id ASC
361
            LIMIT ' . $taskData['index'] . ', ' . $taskData['nb']
362
        );
363
        foreach ($rows as $record) {
364
            // Get itemKey from current user
365
            $currentUserKey = DB::queryFirstRow(
366
                'SELECT share_key
367
                FROM ' . prefixTable('sharekeys_fields') . '
368
                WHERE object_id = %i AND user_id = %i',
369
                $record['id'],
370
                $arguments['owner_id']
371
            );
372
373
            if (isset($currentUserKey['share_key']) === true) {
374
                // Decrypt itemkey with admin key
375
                $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $ownerInfo['private_key']);
376
377
                // Encrypt Item key
378
                $share_key_for_item = encryptUserObjectKey($itemKey, $userInfo['public_key']);
379
380
                // Save the key in DB
381
                if ($arguments['user_self_change'] === false) {
382
                    DB::insert(
383
                        prefixTable('sharekeys_fields'),
384
                        array(
385
                            'object_id' => (int) $record['id'],
386
                            'user_id' => (int) $arguments['new_user_id'],
387
                            'share_key' => $share_key_for_item,
388
                        )
389
                    );
390
                } else {
391
                    // Get itemIncrement from selected user
392
                    if ((int) $arguments['new_user_id'] !== (int) $arguments['owner_id']) {
393
                        $currentUserKey = DB::queryFirstRow(
394
                            'SELECT increment_id
395
                            FROM ' . prefixTable('sharekeys_items') . '
396
                            WHERE object_id = %i AND user_id = %i',
397
                            $record['id'],
398
                            $arguments['new_user_id']
399
                        );
400
                    }
401
402
                    // NOw update
403
                    DB::update(
404
                        prefixTable('sharekeys_fields'),
405
                        array(
406
                            'share_key' => $share_key_for_item,
407
                        ),
408
                        'increment_id = %i',
409
                        $currentUserKey['increment_id']
410
                    );
411
                }
412
            }
413
        }
414
415
        // Commit transaction
416
        DB::commit();
417
    }
418
419
420
    /**
421
     * Generate new user keys - step 50
422
     * @param array $taskData Task data
423
     * @param array $arguments Arguments for the task
424
     * @param int $taskId Task ID
425
     * @return void
426
     */
427
    private function generateNewUserStep50($taskData, $arguments, $taskId) {
0 ignored issues
show
Unused Code introduced by
The parameter $taskId is not used and could be removed. ( Ignorable by Annotation )

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

427
    private function generateNewUserStep50($taskData, $arguments, /** @scrutinizer ignore-unused */ $taskId) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
428
        // get user private key
429
        $ownerInfo = $this->getOwnerInfos($arguments['owner_id'], $arguments['creator_pwd']);
430
        $userInfo = $this->getOwnerInfos($arguments['new_user_id'], $arguments['new_user_pwd']);
431
432
        // Start transaction for better performance
433
        DB::startTransaction();
434
435
        // Loop on suggestions
436
        $rows = DB::query(
437
            'SELECT id
438
            FROM ' . prefixTable('suggestion') . '
439
            ORDER BY id ASC
440
            LIMIT ' . $taskData['index'] . ', ' . $taskData['nb']
441
        );
442
        foreach ($rows as $record) {
443
            // Get itemKey from current user
444
            $currentUserKey = DB::queryFirstRow(
445
                'SELECT share_key
446
                FROM ' . prefixTable('sharekeys_suggestions') . '
447
                WHERE object_id = %i AND user_id = %i',
448
                $record['id'],
449
                $arguments['owner_id']
450
            );
451
452
            // do we have any input? (#3481)
453
            if ($currentUserKey === null || count($currentUserKey) === 0) {
454
                continue;
455
            }
456
457
            // Decrypt itemkey with admin key
458
            $itemKey = decryptUserObjectKey($currentUserKey['share_key'], $ownerInfo['private_key']);
459
460
            // Encrypt Item key
461
            $share_key_for_item = encryptUserObjectKey($itemKey, $userInfo['public_key']);
462
463
            // Save the key in DB
464
            if ($arguments['user_self_change'] === false) {
465
                DB::insert(
466
                    prefixTable('sharekeys_suggestions'),
467
                    array(
468
                        'object_id' => (int) $record['id'],
469
                        'user_id' => (int) $arguments['new_user_id'],
470
                        'share_key' => $share_key_for_item,
471
                    )
472
                );
473
            } else {
474
                // Get itemIncrement from selected user
475
                if ((int) $arguments['new_user_id'] !== (int) $arguments['owner_id']) {
476
                    $currentUserKey = DB::queryFirstRow(
477
                        'SELECT increment_id
478
                        FROM ' . prefixTable('sharekeys_items') . '
479
                        WHERE object_id = %i AND user_id = %i',
480
                        $record['id'],
481
                        $arguments['new_user_id']
482
                    );
483
                }
484
485
                // NOw update
486
                DB::update(
487
                    prefixTable('sharekeys_suggestions'),
488
                    array(
489
                        'share_key' => $share_key_for_item,
490
                    ),
491
                    'increment_id = %i',
492
                    $currentUserKey['increment_id']
493
                );
494
            }
495
        }
496
497
        // Commit transaction
498
        DB::commit();
499
    }
500
501
502
    /**
503
     * Generate new user keys - step 60
504
     * @param array $arguments Arguments for the task
505
     */
506
    private function generateNewUserStep60($arguments) {
507
        $lang = new Language('english');
508
509
        // IF USER IS NOT THE SAME
510
        if ((int) $arguments['new_user_id'] === (int) $arguments['owner_id']) {
511
            return [
512
                'new_index' => 0,
513
                'new_action' => 'finished',
514
            ];
515
        }
516
        
517
        // update LOG
518
        logEvents(
519
            $this->settings,
520
            'user_mngt',
521
            'at_user_new_keys',
522
            TP_USER_ID,
523
            "",
524
            (string) $arguments['new_user_id']
525
        );
526
527
        // if done then send email to new user
528
        // get user info
529
        $userInfo = DB::queryFirstRow(
530
            'SELECT email, login, auth_type, special, lastname, name
531
            FROM ' . prefixTable('users') . '
532
            WHERE id = %i',
533
            $arguments['new_user_id']
534
        );
535
536
        // SEND EMAIL TO USER depending on context
537
        // Config 1: new user is a local user
538
        // Config 2: new user is an LDAP user
539
        // Config 3: send new password
540
        // COnfig 4: send new encryption code
541
        if (isset($arguments['send_email']) === true && (int) $arguments['send_email'] === 1) {
542
            sendMailToUser(
543
                filter_var($userInfo['email'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
544
                // @scrutinizer ignore-type
545
                empty($arguments['email_body']) === false ? $arguments['email_body'] : $lang->get('email_body_user_config_1'),
546
                'TEAMPASS - ' . $lang->get('login_credentials'),
547
                (array) filter_var_array(
548
                    [
549
                        '#code#' => cryption($arguments['new_user_code'], '','decrypt', $this->settings)['string'],
550
                        '#lastname#' => isset($userInfo['name']) === true ? $userInfo['name'] : '',
551
                        '#login#' => isset($userInfo['login']) === true ? $userInfo['login'] : '',
552
                    ],
553
                    FILTER_SANITIZE_FULL_SPECIAL_CHARS
554
                ),
555
                false,
556
                $arguments['new_user_pwd']
557
            );
558
        }
559
            
560
        // Set user as ready for usage
561
        DB::update(
562
            prefixTable('users'),
563
            array(
564
                'is_ready_for_usage' => 1,
565
                'otp_provided' => isset($arguments['otp_provided_new_value']) === true && (int) $arguments['otp_provided_new_value'] === 1 ? $arguments['otp_provided_new_value'] : 0
566
            ),
567
            'id = %i',
568
            $arguments['new_user_id']
569
        );
570
    }
571
    
572
573
    /**
574
     * Get owner info
575
     * @param int $owner_id Owner ID
576
     * @param string $owner_pwd Owner password
577
     * @return array Owner information
578
     */
579
    private function getOwnerInfos(int $owner_id, string $owner_pwd) {
580
        $userInfo = DB::queryFirstRow(
581
            'SELECT pw, public_key, private_key, login, name
582
            FROM ' . prefixTable('users') . '
583
            WHERE id = %i',
584
            $owner_id
585
        );
586
587
        // decrypt owner password
588
        $pwd = cryption($owner_pwd, '','decrypt', $this->settings)['string'];
589
        // decrypt private key and send back
590
        return [
591
            'private_key' => decryptPrivateKey($pwd, $userInfo['private_key']),
592
            'public_key' => $userInfo['public_key'],
593
            'login' => $userInfo['login'],
594
            'name' => $userInfo['name'],
595
        ];
596
    }
597
}