diff --git a/doc/ChangeLog b/doc/ChangeLog
index e725e478af..656bd9af29 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -514,6 +514,7 @@ version 28-git (????-??-??):
specific sms-like section configuration variable names [chilan]
- improvement: send document confirm notifications if document is created with 'closed'
status [chilan]
+ - enhancement: added support for user API keys [interduo]
version 27.0 (2021-08-20):
diff --git a/doc/lms.mysql b/doc/lms.mysql
index eb779b4b8f..28f3c52433 100644
--- a/doc/lms.mysql
+++ b/doc/lms.mysql
@@ -225,6 +225,8 @@ CREATE TABLE users (
persistentsettings mediumtext NOT NULL DEFAULT '',
twofactorauth smallint NOT NULL DEFAULT 0,
twofactorauthsecretkey varchar(255) DEFAULT NULL,
+ api smallint DEFAULT 0 NOT NULL,
+ apikey text DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY login (login)
) ENGINE=InnoDB;
@@ -4332,4 +4334,4 @@ INSERT INTO netdevicemodels (name, alternative_name, netdeviceproducerid) VALUES
('XR7', 'XR7 MINI PCI PCBA', 2),
('XR9', 'MINI PCI 600MW 900MHZ', 2);
-INSERT INTO dbinfo (keytype, keyvalue) VALUES ('dbversion', '2023053000');
+INSERT INTO dbinfo (keytype, keyvalue) VALUES ('dbversion', '2023060100');
diff --git a/doc/lms.pgsql b/doc/lms.pgsql
index e18aebc7c7..45c3621ff3 100644
--- a/doc/lms.pgsql
+++ b/doc/lms.pgsql
@@ -38,6 +38,8 @@ CREATE TABLE users (
persistentsettings text NOT NULL DEFAULT '',
twofactorauth smallint NOT NULL DEFAULT 0,
twofactorauthsecretkey varchar(255) DEFAULT NULL,
+ api smallint DEFAULT 0 NOT NULL,
+ apikey text DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE (login)
);
@@ -4377,6 +4379,6 @@ INSERT INTO netdevicemodels (name, alternative_name, netdeviceproducerid) VALUES
('XR7', 'XR7 MINI PCI PCBA', 2),
('XR9', 'MINI PCI 600MW 900MHZ', 2);
-INSERT INTO dbinfo (keytype, keyvalue) VALUES ('dbversion', '2023053000');
+INSERT INTO dbinfo (keytype, keyvalue) VALUES ('dbversion', '2023060100');
COMMIT;
diff --git a/lib/LMS.class.php b/lib/LMS.class.php
index d9977ddd22..fcd1e8b264 100644
--- a/lib/LMS.class.php
+++ b/lib/LMS.class.php
@@ -506,6 +506,12 @@ public function isUserNetworkPasswordSet($id)
return $manager->isUserNetworkPasswordSet($id);
}
+ public function hasUserApiKeySet($id)
+ {
+ $manager = $this->getUserManager();
+ return $manager->hasUserApiKeySet($id);
+ }
+
/*
* Customers functions
*/
diff --git a/lib/LMSDB_common.class.php b/lib/LMSDB_common.class.php
index 9f9728f876..c0da8c52da 100644
--- a/lib/LMSDB_common.class.php
+++ b/lib/LMSDB_common.class.php
@@ -25,7 +25,7 @@
*/
// here should be always the newest version of database!
-define('DBVERSION', '2023053000');
+define('DBVERSION', '2023060100');
/**
*
diff --git a/lib/LMSManagers/LMSUserManager.php b/lib/LMSManagers/LMSUserManager.php
index e8ed46d070..ed21b81a8e 100644
--- a/lib/LMSManagers/LMSUserManager.php
+++ b/lib/LMSManagers/LMSUserManager.php
@@ -39,28 +39,41 @@ class LMSUserManager extends LMSManager implements LMSUserManagerInterface
*/
public function setUserPassword($id, $passwd, $net = false)
{
- if ($net) {
- $args = array(
- 'netpasswd' => empty($passwd) ? null : $passwd,
- SYSLOG::RES_USER => $id
- );
- $result = $this->db->Execute(
- 'UPDATE users SET netpasswd = ?
- WHERE id = ?',
- array_values($args)
- );
- } else {
- $args = array(
- 'passwd' => password_hash($passwd, PASSWORD_DEFAULT),
- 'passwdforcechange' => 0,
- SYSLOG::RES_USER => $id
- );
- $result = $this->db->Execute(
- 'UPDATE users SET passwd = ?, passwdlastchange = ?NOW?, passwdforcechange = ?
- WHERE id = ?',
- array_values($args)
- );
- $this->db->Execute('INSERT INTO passwdhistory (userid, hash) VALUES (?, ?)', array($id, password_hash($passwd, PASSWORD_DEFAULT)));
+ switch ($net) {
+ case 2:
+ $args = array(
+ 'passwd' => empty($passwd) ? null : password_hash($passwd, PASSWORD_DEFAULT),
+ SYSLOG::RES_USER => $id
+ );
+ $result = $this->db->Execute(
+ 'UPDATE users SET apikey = ?
+ WHERE id = ?',
+ array_values($args)
+ );
+ break;
+ case 1:
+ $args = array(
+ 'netpasswd' => empty($passwd) ? null : $passwd,
+ SYSLOG::RES_USER => $id
+ );
+ $result = $this->db->Execute(
+ 'UPDATE users SET netpasswd = ?
+ WHERE id = ?',
+ array_values($args)
+ );
+ break;
+ default:
+ $args = array(
+ 'passwd' => password_hash($passwd, PASSWORD_DEFAULT),
+ 'passwdforcechange' => 0,
+ SYSLOG::RES_USER => $id
+ );
+ $result = $this->db->Execute(
+ 'UPDATE users SET passwd = ?, passwdlastchange = ?NOW?, passwdforcechange = ?
+ WHERE id = ?',
+ array_values($args)
+ );
+ $this->db->Execute('INSERT INTO passwdhistory (userid, hash) VALUES (?, ?)', array($id, password_hash($passwd, PASSWORD_DEFAULT)));
}
if ($result && $this->syslog) {
unset($args['passwd']);
@@ -206,7 +219,7 @@ public function getUserList($params = array())
$userlist = $this->db->GetAllByKey(
'SELECT id, login, name, phone, lastlogindate, lastloginip, passwdexpiration, passwdlastchange, access,
- accessfrom, accessto, rname, twofactorauth'
+ accessfrom, accessto, rname, twofactorauth, api'
. ' FROM ' . (isset($superuser) ? 'vallusers' : 'vusers')
. ' WHERE deleted = 0'
. (isset($userAccess) ? ' AND access = 1 AND accessfrom <= ?NOW? AND (accessto >=?NOW? OR accessto = 0)' : '' )
@@ -274,6 +287,8 @@ public function userAdd($user)
'position' => $user['position'],
'ntype' => !empty($user['ntype']) ? $user['ntype'] : null,
'phone' => !empty($user['phone']) ? $user['phone'] : null,
+ 'api' => empty($user['api']) ? 0 : 1,
+ 'apikey' => empty($user['apikey']) ? null : password_hash($user['apikey'], PASSWORD_DEFAULT),
'passwdforcechange' => isset($user['passwdforcechange']) ? 1 : 0,
'passwdexpiration' => !empty($user['passwdexpiration']) ? $user['passwdexpiration'] : 0,
'access' => !empty($user['access']) ? 1 : 0,
@@ -284,9 +299,9 @@ public function userAdd($user)
);
$user_inserted = $this->db->Execute(
'INSERT INTO users (login, firstname, lastname, issuer, email, passwd, netpasswd, rights,
- hosts, trustedhosts, position, ntype, phone,
+ hosts, trustedhosts, position, ntype, phone, api, apikey,
passwdforcechange, passwdexpiration, access, accessfrom, accessto, twofactorauth, twofactorauthsecretkey)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
array_values($args)
);
if ($user_inserted) {
@@ -507,6 +522,7 @@ public function userUpdate($user)
'position' => $user['position'],
'ntype' => !empty($user['ntype']) ? $user['ntype'] : null,
'phone' => !empty($user['phone']) ? $user['phone'] : null,
+ 'api' => empty($user['api']) ? 0 : 1,
'passwdforcechange' => isset($user['passwdforcechange']) ? 1 : 0,
'passwdexpiration' => !empty($user['passwdexpiration']) ? $user['passwdexpiration'] : 0,
'access' => !empty($user['access']) ? 1 : 0,
@@ -518,7 +534,7 @@ public function userUpdate($user)
);
$res = $this->db->Execute(
'UPDATE users SET login = ?, firstname = ?, lastname = ?, issuer = ?, email = ?, rights = ?,
- hosts = ?, trustedhosts = ?, position = ?, ntype = ?, phone = ?, passwdforcechange = ?, passwdexpiration = ?,
+ hosts = ?, trustedhosts = ?, position = ?, ntype = ?, phone = ?, api = ?, passwdforcechange = ?, passwdexpiration = ?,
access = ?, accessfrom = ?, accessto = ?, twofactorauth = ?, twofactorauthsecretkey = ?
WHERE id = ?',
array_values($args)
@@ -663,15 +679,20 @@ public function PasswdExistsInHistory($id, $passwd)
public function checkPassword($password, $net = false)
{
- if ($net) {
+ if ($net == 1) {
return $this->db->GetOne(
'SELECT netpasswd FROM users WHERE id = ?',
array(Auth::GetCurrentUser())
) == $password;
} else {
$dbpasswd = $this->db->GetOne(
- 'SELECT passwd FROM users WHERE id = ?',
- array(Auth::GetCurrentUser())
+ 'SELECT ? FROM users
+ WHERE
+ id = ?',
+ array(
+ ($net == 2) ? 'apikey' : 'passwd',
+ Auth::GetCurrentUser(),
+ )
);
return password_verify($password, $dbpasswd);
}
@@ -687,4 +708,15 @@ public function isUserNetworkPasswordSet($id)
)
) == 1;
}
+
+ public function hasUserApiKeySet($id)
+ {
+ return $this->db->GetOne(
+ 'SELECT 1 FROM users
+ WHERE
+ id = ?
+ AND apikey IS NOT NULL',
+ array($id)
+ ) == 1;
+ }
}
diff --git a/lib/LMSManagers/LMSUserManagerInterface.php b/lib/LMSManagers/LMSUserManagerInterface.php
index 40a090601e..4347f0a49c 100644
--- a/lib/LMSManagers/LMSUserManagerInterface.php
+++ b/lib/LMSManagers/LMSUserManagerInterface.php
@@ -69,4 +69,6 @@ public function PasswdExistsInHistory($id, $passwd);
public function checkPassword($password, $net = false);
public function isUserNetworkPasswordSet($id);
+
+ public function hasUserApiKeySet($id);
}
diff --git a/lib/common.php b/lib/common.php
index a7eb84ba09..4df9932189 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -1100,10 +1100,12 @@ function is_natural($var)
return preg_match('/^[1-9][0-9]*$/', $var);
}
-function check_password_strength($password)
+function check_password_strength($password, $passlength = 8)
{
- return (preg_match('/[a-z]/', $password) && preg_match('/[A-Z]/', $password)
- && preg_match('/[0-9]/', $password) && mb_strlen($password) >= 8);
+ return preg_match('/[a-z]/', $password)
+ && preg_match('/[A-Z]/', $password)
+ && preg_match('/[0-9]/', $password)
+ && mb_strlen($password) > $passlength;
}
function access_denied()
diff --git a/lib/locale/pl_PL/strings.php b/lib/locale/pl_PL/strings.php
index 676c42b3fa..d2fc44475c 100644
--- a/lib/locale/pl_PL/strings.php
+++ b/lib/locale/pl_PL/strings.php
@@ -1887,6 +1887,14 @@
$_LANG['Sale Registry'] = 'Rejestr sprzedaży';
$_LANG['Sale Registry for period $a - $b'] = 'Rejestr sprzedaży za okres $a - $b';
$_LANG['Save'] = 'Zapisz';
+$_LANG['Please input API Key'] = 'Podaj klucz API';
+$_LANG['API access'] = 'Dostęp API';
+$_LANG['API access could not be turned on when API key is empty!'] = 'Dostęp API nie może być włączony gdy klucz API jest pusty!';
+$_LANG['Enter new API key'] = 'Wprowadź nowy klucz API';
+$_LANG['Change API key'] = 'Zmień klucz API';
+$_LANG['Repeat new API key'] = 'Powtórz nowy klucz API';
+$_LANG['API keys do not match!'] = 'Klucze API nie są takie same!';
+$_LANG['API key should contain at least one capital letter, one lower case letter, one digit and should consist of at least 16 and maximum 8192 characters!'] = 'Klucz API powinien zawierać co najmniej jedną dużą, małą literę, cyfrę, co najmniej 16 i maksymalnie 8192 znaki!';
$_LANG['Save & Print'] = 'Zapisz i drukuj';
$_LANG['Scan'] = 'Skanuj';
$_LANG['Search'] = 'Szukaj';
@@ -5673,7 +5681,7 @@
$_LANG['Network password'] = 'Hasło sieciowe';
$_LANG['Network Password'] = 'Hasło sieciowe';
$_LANG['Change your network password'] = 'Zmiana Twojego hasła sieciowego';
-$_LANG['Repeat network password'] = 'Powtórz hasło sieciowe';
+$_LANG['Repeat new network password'] = 'Powtórz nowe hasło sieciowe';
$_LANG['Network password can be used to authenticate users via Radius server.'] = 'Hasło sieciowe może być używane do uwierzytelniania użytkowników w oparciu o serwer Radius.';
$_LANG['Change network password'] = 'Zmiana hasła sieciowego';
diff --git a/lib/upgradedb/mysql.2023060100.php b/lib/upgradedb/mysql.2023060100.php
new file mode 100644
index 0000000000..09949b928e
--- /dev/null
+++ b/lib/upgradedb/mysql.2023060100.php
@@ -0,0 +1,53 @@
+BeginTrans();
+
+$this->Execute("ALTER TABLE users ADD COLUMN api smallint DEFAULT 0 NOT NULL");
+$this->Execute("ALTER TABLE users ADD COLUMN apikey text DEFAULT NULL");
+
+$this->Execute("DROP VIEW vallusers");
+$this->Execute("DROP VIEW vusers");
+
+$this->Execute("
+ CREATE VIEW vusers AS
+ SELECT u.*, CONCAT(u.firstname, ' ', u.lastname) AS name, CONCAT(u.lastname, ' ', u.firstname) AS rname
+ FROM users u
+ LEFT JOIN userdivisions ud ON u.id = ud.userid
+ WHERE lms_current_user() = 0 OR ud.divisionid IN (
+ SELECT ud2.divisionid
+ FROM userdivisions ud2
+ WHERE ud2.userid = lms_current_user()
+ )
+ GROUP BY u.id
+ ");
+
+$this->Execute("
+ CREATE VIEW vallusers AS
+ SELECT *, CONCAT(firstname, ' ', lastname) AS name, CONCAT(lastname, ' ', firstname) AS rname
+ FROM users
+ ");
+
+$this->Execute("UPDATE dbinfo SET keyvalue = ? WHERE keytype = ?", array('2023060100', 'dbversion'));
+
+$this->CommitTrans();
diff --git a/lib/upgradedb/postgres.2023060100.php b/lib/upgradedb/postgres.2023060100.php
new file mode 100644
index 0000000000..89e8e34d68
--- /dev/null
+++ b/lib/upgradedb/postgres.2023060100.php
@@ -0,0 +1,53 @@
+BeginTrans();
+
+$this->Execute("ALTER TABLE users ADD COLUMN api smallint DEFAULT 0 NOT NULL");
+$this->Execute("ALTER TABLE users ADD COLUMN apikey text DEFAULT NULL");
+
+$this->Execute("DROP VIEW vallusers");
+$this->Execute("DROP VIEW vusers");
+
+$this->Execute("
+ CREATE VIEW vusers AS
+ SELECT u.*, (u.firstname || ' ' || u.lastname) AS name, (u.lastname || ' ' || u.firstname) AS rname
+ FROM users u
+ LEFT JOIN userdivisions ud ON u.id = ud.userid
+ WHERE lms_current_user() = 0 OR ud.divisionid IN (
+ SELECT ud2.divisionid
+ FROM userdivisions ud2
+ WHERE ud2.userid = lms_current_user()
+ )
+ GROUP BY u.id
+");
+
+$this->Execute("
+ CREATE VIEW vallusers AS
+ SELECT *, (firstname || ' ' || lastname) AS name, (lastname || ' ' || firstname) AS rname
+ FROM users
+");
+
+$this->Execute("UPDATE dbinfo SET keyvalue = ? WHERE keytype = ?", array('2023060100', 'dbversion'));
+
+$this->CommitTrans();
diff --git a/modules/useradd.php b/modules/useradd.php
index 2ed0501051..87d267cd80 100644
--- a/modules/useradd.php
+++ b/modules/useradd.php
@@ -92,6 +92,14 @@
}
}
+ if (!empty($useradd['apikey']) && !check_password_strength($useradd['apikey'])) {
+ $error['apikey'] = trans('The password should contain at least one capital letter, one lower case letter, one digit and should consist of at least 16 characters!');
+ }
+
+ if (!empty($useradd['api']) && empty($useradd['apikey'])) {
+ $error['apikey'] = $error['api'] = trans('API access could not be turned on when API key is empty!');
+ }
+
if (!empty($useradd['accessfrom'])) {
$accessfrom = date_to_timestamp($useradd['accessfrom']);
if (empty($accessfrom)) {
diff --git a/modules/userpasswd.php b/modules/userpasswd.php
index 5e16815eba..6b8a3a9c13 100644
--- a/modules/userpasswd.php
+++ b/modules/userpasswd.php
@@ -27,32 +27,44 @@
$id = (isset($_GET['id'])) ? $_GET['id'] : Auth::GetCurrentUser();
if ($LMS->UserExists($id)) {
- $net = isset($_GET['net']) ? 1 : 0;
+ $net = empty($_GET['net']) ? 0 : intval($_GET['net']);
if (isset($_POST['password'])) {
$passwd = $_POST['password'];
- if ($net) {
- if ($id == Auth::GetCurrentUser()
- && $LMS->isUserNetworkPasswordSet($id) && !$LMS->checkPassword($passwd['currentpasswd'], true)) {
- $error['currentpasswd'] = trans('Wrong current password!');
- } elseif ($passwd['passwd'] != $passwd['confirm']) {
- $error['passwd'] = trans('Passwords do not match!');
- } elseif ($passwd['passwd'] != '' && !check_password_strength($passwd['passwd'])) {
- $error['passwd'] = trans('The password should contain at least one capital letter, one lower case letter, one digit and should consist of at least 8 characters!');
- }
- } else {
- if ($id == Auth::GetCurrentUser() && !$LMS->checkPassword($passwd['currentpasswd'])) {
- $error['currentpasswd'] = trans('Wrong current password!');
- } elseif ($passwd['passwd'] == '' || $passwd['confirm'] == '') {
- $error['passwd'] = trans('Empty passwords are not allowed!').'
';
- } elseif ($passwd['passwd'] != $passwd['confirm']) {
- $error['passwd'] = trans('Passwords do not match!');
- } elseif (!check_password_strength($passwd['passwd'])) {
- $error['passwd'] = trans('The password should contain at least one capital letter, one lower case letter, one digit and should consist of at least 8 characters!');
- } elseif ($LMS->PasswdExistsInHistory($id, $passwd['passwd'])) {
- $error['passwd'] = trans('You already used this password!');
- }
+ switch ($net) {
+ case 2:
+ if ($id == Auth::GetCurrentUser() && $LMS->hasUserApiKeySet($id)
+ && !$LMS->checkPassword($passwd['currentpasswd'])) {
+ $error['currentpasswd'] = trans('Wrong current password!');
+ } elseif ($passwd['passwd'] != $passwd['confirm']) {
+ $error['passwd'] = $error['confirm'] = trans('API keys do not match!');
+ } elseif ($passwd['passwd'] != '' && !check_password_strength($passwd['passwd'], 16)) {
+ $error['passwd'] = trans('API key should contain at least one capital letter, one lower case letter, one digit and should consist of at least 16 and maximum 8192 characters!');
+ }
+ break;
+ case 1:
+ if ($id == Auth::GetCurrentUser()
+ && $LMS->isUserNetworkPasswordSet($id) && !$LMS->checkPassword($passwd['currentpasswd'], 1)) {
+ $error['currentpasswd'] = trans('Wrong current password!');
+ } elseif ($passwd['passwd'] != $passwd['confirm']) {
+ $error['passwd'] = trans('Passwords do not match!');
+ } elseif ($passwd['passwd'] != '' && !check_password_strength($passwd['passwd'])) {
+ $error['passwd'] = trans('The password should contain at least one capital letter, one lower case letter, one digit and should consist of at least 8 characters!');
+ }
+ break;
+ default:
+ if ($id == Auth::GetCurrentUser() && !$LMS->checkPassword($passwd['currentpasswd'])) {
+ $error['currentpasswd'] = trans('Wrong current password!');
+ } elseif ($passwd['passwd'] == '' || $passwd['confirm'] == '') {
+ $error['passwd'] = trans('Empty passwords are not allowed!') . '
';
+ } elseif ($passwd['passwd'] != $passwd['confirm']) {
+ $error['passwd'] = trans('Passwords do not match!');
+ } elseif (!check_password_strength($passwd['passwd'])) {
+ $error['passwd'] = trans('The password should contain at least one capital letter, one lower case letter, one digit and should consist of at least 8 characters!');
+ } elseif ($LMS->PasswdExistsInHistory($id, $passwd['passwd'])) {
+ $error['passwd'] = trans('You already used this password!');
+ }
}
if (!$error) {
@@ -62,10 +74,13 @@
}
$passwd['id'] = $id;
- if ($net) {
+ if ($net == 1) {
$passwd['netpasswd'] = $LMS->isUserNetworkPasswordSet($id);
+ } elseif ($net == 2) {
+ $passwd['apikey'] = $LMS->hasUserApiKeySet($id);
}
+
$layout['pagetitle'] = trans('Password Change for User $a', $DB->GetOne('SELECT name FROM vusers WHERE id = ?', array($id)));
$SMARTY->assign('error', $error);
diff --git a/templates/default/user/useradd.html b/templates/default/user/useradd.html
index 8463d6d93a..2e02c97575 100644
--- a/templates/default/user/useradd.html
+++ b/templates/default/user/useradd.html
@@ -206,6 +206,20 @@