diff --git a/src/box/alter.cc b/src/box/alter.cc
index 280831565647e83092047b48893c6fff2e4fb41c..c2b1b971826b3535a98f584f7ad137c0af76e880 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -1502,7 +1502,8 @@ priv_def_check(struct priv_def *priv)
 				  role ? role->name :
 				  int2str(priv->object_id));
 		}
-		/* Only the creator of the role can grant or revoke it.
+		/*
+		 * Only the creator of the role can grant or revoke it.
 		 * Everyone can grant 'PUBLIC' role.
 		 */
 		if (role->owner != grantor->uid && grantor->uid != ADMIN &&
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index cd2303342ddb93cc573a1b60030690e45c26be45..6df1b9b413c6d78fa283f8c6edda40a81d8011fd 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1044,7 +1044,7 @@ box.schema.user.grant = function(user_name, privilege, object_type,
     if uid == nil then
         box.error(box.error.NO_SUCH_USER, user_name)
     end
-    privilege = privilege_resolve(privilege)
+    privilege_hex = privilege_resolve(privilege)
     local oid = object_resolve(object_type, object_name)
     if grantor == nil then
         grantor = session.uid()
@@ -1060,11 +1060,19 @@ box.schema.user.grant = function(user_name, privilege, object_type,
     else
         old_privilege = 0
     end
-    privilege = bit.bor(privilege, old_privilege)
+    privilege_hex = bit.bor(privilege_hex, old_privilege)
     -- do not execute a replace if it does not change anything
-    -- XXX bug: new grantor replaces the old one, old grantor is lost
-    if privilege ~= old_privilege then
-        _priv:replace{grantor, uid, object_type, oid, privilege}
+    -- XXX bug if we decide to add a grant option: new grantor
+    -- replaces the old one, old grantor is lost
+    if privilege_hex ~= old_privilege then
+        _priv:replace{grantor, uid, object_type, oid, privilege_hex}
+    else
+        if object_type == 'role' then
+            box.error(box.error.ROLE_GRANTED, user_name, object_name)
+        else
+            box.error(box.error.PRIV_GRANTED, user_name, privilege,
+                      object_type, object_name)
+        end
     end
 end
 
@@ -1082,13 +1090,18 @@ box.schema.user.revoke = function(user_name, privilege, object_type, object_name
     if uid == nil then
         box.error(box.error.NO_SUCH_USER, name)
     end
-    privilege = privilege_resolve(privilege)
     local oid = object_resolve(object_type, object_name)
     local _priv = box.space[box.schema.PRIV_ID]
     local tuple = _priv:get{uid, object_type, oid}
     if tuple == nil then
-        return
+        if object_type == 'role' then
+            box.error(box.error.ROLE_NOT_GRANTED, user_name, object_name)
+        else
+            box.error(box.error.PRIV_NOT_GRANTED, user_name, privilege,
+                      object_type, object_name)
+        end
     end
+    privilege = privilege_resolve(privilege)
     local old_privilege = tuple[5]
     local grantor = tuple[1]
     -- sic:
diff --git a/src/errcode.h b/src/errcode.h
index 5160e869884d29f6601813d12d1ba039f7b3fc3f..a128fe3319cd0c905a34418de487a224cfc63ae1 100644
--- a/src/errcode.h
+++ b/src/errcode.h
@@ -138,6 +138,10 @@ enum { TNT_ERRMSG_MAX = 512 };
 	/* 86 */_(ER_TUPLE_REF_OVERFLOW,	1, "Tuple reference counter overflow") \
 	/* 87 */_(ER_ROLE_LOOP,			2, "Granting role '%s' to role '%s' would create a loop") \
 	/* 88 */_(ER_GRANT,			2, "Incorrect grant arguments: %s") \
+	/* 89 */_(ER_PRIV_GRANTED,		2, "User '%s' already has %s access on %s '%s'") \
+	/* 90 */_(ER_ROLE_GRANTED,		2, "User '%s' already has role '%s'") \
+	/* 91 */_(ER_PRIV_NOT_GRANTED,		2, "User '%s' does not have %s access on %s '%s'") \
+	/* 92 */_(ER_ROLE_NOT_GRANTED,		2, "User '%s' does not have role '%s'") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/test/box/access.result b/test/box/access.result
index 975ddea6cc2c035cb9871db15e6664b6f844c0a8..2e5bf7f9375fd0069ca1e950c410a8968c09b78f 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -360,6 +360,7 @@ box.space._priv:select{id}
 ...
 box.schema.user.grant('user', 'read', 'universe')
 ---
+- error: User 'user' already has read access on universe 'nil'
 ...
 box.space._priv:select{id}
 ---
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index 27e7d7afb79cdceb16323e25ce36051465f2b08f..b181cbba871c254d16255276042565abb065330f 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -53,6 +53,7 @@ box.schema.user.grant('testus', 'read', 'space', 'admin_space')
 ...
 box.schema.user.grant('testus', 'read', 'space', 'admin_space')
 ---
+- error: User 'testus' already has read access on space 'admin_space'
 ...
 session.su('testus')
 ---
@@ -84,6 +85,7 @@ box.schema.user.revoke('testus', 'read', 'space', 'admin_space')
 ...
 box.schema.user.revoke('testus', 'read', 'space', 'admin_space')
 ---
+- error: User 'testus' does not have read access on space 'admin_space'
 ...
 session.su('testus')
 ---
@@ -402,6 +404,7 @@ session.su('admin')
 ...
 box.schema.user.revoke('testuser', 'read, write, execute', 'universe')
 ---
+- error: User 'testuser' does not have read, write, execute access on universe 'nil'
 ...
 --
 -- Check that itertors check privileges
diff --git a/test/box/box.net.box.result b/test/box/box.net.box.result
index 19202db6c0334bf4db5850dc6a0902dac99aca6a..448a5644ed972235e3ca459023019b4bf34e0857 100644
--- a/test/box/box.net.box.result
+++ b/test/box/box.net.box.result
@@ -758,3 +758,6 @@ file_log:close()
 ---
 - true
 ...
+box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+---
+...
diff --git a/test/box/box.net.box.test.lua b/test/box/box.net.box.test.lua
index a244dd421affa4e13e09f89a24b1be7ab91f2154..2839f9eb834b96bce96fbfa101a55f3eb3f6dfb9 100644
--- a/test/box/box.net.box.test.lua
+++ b/test/box/box.net.box.test.lua
@@ -311,3 +311,4 @@ end;
 --# setopt delimiter ''
 
 file_log:close()
+box.schema.user.revoke('guest', 'read,write,execute', 'universe')
diff --git a/test/box/misc.result b/test/box/misc.result
index e08f06bee68af9cc5835a80a771bf2857e7ce0c6..4a6b2751e572cbe622e672e66348f3227db44573 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -207,8 +207,12 @@ t;
   - 'box.error.USER_EXISTS : 46'
   - 'box.error.WAL_IO : 40'
   - 'box.error.CREATE_USER : 43'
+  - 'box.error.DROP_PRIMARY_KEY : 17'
+  - 'box.error.PRIV_GRANTED : 89'
   - 'box.error.CREATE_SPACE : 9'
+  - 'box.error.PRIV_NOT_GRANTED : 91'
   - 'box.error.GRANT : 88'
+  - 'box.error.ROLE_GRANTED : 90'
   - 'box.error.TUPLE_REF_OVERFLOW : 86'
   - 'box.error.UNKNOWN_SCHEMA_OBJECT : 49'
   - 'box.error.PROC_LUA : 32'
@@ -258,7 +262,7 @@ t;
   - 'box.error.ARG_TYPE : 26'
   - 'box.error.INDEX_FIELD_COUNT : 39'
   - 'box.error.READONLY : 7'
-  - 'box.error.DROP_PRIMARY_KEY : 17'
+  - 'box.error.ROLE_NOT_GRANTED : 92'
   - 'box.error.DROP_SPACE : 11'
   - 'box.error.UNKNOWN_REQUEST_TYPE : 48'
   - 'box.error.INVALID_XLOG_ORDER : 76'
diff --git a/test/box/protocol.result b/test/box/protocol.result
index 86698ffceaa0bb801cdbf272a034cf0bb31dc237..0783bf6302f9c5c307d867ef0d32d5945cc13302 100644
--- a/test/box/protocol.result
+++ b/test/box/protocol.result
@@ -51,3 +51,6 @@ conn:close()
 space:drop()
 ---
 ...
+box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+---
+...
diff --git a/test/box/protocol.test.lua b/test/box/protocol.test.lua
index 1349541bbcc147ba52d8859e77ca61cf6b14281e..307909c843927c63dc01597b265116aa94cb37f5 100644
--- a/test/box/protocol.test.lua
+++ b/test/box/protocol.test.lua
@@ -18,3 +18,4 @@ conn.space[space.id]:select(3, { iterator = 'LT' })
 conn:close()
 
 space:drop()
+box.schema.user.revoke('guest', 'read,write,execute', 'universe')
diff --git a/test/box/role.result b/test/box/role.result
index df3d02c808522cd7cb0848924ca2f733b9a38030..310edbaabf8443c8de2ea57e9dac782ebf26d710 100644
--- a/test/box/role.result
+++ b/test/box/role.result
@@ -67,9 +67,11 @@ box.schema.user.grant('tester', 'execute', 'role', 'tester')
 -- test granting a non-execute grant on a role - error
 box.schema.user.grant('tester', 'write', 'role', 'iddqd')
 ---
+- error: User 'tester' already has role 'iddqd'
 ...
 box.schema.user.grant('tester', 'read', 'role', 'iddqd')
 ---
+- error: User 'tester' already has role 'iddqd'
 ...
 -- test granting role to a role
 box.schema.user.grant('iddqd', 'execute', 'role', 'iddqd')
@@ -82,9 +84,11 @@ box.schema.user.grant('iddqd', 'iddqd')
 ...
 box.schema.user.revoke('iddqd', 'iddqd')
 ---
+- error: User 'iddqd' does not have role 'iddqd'
 ...
 box.schema.user.grant('tester', 'iddqd')
 ---
+- error: User 'tester' already has role 'iddqd'
 ...
 box.schema.user.revoke('tester', 'iddqd')
 ---
@@ -631,10 +635,12 @@ box.schema.user.create('john')
 box.session.su('john')
 ---
 ...
+-- error
 box.schema.role.grant('grantee', 'role')
 ---
 - error: Read access denied for user 'john' to space '_user'
 ...
+--
 box.session.su('admin')
 ---
 ...
@@ -655,6 +661,24 @@ box.schema.role.grant('grantee', 'read', 'space', 'test')
 ---
 - error: Read access denied for user 'john'
 ...
+--
+-- granting 'public' is however an exception - everyone
+-- can grant 'public' role, it's implicitly granted with 
+-- a grant option.
+-- 
+box.schema.role.grant('grantee', 'public')
+---
+- error: User 'grantee' already has role 'public'
+...
+-- 
+-- revoking role 'public' is another deal - only the 
+-- superuser can do that, and even that would be useless,
+-- since one can still re-grant it back to oneself.
+-- 
+box.schema.role.revoke('grantee', 'public')
+---
+- error: Create or drop access denied for user 'john'
+...
 box.session.su('admin')
 ---
 ...
diff --git a/test/box/role.test.lua b/test/box/role.test.lua
index 3af64a559f61cf7232a96cb4d8efa83ef3e52353..14acba2421be045be6ae51bd182a23ce93e5799c 100644
--- a/test/box/role.test.lua
+++ b/test/box/role.test.lua
@@ -244,13 +244,27 @@ box.schema.role.grant('grantee', 'role')
 box.schema.role.revoke('grantee', 'role')
 box.schema.user.create('john')
 box.session.su('john')
+-- error
 box.schema.role.grant('grantee', 'role')
+--
 box.session.su('admin')
 _ = box.schema.space.create('test')
 box.schema.user.grant('john', 'read,write,execute', 'universe')
 box.session.su('john')
 box.schema.role.grant('grantee', 'role')
 box.schema.role.grant('grantee', 'read', 'space', 'test')
+--
+-- granting 'public' is however an exception - everyone
+-- can grant 'public' role, it's implicitly granted with 
+-- a grant option.
+-- 
+box.schema.role.grant('grantee', 'public')
+-- 
+-- revoking role 'public' is another deal - only the 
+-- superuser can do that, and even that would be useless,
+-- since one can still re-grant it back to oneself.
+-- 
+box.schema.role.revoke('grantee', 'public')
 
 box.session.su('admin')
 box.schema.user.drop('john')
diff --git a/test/box/session.result b/test/box/session.result
index a087de691b433742a6ee07ff1ef498d1a00e1957..49e15c219637e1000144c27056902024692a4954 100644
--- a/test/box/session.result
+++ b/test/box/session.result
@@ -224,3 +224,6 @@ fiber = nil
 session = nil
 ---
 ...
+box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+---
+...
diff --git a/test/box/session.test.lua b/test/box/session.test.lua
index da1bc63e8d42ef24ae9200e76fc632f8edbe39e6..367755ebd15afe13b67412bc60304dbc4654ec81 100644
--- a/test/box/session.test.lua
+++ b/test/box/session.test.lua
@@ -88,3 +88,4 @@ session.uid()
 session.user()
 fiber = nil
 session = nil
+box.schema.user.revoke('guest', 'read,write,execute', 'universe')
diff --git a/test/replication/cluster.result b/test/replication/cluster.result
index 8be88a719587137e3452a009ac329aa811736783..fc11db893f4e0c4a83194d7f10c6c14978ee663b 100644
--- a/test/replication/cluster.result
+++ b/test/replication/cluster.result
@@ -131,3 +131,6 @@ box.info.vclock[11]
 ---
 - 0
 ...
+box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+---
+...
diff --git a/test/replication/cluster.test.py b/test/replication/cluster.test.py
index aa41dc0a3010e5390e94d1723becd99020a2c54d..5beec7a8e80cfecfa15e353f476f0a93cf6eeed4 100644
--- a/test/replication/cluster.test.py
+++ b/test/replication/cluster.test.py
@@ -98,3 +98,4 @@ replica.admin('box.info.vclock[%d]' % replica_id3)
 # Cleanup
 sys.stdout.pop_filter()
 
+master.admin("box.schema.user.revoke('guest', 'read,write,execute', 'universe')")