Passed
Push — master ( fd5210...56a3aa )
by Nils
04:41
created

AuthModel::createUserJWT()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 36
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 17
c 0
b 0
f 0
nc 1
nop 13
dl 0
loc 36
rs 9.7

How to fix   Many Parameters   

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 library is distributed in the hope that it will be useful,
6
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
7
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8
 * ---
9
 *
10
 * @project   Teampass
11
 * @version    API
12
 *
13
 * @file      AuthModel.php
14
 * ---
15
 *
16
 * @author    Nils Laumaillé ([email protected])
17
 *
18
 * @copyright 2009-2024 Teampass.net
19
 *
20
 * @license   https://spdx.org/licenses/GPL-3.0-only.html#licenseText GPL-3.0
21
 * ---
22
 *
23
 * @see       https://www.teampass.net
24
 */
25
use TeampassClasses\PasswordManager\PasswordManager;
26
use TeampassClasses\NestedTree\NestedTree;
27
use Firebase\JWT\JWT;
28
use Firebase\JWT\Key;
29
30
require_once API_ROOT_PATH . "/Model/Database.php";
31
32
33
class AuthModel extends Database
34
{
35
36
37
    /**
38
     * Is the user allowed
39
     *
40
     * @param string $login
41
     * @param string $password
42
     * @param string $apikey
43
     * @return array
44
     */
45
    public function getUserAuth(string $login, string $password, string $apikey): array
46
    {
47
        // Sanitize
48
        include_once API_ROOT_PATH . '/../sources/main.functions.php';
49
        $inputData = dataSanitizer(
50
            [
51
                'login' => isset($login) === true ? $login : '',
52
                'password' => isset($password) === true ? $password : '',
53
                'apikey' => isset($apikey) === true ? $apikey : '',
54
            ],
55
            [
56
                'login' => 'trim|escape',
57
                'password' => 'trim|escape',
58
                'apikey' => 'trim|escape',
59
            ],
60
            API_ROOT_PATH . '/..'
0 ignored issues
show
Unused Code introduced by
The call to dataSanitizer() has too many arguments starting with API_ROOT_PATH . '/..'. ( Ignorable by Annotation )

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

60
        $inputData = /** @scrutinizer ignore-call */ dataSanitizer(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
61
        );
62
        if (empty($inputData['login']) === true || empty($inputData['apikey']) === true) {
63
            return ["error" => "Login failed.", "info" => "Empty entry"];
64
        }
65
        
66
        // Check apikey
67
        if (empty($inputData['password']) === true) {
68
            // case where it is a generic key
69
            $apiInfo = $this->select("SELECT count(*) FROM " . prefixTable('api') . " WHERE value='".$inputData['apikey']."' AND label='".$inputData['login']."'");
70
            if ((int) $apiInfo[0]['count(*)'] === 0) {
71
                return ["error" => "Login failed.", "info" => "apikey : Not valid"];
72
            }
73
74
            return ["error" => "Login failed.", "info" => "Not managed."];
75
        } else {
76
            // case where it is a user api key
77
            // Check if user exists
78
            $userInfoRes = $this->select(
79
                "SELECT u.id, u.pw, u.login, u.admin, u.gestionnaire, u.can_manage_all_users, u.fonction_id, u.can_create_root_folder, u.public_key, u.private_key, u.personal_folder, u.fonction_id, u.groupes_visibles, u.groupes_interdits, a.value AS user_api_key
80
                FROM " . prefixTable('users') . " AS u
81
                INNER JOIN " . prefixTable('api') . " AS a ON (a.user_id=u.id)
82
                WHERE login='".$inputData['login']."'");
83
            if (count($userInfoRes) === 0) {
84
                return ["error" => "Login failed.", "info" => "apikey : Not valid"];
85
            }
86
            $userInfoRes[0]['special'] = '';
87
            $userInfo = $userInfoRes[0];
88
            
89
            // Check password
90
            $passwordManager = new PasswordManager();
91
            if ($passwordManager->verifyPassword($userInfo['pw'], $inputData['password']) === true) {
92
                // Correct credentials
93
                // get user keys
94
                $privateKeyClear = decryptPrivateKey($inputData['password'], (string) $userInfo['private_key']);
95
96
                // check API key
97
                if ($inputData['apikey'] !== base64_decode(decryptUserObjectKey($userInfo['user_api_key'], $privateKeyClear))) {
98
                    return ["error" => "Login failed.", "apikey" => "Not valid"];
99
                }
100
101
                // Update user's key_tempo
102
                $keyTempo = bin2hex(random_bytes(16));
103
                $this->update(
104
                    "UPDATE " . prefixTable('users') . "
105
                    SET key_tempo='".$keyTempo."'
106
                    WHERE id=".$userInfo['id']
107
                );
108
                
109
                // get user folders list
110
                $ret = $this->buildUserFoldersList($userInfo);
111
112
                // Log user
113
                include API_ROOT_PATH . '/../includes/config/tp.config.php';
114
                logEvents($SETTINGS, 'api', 'user_connection', (string) $userInfo['id'], stripslashes($userInfo['login']));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $SETTINGS seems to be never defined.
Loading history...
115
116
                // create JWT
117
                return $this->createUserJWT(
118
                    $userInfo['id'],
119
                    $inputData['login'],
120
                    $userInfo['personal_folder'],
121
                    $userInfo['public_key'],
122
                    $privateKeyClear,
123
                    implode(",", $ret['folders']),
124
                    implode(",", $ret['items']),
125
                    $keyTempo,
126
                    $userInfo['admin'],
127
                    $userInfo['gestionnaire'],
128
                    $userInfo['can_create_root_folder'],
129
                    $userInfo['can_manage_all_users'],
130
                    $userInfo['fonction_id'],
131
                );
132
            } else {
133
                return ["error" => "Login failed.", "info" => "password : Not valid"];
134
            }
135
        }
136
    }
137
    //end getUserAuth
138
139
    /**
140
     * Create a JWT
141
     *
142
     * @param integer $id
143
     * @param string $login
144
     * @param integer $pf_enabled
145
     * @param string $pubkey
146
     * @param string $privkey
147
     * @param string $folders
148
     * @param string $keyTempo
149
     * @param integer $admin
150
     * @param integer $manager
151
     * @param integer $can_create_root_folder
152
     * @param integer $can_manage_all_users
153
     * @param string $roles
154
     * @return array
155
     */
156
    private function createUserJWT(
157
        int $id,
158
        string $login,
159
        int $pf_enabled,
160
        string $pubkey,
161
        string $privkey,
162
        string $folders,
163
        string $items,
164
        string $keyTempo,
165
        int $admin,
166
        int $manager,
167
        int $can_create_root_folder,
168
        int $can_manage_all_users,
169
        string $roles
170
    ): array
171
    {
172
        include API_ROOT_PATH . '/../includes/config/tp.config.php';
173
        
174
		$payload = [
175
            'username' => $login,
176
            'id' => $id, 
177
            'exp' => (time() + $SETTINGS['api_token_duration'] + 600),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $SETTINGS seems to be never defined.
Loading history...
178
            'public_key' => $pubkey,
179
            'private_key' => $privkey,
180
            'pf_enabled' => $pf_enabled,
181
            'folders_list' => $folders,
182
            'restricted_items_list' => $items,
183
            'key_tempo' => $keyTempo,
184
            'is_admin' => $admin,
185
            'is_manager' => $manager,
186
            'user_can_create_root_folder' => $can_create_root_folder,
187
            'user_can_manage_all_users' => $can_manage_all_users,
188
            'roles' => $roles,
189
        ];
190
        
191
        return ['token' => JWT::encode($payload, DB_PASSWD, 'HS256')];
192
    }
193
194
    //end createUserJWT
195
196
197
    /**
198
     * Permit to build the list of folders the user can access
199
     *
200
     * @param array $userInfo
201
     * @return array
202
     */
203
    private function buildUserFoldersList(array $userInfo): array
204
    {
205
        //Build tree
206
        $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
207
        
208
        // Start by adding the manually added folders
209
        $allowedFolders = array_map('intval', explode(";", $userInfo['groupes_visibles']));
210
        $readOnlyFolders = [];
211
        $allowedFoldersByRoles = [];
212
        $restrictedFoldersForItems = [];
213
        $foldersLimited = [];
214
        $foldersLimitedFull = [];
215
        $restrictedItems = [];
216
        $personalFolders = [];
217
218
        $userFunctionId = str_replace(";", ",", $userInfo['fonction_id']);
219
220
        // Get folders from the roles
221
        if (empty($userFunctionId) === false) {
222
            $rows = $this->select("SELECT * FROM " . prefixTable('roles_values') . " WHERE role_id IN (".$userFunctionId.") AND type IN ('W', 'ND', 'NE', 'NDNE', 'R')");
223
            foreach ($rows as $record) {
224
                if ($record['type'] === 'R') {
225
                    array_push($readOnlyFolders, $record['folder_id']);
226
                } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
227
                    array_push($allowedFoldersByRoles, $record['folder_id']);
228
                }
229
            }
230
            $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
231
            $readOnlyFolders = array_unique($readOnlyFolders);
232
            // Clean arrays
233
            foreach ($allowedFoldersByRoles as $value) {
234
                $key = array_search($value, $readOnlyFolders);
235
                if ($key !== false) {
236
                    unset($readOnlyFolders[$key]);
237
                }
238
            }
239
        }
240
        
241
        // Does this user is allowed to see other items
242
        $inc = 0;
243
        $rows = $this->select("SELECT id, id_tree FROM " . prefixTable('items') . " WHERE restricted_to LIKE '".$userInfo['id']."'".
244
            (empty($userFunctionId) === false ? ' AND id_tree NOT IN ('.$userFunctionId.')' : ''));
245
        foreach ($rows as $record) {
246
            // Exclude restriction on item if folder is fully accessible
247
            $restrictedFoldersForItems[$inc] = $record['id_tree'];
248
            ++$inc;
249
        }
250
251
        // Check for the users roles if some specific rights exist on items
252
        $rows = $this->select("SELECT i.id_tree, r.item_id
253
            FROM " . prefixTable('items') . " as i
254
            INNER JOIN " . prefixTable('restriction_to_roles') . " as r ON (r.item_id=i.id)
255
            WHERE ".(empty($userFunctionId) === false ? ' id_tree NOT IN ('.$userFunctionId.') AND ' : '')." i.id_tree != ''
256
            ORDER BY i.id_tree ASC");
257
        foreach ($rows as $record) {
258
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
259
            //array_push($foldersLimitedFull, $record['item_id']);
260
            array_push($restrictedItems, $record['item_id']);
261
            array_push($foldersLimitedFull, $record['id_tree']);
262
            ++$inc;
263
        }
264
265
        // Add all personal folders
266
        $rows = $this->select(
267
            'SELECT id
268
            FROM ' . prefixTable('nested_tree') . '
269
            WHERE title = '.$userInfo['id'].' AND personal_folder = 1'.
270
            (empty($userFunctionId) === false ? ' AND id NOT IN ('.$userFunctionId.')' : '').
271
            ' LIMIT 0,1'
272
        );
273
        if (empty($rows['id']) === false) {
274
            array_push($personalFolders, $rows['id']);
275
            // get all descendants
276
            $ids = $tree->getDescendants($rows['id'], false, false, true);
277
            foreach ($ids as $id) {
278
                array_push($personalFolders, $id);
279
            }
280
        }
281
282
        // All folders visibles
283
        return [
284
            'folders' => array_unique(
285
            array_filter(
286
                array_merge(
287
                    $allowedFolders,
288
                    $foldersLimitedFull,
289
                    $allowedFoldersByRoles,
290
                    $restrictedFoldersForItems,
291
                    $readOnlyFolders,
292
                    $personalFolders
293
                )
294
                )
295
            ),
296
            'items' => array_unique($restrictedItems),
297
        ];
298
    }
299
    //end buildUserFoldersList
300
}