mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
MDEV-23729 MDEV-32218 INFORMATION_SCHEMA table for user data
* A new table INFORMATION_SCHEMA.USERS is introduced. * It stores auxiliary user data * An unprivileged user can access their own data, and that is the main difference with what mysql.global_priv provides * The fields are currently: USER, PASSWORD_ERRORS, PASSWORD_EXPIRATION_TIME * If password_errors is ignored for the user, PASSWORD_ERRORS is NULL * PASSWORD_EXPIRATION_TIME is a timestamp with exact point in time, calculated from password_last_changed and password_lifetime (i.e. days) stored for the user
This commit is contained in:
@ -83,3 +83,86 @@ TABLE_SCHEMA TABLE_NAME INDEX_NAME ROWS_READ QUERIES
|
||||
select * from information_schema.table_statistics where table_schema='test' and table_name='just_a_test';
|
||||
TABLE_SCHEMA TABLE_NAME ROWS_READ ROWS_CHANGED ROWS_CHANGED_X_INDEXES ROWS_INSERTED ROWS_UPDATED ROWS_DELETED KEY_READ_HITS KEY_READ_MISSES
|
||||
set global userstat=@save_userstat;
|
||||
#
|
||||
# MDEV-23729 INFORMATION_SCHEMA Table info. about user locked due to
|
||||
# max_password_errors
|
||||
#
|
||||
# MDEV-32218 message to notify end-user N-days prior the password get
|
||||
# expired
|
||||
#
|
||||
set @old_max_password_errors=@@max_password_errors;
|
||||
set global max_password_errors=2;
|
||||
select * from information_schema.users;
|
||||
USER PASSWORD_ERRORS PASSWORD_EXPIRATION_TIME
|
||||
'mariadb.sys'@'localhost' 0 NULL
|
||||
'root'@'neo' 0 NULL
|
||||
set timestamp= 123;
|
||||
create user nice_user;
|
||||
create user naughty_user identified by 'naughty_user_passwd';
|
||||
alter user naughty_user password expire interval 10 day;
|
||||
select 3600*24;
|
||||
3600*24
|
||||
86400
|
||||
select * from information_schema.users;
|
||||
USER PASSWORD_ERRORS PASSWORD_EXPIRATION_TIME
|
||||
'mariadb.sys'@'localhost' 0 NULL
|
||||
'naughty_user'@'%' 0 864123
|
||||
'nice_user'@'%' 0 NULL
|
||||
'root'@HOSTNAME 0 NULL
|
||||
alter user nice_user password expire interval 10 day;
|
||||
select * from information_schema.users;
|
||||
USER PASSWORD_ERRORS PASSWORD_EXPIRATION_TIME
|
||||
'mariadb.sys'@'localhost' 0 NULL
|
||||
'naughty_user'@'%' 0 864123
|
||||
'nice_user'@'%' 0 864123
|
||||
'root'@HOSTNAME 0 NULL
|
||||
connect(localhost,naughty_user,wrong_passwd,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect con1, localhost, naughty_user, wrong_passwd;
|
||||
ERROR 28000: Access denied for user 'naughty_user'@'localhost' (using password: YES)
|
||||
select * from information_schema.users;
|
||||
USER PASSWORD_ERRORS PASSWORD_EXPIRATION_TIME
|
||||
'mariadb.sys'@'localhost' 0 NULL
|
||||
'naughty_user'@'%' 1 864123
|
||||
'nice_user'@'%' 0 864123
|
||||
'root'@HOSTNAME 0 NULL
|
||||
connect(localhost,naughty_user,wrong_passwd,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect con1, localhost, naughty_user, wrong_passwd;
|
||||
ERROR 28000: Access denied for user 'naughty_user'@'localhost' (using password: YES)
|
||||
select * from information_schema.users;
|
||||
USER PASSWORD_ERRORS PASSWORD_EXPIRATION_TIME
|
||||
'mariadb.sys'@'localhost' 0 NULL
|
||||
'naughty_user'@'%' 2 864123
|
||||
'nice_user'@'%' 0 864123
|
||||
'root'@HOSTNAME 0 NULL
|
||||
# Show all users that are blocked due to max_password_errors reached.
|
||||
select user from information_schema.users
|
||||
where password_errors >= @@global.max_password_errors;
|
||||
user
|
||||
'naughty_user'@'%'
|
||||
set global max_password_errors=3;
|
||||
connect con1, localhost, naughty_user, naughty_user_passwd;
|
||||
connection default;
|
||||
select * from information_schema.users;
|
||||
USER PASSWORD_ERRORS PASSWORD_EXPIRATION_TIME
|
||||
'mariadb.sys'@'localhost' 0 NULL
|
||||
'naughty_user'@'%' 0 864123
|
||||
'nice_user'@'%' 0 864123
|
||||
'root'@HOSTNAME 0 NULL
|
||||
disconnect con1;
|
||||
# Test unprivileged output
|
||||
connect con2, localhost, nice_user;
|
||||
set timestamp= 123;
|
||||
set password= password('nice_passwd');
|
||||
select * from information_schema.users;
|
||||
USER PASSWORD_ERRORS PASSWORD_EXPIRATION_TIME
|
||||
'nice_user'@'%' 0 864123
|
||||
# Delete user while some connection is still alive, then select.
|
||||
connection default;
|
||||
drop user nice_user;
|
||||
connection con2;
|
||||
select * from information_schema.users;
|
||||
ERROR 0L000: The current user is invalid
|
||||
disconnect con2;
|
||||
connection default;
|
||||
drop user naughty_user;
|
||||
set global max_password_errors=@old_max_password_errors;
|
||||
|
@ -54,3 +54,89 @@ select * from information_schema.index_statistics where table_schema='test' and
|
||||
select * from information_schema.table_statistics where table_schema='test' and table_name='just_a_test';
|
||||
set global userstat=@save_userstat;
|
||||
--enable_ps2_protocol
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-23729 INFORMATION_SCHEMA Table info. about user locked due to
|
||||
--echo # max_password_errors
|
||||
--echo #
|
||||
--echo # MDEV-32218 message to notify end-user N-days prior the password get
|
||||
--echo # expired
|
||||
--echo #
|
||||
|
||||
set @old_max_password_errors=@@max_password_errors;
|
||||
set global max_password_errors=2;
|
||||
|
||||
select * from information_schema.users;
|
||||
|
||||
let $hostname= `select concat('@\'', @@hostname, '\'')`;
|
||||
# set the password_last_changed value
|
||||
set timestamp= 123;
|
||||
|
||||
create user nice_user;
|
||||
create user naughty_user identified by 'naughty_user_passwd';
|
||||
|
||||
alter user naughty_user password expire interval 10 day;
|
||||
|
||||
select 3600*24;
|
||||
--replace_result $hostname @HOSTNAME
|
||||
eval select * from information_schema.users;
|
||||
|
||||
alter user nice_user password expire interval 10 day;
|
||||
--replace_result $hostname @HOSTNAME
|
||||
select * from information_schema.users;
|
||||
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
--error ER_ACCESS_DENIED_ERROR
|
||||
connect(con1, localhost, naughty_user, wrong_passwd);
|
||||
|
||||
--replace_result $hostname @HOSTNAME
|
||||
select * from information_schema.users;
|
||||
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
--error ER_ACCESS_DENIED_ERROR
|
||||
connect(con1, localhost, naughty_user, wrong_passwd);
|
||||
|
||||
--replace_result $hostname @HOSTNAME
|
||||
select * from information_schema.users;
|
||||
|
||||
|
||||
--echo # Show all users that are blocked due to max_password_errors reached.
|
||||
select user from information_schema.users
|
||||
where password_errors >= @@global.max_password_errors;
|
||||
|
||||
|
||||
set global max_password_errors=3;
|
||||
|
||||
connect(con1, localhost, naughty_user, naughty_user_passwd);
|
||||
connection default;
|
||||
|
||||
--replace_result $hostname @HOSTNAME
|
||||
select * from information_schema.users;
|
||||
disconnect con1;
|
||||
|
||||
--echo # Test unprivileged output
|
||||
|
||||
connect(con2, localhost, nice_user);
|
||||
set timestamp= 123;
|
||||
# timestamp was normal at the login moment, so the password was expired
|
||||
set password= password('nice_passwd');
|
||||
|
||||
--replace_result $hostname @HOSTNAME
|
||||
select * from information_schema.users;
|
||||
|
||||
--echo # Delete user while some connection is still alive, then select.
|
||||
connection default;
|
||||
drop user nice_user;
|
||||
connection con2;
|
||||
# and here you are, select from your table
|
||||
--error ER_INVALID_CURRENT_USER
|
||||
select * from information_schema.users;
|
||||
|
||||
disconnect con2;
|
||||
connection default;
|
||||
drop user naughty_user;
|
||||
set global max_password_errors=@old_max_password_errors;
|
||||
|
||||
#
|
||||
# End of 11.5 tests
|
||||
#
|
||||
|
@ -1110,6 +1110,7 @@ enum enum_schema_tables
|
||||
SCH_TABLE_NAMES,
|
||||
SCH_TABLE_PRIVILEGES,
|
||||
SCH_TRIGGERS,
|
||||
SCH_USERS,
|
||||
SCH_USER_PRIVILEGES,
|
||||
SCH_VIEWS,
|
||||
SCH_ENUM_SIZE
|
||||
|
@ -13017,6 +13017,95 @@ err:
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace Show
|
||||
{
|
||||
ST_FIELD_INFO users_fields_info[] =
|
||||
{
|
||||
Column("USER", Userhost(), NOT_NULL),
|
||||
Column("PASSWORD_ERRORS", SLonglong(), NULLABLE),
|
||||
Column("PASSWORD_EXPIRATION_TIME", SLonglong(), NULLABLE),
|
||||
CEnd()
|
||||
};
|
||||
};
|
||||
|
||||
static bool ignore_max_password_errors(const ACL_USER *acl_user);
|
||||
|
||||
static int fill_users_schema_record(THD *thd, TABLE * table, ACL_USER *user)
|
||||
{
|
||||
ulonglong lifetime= user->password_lifetime < 0
|
||||
? default_password_lifetime
|
||||
: user->password_lifetime;
|
||||
|
||||
bool ignore_password_errors= ignore_max_password_errors(user);
|
||||
bool ignore_expiration_date= lifetime == 0;
|
||||
|
||||
/* Skip user if nothing to show */
|
||||
if (ignore_password_errors && ignore_expiration_date)
|
||||
return 0;
|
||||
|
||||
Grantee_str grantee(user->user,
|
||||
Lex_cstring_strlen(safe_str(user->host.hostname)));
|
||||
table->field[0]->store(grantee, strlen(grantee), system_charset_info);
|
||||
if (ignore_password_errors)
|
||||
{
|
||||
table->field[1]->set_null();
|
||||
}
|
||||
else
|
||||
{
|
||||
table->field[1]->set_notnull();
|
||||
table->field[1]->store(user->password_errors);
|
||||
}
|
||||
if (ignore_expiration_date)
|
||||
{
|
||||
table->field[2]->set_null();
|
||||
}
|
||||
else
|
||||
{
|
||||
table->field[2]->set_notnull();
|
||||
table->field[2]->store(user->password_last_changed
|
||||
+ user->password_lifetime * 3600 * 24, true);
|
||||
}
|
||||
|
||||
return schema_table_store_record(thd, table);
|
||||
}
|
||||
|
||||
int fill_users_schema_table(THD *thd, TABLE_LIST *tables, COND *cond)
|
||||
{
|
||||
int res= 0;
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
bool see_whole_table= check_access(thd, SELECT_ACL, "mysql", NULL, NULL,
|
||||
true, true) == 0;
|
||||
TABLE *table= tables->table;
|
||||
|
||||
if (!see_whole_table)
|
||||
{
|
||||
Security_context *sctx= thd->security_ctx;
|
||||
mysql_mutex_lock(&acl_cache->lock);
|
||||
ACL_USER *cur_user= find_user_exact(Lex_cstring_strlen(sctx->priv_host),
|
||||
Lex_cstring_strlen(sctx->priv_user));
|
||||
if (!cur_user)
|
||||
{
|
||||
mysql_mutex_unlock(&acl_cache->lock);
|
||||
my_error(ER_INVALID_CURRENT_USER, MYF(0));
|
||||
return 1;
|
||||
}
|
||||
|
||||
res= fill_users_schema_record(thd, table, cur_user);
|
||||
mysql_mutex_unlock(&acl_cache->lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
mysql_mutex_lock(&acl_cache->lock);
|
||||
for (size_t i= 0; res == 0 && i < acl_users.elements; i++)
|
||||
{
|
||||
ACL_USER *user= dynamic_element(&acl_users, i, ACL_USER*);
|
||||
res= fill_users_schema_record(thd, table, user);
|
||||
}
|
||||
mysql_mutex_unlock(&acl_cache->lock);
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
/*
|
||||
|
@ -150,6 +150,7 @@ bool check_routine_level_acl(THD *thd, privilege_t acl,
|
||||
const char *db, const char *name,
|
||||
const Sp_handler *sph);
|
||||
bool is_acl_user(const LEX_CSTRING &host, const LEX_CSTRING &user);
|
||||
int fill_users_schema_table(THD *thd, TABLE_LIST *tables, COND *cond);
|
||||
int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond);
|
||||
int fill_schema_schema_privileges(THD *thd, TABLE_LIST *tables, COND *cond);
|
||||
int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond);
|
||||
|
@ -10243,6 +10243,7 @@ ST_FIELD_INFO files_fields_info[]=
|
||||
CEnd()
|
||||
};
|
||||
|
||||
extern ST_FIELD_INFO users_fields_info[];
|
||||
}; // namespace Show
|
||||
|
||||
|
||||
@ -10556,6 +10557,8 @@ ST_SCHEMA_TABLE schema_tables[]=
|
||||
{"TRIGGERS"_Lex_ident_i_s_table, Show::triggers_fields_info, 0,
|
||||
get_all_tables, make_old_format, get_schema_triggers_record, 5, 6, 0,
|
||||
OPEN_TRIGGER_ONLY|OPTIMIZE_I_S_TABLE},
|
||||
{"USERS"_Lex_ident_i_s_table, Show::users_fields_info, 0, fill_users_schema_table,
|
||||
0, 0, -1, -1, 0, 0},
|
||||
{"USER_PRIVILEGES"_Lex_ident_i_s_table,
|
||||
Show::user_privileges_fields_info, 0,
|
||||
fill_schema_user_privileges, 0, 0, -1, -1, 0, 0},
|
||||
|
Reference in New Issue
Block a user