Passed
Push — EXTRACT_CLASSES ( 231cec...0382f2 )
by Rafael
65:54 queued 05:18
created

DolibarrApiAccess::__isAllowed()   F

Complexity

Conditions 27
Paths 736

Size

Total Lines 137
Code Lines 77

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 27
eloc 77
nc 736
nop 0
dl 0
loc 137
rs 0.3666
c 0
b 0
f 0

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
/* Copyright (C) 2015       Jean-François Ferry         <[email protected]>
4
 * Copyright (C) 2016	    Laurent Destailleur		    <[email protected]>
5
 * Copyright (C) 2023	    Ferran Marcet			    <[email protected]>
6
 * Copyright (C) 2024		MDW							<[email protected]>
7
 * Copyright (C) 2024       Rafael San José             <[email protected]>
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 3 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21
 */
22
23
namespace Dolibarr\Code\Api\Classes;
24
25
// Create the autoloader for Luracast
26
require_once constant('DOL_DOCUMENT_ROOT') . '/includes/restler/framework/Luracast/Restler/AutoLoader.php';
27
call_user_func(
28
    /**
29
     * @return Luracast\Restler\AutoLoader
30
     */
31
    static function () {
32
        $loader = Luracast\Restler\AutoLoader::instance();
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Api\Classe...cast\Restler\AutoLoader was not found. Did you mean Luracast\Restler\AutoLoader? If so, make sure to prefix the type with \.
Loading history...
33
        spl_autoload_register($loader);
34
        return $loader;
35
    }
36
);
37
38
require_once constant('DOL_DOCUMENT_ROOT') . '/includes/restler/framework/Luracast/Restler/iAuthenticate.php';
39
require_once constant('DOL_DOCUMENT_ROOT') . '/includes/restler/framework/Luracast/Restler/iUseAuthentication.php';
40
require_once constant('DOL_DOCUMENT_ROOT') . '/includes/restler/framework/Luracast/Restler/Resources.php';
41
require_once constant('DOL_DOCUMENT_ROOT') . '/includes/restler/framework/Luracast/Restler/Defaults.php';
42
require_once constant('DOL_DOCUMENT_ROOT') . '/includes/restler/framework/Luracast/Restler/RestException.php';
43
44
use Luracast\Restler\iAuthenticate;
45
use Luracast\Restler\Resources;
46
use Luracast\Restler\Defaults;
47
use Luracast\Restler\RestException;
48
49
/**
50
 * Dolibarr API access class
51
 *
52
 */
