[tarantool-patches] [PATCH v2 3/4] Add single object privilege checks to access_check_ddl.

Serge Petrenko sergepetrenko at tarantool.org
Tue Jul 17 19:08:40 MSK 2018


access_check_ddl() didn't check for single object privileges, e.g. user
with alter access on a space couldn't create an index in this space. It
would only succeed if it had alter on entire entity space.
Fix this by adding single object privilege checks to access_check_ddl and
adding access cache to struct user, to hold other users' privileges on it.

Also checking for single object privilege made it possible to grant
every user alter privilege on itself, so that a user may change its own
password (previously it was possible because of a hack). Removed the
hack, and added grant alter to itself upon user creation.
Modified tests accordingly, and added a couple of test cases.

Closes #3530
---
 src/box/alter.cc         | 123 ++++++++++++++++++++++----------
 src/box/lua/schema.lua   |   5 +-
 src/box/user.cc          |  10 ++-
 src/box/user.h           |   2 +
 test/box/access.result   | 182 +++++++++++++++++++++++++++++++++++++++++++++++
 test/box/access.test.lua |  53 ++++++++++++++
 test/box/role.result     |   9 +++
 test/box/sequence.result |   3 +
 8 files changed, 347 insertions(+), 40 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 6293dcc50..54a09664b 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -62,7 +62,8 @@
 /* {{{ Auxiliary functions and methods. */
 
 static void
