Passed
Branch wip_sessions (2e0cc8)
by Nils
04:59
created

AuthModel::buildUserFoldersList()   C

Complexity

Conditions 14
Paths 16

Size

Total Lines 94
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 60
c 0
b 0
f 0
nc 16
nop 1
dl 0
loc 94
rs 6.2666

How to fix   Long Method    Complexity   

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 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-2023 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 PasswordLib\PasswordLib;
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 failed0.", "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 failed1.", "info" => "apikey : Not valid"];
72
            }
73
74
            return ["error" => "Login failed2.", "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.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 failed3.", "info" => "apikey : Not valid"];
85
            }
86
            $userInfoRes[0]['special'] = '';
87
            $userInfo = $userInfoRes[0];
88
            
89
            // Check password
90
            $pwdlib = new PasswordLib();
91
            if ($pwdlib->verifyPasswordHash($inputData['password'], $userInfo['pw']) === 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 failed4.", "apikey" => "Not valid"];
99
                }
100
101
                // get user folders list
102
                $ret = $this->buildUserFoldersList($userInfo);
103
104
                // create JWT
105
                return $this->createUserJWT(
106
                    $userInfo['id'],
107
                    $inputData['login'],
108
                    $userInfo['personal_folder'],
109
                    $userInfo['public_key'],
110
                    $privateKeyClear,
111
                    implode(",", $ret['folders']),
112
                    implode(",", $ret['items'])
113
                );
114
            } else {
115
                return ["error" => "Login failed5.", "info" => "password : Not valid"];
116
            }
117
        }
118
    }
119
    //end getUserAuth
120
121
    /**
122
     * Create a JWT
123
     *
124
     * @param integer $id
125
     * @param string $login
126
     * @param integer $pf_enabled
127
     * @param string $pubkey
128
     * @param string $privkey
129
     * @param string $folders
130
     * @return array
131
     */
132
    private function createUserJWT(int $id, string $login, int $pf_enabled, string $pubkey, string $privkey, string $folders, string $items): array
133
    {
134
        include API_ROOT_PATH . '/../includes/config/tp.config.php';
135
        
136
		$payload = [
137
            'username' => $login,
138
            'id' => $id, 
139
            '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...
140
            'public_key' => $pubkey,
141
            'private_key' => $privkey,
142
            'pf_enabled' => $pf_enabled,
143
            'folders_list' => $folders,
144
            'restricted_items_list' => $items,
145
        ];
146
147
        return ['token' => JWT::encode($payload, DB_PASSWD, 'HS256')];
148
    }
149
150
    //end createUserJWT
151
152
153
    /**
154
     * Permit to build the list of folders the user can access
155
     *
156
     * @param array $userInfo
157
     * @return array
158
     */
159
    private function buildUserFoldersList(array $userInfo): array
160
    {
161
        //Build tree
162
        $tree = new NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
163
        
164
        // Start by adding the manually added folders
165
        $allowedFolders = explode(";", $userInfo['groupes_visibles']);
166
        $readOnlyFolders = [];
167
        $allowedFoldersByRoles = [];
168
        $restrictedFoldersForItems = [];
169
        $foldersLimited = [];
170
        $foldersLimitedFull = [];
171
        $restrictedItems = [];
172
        $personalFolders = [];
173
174
        $userFunctionId = str_replace(";", ",", $userInfo['fonction_id']);
175
176
        // Get folders from the roles
177
        if (empty($userFunctionId) === false) {
178
            $rows = $this->select("SELECT * FROM " . prefixTable('roles_values') . " WHERE role_id IN (".$userFunctionId.") AND type IN ('W', 'ND', 'NE', 'NDNE', 'R')");
179
            foreach ($rows as $record) {
180
                if ($record['type'] === 'R') {
181
                    array_push($readOnlyFolders, $record['folder_id']);
182
                } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
183
                    array_push($allowedFoldersByRoles, $record['folder_id']);
184
                }
185
            }
186
            $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
187
            $readOnlyFolders = array_unique($readOnlyFolders);
188
            // Clean arrays
189
            foreach ($allowedFoldersByRoles as $value) {
190
                $key = array_search($value, $readOnlyFolders);
191
                if ($key !== false) {
192
                    unset($readOnlyFolders[$key]);
193
                }
194
            }
195
        }
196
        
197
        // Does this user is allowed to see other items
198
        $inc = 0;
199
        $rows = $this->select("SELECT id, id_tree FROM " . prefixTable('items') . " WHERE restricted_to LIKE '".$userInfo['id']."'".
200
            (empty($userFunctionId) === false ? ' AND id_tree NOT IN ('.$userFunctionId.')' : ''));
201
        foreach ($rows as $record) {
202
            // Exclude restriction on item if folder is fully accessible
203
            $restrictedFoldersForItems[$inc] = $record['id_tree'];
204
            ++$inc;
205
        }
206
207
        // Check for the users roles if some specific rights exist on items
208
        $rows = $this->select("SELECT i.id_tree, r.item_id
209
            FROM " . prefixTable('items') . " as i
210
            INNER JOIN " . prefixTable('restriction_to_roles') . " as r ON (r.item_id=i.id)
211
            WHERE ".(empty($userFunctionId) === false ? ' id_tree NOT IN ('.$userFunctionId.') AND ' : '')." i.id_tree != ''
212
            ORDER BY i.id_tree ASC");
213
        foreach ($rows as $record) {
214
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
215
            //array_push($foldersLimitedFull, $record['item_id']);
216
            array_push($restrictedItems, $record['item_id']);
217
            array_push($foldersLimitedFull, $record['id_tree']);
218
            ++$inc;
219
        }
220
221
        // Add all personal folders
222
        $rows = $this->select(
223
            'SELECT id
224
            FROM ' . prefixTable('nested_tree') . '
225
            WHERE title = '.$userInfo['id'].' AND personal_folder = 1'.
226
            (empty($userFunctionId) === false ? ' AND id NOT IN ('.$userFunctionId.')' : '').
227
            ' LIMIT 0,1'
228
        );
229
        if (empty($rows['id']) === false) {
230
            array_push($personalFolders, $rows['id']);
231
            // get all descendants
232
            $ids = $tree->getDescendants($rows['id'], false, false, true);
233
            foreach ($ids as $id) {
234
                array_push($personalFolders, $id);
235
            }
236
        }
237
238
        // All folders visibles
239
        return [
240
            'folders' => array_unique(
241
            array_filter(
242
                array_merge(
243
                    $allowedFolders,
244
                    $foldersLimitedFull,
245
                    $allowedFoldersByRoles,
246
                    $restrictedFoldersForItems,
247
                    $readOnlyFolders,
248
                    $personalFolders
249
                )
250
                )
251
            ),
252
            'items' => array_unique($restrictedItems),
253
        ];
254
    }
255
    //end buildUserFoldersList
256
}