diff --git a/htdocs/api/admin/token_list.php b/htdocs/api/admin/token_list.php index a9fbe95c692..b16cb2e8311 100644 --- a/htdocs/api/admin/token_list.php +++ b/htdocs/api/admin/token_list.php @@ -183,7 +183,7 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { /* The fast and low memory method to get and count full list converts the sql into a sql count */ $sqlforcount = 'SELECT COUNT(*) as nbtotalofrecords'; $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; - $sqlforcount .= " WHERE entity = ".((int) $conf->entity).")"; + $sqlforcount .= " WHERE entity IN (0, ".((int) $conf->entity).")"; $sqlforcount .= " AND service = 'dolibarr_rest_api'"; $resql = $db->query($sqlforcount); if ($resql) { @@ -200,27 +200,16 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { $db->free($resql); } -$sql = "SELECT oat.rowid, oat.token, oat.entity, oat.state as rights, oat.fk_user, oat.datec as date_creation, oat.tms as date_modification"; -if (isModEnabled('multicompany')) { - $sql .= ", e.label as entity_name"; -} +$sql = "SELECT oat.rowid, oat.tokenstring, oat.entity, oat.state as rights, oat.fk_user, oat.datec as date_creation, oat.tms as date_modification,"; +$sql .= " oat.lastaccess, oat.apicount_total"; $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; -if (isModEnabled('multicompany')) { - $sql .= " JOIN ".$db->prefix()."entity as e ON oat.entity = e.rowid"; -} $sql .= " WHERE service = 'dolibarr_rest_api'"; $sql .= " AND EXISTS(SELECT 'exist' FROM llx_user as u WHERE u.api_key IS NOT NULL AND u.rowid = oat.fk_user)"; -if (!isModEnabled('multicompany') || $conf->entity > 1) { - $sql .= " AND oat.entity = ".((int) $conf->entity); -} if ($search_user) { $sql .= " AND EXISTS (SELECT 'exist' FROM ".MAIN_DB_PREFIX."user u"; $sql .= " WHERE (u.lastname LIKE '%".$db->escape($search_user)."%'"; $sql .= " OR u.firstname LIKE '%".$db->escape($search_user)."%')"; - $sql .= " AND oat.fk_user = u.rowid)"; -} -if ($search_entity) { - $sql .= natural_search('e.label', $search_entity); + $sql .= " AND oat.fk_user = u.rowid))"; } if ($search_datec_start) { $sql .= " AND oat.datec >= '".$db->idate($search_datec_start)."'"; diff --git a/htdocs/api/class/api_access.class.php b/htdocs/api/class/api_access.class.php index f23a5a9b190..a4bc2f7ca02 100644 --- a/htdocs/api/class/api_access.class.php +++ b/htdocs/api/class/api_access.class.php @@ -95,7 +95,6 @@ class DolibarrApiAccess implements iAuthenticate $login = ''; $stored_key = ''; - $use_api = ''; $userClass = Defaults::$userIdentifierClass; @@ -133,24 +132,33 @@ class DolibarrApiAccess implements iAuthenticate if ($api_key) { $userentity = 0; + $token_rowid = 0; - $sql = "SELECT u.login, u.api_key as use_api, u.entity, oat.token as api_key, oat.entity as token_entity"; - $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token AS oat"; - $sql .= " JOIN ".MAIN_DB_PREFIX."user AS u ON u.rowid = oat.fk_user"; - $sql .= " WHERE oat.token = '".$this->db->escape($api_key)."'"; - $sql .= " OR oat.token = '".$this->db->escape(dolEncrypt($api_key, '', '', 'dolibarr'))."'"; - $sql .= " AND oat.service = 'dolibarr_rest_api'"; + if (!getDolGlobalString('API_IN_TOKEN_TABLE')) { + $sql = "SELECT u.login, u.datec, u.api_key as use_api, u.entity, u.api_key as api_key, u.entity as token_entity, 0 as token_rowid"; + $sql .= " u.tms as date_modification, u.entity"; + $sql .= " FROM ".MAIN_DB_PREFIX."user as u"; + $sql .= " WHERE u.api_key = '".$this->db->escape($api_key)."' OR u.api_key = '".$this->db->escape(dolEncrypt($api_key, '', '', 'dolibarr'))."'"; + } else { + $sql = "SELECT u.login, u.datec, u.api_key as use_api, u.entity, oat.tokenstring as api_key, oat.entity as token_entity, rowid as token_rowid"; + $sql .= " FROM ".MAIN_DB_PREFIX."oauth_token AS oat"; + $sql .= " JOIN ".MAIN_DB_PREFIX."user AS u ON u.rowid = oat.fk_user"; + $sql .= " WHERE (oat.tokenstring = '".$this->db->escape($api_key)."'"; + $sql .= " OR oat.tokenstring = '".$this->db->escape(dolEncrypt($api_key, '', '', 'dolibarr'))."')"; + $sql .= " AND oat.service = 'dolibarr_rest_api'"; + } $result = $this->db->query($sql); if ($result) { $nbrows = $this->db->num_rows($result); if ($nbrows == 1) { $obj = $this->db->fetch_object($result); + $login = $obj->login; $stored_key = dolDecrypt($obj->api_key); $userentity = $obj->entity; - $tokenentity = $obj->token_entity; - $use_api = $obj->use_api; + $token_entity = $obj->token_entity; + $token_rowid = $obj->token_rowid; 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 $conf->entity = ($obj->entity ? $obj->entity : 1); @@ -199,7 +207,7 @@ class DolibarrApiAccess implements iAuthenticate $mysoc = $fmysoc; // Reload langs - $langcode = (!getDolGlobalString('MAIN_LANG_DEFAULT') ? 'auto' : $conf->global->MAIN_LANG_DEFAULT); + $langcode = getDolGlobalString('MAIN_LANG_DEFAULT', 'auto'); if (!empty($user->conf->MAIN_LANG_DEFAULT)) { $langcode = $user->conf->MAIN_LANG_DEFAULT; } @@ -209,8 +217,9 @@ class DolibarrApiAccess implements iAuthenticate $langs->loadLangs(array('main')); } } - if ($conf->entity != ($tokenentity ? $tokenentity : 1)) { - throw new RestException(401, 'Forbidden'); + + if ($conf->entity != ($token_entity ? $token_entity : 1)) { + throw new RestException(401, "functions_isallowed::check_user_api_key Authentication KO for '".$login."': Token not valid (may be a typo or a wrong entity)"); } } elseif ($nbrows > 1) { throw new RestException(503, 'Error when fetching user api_key : More than 1 user with this apikey'); @@ -232,12 +241,6 @@ class DolibarrApiAccess implements iAuthenticate throw new RestException(401, $genericmessageerroruser); } - if (!$use_api) { - dol_syslog("functions_isallowed::check_user_api_key Authentication KO for api key: API not enabled for user", LOG_NOTICE); - sleep(1); // Anti brute force protection. Must be same delay when user and password are not valid. - throw new RestException(401, $genericmessageerroruser); - } - $fuser = new User($this->db); $result = $fuser->fetch(0, $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) if ($result <= 0) { @@ -270,16 +273,31 @@ class DolibarrApiAccess implements iAuthenticate throw new RestException(401, $genericmessageerroruser); } - // TODO // Increase counter of API access if (getDolGlobalString('API_COUNTER_ENABLED')) { - include DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; - dolibarr_set_const($this->db, 'API_COUNTER_COUNT', getDolGlobalInt('API_COUNTER_COUNT') + 1); - //var_dump('eeee');exit; + if (!getDolGlobalString('API_IN_TOKEN_TABLE')) { + // Update the counter into table llx_const + include DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; + dolibarr_set_const($this->db, 'API_COUNTER_COUNT', getDolGlobalInt('API_COUNTER_COUNT') + 1); + //var_dump('eeee');exit; + } else { + // Update the counter into table llx_oauth_token + $tmpnow = dol_getdate(dol_now('gmt'), true, 'gmt'); + + $sqlforcounter = "UPDATE ".$db->prefix()."oauth_token SET "; + $sqlforcounter .= " apicount_total = apicount_total + 1,"; + $sqlforcounter .= " apicount_month = apicount_month + 1,"; + // if last access was done during previous month, we save pageview_month into pageviews_previous_month + $sqlforcounter .= " pageviews_previous_month = ".$db->ifsql("lastaccess < '".$db->idate(dol_mktime(0, 0, 0, $tmpnow['mon'], 1, $tmpnow['year'], 'gmt', 0), 'gmt')."'", 'apicount_month', 'apicount_previous_month').","; + $sqlforcounter .= " lastaccess = '".$db->idate(dol_now('gmt'), 'gmt')."'"; + $sqlforcounter .= " WHERE rowid = ".((int) $token_rowid); + + $this->db->query($sqlforcounter); + } } // User seems valid - $fuser->loadRights('', 0, $stored_key); + $fuser->loadRights(); // Set the property $user to the $user of API static::$user = $fuser; diff --git a/htdocs/core/lib/api.lib.php b/htdocs/core/lib/api.lib.php index fcc16f0a29a..0f4b47784f3 100644 --- a/htdocs/core/lib/api.lib.php +++ b/htdocs/core/lib/api.lib.php @@ -1,7 +1,7 @@ * Copyright (C) 2005-2012 Regis Houssin - * Copyright (C) 2024 MDW + * Copyright (C) 2024 MDW * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ */ /** - * \file htdocs/core/lib/propal.lib.php + * \file htdocs/core/lib/api.lib.php * \brief Ensemble de functions de base pour le module propal * \ingroup propal */ @@ -31,7 +31,7 @@ */ function api_admin_prepare_head() { - global $langs, $conf, $user, $db; + global $langs; $h = 0; $head = array(); @@ -41,10 +41,12 @@ function api_admin_prepare_head() $head[$h][2] = 'parameter'; $h++; - $head[$h][0] = DOL_URL_ROOT.'/api/admin/token_list.php'; - $head[$h][1] = $langs->trans("ListOfTokens"); - $head[$h][2] = 'token_list'; - $h++; + if (getDolGlobalString('API_IN_TOKEN_TABLE')) { + $head[$h][0] = DOL_URL_ROOT.'/api/admin/token_list.php'; + $head[$h][1] = $langs->trans("ListOfTokens"); + $head[$h][2] = 'token_list'; + $h++; + } return $head; } diff --git a/htdocs/core/lib/usergroups.lib.php b/htdocs/core/lib/usergroups.lib.php index 081dc7d85a1..0a164de2404 100644 --- a/htdocs/core/lib/usergroups.lib.php +++ b/htdocs/core/lib/usergroups.lib.php @@ -159,12 +159,14 @@ function user_prepare_head(User $object) $h++; } + /* if (isModEnabled('api') && !empty($object->api_key) && ($user->admin || $user->id == $object->id)) { $head[$h][0] = DOL_URL_ROOT.'/user/api_token/list.php?id='.$object->id; $head[$h][1] = $langs->trans("ApiTokens"); $head[$h][2] = 'apitoken'; $h++; } + */ // Such info on users is visible only by internal user if (empty($user->socid)) { diff --git a/htdocs/core/tpl/apitoken_list.tpl.php b/htdocs/core/tpl/apitoken_list.tpl.php index d0b7bf5b7d0..63ba78a2fc0 100644 --- a/htdocs/core/tpl/apitoken_list.tpl.php +++ b/htdocs/core/tpl/apitoken_list.tpl.php @@ -80,13 +80,6 @@ if (!empty($arrayfields['u.login']['checked'])) { print ''; } -// Entity -if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { - print ''; - print ''; - print ''; -} - // Number of perms // We don't search out number of perms because it is a string field, // and we don't want to count into it with sql query @@ -137,10 +130,7 @@ if (!empty($arrayfields['u.login']['checked'])) { // @phan-suppress-next-line PhanTypeInvalidDimOffset print_liste_field_titre($arrayfields['u.login']['label'], $_SERVER["PHP_SELF"], 'u.login', '', $param, '', $sortfield, $sortorder); } -if (!empty($arrayfields['e.label']['checked']) && isModEnabled('multicompany')) { - print_liste_field_titre($arrayfields['e.label']['label'], $_SERVER["PHP_SELF"], 'e.label', '', $param, '', $sortfield, $sortorder); -} -print ''.$langs->trans("NumberOfPermissions").''; +print ''.$langs->trans("LastAccess").''; if (!empty($arrayfields['oat.datec']['checked'])) { print_liste_field_titre($arrayfields['oat.datec']['label'], $_SERVER["PHP_SELF"], 'oat.datec', '', $param, '', $sortfield, $sortorder, 'center '); } @@ -161,20 +151,26 @@ if ($num > 0) { while ($i < $imaxinloop) { // Compute number of perms $obj = $db->fetch_object($resql); + $useridparam = isset($obj->fk_user) ? $obj->fk_user : $object->id; - $numperms = 0; + if (isset($obj->fk_user)) { $currentuser = new User($db); $currentuser->fetch($obj->fk_user); } else { $currentuser = $object; } + + /* + $numperms = 0; if (!empty($obj->rights)) { $numperms = count(explode(",", $obj->rights)); } elseif (!(strlen($obj->rights) == 1 && substr($obj->rights, 0, 1) == 0)) { $currentuser->loadRights(); $numperms = $currentuser->nb_rights; } + */ + print ''; // Action column if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) { @@ -190,7 +186,7 @@ if ($num > 0) { } print ''; print ''; - print dolDecrypt($obj->token); + print dolDecrypt($obj->tokenstring); print ''; print ''; if (!empty($arrayfields['u.login']['checked'])) { @@ -200,16 +196,8 @@ if ($num > 0) { print ''; print ''; } - if (isModEnabled('multicompany')) { - print ''; - print ''; - print ''; - print $obj->entity_name; - print ' '; - print ''; - } print ''; - print $numperms; + print dol_print_date($db->jdate($obj->lastaccess)); print ''; print ''; print dol_print_date($db->jdate($obj->date_creation), 'dayhour'); diff --git a/htdocs/user/api_token/list.php b/htdocs/user/api_token/list.php index b9f15bc51da..442b3684096 100644 --- a/htdocs/user/api_token/list.php +++ b/htdocs/user/api_token/list.php @@ -21,14 +21,12 @@ */ /** - * \file htdocs/user/api_toke/list.php + * \file htdocs/user/api_token/list.php * \brief Page to show user list of token */ // Load Dolibarr environment require '../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/core/lib/usergroups.lib.php'; - /** * @var Conf $conf * @var DoliDB $db @@ -36,6 +34,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/usergroups.lib.php'; * @var Translate $langs * @var User $user */ +require_once DOL_DOCUMENT_ROOT.'/core/lib/usergroups.lib.php'; // Load translation files required by page $langs->loadLangs(array('admin', 'users')); @@ -110,7 +109,6 @@ if (!$sortorder) { } $arrayfields = array( - 'e.label' => array('label' => "Entity", 'checked' => '1'), 'oat.datec' => array('label' => "DateCreation", 'checked' => '1'), 'oat.tms' => array('label' => "DateModification", 'checked' => '1'), ); @@ -126,6 +124,7 @@ if (empty($object->api_key)) { $form = new Form($db); + /* * Actions */ @@ -206,7 +205,7 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { $sqlforcount = 'SELECT COUNT(*) as nbtotalofrecords'; $sqlforcount .= " FROM ".MAIN_DB_PREFIX."oauth_token as oat"; $sqlforcount .= " WHERE entity IN (".$conf->entity.")"; - $sqlforcount .= " AND fk_user = ".$id; + $sqlforcount .= " AND fk_user = ".((int) $id); $sqlforcount .= " AND service = 'dolibarr_rest_api'"; $resql = $db->query($sqlforcount); if ($resql) { @@ -313,38 +312,6 @@ $morehtmlref .= dolButtonToOpenUrlInDialogPopup('publicvirtualcard', $langs->tra llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-user page-card_param_ihm'); -print dol_get_fiche_head($head, 'apitoken', $langs->trans("User"), -1, 'user'); - -dol_banner_tab($object, 'id', $linkback, $user->hasRight("user", "user", "read") || $user->admin, 'rowid', 'ref', $morehtmlref); - -print '
'; -print '
'; -print ''; - -// Login -print ''; -if (!empty($object->ldap_sid) && $object->status == 0) { - print ''; -} else { - print ''; -} -print ''."\n"; -print '
'.$langs->trans("Login").''; - print $langs->trans("LoginAccountDisableInDolibarr"); - print ''; - $addadmin = ''; - if (property_exists($object, 'admin')) { - if (isModEnabled('multicompany') && !empty($object->admin) && empty($object->entity)) { - $addadmin .= img_picto($langs->trans("SuperAdministratorDesc"), "redstar", 'class="paddingleft valignmiddle"'); - } elseif (!empty($object->admin)) { - $addadmin .= img_picto($langs->trans("AdministratorDesc"), "star", 'class="paddingleft valignmiddle"'); - } - } - print showValueWithClipboardCPButton($object->login).$addadmin; - print '
'; -print '
'; - -print dol_get_fiche_end(); print ''."\n"; diff --git a/htdocs/user/card.php b/htdocs/user/card.php index 0baba02c39e..01598edab5b 100644 --- a/htdocs/user/card.php +++ b/htdocs/user/card.php @@ -1247,23 +1247,24 @@ if ($action == 'create' || $action == 'adduserldap') { print $valuetoshow; print ''; - if (isModEnabled('api')) { - // API key - //$generated_password = getRandomPassword(false); - print ''.$langs->trans("ApiKey").''; - print ''; - print ''; - if (!empty($conf->use_javascript_ajax)) { - print img_picto($langs->transnoentities('Generate'), 'refresh', 'id="generate_api_key" class="linkobject paddingleft"'); + if (!getDolGlobalString('API_IN_TOKEN_TABLE')) { + if (isModEnabled('api')) { + // API key + //$generated_password = getRandomPassword(false); + print ''.$langs->trans("ApiKey").''; + print ''; + print ''; + if (!empty($conf->use_javascript_ajax)) { + print img_picto($langs->transnoentities('Generate'), 'refresh', 'id="generate_api_key" class="linkobject paddingleft"'); + } + print ''; + } else { + // PARTIAL WORKAROUND + $generated_fake_api_key = getRandomPassword(false); + print ''; } - print ''; - } else { - // PARTIAL WORKAROUND - $generated_fake_api_key = getRandomPassword(false); - print ''; } - print '
'; @@ -1973,6 +1974,9 @@ if ($action == 'create' || $action == 'adduserldap') { // Credentials section + // MAIN_SECURITY_ALLOW_TOTP=1 to enabled 2FA + // API_IN_TOKEN_TABLE=1 to enable use of oaut_token table for API tokens + print '
'; print ''."\n"; print '
'; @@ -1984,12 +1988,6 @@ if ($action == 'create' || $action == 'adduserldap') { print '
'; print img_picto('', 'security', 'class="paddingleft pictofixedwidth"').$langs->trans("SecurityForConnection"); print '
'; - //print ''; - //print '
'; print ''; @@ -2046,6 +2044,19 @@ if ($action == 'create' || $action == 'adduserldap') { print ''."\n"; } + // Token for 2FA + if (getDolGlobalString('MAIN_SECURITY_ALLOW_TOTP') && $permissiontoeditpasswordandsee) { + print ''; + print ''; + } + // Token for OAuth $tmparrayofauthmode = explode(',', $dolibarr_main_authentication); foreach ($tmparrayofauthmode as $tmpauthmode) { @@ -2082,16 +2093,32 @@ if ($action == 'create' || $action == 'adduserldap') { if (isModEnabled('api') && ($user->id == $id || $user->admin || $user->hasRight("api", "apikey", "generate"))) { print ''; print ''; } + // Show private information about login if ((getDolGlobalInt('MAIN_ENABLE_LOGINS_PRIVACY') == 0) || (getDolGlobalInt('MAIN_ENABLE_LOGINS_PRIVACY') == 1 && $object->id == $user->id)) { print ''; @@ -2686,16 +2713,18 @@ if ($action == 'create' || $action == 'adduserldap') { print "\n"; // API key - if (isModEnabled('api')) { - print ''; - print ''; + print ''; } - print ''; } // OpenID url diff --git a/htdocs/user/credentials.php b/htdocs/user/credentials.php index 72ba6403ac6..0fc99e69291 100644 --- a/htdocs/user/credentials.php +++ b/htdocs/user/credentials.php @@ -341,23 +341,10 @@ if (isModEnabled('api')) { -// Section API - - - - - - - - - - - - - - +// Section Other +// ...
'; - if (getDolGlobalString('MAIN_SECURITY_ALLOW_TOTP') && $permissiontoeditpasswordandsee) { - $s = ''; - print dolButtonToOpenUrlInDialogPopup('openpopuptoaddcredential', $langs->transnoentitiesnoconv("AddCredential"), $s, '/user/credentials.php?userid='.$object->id.'&token='.newToken()); - } print ''; print '
'.$langs->trans("2FA").''; + print '
'; + print '999'; + print '
'; + $s = ''; + print dolButtonToOpenUrlInDialogPopup('openpopuptoaddcredential', $langs->transnoentitiesnoconv("Add2FA"), $s, '/user/credentials.php?userid='.$object->id.'&token='.newToken()); + print ''; + print '
'.$langs->trans("ApiKey").''; - if (!empty($object->api_key)) { - print ''; - print showValueWithClipboardCPButton($object->api_key, 1, $langs->transnoentities("Hidden")); // TODO Add an option to also reveal the hash, not only copy paste - print ''; - } - if (getDolGlobalString('API_ENABLE_COUNT_CALLS') || !empty($dolibarr_api_count_always_enabled)) { - print '   '.getDolUserInt('API_COUNT_CALL').''; + if (getDolGlobalString('API_IN_TOKEN_TABLE')) { + print '
'; + print '999'; + /*print ''; + print $langs->trans("APIKeys"); + print '';*/ + print '
'; + $s = ''; + print dolButtonToOpenUrlInDialogPopup('openpopuptoaddapitoken', $langs->transnoentitiesnoconv("APIKeys"), $s, '/user/api_token/list.php?id='.$object->id.'&token='.newToken()); + print '
'; + print '
'; + } else { + if (!empty($object->api_key)) { + print ''; + print showValueWithClipboardCPButton($object->api_key, 1, $langs->transnoentities("Hidden")); // TODO Add an option to also reveal the hash, not only copy paste + print ''; + } + if (getDolGlobalString('API_ENABLE_COUNT_CALLS') || !empty($dolibarr_api_count_always_enabled)) { + print '   '; + print getDolUserInt('API_COUNT_CALL'); + print ''; + } } print '
'.$langs->trans("LastConnexion").'
'.$langs->trans("ApiKey").''; - if ($permissiontoeditpasswordandsee || $user->hasRight("api", "apikey", "generate")) { - print ''; - if (!empty($conf->use_javascript_ajax)) { - print img_picto($langs->transnoentities('Generate'), 'refresh', 'id="generate_api_key" class="linkobject paddingleft"'); + if (!getDolGlobalString('API_IN_TOKEN_TABLE')) { + if (isModEnabled('api')) { + print '
'.$langs->trans("ApiKey").''; + if ($permissiontoeditpasswordandsee || $user->hasRight("api", "apikey", "generate")) { + print ''; + if (!empty($conf->use_javascript_ajax)) { + print img_picto($langs->transnoentities('Generate'), 'refresh', 'id="generate_api_key" class="linkobject paddingleft"'); + } } + print '