-access_check_ddl(const char *name, uint32_t owner_uid,
+access_check_ddl(const char *name, uint32_t object_id,
+		 uint32_t owner_uid,
 		 enum schema_object_type type,
 		 enum priv_type priv_type,
 		 bool is_17_compat_mode)
@@ -103,7 +104,48 @@ access_check_ddl(const char *name, uint32_t owner_uid,
 	 */
 	if (access == 0 || (is_owner && !(access & (PRIV_U | PRIV_C))))
 		return; /* Access granted. */
-
+	/*
+	 * You can't grant CREATE privilege to a non-existing object.
+	 * USAGE can be granted only globally.
+	 */
+	if (!(access & (PRIV_U | PRIV_C))) {
+		/* Check for privileges on a single object. */
+		switch (type) {
+		case SC_SPACE:
+		{
+			struct space *space = space_by_id(object_id);
+			if (space)
+				access &= ~space->access[cr->auth_token].effective;
+			break;
+		}
+		case SC_FUNCTION:
+		{
+			struct func *func = func_by_id(object_id);
+			if (func)
+				access &= ~func->access[cr->auth_token].effective;
+			break;
+		}
+		case SC_USER:
+		case SC_ROLE:
+		{
+			struct user *user_or_role = user_by_id(object_id);
+			if (user_or_role)
+				access &= ~user_or_role->access[cr->auth_token].effective;
+			break;
+		}
+		case SC_SEQUENCE:
+		{
+			struct sequence *seq = sequence_by_id(object_id);
+			if (seq)
+				access &= ~seq->access[cr->auth_token].effective;
+			break;
+		}
+		default:
+			break;
+		}
+	}
+	if (access == 0)
+	    return; /* Access granted. */
 	/* Create a meaningful error message. */
 	struct user *user = user_find_xc(cr->uid);
 	const char *object_name;
@@ -1590,7 +1632,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		struct space_def *def =
 			space_def_new_from_tuple(new_tuple, ER_CREATE_SPACE,
 						 region);
-		access_check_ddl(def->name, def->uid, SC_SPACE, PRIV_C, true);
+		access_check_ddl(def->name, def->id, def->uid, SC_SPACE,
+				 PRIV_C, true);
 		auto def_guard =
 			make_scoped_guard([=] { space_def_delete(def); });
 		RLIST_HEAD(empty_list);
@@ -1623,8 +1666,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 			txn_alter_trigger_new(on_create_space_rollback, space);
 		txn_on_rollback(txn, on_rollback);
 	} else if (new_tuple == NULL) { /* DELETE */
-		access_check_ddl(old_space->def->name, old_space->def->uid,
-				 SC_SPACE, PRIV_D, true);
+		access_check_ddl(old_space->def->name, old_space->def->id,
+				 old_space->def->uid, SC_SPACE, PRIV_D, true);
 		/* Verify that the space is empty (has no indexes) */
 		if (old_space->index_count) {
 			tnt_raise(ClientError, ER_DROP_SPACE,
@@ -1669,7 +1712,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		struct space_def *def =
 			space_def_new_from_tuple(new_tuple, ER_ALTER_SPACE,
 						 region);
-		access_check_ddl(def->name, def->uid, SC_SPACE, PRIV_A, true);
+		access_check_ddl(def->name, def->id, def->uid, SC_SPACE,
+				 PRIV_A, true);
 		auto def_guard =
 			make_scoped_guard([=] { space_def_delete(def); });
 		if (def->id != space_id(old_space))
@@ -1773,8 +1817,8 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	struct space *old_space = space_cache_find_xc(id);
 	bool have_alter = true;
 	try {
-		access_check_ddl(old_space->def->name, old_space->def->uid, SC_SPACE,
-				 PRIV_A, true);
+		access_check_ddl(old_space->def->name, old_space->def->id,
+				 old_space->def->uid, SC_SPACE, PRIV_A, true);
 	} catch(AccessDeniedError *e) {
 		/*
 		 * We need alter access on space in case of creating or
@@ -1847,7 +1891,8 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 		 * dropping an index, check for drop on space.
 		 */
 		if (!have_alter)
-		    access_check_ddl(old_space->def->name, old_space->def->uid,
+		    access_check_ddl(old_space->def->name, old_space->def->id,
+				     old_space->def->uid,
 				     SC_SPACE, PRIV_D, true);
 		alter_space_move_indexes(alter, 0, iid);
 		(void) new DropIndex(alter, old_index->def);
@@ -2186,7 +2231,8 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 	struct user *old_user = user_by_id(uid);
 	if (new_tuple != NULL && old_user == NULL) { /* INSERT */
 		struct user_def *user = user_def_new_from_tuple(new_tuple);
-		access_check_ddl(user->name, user->owner, user->type, PRIV_C, true);
+		access_check_ddl(user->name, user->uid, user->owner, user->type,
+				 PRIV_C, true);
 		auto def_guard = make_scoped_guard([=] { free(user); });
 		(void) user_cache_replace(user);
 		def_guard.is_active = false;
@@ -2194,7 +2240,8 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 			txn_alter_trigger_new(user_cache_remove_user, NULL);
 		txn_on_rollback(txn, on_rollback);
 	} else if (new_tuple == NULL) { /* DELETE */
-		access_check_ddl(old_user->def->name, old_user->def->owner,
+		access_check_ddl(old_user->def->name, old_user->def->uid,
+				 old_user->def->owner,
 				 old_user->def->type, PRIV_D, true);
 		/* Can't drop guest or super user */
 		if (uid <= (uint32_t) BOX_SYSTEM_USER_ID_MAX || uid == SUPER) {
@@ -2229,8 +2276,8 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 			tnt_raise(AccessDeniedError, "alter", "user or role",
 				  old_user->def->name, current_user->def->name);
 		}
-		access_check_ddl(user->name, user->uid, SC_USER, PRIV_A,
-				 true);
+		access_check_ddl(user->name, user->uid, user->owner, SC_USER,
+				 PRIV_A, true);
 		auto def_guard = make_scoped_guard([=] { free(user); });
 		struct trigger *on_commit =
 			txn_alter_trigger_new(user_cache_alter_user, NULL);
@@ -2332,7 +2379,8 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 	struct func *old_func = func_by_id(fid);
 	if (new_tuple != NULL && old_func == NULL) { /* INSERT */
 		struct func_def *def = func_def_new_from_tuple(new_tuple);
-		access_check_ddl(def->name, def->uid, SC_FUNCTION, PRIV_C, true);
+		access_check_ddl(def->name, def->fid, def->uid, SC_FUNCTION,
+				 PRIV_C, true);
 		auto def_guard = make_scoped_guard([=] { free(def); });
 		func_cache_replace(def);
 		def_guard.is_active = false;
@@ -2346,7 +2394,7 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 		 * Can only delete func if you're the one
 		 * who created it or a superuser.
 		 */
-		access_check_ddl(old_func->def->name, uid, SC_FUNCTION,
+		access_check_ddl(old_func->def->name, fid, uid, SC_FUNCTION,
 				 PRIV_D, true);
 		/* Can only delete func if it has no grants. */
 		if (schema_find_grants("function", old_func->def->fid)) {
@@ -2360,8 +2408,8 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 	} else {                                /* UPDATE, REPLACE */
 		struct func_def *def = func_def_new_from_tuple(new_tuple);
 		auto def_guard = make_scoped_guard([=] { free(def); });
-		access_check_ddl(def->name, def->uid, SC_FUNCTION, PRIV_A,
-				 true);
+		access_check_ddl(def->name, def->fid, def->uid,
+				 SC_FUNCTION, PRIV_A, true);
 		struct trigger *on_commit =
 			txn_alter_trigger_new(func_cache_replace_func, NULL);
 		txn_on_commit(txn, on_commit);
@@ -2517,8 +2565,9 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event)
 						    BOX_COLLATION_FIELD_ID);
 		struct coll_id *old_coll_id = coll_by_id(old_id);
 		assert(old_coll_id != NULL);
-		access_check_ddl(old_coll_id->name, old_coll_id->owner_id,
-				 SC_COLLATION, PRIV_D, false);
+		access_check_ddl(old_coll_id->name, old_id,
+				 old_coll_id->owner_id, SC_COLLATION, PRIV_D,
+				 false);
 		/*
 		 * Set on_commit/on_rollback triggers after
 		 * deletion from the cache to make trigger logic
@@ -2533,8 +2582,8 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event)
 		/* INSERT */
 		struct coll_id_def new_def;
 		coll_id_def_new_from_tuple(new_tuple, &new_def);
-		access_check_ddl(new_def.name, new_def.owner_id, SC_COLLATION,
-				 PRIV_C, false);
+		access_check_ddl(new_def.name, new_def.id, new_def.owner_id,
+				 SC_COLLATION, PRIV_C, false);
 		struct coll_id *new_coll_id = coll_id_new(&new_def);
 		if (new_coll_id == NULL)
 			diag_raise();
@@ -2593,8 +2642,8 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type)
 			  int2str(priv->grantee_id));
 	}
 	const char *name = schema_find_name(priv->object_type, priv->object_id);
-	access_check_ddl(name, grantor->def->uid, priv->object_type, priv_type,
-			 false);
+	access_check_ddl(name, priv->object_id, grantor->def->uid,
+			 priv->object_type, priv_type, false);
 	switch (priv->object_type) {
 	case SC_UNIVERSE:
 		if (grantor->def->uid != ADMIN) {
@@ -3090,8 +3139,8 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event)
 		new_def = sequence_def_new_from_tuple(new_tuple,
 						      ER_CREATE_SEQUENCE);
 		assert(sequence_by_id(new_def->id) == NULL);
-		access_check_ddl(new_def->name, new_def->uid, SC_SEQUENCE,
-				 PRIV_C, false);
+		access_check_ddl(new_def->name, new_def->id, new_def->uid,
+				 SC_SEQUENCE, PRIV_C, false);
 		sequence_cache_replace(new_def);
 		alter->new_def = new_def;
 	} else if (old_tuple != NULL && new_tuple == NULL) {	/* DELETE */
@@ -3099,8 +3148,8 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event)
 						 BOX_SEQUENCE_DATA_FIELD_ID);
 		struct sequence *seq = sequence_by_id(id);
 		assert(seq != NULL);
-		access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE,
-				 PRIV_D, false);
+		access_check_ddl(seq->def->name, seq->def->id, seq->def->uid,
+				 SC_SEQUENCE, PRIV_D, false);
 		if (space_has_data(BOX_SEQUENCE_DATA_ID, 0, id))
 			tnt_raise(ClientError, ER_DROP_SEQUENCE,
 				  seq->def->name, "the sequence has data");
@@ -3116,8 +3165,8 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event)
 						      ER_ALTER_SEQUENCE);
 		struct sequence *seq = sequence_by_id(new_def->id);
 		assert(seq != NULL);
-		access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE,
-				 PRIV_A, false);
+		access_check_ddl(seq->def->name, seq->def->id, seq->def->uid,
+				 SC_SEQUENCE, PRIV_A, false);
 		alter->old_def = seq->def;
 		alter->new_def = new_def;
 	}
@@ -3196,21 +3245,21 @@ on_replace_dd_space_sequence(struct trigger * /* trigger */, void *event)
 		priv_type = PRIV_A;
 	/* Check we have the correct access type on the sequence.  * */
 	if (is_generated || !stmt->new_tuple) {
-		access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE,
-				 priv_type, false);
+		access_check_ddl(seq->def->name, seq->def->id, seq->def->uid,
+				 SC_SEQUENCE, priv_type, false);
 	} else {
 		/*
 		 * In case user wants to attach an existing sequence,
 		 * check that it has read and write access.
 		 */
-		access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE,
-				 PRIV_R, false);
-		access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE,
-				 PRIV_W, false);
+		access_check_ddl(seq->def->name, seq->def->id, seq->def->uid,
+				 SC_SEQUENCE, PRIV_R, false);
+		access_check_ddl(seq->def->name, seq->def->id, seq->def->uid,
+				 SC_SEQUENCE, PRIV_W, false);
 	}
 	/** Check we have alter access on space. */
-	access_check_ddl(space->def->name, space->def->uid, SC_SPACE, PRIV_A,
-			 false);
+	access_check_ddl(space->def->name, space->def->id, space->def->uid,
+			 SC_SPACE, PRIV_A, false);
 
 	struct trigger *on_commit =
 		txn_alter_trigger_new(on_commit_dd_space_sequence, space);
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 4b7a14411..a098e44fe 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1740,7 +1740,7 @@ local priv_object_combo = {
     ["role"]     = bit.bor(box.priv.X, box.priv.U,
                            box.priv.C, box.priv.D),
     ["user"]	 = bit.bor(box.priv.C, box.priv.U,
-                           box.priv.D),
+                           box.priv.A, box.priv.D),
 }
 
 --
@@ -2056,6 +2056,9 @@ box.schema.user.create = function(name, opts)
     uid = _user:auto_increment{session.euid(), name, 'user', auth_mech_list}[1]
     -- grant role 'public' to the user
     box.schema.user.grant(uid, 'public')
+    -- grant user 'alter' on itself, so it can
+    -- change its password or username.
+    box.schema.user.grant(uid, 'alter', 'user', uid)
     -- we have to grant global privileges from setuid function, since
     -- only admin has the ownership over universe and we don't have
     -- grant option
diff --git a/src/box/user.cc b/src/box/user.cc
index 4edef1d5f..36e29f478 100644
--- a/src/box/user.cc
+++ b/src/box/user.cc
@@ -241,7 +241,10 @@ access_find(struct priv_def *priv)
 			access = entity_access.user;
 			break;
 		}
-		/* No grants on a single object user yet. */
+		struct user *user = user_by_id(priv->object_id);
+		if (user)
+			access = user->access;
+		break;
 	}
 	case SC_ROLE:
 	{
@@ -250,7 +253,10 @@ access_find(struct priv_def *priv)
 			access = entity_access.role;
 			break;
 		}
-		/* No grants on a single object role yet. */
+		struct user *role = user_by_id(priv->object_id);
+		if (role)
+			access = role->access;
+		break;
 	}
 	case SC_SEQUENCE:
 	{
diff --git a/src/box/user.h b/src/box/user.h
index 07c4dc504..069d9b77e 100644
--- a/src/box/user.h
+++ b/src/box/user.h
@@ -88,6 +88,8 @@ struct user
 	bool is_dirty;
 	/** Memory pool for privs */
 	struct region pool;
+	/** Cached runtime access imformation. */
+	struct access access[BOX_USER_MAX];
 };
 
 /** Find user by id. */
