Compare commits

...

4 commits

Author SHA1 Message Date
Benjamin Renard
214545cfc2
DbObject: Fix return type of get & list methods 2024-09-19 10:38:47 +02:00
Benjamin Renard
18adcc4bb0
Auth\Db: replace add_user/edit_user CLI commands by user CLI command with subcommands 2024-09-19 09:57:42 +02:00
Benjamin Renard
7b2ce7ab73
Auth\Db: add edit_user CLI command 2024-09-15 10:01:13 +02:00
Benjamin Renard
bfac58c43a
Auth\Db: implement update_user() 2024-09-15 10:01:12 +02:00
6 changed files with 304 additions and 50 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"POT-Creation-Date: 2024-02-21 09:12+0000\n" "POT-Creation-Date: 2024-09-17 07:17+0000\n"
"PO-Revision-Date: \n" "PO-Revision-Date: \n"
"Last-Translator: Benjamin Renard <brenard@easter-eggs.com>\n" "Last-Translator: Benjamin Renard <brenard@easter-eggs.com>\n"
"Language-Team: \n" "Language-Team: \n"
@ -34,6 +34,33 @@ msgstr "Inconnu"
msgid "Unable to connect to the database." msgid "Unable to connect to the database."
msgstr "Impossible de se connecter à la base de données." msgstr "Impossible de se connecter à la base de données."
#: Auth/Db.php:315
msgid "Failed to list users from database."
msgstr ""
"Une erreur est survenue en listant les utilisateurs dans la base de données."
#: Auth/Db.php:318
msgid "No user found."
msgstr "Aucun utilisateur."
#: Auth/Db.php:323
msgid "Username"
msgstr "Nom d'utilisateur"
#: Auth/Db.php:334
#, php-format
msgid "%d users(s)"
msgstr "%d utilisateur(s)"
#: Auth/Db.php:361 Auth/Db.php:392
msgid "Username is missing"
msgstr "Nom d'utilisateur manquant"
#: Auth/Db.php:403
#, php-format
msgid "Unknown subcommand %s"
msgstr "Sous-commande %s inconnue"
#: Auth/Form.php:51 #: Auth/Form.php:51
msgid "Invalid username or password." msgid "Invalid username or password."
msgstr "Nom d'utilisateur ou not de passe invalid." msgstr "Nom d'utilisateur ou not de passe invalid."
@ -54,7 +81,7 @@ msgstr "Accès interdit"
msgid "You must login to access this page." msgid "You must login to access this page."
msgstr "Vous devez vous connecter pour accéder à cette page." msgstr "Vous devez vous connecter pour accéder à cette page."
#: Auth/Casuser.php:88 Auth/Casuser.php:112 #: Auth/Casuser.php:88 Auth/Casuser.php:121
msgid "Configuration error in CAS auth backend." msgid "Configuration error in CAS auth backend."
msgstr "Erreur de configuration dans le backend d'authentification CAS." msgstr "Erreur de configuration dans le backend d'authentification CAS."
@ -96,7 +123,7 @@ msgstr ""
"Une erreur inconnue est survenue. Si le problème persiste, merci de prendre " "Une erreur inconnue est survenue. Si le problème persiste, merci de prendre "
"contact avec le support." "contact avec le support."
#: Url.php:263 #: Url.php:271
msgid "" msgid ""
"Unable to determine the requested page. If the problem persists, please " "Unable to determine the requested page. If the problem persists, please "
"contact support." "contact support."
@ -104,7 +131,7 @@ msgstr ""
"Impossible de déterminer la page demandée. Si le problème persiste, merci de " "Impossible de déterminer la page demandée. Si le problème persiste, merci de "
"prendre contact avec le support." "prendre contact avec le support."
#: Url.php:411 #: Url.php:419
msgid "" msgid ""
"Unable to determine the requested page (loop detected). If the problem " "Unable to determine the requested page (loop detected). If the problem "
"persists, please contact support." "persists, please contact support."
@ -112,15 +139,15 @@ msgstr ""
"Impossible de déterminer la page demandée (boucle détectée). Si le problème " "Impossible de déterminer la page demandée (boucle détectée). Si le problème "
"persiste, merci de prendre contact avec le support." "persiste, merci de prendre contact avec le support."
#: Url.php:440 #: Url.php:448
msgid "This request cannot be processed." msgid "This request cannot be processed."
msgstr "Cette requête ne peut être traitée." msgstr "Cette requête ne peut être traitée."
#: Url.php:450 #: Url.php:458
msgid "Authentication required but fail to authenticate you." msgid "Authentication required but fail to authenticate you."
msgstr "Authentication requise mais impossible pour vous authentifier." msgstr "Authentication requise mais impossible pour vous authentifier."
#: Url.php:469 #: Url.php:479
msgid "This request could not be processed correctly." msgid "This request could not be processed correctly."
msgstr "Cette requête n'a put être traitée correctement." msgstr "Cette requête n'a put être traitée correctement."
@ -626,10 +653,6 @@ msgstr ""
"\n" "\n"
"%s: %s" "%s: %s"
#: templates/login.tpl:19 templates/login.tpl:20
msgid "Username"
msgstr "Nom d'utilisateur"
#: templates/login.tpl:24 templates/login.tpl:25 #: templates/login.tpl:24 templates/login.tpl:25
msgid "Password" msgid "Password"
msgstr "Not de passe" msgstr "Not de passe"

