From c4453b330700a6cd46ce3fbd5690938ddc39be70 Mon Sep 17 00:00:00 2001 From: Mike Place Date: Mon, 21 Oct 2013 13:16:54 -0600 Subject: [PATCH] Add a lexical parser for MySQL grants to the MySQL module. Change the behavior of the grant-checker to use this new method by default and fallback to the strict method if it fails. --- salt/modules/mysql.py | 78 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/salt/modules/mysql.py b/salt/modules/mysql.py index c708bf88d4..b30489ed53 100644 --- a/salt/modules/mysql.py +++ b/salt/modules/mysql.py @@ -34,6 +34,9 @@ import sys # Import salt libs import salt.utils +#import shlex which should be distributed with Python +import shlex + # Import third party libs try: import MySQLdb @@ -148,6 +151,59 @@ def _connect(**kwargs): return dbc +def _grant_to_tokens(grant): + ''' + + This should correspond fairly closely to the YAML rendering of a mysql_grants state which comes out + as follows: + + OrderedDict([('whatever_identifier', OrderedDict([('mysql_grants.present', + [OrderedDict([('database', 'testdb.*')]), OrderedDict([('user', 'testuser')]), + OrderedDict([('grant', 'ALTER, SELECT, LOCK TABLES')]), OrderedDict([('host', 'localhost')])])]))]) + + :param grant: An un-parsed MySQL GRANT statement str, like + "GRANT SELECT, ALTER, LOCK TABLES ON `testdb`.* TO 'testuser'@'localhost'" + :return: + A Python dict with the following keys/values: + - user: MySQL User + - host: MySQL host + - grant: [grant1, grant2] (ala SELECT, USAGE, etc) + - database: MySQL DB + ''' + exploded_grant = shlex.split(grant) + grant_tokens = [] + position_tracker = 1 # Skip the initial 'GRANT' word token + phrase = 'grants' + + for token in exploded_grant[position_tracker:]: + if phrase == 'grants': + cleaned_token = token.rstrip(',') + grant_tokens.append(cleaned_token) + position_tracker += 1 + + if phrase == 'db': + database = token.strip('`') + phrase = 'tables' + + if phrase == 'user': + user, host = token.split('@') + + if token == 'ON': + phrase = 'db' + + if token == 'TO': + phrase = 'user' + + return { + 'user': user, + 'host': host, + 'grant': grant_tokens, + 'database': database, + + } + + + def query(database, query, **connection_args): ''' Run an arbitrary SQL query and return the results or @@ -1080,14 +1136,30 @@ def grant_exists(grant, ) grants = user_grants(user, host, **connection_args) - if grants is not False and target in grants: - log.debug('Grant exists.') - return True + + for grant in grants: + try: + target_tokens = None + if not target_tokens: # Avoid the overhead of re-calc in loop + target_tokens = _grant_to_tokens(target) + grant_tokens = _grant_to_tokens(grant) + if grant_tokens['user'] == target_tokens['user'] and \ + grant_tokens['database'] == target_tokens['database'] and \ + grant_tokens['host'] == target_tokens['host']: + if set(grant_tokens['grant']) == set(target_tokens['grant']): + return True + + except Exception as exc: # Fallback to strict parsing + log.debug("OH NO CAUGHT EXCEPTION: {0}".format(exc)) + if grants is not False and target in grants: + log.debug('Grant exists.') + return True log.debug('Grant does not exist, or is perhaps not ordered properly?') return False + def grant_add(grant, database, user,