53
class DolibarrApiAccess implements iAuthenticate
54
{
55
    const REALM = 'Restricted Dolibarr API';
56
57
    /**
58
     * @var DoliDB  Database handler
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Api\Classes\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
59
     */
60
    public $db;
61
62
    /**
63
     * @var array $requires role required by API method     user / external / admin
64
     */
65
    public static $requires = array('user', 'external', 'admin');
66
67
    /**
68
     * @var string $role        user role
69
     */
70
    public static $role = 'user';
71
72
    /**
73
     * @var User        $user   Loggued user
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Api\Classes\User was not found. Did you mean User? If so, make sure to prefix the type with \.
Loading history...
74
     */
75
    public static $user = null;
76
77
78
    /**
79
     * Constructor
80
     */
81
    public function __construct()
82
    {
83
        global $db;
84
        $this->db = $db;
85
    }
86
87
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName
88
    /**
89
     * Check access
90
     *
91
     * @return bool
92
     *
93
     * @throws RestException 401 Forbidden
94
     * @throws RestException 503 Technical error
95
     */
96
    public function __isAllowed()
97
    {
98
		// phpcs:enable
99
        global $conf, $db, $user;
100
101
        $login = '';
102
        $stored_key = '';
103
104
        $userClass = Defaults::$userIdentifierClass;
105
106
        /*foreach ($_SERVER as $key => $val)
107
        {
108
            dol_syslog($key.' - '.$val);
109
        }*/
110
111
        // api key can be provided in url with parameter api_key=xxx or ni header with header DOLAPIKEY:xxx
112
        $api_key = '';
113
        if (isset($_GET['api_key'])) {  // For backward compatibility. Keep $_GET here.
114
            // TODO Add option to disable use of api key on url. Return errors if used.
115
            $api_key = $_GET['api_key'];
116
        }
117
        if (isset($_GET['DOLAPIKEY'])) {
118
            // TODO Add option to disable use of api key on url. Return errors if used.
119
            $api_key = $_GET['DOLAPIKEY']; // With GET method
120
        }
121
        if (isset($_SERVER['HTTP_DOLAPIKEY'])) {         // Param DOLAPIKEY in header can be read with HTTP_DOLAPIKEY
122
            $api_key = $_SERVER['HTTP_DOLAPIKEY']; // With header method (recommended)
123
        }
124
125
        $api_key = dol_string_nounprintableascii($api_key);
126
127
        if (preg_match('/^dolcrypt:/i', $api_key)) {
128
            throw new RestException(503, 'Bad value for the API key. An API key should not start with dolcrypt:');
129
        }
130
131
        if ($api_key) {
132
            $userentity = 0;
133
134
            $sql = "SELECT u.login, u.datec, u.api_key,";
135
            $sql .= " u.tms as date_modification, u.entity";
136
            $sql .= " FROM " . MAIN_DB_PREFIX . "user as u";
137
            $sql .= " WHERE u.api_key = '" . $this->db->escape($api_key) . "' OR u.api_key = '" . $this->db->escape(dolEncrypt($api_key, '', '', 'dolibarr')) . "'";
138
139
            $result = $this->db->query($sql);
140
            if ($result) {
141
                $nbrows = $this->db->num_rows($result);
142
                if ($nbrows == 1) {
143
                    $obj = $this->db->fetch_object($result);
144
                    $login = $obj->login;
145
                    $stored_key = dolDecrypt($obj->api_key);
146
                    $userentity = $obj->entity;
147
148
                    if (!defined("DOLENTITY") && $conf->entity != ($obj->entity ? $obj->entity : 1)) {      // If API was not forced with HTTP_DOLENTITY, and user is on another entity, so we reset entity to entity of user
149
                        $conf->entity = ($obj->entity ? $obj->entity : 1);
150
                        // We must also reload global conf to get params from the entity
151
                        dol_syslog("Entity was not set on http header with HTTP_DOLAPIENTITY (recommended for performance purpose), so we switch now on entity of user (" . $conf->entity . ") and we have to reload configuration.", LOG_WARNING);
152
                        $conf->setValues($this->db);
153
                    }
154
                } elseif ($nbrows > 1) {
155
                    throw new RestException(503, 'Error when fetching user api_key : More than 1 user with this apikey');
156
                }
157
            } else {
158
                throw new RestException(503, 'Error when fetching user api_key :' . $this->db->error_msg);
159
            }
160
161
            if ($login && $stored_key != $api_key) {        // This should not happen since we did a search on api_key
162
                $userClass::setCacheIdentifier($api_key);
163
                return false;
164
            }
165
166
            $genericmessageerroruser = 'Error user not valid (not found with api key or bad status or bad validity dates) (conf->entity=' . $conf->entity . ')';
167
168
            if (!$login) {
169
                dol_syslog("functions_isallowed::check_user_api_key Authentication KO for api key: Error when searching login user from api key", LOG_NOTICE);
170
                sleep(1); // Anti brute force protection. Must be same delay when user and password are not valid.
171
                throw new RestException(401, $genericmessageerroruser);
172
            }
173
174
            $fuser = new User($this->db);
175
            $result = $fuser->fetch('', $login, '', 0, (empty($userentity) ? -1 : $conf->entity)); // If user is not entity 0, we search in working entity $conf->entity  (that may have been forced to a different value than user entity)
176
            if ($result <= 0) {
177
                dol_syslog("functions_isallowed::check_user_api_key Authentication KO for '" . $login . "': Failed to fetch on entity", LOG_NOTICE);
178
                sleep(1); // Anti brute force protection. Must be same delay when user and password are not valid.
179
                throw new RestException(401, $genericmessageerroruser);
180
            }
181
182
            // Check if user status is enabled
183
            if ($fuser->statut != $fuser::STATUS_ENABLED) {
184
                // Status is disabled
185
                dol_syslog("functions_isallowed::check_user_api_key Authentication KO for '" . $login . "': The user has been disabled", LOG_NOTICE);
186
                sleep(1); // Anti brute force protection. Must be same delay when user and password are not valid.
187
                throw new RestException(401, $genericmessageerroruser);
188
            }
189
190
            // Check if session was unvalidated by a password change
191
            if (($fuser->flagdelsessionsbefore && !empty($_SESSION["dol_logindate"]) && $fuser->flagdelsessionsbefore > $_SESSION["dol_logindate"])) {
192
                // Session is no more valid
193
                dol_syslog("functions_isallowed::check_user_api_key Authentication KO for '" . $login . "': The user has a date for session invalidation = " . $fuser->flagdelsessionsbefore . " and a session date = " . $_SESSION["dol_logindate"] . ". We must invalidate its sessions.");
194
                sleep(1); // Anti brute force protection. Must be same delay when user and password are not valid.
195
                throw new RestException(401, $genericmessageerroruser);
196
            }
197
198
            // Check date validity
199
            if ($fuser->isNotIntoValidityDateRange()) {
200
                // User validity dates are no more valid
201
                dol_syslog("functions_isallowed::check_user_api_key Authentication KO for '" . $login . "': The user login has a validity between [" . $fuser->datestartvalidity . " and " . $fuser->dateendvalidity . "], current date is " . dol_now());
202
                sleep(1); // Anti brute force protection. Must be same delay when user and password are not valid.
203
                throw new RestException(401, $genericmessageerroruser);
204
            }
205
206
            // User seems valid
207
            $fuser->getrights();
208
209
            // Set the property $user to the $user of API
210
            static::$user = $fuser;
211
212
            // Set also the global variable $user to the $user of API
213
            $user = $fuser;
214
215
            if ($fuser->socid) {
216
                static::$role = 'external';
217
            }
218
219
            if ($fuser->admin) {
220
                static::$role = 'admin';
221
            }
222
        } else {
223
            throw new RestException(401, "Failed to login to API. No parameter 'HTTP_DOLAPIKEY' on HTTP header (and no parameter DOLAPIKEY in URL).");
224
        }
225
226
        $userClass::setCacheIdentifier(static::$role);
227
        Resources::$accessControlFunction = 'DolibarrApiAccess::verifyAccess';
0 ignored issues
show
Documentation Bug introduced by
It seems like 'DolibarrApiAccess::verifyAccess' of type string is incompatible with the declared type callable|null of property $accessControlFunction.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
228
        $requirefortest = static::$requires;
229
        if (!is_array($requirefortest)) {
230
            $requirefortest = explode(',', $requirefortest);
231
        }
232
        return in_array(static::$role, (array) $requirefortest) || static::$role == 'admin';
233
    }
234
235
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName
236
    /**
237
     * @return string string to be used with WWW-Authenticate header
238
     */
239
    public function __getWWWAuthenticateString()
240
    {
241
		// phpcs:enable
242
        return '';
243
    }
244
245
    /**
246
     * Verify access
247
     *
248
     * @param   array $m Properties of method
249
     *
250
     * @access private
251
     * @return bool
252
     */
253
    public static function verifyAccess(array $m)
254
    {
255
        $requires = isset($m['class']['DolibarrApiAccess']['properties']['requires'])
256
                ? $m['class']['DolibarrApiAccess']['properties']['requires']
257
                : false;
258
259
260
        return $requires
261
            ? static::$role == 'admin' || in_array(static::$role, (array) $requires)
262
            : true;
263
    }
264
}
265