diff --git a/test/box/access.result b/test/box/access.result
index 31095aec2..9ea5568a5 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -136,6 +136,9 @@ box.schema.user.revoke('rich', 'read,write', 'universe')
 box.schema.user.revoke('rich', 'public')
 ---
 ...
+box.schema.user.revoke('rich', 'alter', 'user', 'rich')
+---
+...
 box.schema.user.disable("rich")
 ---
 ...
@@ -501,6 +504,7 @@ box.space._priv:select{id}
 ---
 - - [1, 32, 'role', 2, 4]
   - [1, 32, 'universe', 0, 27]
+  - [1, 32, 'user', 32, 128]
 ...
 box.schema.user.grant('user', 'read', 'universe')
 ---
@@ -510,6 +514,7 @@ box.space._priv:select{id}
 ---
 - - [1, 32, 'role', 2, 4]
   - [1, 32, 'universe', 0, 27]
+  - [1, 32, 'user', 32, 128]
 ...
 box.schema.user.revoke('user', 'write', 'universe')
 ---
@@ -518,6 +523,7 @@ box.space._priv:select{id}
 ---
 - - [1, 32, 'role', 2, 4]
   - [1, 32, 'universe', 0, 25]
+  - [1, 32, 'user', 32, 128]
 ...
 box.schema.user.revoke('user', 'read', 'universe')
 ---
@@ -526,6 +532,7 @@ box.space._priv:select{id}
 ---
 - - [1, 32, 'role', 2, 4]
   - [1, 32, 'universe', 0, 24]
+  - [1, 32, 'user', 32, 128]
 ...
 box.schema.user.grant('user', 'write', 'universe')
 ---
@@ -534,6 +541,7 @@ box.space._priv:select{id}
 ---
 - - [1, 32, 'role', 2, 4]
   - [1, 32, 'universe', 0, 26]
+  - [1, 32, 'user', 32, 128]
 ...
 box.schema.user.grant('user', 'read', 'universe')
 ---
@@ -542,6 +550,7 @@ box.space._priv:select{id}
 ---
 - - [1, 32, 'role', 2, 4]
   - [1, 32, 'universe', 0, 27]
+  - [1, 32, 'user', 32, 128]
 ...
 box.schema.user.drop('user')
 ---
@@ -965,6 +974,9 @@ box.schema.user.info('test_user')
   - - session,usage
     - universe
     - 
+  - - alter
+    - user
+    - test_user
 ...
 box.schema.role.info('test_role')
 ---
@@ -995,6 +1007,9 @@ box.schema.user.info('test_user')
   - - session,usage
     - universe
     - 
+  - - alter
+    - user
+    - test_user
 ...
 box.schema.role.info('test_role')
 ---
@@ -1860,3 +1875,170 @@ box.session.su('admin')
 box.schema.user.drop('tester')
 ---
 ...
