diff --git a/extra/schema_fill.lua b/extra/schema_fill.lua
index a18adb532a2e1c1a7335b95a2c2ed60c986244df..126b3161aaa75569beb3a877a1924d36f0b4bc25 100644
--- a/extra/schema_fill.lua
+++ b/extra/schema_fill.lua
@@ -62,4 +62,10 @@ _index:insert{_cluster.id, 1, 'uuid', 'tree', 1, 1, 1, 'str'}
 _user:insert{GUEST, ADMIN, 'guest', 'user'}
 _user:insert{ADMIN, ADMIN, 'admin', 'user'}
 _user:insert{PUBLIC, ADMIN, 'public', 'role'}
+-- grant admin access to the universe
 _priv:insert{1, 1, 'universe', 0, 7}
+-- grant 'public' role access to 'box.schema.user.info' function
+_func:insert{1, 1, 'box.schema.user.info', 1}
+_priv:insert{1, 2, 'function', 1, 4}
+-- grant 'guest' role 'public'
+_priv:insert{1, 0, 'role', 2, 4}
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 1f55dc9fa71d4652884b98f656827b3e2ad4906e..cfafda8de106173f9362a2e051837516f4130df2 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -1494,12 +1494,16 @@ priv_def_check(struct priv_def *priv)
 				  role ? role->name :
 				  int2str(priv->object_id));
 		}
-		/* Only the creator of the role can grant it. */
-		if (role->owner != grantor->uid && grantor->uid != ADMIN) {
+		/* Only the creator of the role can grant or revoke it.
+		 * Everyone can grant 'PUBLIC' role.
+		 */
+		if (role->owner != grantor->uid && grantor->uid != ADMIN &&
+		    (role->uid != PUBLIC || priv->access < PRIV_X)) {
 			tnt_raise(ClientError, ER_ACCESS_DENIED,
 				  role->name, grantor->name);
 		}
-		role_check(role, grantee);
+		/* Not necessary to do during revoke, but who cares. */
+		role_check(grantee, role);
 	}
 	default:
 		break;
@@ -1555,10 +1559,8 @@ grant_or_revoke(struct priv_def *priv)
 	default:
 		break;
 	}
-	if (access) {
-		access[grantee->auth_token].granted = priv->access;
-		access[grantee->auth_token].effective = priv->access;
-	}
+	if (access)
+		privilege_grant(grantee, access, priv->access);
 }
 
 /** A trigger called on rollback of grant, or on commit of revoke. */
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index b429bfee3548f5bf6563426246cbce7b6a7302b2..b05c412abd7caacb3a4f7d12befa761f39e198e8 100644
Binary files a/src/box/bootstrap.snap and b/src/box/bootstrap.snap differ
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 694c77ba1c41e058bfb19aaf6057d8d3af710a2c..cf918b9f17058852e4a0f8ca511b6d3574160a2a 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -986,7 +986,9 @@ box.schema.user.create = function(name, opts)
         auth_mech_list["chap-sha1"] = box.schema.user.password(opts.password)
     end
     local _user = box.space[box.schema.USER_ID]
-    _user:auto_increment{session.uid(), name, 'user', auth_mech_list}
+    uid = _user:auto_increment{session.uid(), name, 'user', auth_mech_list}[1]
+    -- grant role 'public' to the user
+    box.schema.user.grant(uid, 'public')
 end
 
 box.schema.user.exists = function(name)
@@ -1132,10 +1134,22 @@ box.schema.role.drop = function(name)
     end
     return box.schema.user.drop(name)
 end
-box.schema.role.grant = function(user_name, role_name, grantor)
-    return box.schema.user.grant(user_name, 'execute', 'role', role_name, grantor)
+box.schema.role.grant = function(user_name, privilege, object_type,
+                                 object_name, grantor)
+    local uid = user_resolve(user_name)
+    if uid == nil then
+        box.error(box.error.NO_SUCH_ROLE, user_name)
+    end
+    return box.schema.user.grant(user_name, privilege, object_type,
+                                 object_name, grantor)
 end
-box.schema.role.revoke = function(user_name, role_name)
-    return box.schema.user.revoke(user_name, 'execute', 'role', role_name)
+box.schema.role.revoke = function(user_name, privilege, object_type,
+                                  object_name)
+    local uid = user_resolve(user_name)
+    if uid == nil then
+        box.error(box.error.NO_SUCH_ROLE, user_name)
+    end
+    return box.schema.user.revoke(user_name, privilege, object_type,
+                                  object_name)
 end
 box.schema.role.info = box.schema.user.info
diff --git a/src/box/user.cc b/src/box/user.cc
index 61cf2a5f86ba4bec52f82be20fb7dd29472e2434..2b7ae6e5dc5c845ccad39980a12e4d9241ef0946 100644
--- a/src/box/user.cc
+++ b/src/box/user.cc
@@ -83,6 +83,15 @@ user_map_is_set(struct user_map *map, uint8_t auth_token)
 	return map->m[idx] & (((umap_int_t) 1) << bit_no);
 }
 
+static inline bool
+user_map_is_empty(struct user_map *map)
+{
+	for (int i = 0; i < USER_MAP_SIZE; i++)
+		if (map->m[i])
+			return false;
+	return true;
+}
+
 /**
  * Merge two sets of users: add all users from right argument
  * to the left one.
@@ -291,7 +300,7 @@ user_cache_free()
 /** {{{ roles */
 
 void
-role_check(struct user *role, struct user *grantee)
+role_check(struct user *grantee, struct user *role)
 {
 	/*
 	 * Check that there is no loop from grantee to role:
@@ -303,7 +312,7 @@ role_check(struct user *role, struct user *grantee)
 	user_map_init(&transitive_closure);
 	user_map_set(&transitive_closure, grantee->auth_token);
 	struct user_map current_layer = transitive_closure;
-	while (true) {
+	while (! user_map_is_empty(&current_layer)) {
 		/*
 		 * As long as we're traversing a directed
 		 * acyclic graph, we're bound to end at some
@@ -311,17 +320,12 @@ role_check(struct user *role, struct user *grantee)
 		 */
 		struct user_map next_layer;
 		user_map_init(&next_layer);
-		bool found = false;
 		struct user_map_iterator it;
 		user_map_iterator_init(&it, &current_layer);
 		struct user *user;
-		while ((user = user_map_iterator_next(&it))) {
+		while ((user = user_map_iterator_next(&it)))
 			user_map_union(&next_layer, &user->users);
-			found = true;
-		}
 		user_map_union(&transitive_closure, &next_layer);
-		if (! found)
-			break;
 		current_layer = next_layer;
 	}
 
@@ -336,18 +340,59 @@ void
 role_grant(struct user *grantee, struct user *role)
 {
 	user_map_set(&role->users, grantee->auth_token);
+	/**
+	 * Todo: grant all effective privileges of
+	 * the role to whoever this role was granted
+	 * to.
+	 */
 }
 
 void
 role_revoke(struct user *grantee, struct user *role)
 {
 	user_map_clear(&role->users, grantee->auth_token);
+	/**
+	 * Todo: rebuild effective privileges of grantee,
+	 * for all effective privileges which he/she
+	 * might have inherited through the revoked role.
+	 */
 }
 
 void
-user_set_effective_access(struct user * /* grantee */,
-			  struct user * /* role */)
+privilege_grant(struct user *user,
+		struct access *object, uint8_t access)
 {
+	bool grant = access > object[user->auth_token].granted;
+	object[user->auth_token].granted = access;
+	if (grant) {
+		/*
+		 * Grant the privilege to this user or
+		 * role and all users to which this
+		 * role has been granted, if this is
+		 * a role.
+		 */
+		struct user_map current_layer;
+		user_map_init(&current_layer);
+		user_map_set(&current_layer, user->auth_token);
+		while (!user_map_is_empty(&current_layer)) {
+			struct user_map next_layer;
+			user_map_init(&next_layer);
+			struct user_map_iterator it;
+			user_map_iterator_init(&it, &current_layer);
+			while ((user = user_map_iterator_next(&it))) {
+				object[user->auth_token].effective |= access;
+				user_map_union(&next_layer, &user->users);
+			}
+			current_layer = next_layer;
+		}
+	} else {
+		/**
+		 * @fixme: this only works for users and
+		 * non-recursive roles
+		 */
+		object[user->auth_token].effective =
+			object[user->auth_token].granted;
+	}
 }
 
 /** }}} */
diff --git a/src/box/user.h b/src/box/user.h
index dfb0d551ab2931f2d79369a2a0705699f084a44e..5bfb4b63794b62cb167c8f341c82d630eb139fe1 100644
--- a/src/box/user.h
+++ b/src/box/user.h
@@ -124,7 +124,7 @@ user_cache_free();
  * a given role.
  */
 void
-role_check(struct user *role, struct user *grantee);
+role_check(struct user *grantee, struct user *role);
 
 /**
  * Grant a role to a user or another role.
@@ -139,12 +139,13 @@ void
 role_revoke(struct user *grantee, struct user *role);
 
 /**
- * Re-evaluate effective access of a user based on this role
- * that is granted to him/her.
- * The role has its effective access already re-evaluated.
+ * Grant or revoke a single privilege to a user or role
+ * and re-evaluate effective access of all users of this
+ * role if this role.
  */
 void
-user_set_effective_access(struct user *grantee, struct user *role);
+privilege_grant(struct user *grantee, struct access *object,
+		uint8_t access);
 
 /* }}} */
 
diff --git a/test/box/access.result b/test/box/access.result
index 56ff7199447ce591bf06a8b9b6ad6d8385507fa5..975ddea6cc2c035cb9871db15e6664b6f844c0a8 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -117,6 +117,9 @@ box.space['_user']:delete{uid}
 box.schema.user.revoke('rich', 'read,write', 'universe')
 ---
 ...
+box.schema.user.revoke('rich', 'public')
+---
+...
 box.space['_user']:delete{uid}
 ---
 - [4, 1, 'rich', 'user', []]
@@ -352,42 +355,47 @@ box.schema.user.grant('user', 'read,write', 'universe')
 ...
 box.space._priv:select{id}
 ---
-- - [1, 3, 'universe', 0, 3]
+- - [1, 3, 'role', 2, 4]
+  - [1, 3, 'universe', 0, 3]
 ...
 box.schema.user.grant('user', 'read', 'universe')
 ---
 ...
 box.space._priv:select{id}
 ---
-- - [1, 3, 'universe', 0, 3]
+- - [1, 3, 'role', 2, 4]
+  - [1, 3, 'universe', 0, 3]
 ...
 box.schema.user.revoke('user', 'write', 'universe')
 ---
 ...
 box.space._priv:select{id}
 ---
-- - [1, 3, 'universe', 0, 1]
+- - [1, 3, 'role', 2, 4]
+  - [1, 3, 'universe', 0, 1]
 ...
 box.schema.user.revoke('user', 'read', 'universe')
 ---
 ...
 box.space._priv:select{id}
 ---
-- []
+- - [1, 3, 'role', 2, 4]
 ...
 box.schema.user.grant('user', 'write', 'universe')
 ---
 ...
 box.space._priv:select{id}
 ---
-- - [1, 3, 'universe', 0, 2]
+- - [1, 3, 'role', 2, 4]
+  - [1, 3, 'universe', 0, 2]
 ...
 box.schema.user.grant('user', 'read', 'universe')
 ---
 ...
 box.space._priv:select{id}
 ---
-- - [1, 3, 'universe', 0, 3]
+- - [1, 3, 'role', 2, 4]
+  - [1, 3, 'universe', 0, 3]
 ...
 box.schema.user.drop('user')
 ---
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index bdbc654a41470bcbba9c67b1984b94330b9c19ae..f0cd9f6d2e938193cc7ba62c86e1df6e292226f8 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -56,6 +56,7 @@ box.space['_user']:delete{uid}
 box.schema.func.drop('dummy')
 box.space['_user']:delete{uid}
 box.schema.user.revoke('rich', 'read,write', 'universe')
+box.schema.user.revoke('rich', 'public')
 box.space['_user']:delete{uid}
 box.schema.user.drop('test')
 
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index fedbfeb0f1702f37efcc2b2790482cfbd832b776..27e7d7afb79cdceb16323e25ce36051465f2b08f 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -580,7 +580,7 @@ box.space._space:select()
 ...
 box.space._func:select()
 ---
-- []
+- - [1, 1, 'box.schema.user.info', 1]
 ...
 session = nil
 ---
diff --git a/test/box/bootstrap.result b/test/box/bootstrap.result
index 670fab9ff86cb24ef0e53e7f1c082742492de37f..d42ed4f1823a39acc9b6088e2caf080b5a1b13ee 100644
--- a/test/box/bootstrap.result
+++ b/test/box/bootstrap.result
@@ -83,9 +83,11 @@ box.space._user:select{}
 ...
 box.space._func:select{}
 ---
-- []
+- - [1, 1, 'box.schema.user.info', 1]
 ...
 box.space._priv:select{}
 ---
-- - [1, 1, 'universe', 0, 7]
+- - [1, 0, 'role', 2, 4]
+  - [1, 1, 'universe', 0, 7]
+  - [1, 2, 'function', 1, 4]
 ...
diff --git a/test/box/role.result b/test/box/role.result
index 3114636230cd695cd1fe10911ecda9c767a59a94..ea2e0877d5819397d6471b4456a91e2b5fa31681 100644
--- a/test/box/role.result
+++ b/test/box/role.result
@@ -23,15 +23,15 @@ box.session.su('iddqd')
 -- test granting privilege to a role
 box.schema.role.grant('iddqd', 'execute', 'universe')
 ---
-- error: Role 'execute' is not found
 ...
 box.schema.role.info('iddqd')
 ---
-- []
+- - - execute
+    - universe
+    - 
 ...
 box.schema.role.revoke('iddqd', 'execute', 'universe')
 ---
-- error: Role 'execute' is not found
 ...
 box.schema.role.info('iddqd')
 ---
@@ -43,7 +43,9 @@ box.schema.user.create('tester')
 ...
 box.schema.user.info('tester')
 ---
-- []
+- - - execute
+    - role
+    - public
 ...
 box.schema.user.grant('tester', 'execute', 'role', 'iddqd')
 ---
@@ -51,6 +53,9 @@ box.schema.user.grant('tester', 'execute', 'role', 'iddqd')
 box.schema.user.info('tester')
 ---
 - - - execute
+    - role
+    - public
+  - - execute
     - role
     - iddqd
 ...
@@ -154,3 +159,79 @@ box.schema.user.info('b')
 box.schema.role.drop('b')
 ---
 ...
+-- check a grant received via a role
+box.schema.user.create('test')
+---
+...
+box.schema.user.create('grantee')
+---
+...
+box.schema.role.create('liaison')
+---
+...
+box.schema.user.grant('grantee', 'liaison')
+---
+...
+box.schema.user.grant('test', 'read,write', 'universe')
+---
+...
+box.session.su('test')
+---
+...
+s = box.schema.space.create('test')
+---
+...
+s:create_index('i1')
+---
+- unique: true
+  parts:
+  - type: NUM
+    fieldno: 1
+  id: 0
+  space_id: 512
+  name: i1
+  type: TREE
+...
+box.schema.role.grant('liaison', 'read,write', 'space', 'test')
+---
+...
+box.session.su('grantee')
+---
+...
+box.space.test:insert{1}
+---
+- [1]
+...
+box.space.test:select{1}
+---
+- - [1]
+...
+box.session.su('test')
+---
+...
+box.schema.user.revoke('liaison', 'read,write', 'space', 'test')
+---
+...
+box.session.su('grantee')
+---
+...
+box.space.test:insert{1}
+---
+- error: Duplicate key exists in unique index 0
+...
+box.space.test:select{1}
+---
+- - [1]
+...
+box.session.su('admin')
+---
+...
+box.schema.user.drop('test')
+---
+...
+box.schema.user.drop('grantee')
+---
+...
+box.schema.user.drop('liaison')
+---
+...
diff --git a/test/box/role.test.lua b/test/box/role.test.lua
index 33e5039248f7d7c502439571190213985a9e9226..5ec9efe38e7385c0141b72729e21d140e3753e7a 100644
--- a/test/box/role.test.lua
+++ b/test/box/role.test.lua
@@ -50,3 +50,26 @@ box.schema.user.grant('b', 'a')
 box.schema.role.drop('a')
 box.schema.user.info('b')
 box.schema.role.drop('b')
+-- check a grant received via a role
+box.schema.user.create('test')
+box.schema.user.create('grantee')
+box.schema.role.create('liaison')
+box.schema.user.grant('grantee', 'liaison')
+box.schema.user.grant('test', 'read,write', 'universe')
+box.session.su('test')
+s = box.schema.space.create('test')
+s:create_index('i1')
+box.schema.role.grant('liaison', 'read,write', 'space', 'test')
+box.session.su('grantee')
+box.space.test:insert{1}
+box.space.test:select{1}
+box.session.su('test')
+box.schema.user.revoke('liaison', 'read,write', 'space', 'test')
+box.session.su('grantee')
+box.space.test:insert{1}
+box.space.test:select{1}
+box.session.su('admin')
+box.schema.user.drop('test')
+box.schema.user.drop('grantee')
+box.schema.user.drop('liaison')
+
diff --git a/test/wal/func_max.result b/test/wal/func_max.result
index 88f4d84ab146f3007f77415ea05162831b93704d..f52366ec990e37f98c0662532c9873ef5ffceeb4 100644
--- a/test/wal/func_max.result
+++ b/test/wal/func_max.result
@@ -33,7 +33,7 @@ func_limit();
 ...
 drop_limit_func();
 ---
-- error: Function 'func32001' does not exist
+- error: Function 'func32000' does not exist
 ...
 box.schema.user.create('testuser');
 ---
@@ -50,7 +50,7 @@ func_limit();
 ...
 drop_limit_func();
 ---
-- error: Function 'func32001' does not exist
+- error: Function 'func32000' does not exist
 ...
 session.su('admin')
 box.schema.user.drop('testuser');