From 7102503bb287f076cd689bcd59a448a8ff787805 Mon Sep 17 00:00:00 2001
From: Stijn Tintel <stijn@linux-ipv6.be>
Date: Tue, 23 Jul 2013 13:00:29 +0200
Subject: [PATCH] Only revoke GRANT OPTION when user actually has it

When revoking privileges from a user, the GRANT OPTION is always
revoked, even if the user doesn't have it. If the user exists, this
doesn't give an error, but if the user doesn't exist, it does:

mysql> GRANT ALL ON test.* TO 'test'@'localhost';
Query OK, 0 rows affected (0.00 sec)

mysql> REVOKE GRANT OPTION ON test.* FROM 'test'@'localhost';
Query OK, 0 rows affected (0.00 sec)

mysql> REVOKE GRANT OPTION ON test.* FROM 'test'@'localhost';
Query OK, 0 rows affected (0.00 sec)

mysql> REVOKE ALL ON test.* FROM 'test'@'localhost';
Query OK, 0 rows affected (0.00 sec)

mysql> REVOKE GRANT OPTION ON test.* FROM 'test'@'localhost';
ERROR 1141 (42000): There is no such grant defined for user 'test' on
host 'localhost'

Additionally, in MySQL 5.6 this breaks replication because of
http://bugs.mysql.com/bug.php?id=68892.

Rather than revoking the GRANT OPTION and catching the error, check if
the user actually has it and only revoke it when he does.
---
 database/mysql_user | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)

diff --git a/database/mysql_user b/database/mysql_user
index bff1e4e74fe..85551b58cf0 100644
--- a/database/mysql_user
+++ b/database/mysql_user
@@ -144,6 +144,7 @@ def user_add(cursor, user, host, password, new_priv):
 
 def user_mod(cursor, user, host, password, new_priv):
     changed = False
+    grant_option = False
 
     # Handle passwords.
     if password is not None:
@@ -162,9 +163,12 @@ def user_mod(cursor, user, host, password, new_priv):
         # If the user has privileges on a db.table that doesn't appear at all in
         # the new specification, then revoke all privileges on it.
         for db_table, priv in curr_priv.iteritems():
+            # If the user has the GRANT OPTION on a db.table, revoke it first.
+            if "GRANT" in priv:
+                grant_option = True
             if db_table not in new_priv:
                 if user != "root" and "PROXY" not in priv:
-                    privileges_revoke(cursor, user,host,db_table)
+                    privileges_revoke(cursor, user,host,db_table,grant_option)
                     changed = True
 
         # If the user doesn't currently have any privileges on a db.table, then
@@ -180,7 +184,7 @@ def user_mod(cursor, user, host, password, new_priv):
         for db_table in db_table_intersect:
             priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table])
             if (len(priv_diff) > 0):
-                privileges_revoke(cursor, user,host,db_table)
+                privileges_revoke(cursor, user,host,db_table,grant_option)
                 privileges_grant(cursor, user,host,db_table,new_priv[db_table])
                 changed = True
 
@@ -243,17 +247,12 @@ def privileges_unpack(priv):
 
     return output
 
-def privileges_revoke(cursor, user,host,db_table):
+def privileges_revoke(cursor, user,host,db_table,grant_option):
+    if grant_option:
+        query = "REVOKE GRANT OPTION ON %s FROM '%s'@'%s'" % (db_table,user,host)
+        cursor.execute(query)
     query = "REVOKE ALL PRIVILEGES ON %s FROM '%s'@'%s'" % (db_table,user,host)
     cursor.execute(query)
-    query = "REVOKE GRANT OPTION ON %s FROM '%s'@'%s'" % (db_table,user,host)
-    try:
-        cursor.execute(query)
-    except MySQLdb.OperationalError, e:
-        # 1141 -> There is no such grant defined for user ... on host ...
-        # If this exception is raised, there is no need to revoke the GRANT privilege
-        if e.args[0] != 1141 or not e.args[1].startswith("There is no such grant defined for user"):
-            raise e
 
 def privileges_grant(cursor, user,host,db_table,priv):