Passed
Push — master ( 82c165...9440db )
by Nils
10:41
created

ItemModel::addItem()   F

Complexity

Conditions 19
Paths 122

Size

Total Lines 225
Code Lines 141

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 141
nc 122
nop 12
dl 0
loc 225
rs 3.4666
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
 * @version    API
23
 *
24
 * @file      ItemModel.php
25
 * @author    Nils Laumaillé ([email protected])
26
 * @copyright 2009-2024 Teampass.net
27
 * @license   GPL-3.0
28
 * @see       https://www.teampass.net
29
 */
30
31
use TeampassClasses\NestedTree\NestedTree;
32
use TeampassClasses\Language\Language;
33
use ZxcvbnPhp\Zxcvbn;
34
35
require_once API_ROOT_PATH . "/Model/Database.php";
36
37
class ItemModel extends Database
38
{
39
40
41
    /**
42
     * Get the list of items to return
43
     *
44
     * @param string $sqlExtra
45
     * @param integer $limit
46
     * @param string $userPrivateKey
47
     * @param integer $userId
48
     * 
49
     * @return array
50
     */
51
    public function getItems(string $sqlExtra, int $limit, string $userPrivateKey, int $userId): array
52
    {
53
        $rows = $this->select(
54
            "SELECT i.id, label, description, i.pw, i.url, i.id_tree, i.login, i.email, i.viewed_no, i.fa_icon, i.inactif, i.perso, t.title as folder_label
55
            FROM ".prefixTable('items')." as i
56
            LEFT JOIN ".prefixTable('nested_tree')." as t ON (t.id = i.id_tree) ".
57
            $sqlExtra . 
58
            " ORDER BY i.id ASC" .
59
            //($limit > 0 ? " LIMIT ?". ["i", $limit] : '')
60
            ($limit > 0 ? " LIMIT ". $limit : '')
61
        );
62
        $ret = [];
63
        foreach ($rows as $row) {
64
            $userKey = $this->select(
65
                'SELECT share_key
66
                FROM ' . prefixTable('sharekeys_items') . '
67
                WHERE user_id = '.$userId.' AND object_id = '.$row['id']                
68
            );
69
            if (count($userKey) === 0 || empty($row['pw']) === true) {
70
                // No share key found
71
                // Exit this item
72
                continue;
73
            }
74
75
            // Get password
76
            try {
77
                $pwd = base64_decode(
78
                    (string) doDataDecryption(
79
                        $row['pw'],
80
                        decryptUserObjectKey(
81
                            $userKey[0]['share_key'],
82
                            $userPrivateKey
83
                        )
84
                    )
85
                );
86
            } catch (Exception $e) {
87
                // Password is not encrypted
88
                // deepcode ignore ServerLeak: No important data
89
                echo "ERROR";
90
            }
91
            
92
93
            // get path to item
94
            $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
95
            $arbo = $tree->getPath($row['id_tree'], false);
96
            $path = '';
97
            foreach ($arbo as $elem) {
98
                if (empty($path) === true) {
99
                    $path = htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
100
                } else {
101
                    $path .= '/' . htmlspecialchars(stripslashes(htmlspecialchars_decode($elem->title, ENT_QUOTES)), ENT_QUOTES);
102
                }
103
            }
104
105
            array_push(
106
                $ret,
107
                [
108
                    'id' => (int) $row['id'],
109
                    'label' => $row['label'],
110
                    'description' => $row['description'],
111
                    'pwd' => $pwd,
112
                    'url' => $row['url'],
113
                    'login' => $row['login'],
114
                    'email' => $row['email'],
115
                    'viewed_no' => (int) $row['viewed_no'],
116
                    'fa_icon' => $row['fa_icon'],
117
                    'inactif' => (int) $row['inactif'],
118
                    'perso' => (int) $row['perso'],
119
                    'id_tree' => (int) $row['id_tree'],
120
                    'folder_label' => $row['folder_label'],
121
                    'path' => empty($path) === true ? '' : $path,
122
                ]
123
            );
124
        }
125
126
        return $ret;
127
    }
128
    //end getItems() 
129
130
    /**
131
     * Add an item
132
     *
133
     * @param integer $folderId
134
     * @param string $label
135
     * @param string $password
136
     * @param string $description
137
     * @param string $login
138
     * @param string $email
139
     * @param string $url
140
     * @param string $tags
141
     * @param string $anyone_can_modify
142
     * @param string $icon
143
     * @param integer $userId
144
     * 
145
     * 
146
     * @return boolean
147
     */
148
    public function addItem(
149
        int $folderId,
150
        string $label,
151
        string $password,
152
        string $description,
153
        string $login,
154
        string $email,
155
        string $url,
156
        string $tags,
157
        string $anyone_can_modify,
158
        string $icon,
159
        int $userId,
160
        string $username
161
    ) : array
162
    {
163
        include_once API_ROOT_PATH . '/../sources/main.functions.php';
164
        $data = [
165
            'folderId' => $folderId,
166
            'label' => $label,
167
            'password' => $password,
168
            'description' => $description,
169
            'login' => $login,
170
            'email' => $email,
171
            'tags' => $tags,
172
            'anyoneCanModify' => $anyone_can_modify,
173
            'url' => $url,
174
            'icon' => $icon,
175
        ];
176
        
177
        $filters = [
178
            'folderId' => 'cast:integer',
179
            'label' => 'trim|escape',
180
            'password' => 'trim|escape',
181
            'description' => 'trim|escape',
182
            'login' => 'trim|escape',
183
            'email' => 'trim|escape',
184
            'tags' => 'trim|escape',
185
            'anyoneCanModify' => 'trim|escape',
186
            'url' => 'trim|escape',
187
            'icon' => 'trim|escape',
188
        ];
189
        
190
        $inputData = dataSanitizer(
191
            $data,
192
            $filters
193
        );
194
        extract($inputData);
0 ignored issues
show
Bug introduced by
It seems like $inputData can also be of type string; however, parameter $array of extract() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

194
        extract(/** @scrutinizer ignore-type */ $inputData);
Loading history...
195
196
        $lang = new Language();
197
        include API_ROOT_PATH . '/../includes/config/tp.config.php';
198
199
        // is pwd empty?
200
        if ($this->isPasswordEmptyAllowed($password, $SETTINGS['create_item_without_password'], $lang)) {
201
            return [
0 ignored issues
show
Bug Best Practice introduced by
The expression return array('error' => ...ssword is not allowed') returns the type array<string,string|true> which is incompatible with the documented return type boolean.
Loading history...
202
                'error' => true,
203
                'error_header' => 'HTTP/1.1 422 Unprocessable Entity',
204
                'error_message' => 'Empty password is not allowed'
205
            ];
206
        }
207
208
        // Check length
209
        if (strlen($password) > $SETTINGS['pwd_maximum_length']) {
210
            return [
0 ignored issues
show
Bug Best Practice introduced by
The expression return array('error' => ...gth'] . ' characters)') returns the type array<string,string|true> which is incompatible with the documented return type boolean.
Loading history...
211
                'error' => true,
212
                'error_header' => 'HTTP/1.1 422 Unprocessable Entity',
213
                'error_message' => 'Password is too long (max allowed is ' . $SETTINGS['pwd_maximum_length'] . ' characters)'
214
            ];
215
        }
216
217
        // Need info in DB
218
        // About special settings
219
        $dataFolderSettings = DB::queryFirstRow(
220
            'SELECT bloquer_creation, bloquer_modification, personal_folder
221
            FROM ' . prefixTable('nested_tree') . ' 
222
            WHERE id = %i',
223
            $inputData['folderId']
224
        );
225
        $itemInfos = [];
226
        $itemInfos['personal_folder'] = $dataFolderSettings['personal_folder'];
227
        $itemInfos['no_complex_check_on_modification'] = (int) $itemInfos['personal_folder'] === 1 ? 1 : (int) $dataFolderSettings['bloquer_modification'];
228
        $itemInfos['no_complex_check_on_creation'] = (int) $itemInfos['personal_folder'] === 1 ? 1 : (int) $dataFolderSettings['bloquer_creation'];
229
230
        // Get folder complexity
231
        $folderComplexity = DB::queryfirstrow(
232
            'SELECT valeur
233
            FROM ' . prefixTable('misc') . '
234
            WHERE type = %s AND intitule = %i',
235
            'complex',
236
            $inputData['folderId']
237
        );
238
        $itemInfos['requested_folder_complexity'] = $folderComplexity !== null ? (int) $folderComplexity['valeur'] : 0;
239
240
        // Check COMPLEXITY
241
        $zxcvbn = new Zxcvbn();
242
        $passwordStrength = $zxcvbn->passwordStrength($password);
243
        $folderPasswordStrength = convertPasswordStrength($itemInfos['requested_folder_complexity']);
244
        if ($passwordStrength['score'] < $folderPasswordStrength && (int) $itemInfos['no_complex_check_on_creation'] === 0) {
245
            return [
0 ignored issues
show
Bug Best Practice introduced by
The expression return array('error' => ...d strength is too low') returns the type array<string,string|true> which is incompatible with the documented return type boolean.
Loading history...
246
                'error' => true,
247
                'error_header' => 'HTTP/1.1 422 Unprocessable Entity',
248
                'error_message' => 'Password strength is too low'
249
            ];
250
        }
251
252
        // check if element doesn't already exist
253
        DB::queryfirstrow(
254
            'SELECT * FROM ' . prefixTable('items') . '
255
            WHERE label = %s AND inactif = %i',
256
            $label,
257
            0
258
        );
259
        if (
260
            DB::count() > 0
261
            && ((isset($SETTINGS['duplicate_item']) === true && (int) $SETTINGS['duplicate_item'] === 0)
262
            || (int) $itemInfos['personal_folder'] === 0)
263
        ) {
264
            return [
0 ignored issues
show
Bug Best Practice introduced by
The expression return array('error' => ...ates are not allowed.') returns the type array<string,string|true> which is incompatible with the documented return type boolean.
Loading history...
265
                'error' => true,
266
                'error_header' => 'HTTP/1.1 422 Unprocessable Entity',
267
                'error_message' => 'Similar item already exists. Duplicates are not allowed.'
268
            ];
269
        }
270
271
        // Handle case where pw is empty
272
        // if not allowed then warn user
273
        if (
274
            isset($SETTINGS['create_item_without_password']) === true && (int) $SETTINGS['create_item_without_password'] === 0
275
            && empty($password) === true
276
        ) {
277
            return [
0 ignored issues
show
Bug Best Practice introduced by
The expression return array('error' => ...sword is not allowed.') returns the type array<string,string|true> which is incompatible with the documented return type boolean.
Loading history...
278
                'error' => true,
279
                'error_header' => 'HTTP/1.1 422 Unprocessable Entity',
280
                'error_message' => 'Empty password is not allowed.'
281
            ];
282
        }
283
        if (empty($password) === false) {
284
            $cryptedStuff = doDataEncryption($password);
285
            $password = $cryptedStuff['encrypted'];
286
            $passwordKey = $cryptedStuff['objectKey'];
287
        } else {
288
            $passwordKey = '';
289
        }
290
        
291
        // ADD item
292
        DB::insert(
293
            prefixTable('items'),
294
            array(
295
                'label' => $label,
296
                'description' => $description,
297
                'pw' => $password,
298
                'pw_iv' => '',
299
                'email' => $email,
300
                'url' => $url,
301
                'id_tree' => $folderId,
302
                'login' => $login,
303
                'inactif' => 0,
304
                'restricted_to' => '',
305
                'perso' => $itemInfos['personal_folder'],
306
                'anyone_can_modify' => $anyoneCanModify,
307
                'complexity_level' => $passwordStrength['score'],
308
                'encryption_type' => 'teampass_aes',
309
                'fa_icon' => $icon,
310
                'item_key' => uniqidReal(50),
311
                'created_at' => time(),
312
            )
313
        );
314
        $newID = DB::insertId();
315
316
        // Create sharekeys for the user itself
317
        storeUsersShareKey(
318
            prefixTable('sharekeys_items'),
319
            (int) $itemInfos['personal_folder'],
320
            (int) $folderId,
321
            (int) $newID,
322
            $passwordKey,
323
            true,   // only for the item creator
324
            false,  // no delete all
325
            [],
326
            -1,
327
            $userId
328
        );
329
330
        // log
331
        logItems(
332
            $SETTINGS,
333
            (int) $newID,
334
            $label,
335
            $userId,
336
            'at_creation',
337
            $username
338
        );
339
340
        // Create new task for the new item
341
        // If it is not a personnal one
342
        if ((int) $itemInfos['personal_folder'] === 0) {
343
            storeTask(
344
                'new_item',
345
                $userId,
346
                0,
347
                (int) $folderId,
348
                (int) $newID,
349
                $passwordKey,
350
                [],
351
                [],
352
            );
353
        }
354
355
        // Add tags
356
        $tags = explode(',', $tags);
357
        foreach ($tags as $tag) {
358
            if (empty($tag) === false) {
359
                DB::insert(
360
                    prefixTable('tags'),
361
                    array(
362
                        'item_id' => $newID,
363
                        'tag' => strtolower($tag),
364
                    )
365
                );
366
            }
367
        }
368
369
        return [
0 ignored issues
show
Bug Best Practice introduced by
The expression return array('error' => ...ly', 'newId' => $newID) returns the type array<string,false|mixed|string> which is incompatible with the documented return type boolean.
Loading history...
370
            'error' => false,
371
            'message' => 'Item added successfully',
372
            'newId' => $newID,
373
        ];
374
    }
375
376
    private function isPasswordEmptyAllowed($password, $create_item_without_password, $lang)
0 ignored issues
show
Unused Code introduced by
The parameter $lang 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

376
    private function isPasswordEmptyAllowed($password, $create_item_without_password, /** @scrutinizer ignore-unused */ $lang)

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...
377
    {
378
        if (
379
            empty($password) === true
380
            && null !== $create_item_without_password
381
            && (int) $create_item_without_password !== 1
382
        ) {
383
            return true;
384
        }
385
        return false;
386
    }
387
}