+--
+-- test case for 3530: do not ignore single object privileges in
+-- access_check_ddl.
+--
+box.schema.user.create("test")
+---
+...
+_ = box.schema.space.create("space1")
+---
+...
+box.schema.user.grant("test", "read", "space", "space1")
+---
+...
+box.schema.user.grant("test", "write", "space", "_index")
+---
+...
+box.session.su("test")
+---
+...
+box.space.space1:create_index("pk")
+---
+- error: Alter access to space 'space1' is denied for user 'test'
+...
+box.session.su("admin")
+---
+...
+box.space.space1.index[0] == nil
+---
+- true
+...
+box.schema.user.grant("test", "alter", "space", "space1")
+---
+...
+box.session.su("test")
+---
+...
+_ = box.space.space1:create_index("pk")
+---
+...
+box.space.space1:insert{5}
+---
+- error: Write access to space 'space1' is denied for user 'test'
+...
+box.session.su("admin")
+---
+...
+box.space.space1.index[0] ~= nil
+---
+- true
+...
+box.space.space1:select{}
+---
+- []
+...
+box.schema.user.grant("test", "write", "space", "space1")
+---
+...
+box.session.su("test")
+---
+...
+box.space.space1:insert{5}
+---
+- [5]
+...
+box.session.su("admin")
+---
+...
+box.space.space1:select{}
+---
+- - [5]
+...
+box.schema.user.drop("test")
+---
+...
+box.space.space1:drop()
+---
+...
+--
+-- test that it is possible to grant privileges on a single user.
+box.schema.user.create("user1")
+---
+...
+box.schema.user.create("user2")
+---
+...
+box.schema.user.create("user3")
+---
+...
+box.schema.user.grant("user1", "write", "space", "_user")
+---
+...
+box.schema.user.grant("user1", "read", "space", "_user")
+---
+...
+box.space._user:select{}
+---
+- - [0, 1, 'guest', 'user', {'chap-sha1': 'vhvewKp0tNyweZQ+cFKAlsyphfg='}]
+  - [1, 1, 'admin', 'user', {}]
+  - [2, 1, 'public', 'role', {}]
+  - [3, 1, 'replication', 'role', {}]
+  - [31, 1, 'super', 'role', {}]
+  - [32, 1, 'user1', 'user', {}]
+  - [33, 1, 'user2', 'user', {}]
+  - [34, 1, 'user3', 'user', {}]
+...
+box.session.su("user1")
+---
+...
+-- can alter itself, but can't alter others without privileges.
+box.schema.user.passwd("user1", "abcd")
+---
+...
+box.schema.user.passwd("user2", "abcd")
+---
+- error: Alter access to user 'user2' is denied for user 'user1'
+...
+box.session.su("admin")
+---
+...
+box.space._user:select{}
+---
+- - [0, 1, 'guest', 'user', {'chap-sha1': 'vhvewKp0tNyweZQ+cFKAlsyphfg='}]
+  - [1, 1, 'admin', 'user', {}]
+  - [2, 1, 'public', 'role', {}]
+  - [3, 1, 'replication', 'role', {}]
+  - [31, 1, 'super', 'role', {}]
+  - [32, 1, 'user1', 'user', {'chap-sha1': 'oVTFJWXp5/lL/Aih/nAmJO2O/9o='}]
+  - [33, 1, 'user2', 'user', {}]
+  - [34, 1, 'user3', 'user', {}]
+...
+box.schema.user.grant("user1", "alter", "user", "user2")
+---
+...
+box.session.su("user1")
+---
+...
+box.schema.user.passwd("user2", "abcd")
+---
+...
+-- still fails
+box.schema.user.passwd("user3", "qewr")
+---
+- error: Alter access to user 'user3' is denied for user 'user1'
+...
+box.session.su("admin")
+---
+...
+box.space._user:select{}
+---
+- - [0, 1, 'guest', 'user', {'chap-sha1': 'vhvewKp0tNyweZQ+cFKAlsyphfg='}]
+  - [1, 1, 'admin', 'user', {}]
+  - [2, 1, 'public', 'role', {}]
+  - [3, 1, 'replication', 'role', {}]
+  - [31, 1, 'super', 'role', {}]
+  - [32, 1, 'user1', 'user', {'chap-sha1': 'oVTFJWXp5/lL/Aih/nAmJO2O/9o='}]
+  - [33, 1, 'user2', 'user', {'chap-sha1': 'oVTFJWXp5/lL/Aih/nAmJO2O/9o='}]
+  - [34, 1, 'user3', 'user', {}]
+...
+box.schema.user.drop("user1")
+---
+...
+box.schema.user.drop("user2")
+---
+...
+box.schema.user.drop("user3")
+---
+...
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index 9b7510e64..6ad7ee462 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -60,6 +60,7 @@ 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.schema.user.revoke('rich', 'alter', 'user', 'rich')
 box.schema.user.disable("rich")
 -- test double disable is a no op
 box.schema.user.disable("rich")
