Commit d229b4af authored by Nikita Malyavin's avatar Nikita Malyavin

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
parent bec1f321
...@@ -83,3 +83,86 @@ TABLE_SCHEMA TABLE_NAME INDEX_NAME ROWS_READ QUERIES ...@@ -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'; 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 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; 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 ...@@ -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'; select * from information_schema.table_statistics where table_schema='test' and table_name='just_a_test';
set global userstat=@save_userstat; set global userstat=@save_userstat;
--enable_ps2_protocol --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 ...@@ -1110,6 +1110,7 @@ enum enum_schema_tables
SCH_TABLE_NAMES, SCH_TABLE_NAMES,
SCH_TABLE_PRIVILEGES, SCH_TABLE_PRIVILEGES,
SCH_TRIGGERS, SCH_TRIGGERS,
SCH_USERS,
SCH_USER_PRIVILEGES, SCH_USER_PRIVILEGES,
SCH_VIEWS, SCH_VIEWS,
SCH_ENUM_SIZE SCH_ENUM_SIZE
......
...@@ -13017,6 +13017,95 @@ int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond) ...@@ -13017,6 +13017,95 @@ int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond)
#endif #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 #ifndef NO_EMBEDDED_ACCESS_CHECKS
/* /*
......
...@@ -150,6 +150,7 @@ bool check_routine_level_acl(THD *thd, privilege_t acl, ...@@ -150,6 +150,7 @@ bool check_routine_level_acl(THD *thd, privilege_t acl,
const char *db, const char *name, const char *db, const char *name,
const Sp_handler *sph); const Sp_handler *sph);
bool is_acl_user(const LEX_CSTRING &host, const LEX_CSTRING &user); 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_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond);
int fill_schema_schema_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); int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond);
......
...@@ -10243,6 +10243,7 @@ ST_FIELD_INFO files_fields_info[]= ...@@ -10243,6 +10243,7 @@ ST_FIELD_INFO files_fields_info[]=
CEnd() CEnd()
}; };
extern ST_FIELD_INFO users_fields_info[];
}; // namespace Show }; // namespace Show
...@@ -10556,6 +10557,8 @@ ST_SCHEMA_TABLE schema_tables[]= ...@@ -10556,6 +10557,8 @@ ST_SCHEMA_TABLE schema_tables[]=
{"TRIGGERS"_Lex_ident_i_s_table, Show::triggers_fields_info, 0, {"TRIGGERS"_Lex_ident_i_s_table, Show::triggers_fields_info, 0,
get_all_tables, make_old_format, get_schema_triggers_record, 5, 6, 0, get_all_tables, make_old_format, get_schema_triggers_record, 5, 6, 0,
OPEN_TRIGGER_ONLY|OPTIMIZE_I_S_TABLE}, 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, {"USER_PRIVILEGES"_Lex_ident_i_s_table,
Show::user_privileges_fields_info, 0, Show::user_privileges_fields_info, 0,
fill_schema_user_privileges, 0, 0, -1, -1, 0, 0}, fill_schema_user_privileges, 0, 0, -1, -1, 0, 0},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment