From 405228017c9f0fe2ef47eee998953604e33f6350 Mon Sep 17 00:00:00 2001
From: Konstantin Osipov <kostja@tarantool.org>
Date: Mon, 1 Dec 2014 19:26:50 +0300
Subject: [PATCH] Implement a grant of a privilege to a role.

Introduce 'public' role.
Auto-grant 'public' to every user.
Update the snapshot.
---
 extra/schema_fill.lua       |   6 +++
 src/box/alter.cc            |  16 ++++---
 src/box/bootstrap.snap      | Bin 1781 -> 1926 bytes
 src/box/lua/schema.lua      |  24 ++++++++--
 src/box/user.cc             |  65 ++++++++++++++++++++++----
 src/box/user.h              |  11 +++--
 test/box/access.result      |  20 +++++---
 test/box/access.test.lua    |   1 +
 test/box/access_misc.result |   2 +-
 test/box/bootstrap.result   |   6 ++-
 test/box/role.result        |  89 ++++++++++++++++++++++++++++++++++--
 test/box/role.test.lua      |  23 ++++++++++
 test/wal/func_max.result    |   4 +-
 13 files changed, 225 insertions(+), 42 deletions(-)

diff --git a/extra/schema_fill.lua b/extra/schema_fill.lua
index a18adb532a..126b3161aa 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 1f55dc9fa7..cfafda8de1 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
GIT binary patch
delta 643
zcmey$+s0oX?B^K3WuRwh#1))cRF+y~rC^bgY?_vAW~6IiW^Al$Vs37vYhh-Ps%vCn
zm~3vAmX?&7X2ccdoRgoNZKY6cXr*9K%f)qd7x!u#hI5Ort}t0100B)5Ow5c;0_PYQ
z7zGq3El<uVEiOqdx~Y<CVQ6A$l5D7(W{_y1rE6klmZWQ$mSUuvYH4g?o|a;6nQCeR
zGEaix-0#Q6H(};YT&cniHG1L|T^MsR2cr(0ZH>gvW7LL=FF;~nLa;ZpFnwV5Rbx1}
z_TMzw<w(w!hB#Y8aS9{jmZba&z2fAI)Z9e9(&E%2z0AC{d`6J_q#4fX_2jNY)+jTX
zgH5(RG2$32vZyS?I0KMzOA}LaGxL^!Oahr9!*Fhf>B|&k)pBsvOpMD4N|SOjlb00b
z=cIy^i!+=v;dYWhRxS@wZlO4pkpZlZsh$Pux(8>1)*~xbKqzHgQJR+tjPBx826m|G
z?|17aBdb<KsAgJ`R+^Vwl9`{!2sYc4;aura1C*Fnf~a;-oW!VgQ-zo`bX8#OTL7pu
B%_sl>

delta 450
zcmZqU|H@k*?B^K3WuRwh#1))cRF+y~rI2ctnv!U0Vyp|K%ymr?EsS(6lG03c%~MiR
zOiYXtEse}gxx$=t@{_Zz6sira6bx#)xUTNvUTwp0PEJCwb-6MGG%+wSGd2mFV_;wu
zP@J?pIj6L^B(><K3ebeaRLdj-T>}$CLoHns!(=mE%e16a-Bc4pOT)y(L<2)3V~}|g
z4Cj77HogfnZ{kW7_5+)F>lql1OuVAY?xGInOy*$JVb4v?EvaB&m~0JXGeE%PJVtGH
zhU1L~e(}~%UI1i+7(l7Xmq6kWu$hJF1G9)U!?~pB)8Cf|Kmf?u(vuz8WCcaK&1{iH
zWhUnXMT0qSup^7gPM*sqT?RB{%i7ydkR{|Gez#DZ%E-8)G%phvxW%aq>>!_+GMsz(
aD&#-1YI%ri2gON@H&uv<f~x{+-vR(^O@|Wz

diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 694c77ba1c..cf918b9f17 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 61cf2a5f86..2b7ae6e5dc 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 dfb0d551ab..5bfb4b6379 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 56ff719944..975ddea6cc 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 bdbc654a41..f0cd9f6d2e 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 fedbfeb0f1..27e7d7afb7 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 670fab9ff8..d42ed4f182 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 3114636230..ea2e0877d5 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 33e5039248..5ec9efe38e 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 88f4d84ab1..f52366ec99 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');
-- 
GitLab