@@ -726,3 +727,55 @@ _ = box.schema.sequence.create('test_sequence')
 box.session.su('admin')
 box.schema.user.drop('tester')
 
+
+--
+-- test case for 3530: do not ignore single object privileges in
+-- access_check_ddl.
+--
+box.schema.user.create("test")
+_ = box.schema.space.create("space1")
+box.schema.user.grant("test", "read", "space", "space1")
+box.schema.user.grant("test", "write", "space", "_index")
+box.session.su("test")
+box.space.space1:create_index("pk")
+box.session.su("admin")
+box.space.space1.index[0] == nil
+box.schema.user.grant("test", "alter", "space", "space1")
+box.session.su("test")
+_ = box.space.space1:create_index("pk")
+box.space.space1:insert{5}
+box.session.su("admin")
+box.space.space1.index[0] ~= nil
+box.space.space1:select{}
+box.schema.user.grant("test", "write", "space", "space1")
+box.session.su("test")
+box.space.space1:insert{5}
+box.session.su("admin")
+box.space.space1:select{}
+box.schema.user.drop("test")
+box.space.space1:drop()
+
+--
+-- test that it is possible to grant privileges on a single user.
+box.schema.user.create("user1")
+box.schema.user.create("user2")
+box.schema.user.create("user3")
+box.schema.user.grant("user1", "write", "space", "_user")
+box.schema.user.grant("user1", "read", "space", "_user")
+box.space._user:select{}
+box.session.su("user1")
+-- can alter itself, but can't alter others without privileges.
+box.schema.user.passwd("user1", "abcd")
+box.schema.user.passwd("user2", "abcd")
+box.session.su("admin")
+box.space._user:select{}
+box.schema.user.grant("user1", "alter", "user", "user2")
+box.session.su("user1")
+box.schema.user.passwd("user2", "abcd")
+-- still fails
+box.schema.user.passwd("user3", "qewr")
+box.session.su("admin")
+box.space._user:select{}
+box.schema.user.drop("user1")
+box.schema.user.drop("user2")
+box.schema.user.drop("user3")
diff --git a/test/box/role.result b/test/box/role.result
index 243c7bc6c..5666f7ef7 100644
--- a/test/box/role.result
+++ b/test/box/role.result
@@ -49,6 +49,9 @@ box.schema.user.info('tester')
   - - session,usage
     - universe
     - 
+  - - alter
+    - user
+    - tester
 ...
 box.schema.user.grant('tester', 'execute', 'role', 'iddqd')
 ---
@@ -64,6 +67,9 @@ box.schema.user.info('tester')
   - - session,usage
     - universe
     - 
+  - - alter
+    - user
+    - tester
 ...
 -- test granting user to a user
 box.schema.user.grant('tester', 'execute', 'role', 'tester')
@@ -956,6 +962,9 @@ box.schema.user.info('test_user')
   - - session,usage
     - universe
     - 
+  - - alter
+    - user
+    - test_user
 ...
 box.schema.role.info('test_user')
 ---
diff --git a/test/box/sequence.result b/test/box/sequence.result
index 75d5ea1e6..a39a1155f 100644
--- a/test/box/sequence.result
+++ b/test/box/sequence.result
@@ -1362,6 +1362,9 @@ box.schema.user.info()
   - - session,usage
     - universe
     - 
+  - - alter
+    - user
+    - user
 ...
 sq:set(100) -- ok
 ---
-- 
2.15.2 (Apple Git-101.1)





More information about the Tarantool-patches mailing list