Passed
Push — master ( 7d22e0...478025 )
by Nils
04:17
created

AuthModel::getUserAuth()   B

Complexity

Conditions 10
Paths 6

Size

Total Lines 66
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 10
eloc 39
c 3
b 0
f 0
nc 6
nop 3
dl 0
loc 66
rs 7.6666

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
26
27
require_once API_ROOT_PATH . "/Model/Database.php";
28
29
30
class AuthModel extends Database
31
{
32
33
34
    /**
35
     * Is the user allowed
36
     *
37
     * @param string $login
38
     * @param string $password
39
     * @param string $apikey
40
     * @return array
41
     */
42
    public function getUserAuth(string $login, string $password, string $apikey): array
43
    {
44
        // Sanitize
45
        include_once API_ROOT_PATH . '/../sources/main.functions.php';
46
        $inputData = dataSanitizer(
47
            [
48
                'login' => isset($login) === true ? $login : '',
49
                'password' => isset($password) === true ? $password : '',
50
                'apikey' => isset($apikey) === true ? $apikey : '',
51
            ],
52
            [
53
                'login' => 'trim|escape',
54
                'password' => 'trim|escape',
55
                'apikey' => 'trim|escape',
56
            ],
57
            API_ROOT_PATH . '/..'
58
        );
59
        if (empty($inputData['login']) === true || empty($inputData['apikey']) === true) {
60
            return ["error" => "Login failed."];
61
        }
62
        
63
        // Check apikey
64
        if (empty($inputData['password']) === true) {
65
            // case where it is a generic key
66
            $apiInfo = $this->select("SELECT count(*) FROM " . prefixTable('api') . " WHERE value='".$inputData['apikey']."' AND label='".$inputData['login']."'");
67
            if ((int) $apiInfo[0]['count(*)'] === 0) {
68
                return ["error" => "Login failed.", "apikey" => "Not valid"];
69
            }
70
71
            return ["error" => "Not managed."];
72
        } else {
73
            // case where it is a user api key
74
            $apiInfo = $this->select("SELECT count(*) FROM " . prefixTable('users') . " WHERE user_api_key='".$inputData['apikey']."' AND login='".$inputData['login']."'");
75
            if ((int) $apiInfo[0]['count(*)'] === 0) {
76
                return ["error" => "Login failed.", "apikey" => "Not valid"];
77
            }
78
79
            // Check if user exists
80
            $userInfoRes = $this->select("SELECT id, pw, public_key, private_key, personal_folder, fonction_id, groupes_visibles, groupes_interdits, user_api_key FROM " . prefixTable('users') . " WHERE login='".$inputData['login']."'");
81
            $userInfoRes[0]['special'] = '';
82
            $userInfo = $userInfoRes[0];
83
            
84
            // Check password
85
            include_once API_ROOT_PATH . '/../sources/SplClassLoader.php';
86
            $pwdlib = new SplClassLoader('PasswordLib', API_ROOT_PATH . '/../includes/libraries');
87
            $pwdlib->register();
88
            $pwdlib = new PasswordLib\PasswordLib();
89
            if ($pwdlib->verifyPasswordHash($inputData['password'], $userInfo['pw']) === true) {
90
                // Correct credentials
91
                // get user keys
92
                $privateKeyClear = decryptPrivateKey($inputData['password'], (string) $userInfo['private_key']);
93
94
                // get user folders list
95
                $folders = $this->buildUserFoldersList($userInfo);
96
97
                // create JWT
98
                return $this->createUserJWT(
99
                    $userInfo['id'],
100
                    $inputData['login'],
101
                    $userInfo['personal_folder'],
102
                    $userInfo['public_key'],
103
                    $privateKeyClear,
104
                    implode(",", $folders)
105
                );
106
            } else {
107
                return ["error" => "Login failed.", "password" => "Not valid"];
108
            }
109
        }
110
    }
111
    //end getUserAuth
112
113
    /**
114
     * Create a JWT
115
     *
116
     * @param integer $id
117
     * @param string $login
118
     * @param integer $pf_enabled
119
     * @param string $pubkey
120
     * @param string $privkey
121
     * @param string $folders
122
     * @return array
123
     */
124
    private function createUserJWT(int $id, string $login, int $pf_enabled, string $pubkey, string $privkey, string $folders): array
125
    {
126
        require API_ROOT_PATH . '/../includes/config/tp.config.php';
127
        $headers = ['alg'=>'HS256','typ'=>'JWT'];
128
		$payload = [
129
            'username' => $login,
130
            'id' => $id, 
131
            '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...
132
            'public_key' => $pubkey,
133
            'private_key' => $privkey,
134
            'pf_enabled' => $pf_enabled,
135
            'folders_list' => $folders,
136
        ];
137
138
        include_once API_ROOT_PATH . '/inc/jwt_utils.php';
139
		return ['token' => generate_jwt($headers, $payload)];
140
    }
141
142
    //end createUserJWT
143
144
145
    /**
146
     * Permit to build the list of folders the user can access
147
     *
148
     * @param array $userInfo
149
     * @return array
150
     */
151
    private function buildUserFoldersList(array $userInfo): array
152
    {
153
        //Build tree
154
        $tree = new SplClassLoader('Tree\NestedTree', API_ROOT_PATH . '/../includes/libraries');
155
        $tree->register();
156
        $tree = new Tree\NestedTree\NestedTree(prefixTable('nested_tree'), 'id', 'parent_id', 'title');
157
        
158
        // Start by adding the manually added folders
159
        $allowedFolders = explode(";", $userInfo['groupes_visibles']);
160
        $readOnlyFolders = [];
161
        $allowedFoldersByRoles = [];
162
        $restrictedFoldersForItems = [];
163
        $foldersLimited = [];
164
        $foldersLimitedFull = [];
165
        $personalFolders = [];
166
167
        $userFunctionId = str_replace(";", ",", $userInfo['fonction_id']);
168
169
        // Get folders from the roles
170
        if (empty($userFunctionId) === false) {
171
            $rows = $this->select("SELECT * FROM " . prefixTable('roles_values') . " WHERE role_id IN (".$userFunctionId.") AND type IN ('W', 'ND', 'NE', 'NDNE', 'R')");
172
            foreach ($rows as $record) {
173
                if ($record['type'] === 'R') {
174
                    array_push($readOnlyFolders, $record['folder_id']);
175
                } elseif (in_array($record['folder_id'], $allowedFolders) === false) {
176
                    array_push($allowedFoldersByRoles, $record['folder_id']);
177
                }
178
            }
179
            $allowedFoldersByRoles = array_unique($allowedFoldersByRoles);
180
            $readOnlyFolders = array_unique($readOnlyFolders);
181
            // Clean arrays
182
            foreach ($allowedFoldersByRoles as $value) {
183
                $key = array_search($value, $readOnlyFolders);
184
                if ($key !== false) {
185
                    unset($readOnlyFolders[$key]);
186
                }
187
            }
188
        }
189
        
190
        // Does this user is allowed to see other items
191
        $inc = 0;
192
        $rows = $this->select("SELECT id, id_tree FROM " . prefixTable('items') . " WHERE restricted_to LIKE '".$userInfo['id']."'".
193
            (empty($userFunctionId) === false ? ' AND id_tree NOT IN ('.$userFunctionId.')' : ''));
194
        foreach ($rows as $record) {
195
            // Exclude restriction on item if folder is fully accessible
196
            $restrictedFoldersForItems[$record['id_tree']][$inc] = $record['id'];
197
            ++$inc;
198
        }
199
200
        // Check for the users roles if some specific rights exist on items
201
        $rows = $this->select("SELECT i.id_tree, r.item_id
202
            FROM " . prefixTable('items') . " as i
203
            INNER JOIN " . prefixTable('restriction_to_roles') . " as r ON (r.item_id=i.id)
204
            WHERE ".(empty($userFunctionId) === false ? ' id_tree NOT IN ('.$userFunctionId.') AND ' : '')." i.id_tree != ''
205
            ORDER BY i.id_tree ASC");
206
        foreach ($rows as $record) {
207
            $foldersLimited[$record['id_tree']][$inc] = $record['item_id'];
208
            array_push($foldersLimitedFull, $record['item_id']);
209
            ++$inc;
210
        }
211
212
        // Add all personal folders
213
        $rows = $this->select(
214
            'SELECT id
215
            FROM ' . prefixTable('nested_tree') . '
216
            WHERE title = '.$userInfo['id'].' AND personal_folder = 1'.
217
            (empty($userFunctionId) === false ? ' AND id NOT IN ('.$userFunctionId.')' : '').
218
            ' LIMIT 0,1'
219
        );
220
        if (empty($rows['id']) === false) {
221
            array_push($personalFolders, $rows['id']);
222
            // get all descendants
223
            $ids = $tree->getDescendants($rows['id'], false, false, true);
224
            foreach ($ids as $id) {
225
                array_push($personalFolders, $id);
226
            }
227
        }
228
229
        // All folders visibles
230
        return array_unique(
231
            array_filter(
232
                array_merge(
233
                    $allowedFolders,
234
                    $foldersLimitedFull,
235
                    $allowedFoldersByRoles,
236
                    $restrictedFoldersForItems,
237
                    $readOnlyFolders,
238
                    $personalFolders
239
                )
240
            )
241
        );
242
    }
243
    //end buildUserFoldersList
244
}