feat[mysql_info]: add 'users_info' filter (#580)

* add documentation for new mysql_info users_info filter

* Add integration tests for mysql_info users_info

* fix list parsing when cursor come from mysql_info

Mysql_info use a DictCursor and mysql_user a normal cursor.

* fix case when an account as same user but different host and password

* document why certain authentications plugins cause issues

* add version_added for users_info to the documentation

* Add 'users' description to differentiate it from 'users_info'

---------

Co-authored-by: Andrew Klychkov <aaklychkov@mail.ru>
This commit is contained in:
Laurent Indermühle 2023-10-23 11:26:46 +02:00 committed by GitHub
parent 6b7cc14989
commit 3ef9bda95f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 492 additions and 12 deletions

View file

@ -112,23 +112,40 @@ def get_grants(cursor, user, host):
return grants.split(", ")
def get_existing_authentication(cursor, user):
def get_existing_authentication(cursor, user, host):
# Return the plugin and auth_string if there is exactly one distinct existing plugin and auth_string.
cursor.execute("SELECT VERSION()")
if 'mariadb' in cursor.fetchone()[0].lower():
srv_type = cursor.fetchone()
# Mysql_info use a DictCursor so we must convert back to a list
# otherwise we get KeyError 0
if isinstance(srv_type, dict):
srv_type = list(srv_type.values())
if 'mariadb' in srv_type[0].lower():
# before MariaDB 10.2.19 and 10.3.11, "password" and "authentication_string" can differ
# when using mysql_native_password
cursor.execute("""select plugin, auth from (
select plugin, password as auth from mysql.user where user=%(user)s
and host=%(host)s
union select plugin, authentication_string as auth from mysql.user where user=%(user)s
) x group by plugin, auth limit 2
""", {'user': user})
and host=%(host)s) x group by plugin, auth limit 2
""", {'user': user, 'host': host})
else:
cursor.execute("""select plugin, authentication_string as auth from mysql.user where user=%(user)s
group by plugin, authentication_string limit 2""", {'user': user})
cursor.execute("""select plugin, authentication_string as auth
from mysql.user where user=%(user)s and host=%(host)s
group by plugin, authentication_string limit 2""", {'user': user, 'host': host})
rows = cursor.fetchall()
if len(rows) == 1:
return {'plugin': rows[0][0], 'auth_string': rows[0][1]}
# Mysql_info use a DictCursor so we must convert back to a list
# otherwise we get KeyError 0
if isinstance(rows, dict):
rows = list(rows.values())
if isinstance(rows[0], tuple):
return {'plugin': rows[0][0], 'plugin_auth_string': rows[0][1]}
if isinstance(rows[0], dict):
return {'plugin': rows[0].get('plugin'), 'plugin_auth_string': rows[0].get('auth')}
return None
@ -149,7 +166,7 @@ def user_add(cursor, user, host, host_all, password, encrypted,
used_existing_password = False
if reuse_existing_password:
existing_auth = get_existing_authentication(cursor, user)
existing_auth = get_existing_authentication(cursor, user, host)
if existing_auth:
plugin = existing_auth['plugin']
plugin_hash_string = existing_auth['auth_string']
@ -478,6 +495,12 @@ def privileges_get(cursor, user, host, maria_role=False):
return x
for grant in grants:
# Mysql_info use a DictCursor so we must convert back to a list
# otherwise we get KeyError 0
if isinstance(grant, dict):
grant = list(grant.values())
if not maria_role:
res = re.match("""GRANT (.+) ON (.+) TO (['`"]).*\\3@(['`"]).*\\4( IDENTIFIED BY PASSWORD (['`"]).+\\6)? ?(.*)""", grant[0])
else:
@ -777,6 +800,11 @@ def get_resource_limits(cursor, user, host):
cursor.execute(query, (user, host))
res = cursor.fetchone()
# Mysql_info use a DictCursor so we must convert back to a list
# otherwise we get KeyError 0
if isinstance(res, dict):
res = list(res.values())
if not res:
return None
@ -788,11 +816,22 @@ def get_resource_limits(cursor, user, host):
}
cursor.execute("SELECT VERSION()")
if 'mariadb' in cursor.fetchone()[0].lower():
srv_type = cursor.fetchone()
# Mysql_info use a DictCursor so we must convert back to a list
# otherwise we get KeyError 0
if isinstance(srv_type, dict):
srv_type = list(srv_type.values())
if 'mariadb' in srv_type[0].lower():
query = ('SELECT max_statement_time AS MAX_STATEMENT_TIME '
'FROM mysql.user WHERE User = %s AND Host = %s')
cursor.execute(query, (user, host))
res_max_statement_time = cursor.fetchone()
# Mysql_info use a DictCursor so we must convert back to a list
# otherwise we get KeyError 0
if isinstance(res_max_statement_time, dict):
res_max_statement_time = list(res_max_statement_time.values())
current_limits['MAX_STATEMENT_TIME'] = res_max_statement_time[0]
return current_limits