View file

@ -1,7 +1,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"POT-Creation-Date: 2024-02-21 09:12+0000\n" "POT-Creation-Date: 2024-09-17 07:17+0000\n"
"PO-Revision-Date: 2024-02-21 09:12+0000\n" "PO-Revision-Date: 2024-09-17 07:17+0000\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -26,6 +26,32 @@ msgstr ""
msgid "Unable to connect to the database." msgid "Unable to connect to the database."
msgstr "" msgstr ""
#: Auth/Db.php:315
msgid "Failed to list users from database."
msgstr ""
#: Auth/Db.php:318
msgid "No user found."
msgstr ""
#: Auth/Db.php:323
msgid "Username"
msgstr ""
#: Auth/Db.php:334
#, php-format
msgid "%d users(s)"
msgstr ""
#: Auth/Db.php:361 Auth/Db.php:392
msgid "Username is missing"
msgstr ""
#: Auth/Db.php:403
#, php-format
msgid "Unknown subcommand %s"
msgstr ""
#: Auth/Form.php:51 #: Auth/Form.php:51
msgid "Invalid username or password." msgid "Invalid username or password."
msgstr "" msgstr ""
@ -46,7 +72,7 @@ msgstr ""
msgid "You must login to access this page." msgid "You must login to access this page."
msgstr "" msgstr ""
#: Auth/Casuser.php:88 Auth/Casuser.php:112 #: Auth/Casuser.php:88 Auth/Casuser.php:121
msgid "Configuration error in CAS auth backend." msgid "Configuration error in CAS auth backend."
msgstr "" msgstr ""
@ -84,27 +110,27 @@ msgstr ""
msgid "An unknown error occurred. If problem persist, please contact support." msgid "An unknown error occurred. If problem persist, please contact support."
msgstr "" msgstr ""
#: Url.php:263 #: Url.php:271
msgid "" msgid ""
"Unable to determine the requested page. If the problem persists, please " "Unable to determine the requested page. If the problem persists, please "
"contact support." "contact support."
msgstr "" msgstr ""
#: Url.php:411 #: Url.php:419
msgid "" msgid ""
"Unable to determine the requested page (loop detected). If the problem " "Unable to determine the requested page (loop detected). If the problem "
"persists, please contact support." "persists, please contact support."
msgstr "" msgstr ""
#: Url.php:440 #: Url.php:448
msgid "This request cannot be processed." msgid "This request cannot be processed."
msgstr "" msgstr ""
#: Url.php:450 #: Url.php:458
msgid "Authentication required but fail to authenticate you." msgid "Authentication required but fail to authenticate you."
msgstr "" msgstr ""
#: Url.php:469 #: Url.php:479
msgid "This request could not be processed correctly." msgid "This request could not be processed correctly."
msgstr "" msgstr ""
@ -543,10 +569,6 @@ msgid ""
"%s: %s" "%s: %s"
msgstr "" msgstr ""
#: templates/login.tpl:19 templates/login.tpl:20
msgid "Username"
msgstr ""
#: templates/login.tpl:24 templates/login.tpl:25 #: templates/login.tpl:24 templates/login.tpl:25
msgid "Password" msgid "Password"
msgstr "" msgstr ""

View file

@ -4,10 +4,15 @@ namespace EesyPHP\Auth;
use EesyPHP\App; use EesyPHP\App;
use EesyPHP\Cli; use EesyPHP\Cli;
use EesyPHP\Hook;
use EesyPHP\I18n;
use EesyPHP\Log; use EesyPHP\Log;
use Console_Table;
use Exception; use Exception;
use function EesyPHP\vardump;
class Db extends Backend { class Db extends Backend {
/** /**
@ -70,8 +75,20 @@ class Db extends Backend {
self :: $password_field = App::get('auth.db.password_field', null, 'string'); self :: $password_field = App::get('auth.db.password_field', null, 'string');
self :: $exposed_fields = App::get('auth.db.exposed_fields', null, 'array'); self :: $exposed_fields = App::get('auth.db.exposed_fields', null, 'array');
if (App :: get('cli.enabled')) if (App :: get('cli.enabled')) {
Cli :: add_command('add_user', ['\\EesyPHP\\Auth\\Db', 'cli_add_user'], 'Add user'); Cli :: add_command(
'user',
['\\EesyPHP\\Auth\\Db', 'cli_user'],
'Manage users',
"[list|add|edit|delete]",
[
" list List existing users",
" add Add a user",
" edit [username] Edit a user",
" delete [username] Delete a user",
]
);
}
return true; return true;
} }
@ -165,8 +182,10 @@ class Db extends Backend {
if (isset($info[$field]) && $info[$field]) if (isset($info[$field]) && $info[$field])
$values[$field] = $info[$field]; $values[$field] = $info[$field];
Hook :: trigger("user_adding_in_db", $info);
if (self :: $class :: insert(self :: $users_table, $values)) { if (self :: $class :: insert(self :: $users_table, $values)) {
Log :: info('add_user(%s): user added', $values['username']); Log :: info('add_user(%s): user added', $info['username']);
Hook :: trigger("user_added_in_db", $info);
return true; return true;
} }
Log :: error('add_user(%s): error adding user', $values['username']); Log :: error('add_user(%s): error adding user', $values['username']);
@ -174,30 +193,220 @@ class Db extends Backend {
} }
/** /**
* CLI command to add user * Update a user in database
* @param \EesyPHP\Auth\User $user The user object
* @param array<string,mixed> $changes Array of changes
* @param boolean $no_change_as_success Consider no change provided as success
* (optional, default: false)
* @return boolean True if user was updated, false otherwise
*/
public static function update_user($user, $changes, $no_change_as_success=False) {
Log::debug("update_user(%s): changes=%s", $user->username, vardump($changes));
if (!$user->username) {
Log::error("update_user(): Invalid user provided (no username)");
return false;
}
if (!is_array($changes)) {
Log::error("update_user(%s): Invalid changes provided (not an array)", $user->username);
return false;
}
$values = [];
foreach($changes as $field => $value) {
switch ($field) {
case "username":
if ($value != $user->username) {
// Check username uniqueness
if (self :: get_user($value)) {
Log :: error(
"update_user(%s): invalid new username '%s': another user with this username already exist",
$user->username,
$value
);
return false;
}
$values[self :: $username_field] = $value;
}
break;
case "password":
if (!password_verify($value, $user["password"]))
$values[self :: $password_field] = password_hash(
$value,
constant('PASSWORD_'.strtoupper(App::get('auth.db.password_hash_algo')))
);
break;
default:
if (in_array($field, App :: get('auth.db.exposed_fields'))) {
if ($value != $user[$field])
$values[$field] = $value;
break;
}
Log :: error("update_user: unknown field %s", $field);
return false;
}
}
if (empty($values)) {
Log::log(
$no_change_as_success?"DEBUG":"ERROR",
"update_user(%s): no change",
$user->username
);
return $no_change_as_success;
}
Log::debug("update_user(%s): changes=%s", $user->username, vardump($values));
Hook :: trigger(
"user_updating_in_db",
["user" => $user, "changes" => $changes, "raw_changes" => $values]
);
if (
self :: $class :: update(
self :: $users_table,
$values,
[App::get('auth.db.username_field') => $user->username]
)
) {
Log :: info('update_user(%s): user updated', $user->username);
Hook :: trigger(
"user_updated_in_db",
["user" => $user, "changes" => $changes, "raw_changes" => $values]
);
return true;
}
Log :: error('update_user(%s): error adding user', $user->username);
return false;
}
/**
* Delete a user in database
* @param \EesyPHP\Auth\User $user The user object
* @return boolean True if user was deleted, false otherwise
*/
public static function delete_user($user) {
if (!$user->username) {
Log::error("delete_user(): Invalid user provided (no username)");
return false;
}
Hook :: trigger("user_deleting_in_db", ["user" => $user]);
if (
self :: $class :: delete(
self :: $users_table,
[self :: $username_field => $user->username]
)
) {
Log :: info('delete_user(%s): user updated', $user->username);
Hook :: trigger("user_deleted_in_db", ["user" => $user]);
return true;
}
Log :: error('delete_user(%s): error adding user', $user->username);
return false;
}
/**
* CLI command to manage users
* @param array $command_args Command arguments * @param array $command_args Command arguments
* @return bool * @return bool
*/ */
public static function cli_add_user($command_args) { public static function cli_user($command_args) {
$info = ['username' => null, 'password' => null]; if (!$command_args) Cli::usage();
foreach($info as $field => $value) { switch ($command_args[0]) {
while(!$value) { case "list":
$value = Cli::ask_user("Please enter user $field: ", $field == 'password'); $users = self :: $class :: get_many(
if (empty($value)) self :: $users_table,
print("Invalid value\n"); null,
} array_merge([self :: $username_field], self :: $exposed_fields),
$info[$field] = $value; self :: $username_field
);
if ($users === false)
Log :: fatal(I18n::_("Failed to list users from database."));
if (!$users) {
print(I18n::_("No user found."));
return true;
}
$tbl = new Console_Table();
$headers = [I18n::_("Username")];
foreach(self :: $exposed_fields as $field)
$headers[] = mb_convert_case(str_replace("_", " ", $field), MB_CASE_TITLE);
$tbl->setHeaders($headers);
foreach($users as $user) {
$row = [$user[self::$username_field]];
foreach(self :: $exposed_fields as $field)
$row[] = $user[$field]?strval($user[$field]):"";
$tbl->addRow($row);
}
echo $tbl->getTable();
echo "\n".sprintf(_("%d users(s)"), count($users))."\n";
return true;
case "add":
$info = ['username' => null, 'password' => null];
foreach($info as $field => $value) {
while(!$value) {
$value = Cli::ask_user("Please enter user $field: ", $field == 'password');
if (empty($value))
print("Invalid value\n");
}
$info[$field] = $value;
}
foreach(self :: $exposed_fields as $field) {
$value = readline("Please enter user $field: ");
if (empty($value))
continue;
$info[$field] = $value;
}
if (self :: add_user($info)) {
printf("User %s added\n", $info['username']);
return true;
}
Log :: fatal("Error occurred adding user %s", $info['username']);
case "edit":
if (count($command_args) < 2)
Cli :: usage(I18n::_("Username is missing"));
$username = $command_args[1];
$user = self :: get_user($username);
if (!$user) Cli :: usage("Invalid user '$username'");
$changes = [
"username" => trim(
Cli::ask_user("Please enter user new username [$username]: ")
),
"password" => Cli::ask_user("Please enter user new password [empty = unchange]: ", true),
];
if (!$changes["username"]) unset($changes["username"]);
if (!$changes["password"]) unset($changes["password"]);
foreach(self :: $exposed_fields as $field) {
$value = Cli::ask_user("Please enter user $field [{$user[$field]}]: ");
if (empty($value))
continue;
$changes[$field] = $value;
}
if (!$changes) {
print("No change.\n");
return true;
}
if (self :: update_user($user, $changes)) {
printf("User %s updated\n", $username);
return true;
}
Log :: fatal("Error occurred updating user %s", $username);
case "delete":
if (count($command_args) < 2)
Cli :: usage(I18n::_("Username is missing"));
$username = $command_args[1];
$user = self :: get_user($username);
if (!$user) Cli :: usage("Invalid user '$username'");
if (self :: delete_user($user)) {
printf("User %s deleted\n", $username);
return true;
}
Log :: fatal("Error occurred deleting user %s", $username);
default:
Cli::usage(I18n::_("Unknown subcommand %s"), $command_args[0]);
} }
foreach(self :: $exposed_fields as $field) {
$value = readline("Please enter user $field: ");
if (empty($value))
continue;
$info[$field] = $value;
}
if (self :: add_user($info)) {
printf("User %s added\n", $info['username']);
return true;
}
Log :: fatal("Error occurred adding user %s", $info['username']);
} }
} }

View file

@ -225,7 +225,7 @@ class DbObject {
* @param array<mixed> $args Primary keys value in the same order as primary keys as declared * @param array<mixed> $args Primary keys value in the same order as primary keys as declared
* @see static :: PRIMARY_KEYS * @see static :: PRIMARY_KEYS
* @throws DbException * @throws DbException
* @return DbObject|null The object if found, else otherwise. * @return static|null The object if found, else otherwise.
*/ */
public static function get(...$args) { public static function get(...$args) {
$class = get_called_class(); $class = get_called_class();
@ -340,7 +340,7 @@ class DbObject {
/** /**
* List objects * List objects
* @param array<string,mixed> $where Where clauses as associative array of field name and value * @param array<string,mixed> $where Where clauses as associative array of field name and value
* @return array<DbObject>|false * @return array<static>|false
*/ */
public static function list($where=null) { public static function list($where=null) {
$class = get_called_class(); $class = get_called_class();