[tarantool-patches] [security 2/2] security: Refactor system space access checks

Ilya Markov imarkov at tarantool.org
Wed May 16 15:37:27 MSK 2018


From: imarkov <imarkov at tarantool.org>

Processes of creating, dropping objects, granting contain work with
system spaces which so far must be available only to admin or user who has
write access to universe.

This patch removes the need of special accesses to universe.

Introduce virtual access_check function in space object and
separate its implementation for user spaces and system ones.

Move checks on write to system spaces to on_replace triggers.

Follow-up #945
Closes #3250
---
 src/box/alter.cc              | 280 ++++++++++++++++++++++++++----------------
 src/box/lua/schema.lua        |  12 +-
 src/box/schema.cc             | 105 ++++++++++++++++
 src/box/schema.h              |  14 +++
 src/box/space.c               |   3 +-
 src/box/space.h               |  17 ++-
 src/box/sysview_engine.c      |   1 +
 src/box/user.h                |   7 ++
 test/box/access.result        |  70 ++++++++---
 test/box/access.test.lua      |  33 +++--
 test/box/access_misc.result   |  52 +++++---
 test/box/access_misc.test.lua |  17 +--
 test/box/net.box.result       |   6 +
 test/box/net.box.test.lua     |   2 +
 test/box/role.result          |  25 +++-
 test/box/role.test.lua        |  13 +-
 test/box/sequence.result      |  20 +--
 test/box/sequence.test.lua    |  11 +-
 test/engine/truncate.result   |   2 +-
 19 files changed, 497 insertions(+), 193 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 8766c81..fffde0e 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -64,50 +64,37 @@
 static void
 access_check_ddl(const char *name, uint32_t owner_uid,
 		 enum schema_object_type type,
-		 enum priv_type priv_type,
-		 bool is_17_compat_mode)
+		 enum priv_type priv_type, uint32_t system_space_id)
 {
 	struct credentials *cr = effective_user();
 	user_access_t has_access = cr->universal_access;
+
+	struct space *space = space_by_id(system_space_id);
+	if (space != NULL) {
+		has_access |= space->access[cr->auth_token].effective;
+	}
 	/*
-	 * XXX: pre 1.7.7 there was no specific 'CREATE' or
-	 * 'ALTER' ACL, instead, read and write access on universe
-	 * was used to allow create/alter.
-	 * For backward compatibility, if a user has read and write
-	 * access on the universe, grant it CREATE access
-	 * automatically.
-	 * The legacy fix does not affect sequences since they
-	 * were added in 1.7.7 only.
+	 * If a user has write access on the universe,
+	 * grant it CREATE, DROP, ALTER access automatically.
 	 */
-	if (is_17_compat_mode && has_access & PRIV_R && has_access & PRIV_W)
-		has_access |= PRIV_C | PRIV_A;
+	if (has_access & PRIV_W)
+		has_access |= PRIV_C | PRIV_A | PRIV_D;
 
-	user_access_t access = ((PRIV_U | (user_access_t) priv_type) &
-				~has_access);
+	user_access_t access = ((user_access_t) priv_type & ~has_access);
 	bool is_owner = owner_uid == cr->uid || cr->uid == ADMIN;
 	/*
 	 * Only the owner of the object or someone who has
-	 * specific DDL privilege on the object can execute
-	 * DDL. If a user has no USAGE access and is owner,
-	 * deny access as well.
+	 * specific DDL privilege on the object can execute DDL.
 	 */
-	if (access == 0 || (is_owner && !(access & PRIV_U)))
+	if (access == 0 || is_owner)
 		return; /* Access granted. */
 
 	struct user *user = user_find_xc(cr->uid);
-	if (is_owner) {
-		tnt_raise(AccessDeniedError,
-			  priv_name(PRIV_U),
-			  schema_object_name(SC_UNIVERSE),
-			  "",
-			  user->def->name);
-	} else {
-		tnt_raise(AccessDeniedError,
-			  priv_name(access),
-			  schema_object_name(type),
-			  name,
-			  user->def->name);
-	}
+	tnt_raise(AccessDeniedError,
+		  priv_name(access),
+		  schema_object_name(type),
+		  name,
+		  user->def->name);
 }
 
 /**
@@ -1551,7 +1538,9 @@ 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);
+		/* When object is not created, we assume ADMIN owns it */
+		access_check_ddl(def->name, ADMIN, SC_SPACE, PRIV_C,
+				 BOX_SPACE_ID);
 		auto def_guard =
 			make_scoped_guard([=] { space_def_delete(def); });
 		RLIST_HEAD(empty_list);
@@ -1578,7 +1567,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		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);
+				 SC_SPACE, PRIV_D, BOX_SPACE_ID);
 		/* Verify that the space is empty (has no indexes) */
 		if (old_space->index_count) {
 			tnt_raise(ClientError, ER_DROP_SPACE,
@@ -1617,7 +1606,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->uid, SC_SPACE, PRIV_A,
+				 BOX_SPACE_ID);
 		auto def_guard =
 			make_scoped_guard([=] { space_def_delete(def); });
 		if (def->id != space_id(old_space))
@@ -1717,7 +1707,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	if (old_tuple && new_tuple)
 		priv_type = PRIV_A;
 	access_check_ddl(old_space->def->name, old_space->def->uid, SC_SPACE,
-			 priv_type, true);
+			 priv_type, BOX_SPACE_ID);
 	struct index *old_index = space_index(old_space, iid);
 
 	/*
@@ -1908,14 +1898,20 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event)
 			  space_name(old_space));
 
 	/*
-	 * Check if a write privilege was given, raise an error if not.
+	 * Perform checks specific for space. Then perform usual check_ddl.
 	 */
-	access_check_space_xc(old_space, PRIV_W);
+	credentials *cr = effective_user();
+	uint32_t has_access = cr->universal_access |
+			      old_space->access[cr->auth_token].effective;
+	if (!(has_access & (PRIV_W | PRIV_D))) {
+		access_check_ddl(old_space->def->name, old_space->def->uid,
+				 SC_SPACE,
+				 PRIV_D, BOX_SPACE_ID);
+	}
 
 	struct alter_space *alter = alter_space_new(old_space);
 	auto scoped_guard =
 		make_scoped_guard([=] { alter_space_delete(alter); });
-
 	/*
 	 * Recreate all indexes of the truncated space.
 	 */
@@ -2110,7 +2106,9 @@ 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, SC_USER, PRIV_C, true);
+		/* When object is not created, we assume ADMIN owns it */
+		access_check_ddl(user->name, ADMIN, SC_USER, PRIV_C,
+				 BOX_USER_ID);
 		auto def_guard = make_scoped_guard([=] { free(user); });
 		(void) user_cache_replace(user);
 		def_guard.is_active = false;
@@ -2119,7 +2117,7 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 		txn_on_rollback(txn, on_rollback);
 	} else if (new_tuple == NULL) { /* DELETE */
 		access_check_ddl(old_user->def->name, old_user->def->owner,
-				 SC_USER, PRIV_D, true);
+				 SC_USER, PRIV_D, BOX_USER_ID);
 		/* Can't drop guest or super user */
 		if (uid <= (uint32_t) BOX_SYSTEM_USER_ID_MAX || uid == SUPER) {
 			tnt_raise(ClientError, ER_DROP_USER,
@@ -2146,7 +2144,7 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 		 */
 		struct user_def *user = user_def_new_from_tuple(new_tuple);
 		access_check_ddl(user->name, user->uid, SC_USER, PRIV_A,
-				 true);
+				 BOX_USER_ID);
 		auto def_guard = make_scoped_guard([=] { free(user); });
 		struct trigger *on_commit =
 			txn_alter_trigger_new(user_cache_alter_user, NULL);
@@ -2248,7 +2246,9 @@ 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);
+		/* When object is not created, we assume ADMIN owns it */
+		access_check_ddl(def->name, ADMIN, SC_FUNCTION, PRIV_C,
+				 BOX_FUNC_ID);
 		auto def_guard = make_scoped_guard([=] { free(def); });
 		func_cache_replace(def);
 		def_guard.is_active = false;
@@ -2262,8 +2262,8 @@ 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,
-				 PRIV_D, true);
+		access_check_ddl(old_func->def->name, uid, SC_FUNCTION, PRIV_D,
+				 BOX_FUNC_ID);
 		/* Can only delete func if it has no grants. */
 		if (schema_find_grants("function", old_func->def->fid)) {
 			tnt_raise(ClientError, ER_DROP_FUNCTION,
@@ -2277,7 +2277,7 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 		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);
+				 BOX_FUNC_ID);
 		struct trigger *on_commit =
 			txn_alter_trigger_new(func_cache_replace_func, NULL);
 		txn_on_commit(txn, on_commit);
@@ -2421,6 +2421,7 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event)
 		txn_alter_trigger_new(coll_cache_rollback, NULL);
 	struct trigger *on_commit =
 		txn_alter_trigger_new(coll_cache_commit, NULL);
+	struct credentials *cr = effective_user();
 	if (new_tuple == NULL && old_tuple != NULL) {
 		/* DELETE */
 		/* TODO: Check that no index uses the collation */
@@ -2428,8 +2429,17 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event)
 						    BOX_COLLATION_FIELD_ID);
 		struct coll *old_coll = coll_by_id(old_id);
 		assert(old_coll != NULL);
-		access_check_ddl(old_coll->name, old_coll->owner_id,
-				 SC_COLLATION, PRIV_D, false);
+		/*
+		 * No privilleges may give access to drop a collation.
+		 */
+		if (cr->uid != ADMIN && old_coll->owner_id != cr->uid) {
+			struct user *user = user_find_xc(cr->uid);
+			tnt_raise(AccessDeniedError,
+				  priv_name(PRIV_D),
+				  schema_object_name(SC_COLLATION),
+				  old_coll->name,
+				  user->def->name);
+		}
 		/*
 		 * Set on_commit/on_rollback triggers after
 		 * deletion from the cache to make trigger logic
@@ -2444,8 +2454,8 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event)
 		/* INSERT */
 		struct coll_def new_def;
 		coll_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.owner_id,
+				 SC_COLLATION, PRIV_C, BOX_COLLATION_ID);
 		struct coll *new_coll = coll_new(&new_def);
 		if (new_coll == NULL)
 			diag_raise();
@@ -2483,6 +2493,35 @@ priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple)
 	priv->access = tuple_field_u32_xc(tuple, BOX_PRIV_FIELD_ACCESS);
 }
 
+/**
+ * Handles case, when current user is not an owner of object and not ADMIN
+ * and wants to add or modify privileges.
+ *
+ * The strategy is following:
+ * When user has write, create, drop privileges in hierarchy over object,
+ * she is able to pass privileges.
+ * This case is required for creating, dropping users.
+ * TODO: we have to diffirentiate granting, revoking options. As so far,
+ * this is security breach, user who has create privilege may delete privileges
+ * of other users. Moreover, we have to differentiate promotion/demotion in altering
+ * privillege.
+ */
+static inline void
+priv_check_not_owner(struct user *grantor, struct priv_def *priv,
+		     const char *name, enum priv_type priv_type)
+{
+	uint32_t access = schema_find_access(priv->object_type,
+						priv->object_id,
+						grantor);
+	if (priv->grantee_id == ADMIN || !(access & (PRIV_W | PRIV_C | PRIV_D))) {
+		tnt_raise(AccessDeniedError,
+			  priv_name(priv_type),
+			  schema_object_name(priv->object_type),
+			  name,
+			  grantor->def->name);
+	}
+}
+
 /*
  * This function checks that:
  * - a privilege is granted from an existing user to an existing
@@ -2496,35 +2535,33 @@ priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple)
 static void
 priv_def_check(struct priv_def *priv, enum priv_type priv_type)
 {
-	struct user *grantor = user_find_xc(priv->grantor_id);
 	/* May be a role */
 	struct user *grantee = user_by_id(priv->grantee_id);
+	credentials *cr = effective_user();
+	struct user *grantor = user_find_xc(cr->uid);
+
 	if (grantee == NULL) {
 		tnt_raise(ClientError, ER_NO_SUCH_USER,
 			  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);
+
 	switch (priv->object_type) {
 	case SC_UNIVERSE:
+	{
 		if (grantor->def->uid != ADMIN) {
-			tnt_raise(AccessDeniedError,
-				  priv_name(priv_type),
-				  schema_object_name(SC_UNIVERSE),
-				  name,
-				  grantor->def->name);
+			priv_check_not_owner(grantor, priv, name, priv_type);
 		}
 		break;
+	}
 	case SC_SPACE:
 	{
-		struct space *space = space_cache_find_xc(priv->object_id);
+		struct space *space = space_cache_find_xc(
+			priv->object_id);
 		if (space->def->uid != grantor->def->uid &&
 		    grantor->def->uid != ADMIN) {
-			tnt_raise(AccessDeniedError,
-				  priv_name(priv_type),
-				  schema_object_name(SC_SPACE), name,
-				  grantor->def->name);
+			priv_check_not_owner(grantor, priv, name,
+					     priv_type);
 		}
 		break;
 	}
@@ -2533,22 +2570,19 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type)
 		struct func *func = func_cache_find(priv->object_id);
 		if (func->def->uid != grantor->def->uid &&
 		    grantor->def->uid != ADMIN) {
-			tnt_raise(AccessDeniedError,
-				  priv_name(priv_type),
-				  schema_object_name(SC_FUNCTION), name,
-				  grantor->def->name);
+			priv_check_not_owner(grantor, priv, name,
+					     priv_type);
 		}
 		break;
 	}
 	case SC_SEQUENCE:
 	{
-		struct sequence *seq = sequence_cache_find(priv->object_id);
+		struct sequence *seq = sequence_cache_find(
+			priv->object_id);
 		if (seq->def->uid != grantor->def->uid &&
 		    grantor->def->uid != ADMIN) {
-			tnt_raise(AccessDeniedError,
-				  priv_name(priv_type),
-				  schema_object_name(SC_SEQUENCE), name,
-				  grantor->def->name);
+			priv_check_not_owner(grantor, priv, name,
+					     priv_type);
 		}
 		break;
 	}
@@ -2561,16 +2595,23 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type)
 				  int2str(priv->object_id));
 		}
 		/*
-		 * Only the creator of the role can grant or revoke it.
-		 * Everyone can grant 'PUBLIC' role.
+		 * Only the creator or owner of grantee of the role
+		 * or user with appropriate privileges can grant or revoke it.
+		 * If role is 'PUBLIC' it is allowed to grant for anyone,
+		 * Revoke 'PUBLIC' is allowed only to users who has drop access.
+		 * The latter case is require for dropping users.
 		 */
 		if (role->def->owner != grantor->def->uid &&
-		    grantor->def->uid != ADMIN &&
-		    (role->def->uid != PUBLIC || priv->access != PRIV_X)) {
-			tnt_raise(AccessDeniedError,
-				  priv_name(priv_type),
-				  schema_object_name(SC_ROLE), name,
-				  grantor->def->name);
+		    grantor->def->uid != grantee->def->owner &&
+			!((role->def->uid == PUBLIC) &&
+			  (((cr->universal_access & PRIV_D)
+			    || priv_type == PRIV_GRANT)
+			   && priv->access == PRIV_X))) {
+				tnt_raise(AccessDeniedError,
+					  priv_name(priv_type),
+					  schema_object_name(SC_ROLE),
+					  name,
+					  grantor->def->name);
 		}
 		/* Not necessary to do during revoke, but who cares. */
 		role_check(grantee, role);
@@ -2687,6 +2728,7 @@ on_replace_dd_priv(struct trigger * /* trigger */, void *event)
 	} else {                                       /* modify */
 		priv_def_create_from_tuple(&priv, new_tuple);
 		priv_def_check(&priv, PRIV_GRANT);
+		/*TODO: check that modified by owner of object or ADMIN or grantor of privillege*/
 		struct trigger *on_commit =
 			txn_alter_trigger_new(modify_priv, NULL);
 		txn_on_commit(txn, on_commit);
@@ -2716,31 +2758,54 @@ on_replace_dd_schema(struct trigger * /* trigger */, void *event)
 	struct tuple *new_tuple = stmt->new_tuple;
 	const char *key = tuple_field_cstr_xc(new_tuple ? new_tuple : old_tuple,
 					      BOX_SCHEMA_FIELD_KEY);
-	if (strcmp(key, "cluster") == 0) {
-		if (new_tuple == NULL)
-			tnt_raise(ClientError, ER_REPLICASET_UUID_IS_RO);
-		tt_uuid uu;
-		tuple_field_uuid_xc(new_tuple, BOX_CLUSTER_FIELD_UUID, &uu);
-		REPLICASET_UUID = uu;
-	} else if (strcmp(key, "version") == 0) {
-		if (new_tuple != NULL) {
-			uint32_t major, minor, patch;
-			if (tuple_field_u32(new_tuple, 1, &major) != 0 ||
-			    tuple_field_u32(new_tuple, 2, &minor) != 0)
-				tnt_raise(ClientError, ER_WRONG_DD_VERSION);
-			/* Version can be major.minor with no patch. */
-			if (tuple_field_u32(new_tuple, 3, &patch) != 0)
-				patch = 0;
-			dd_version_id = version_id(major, minor, patch);
-		} else {
-			assert(old_tuple != NULL);
-			/*
-			 * _schema:delete({'version'}) for
-			 * example, for box.internal.bootstrap().
-			 */
-			dd_version_id = tarantool_version_id();
+	credentials *cr = effective_user();
+	if (strncmp(key, "max_id", 6) == 0) {
+		if (!(cr->universal_access & (PRIV_W | PRIV_C)))
+			goto error;
+	} else {
+		uint32_t access = cr->universal_access;
+		struct space *schema_space = space_by_id(BOX_SCHEMA_ID);
+		access |= schema_space->access[cr->auth_token].effective;
+		if (~access & PRIV_W)
+			goto error;
+		else if (strncmp(key, "cluster", 7) == 0) {
+			if (new_tuple == NULL)
+				tnt_raise(ClientError,
+					  ER_REPLICASET_UUID_IS_RO);
+			tt_uuid uu;
+			tuple_field_uuid_xc(new_tuple, BOX_CLUSTER_FIELD_UUID,
+					    &uu);
+			REPLICASET_UUID = uu;
+		} else if (strncmp(key, "version", 7) == 0) {
+			if (new_tuple != NULL) {
+				uint32_t major, minor, patch;
+				if (tuple_field_u32(new_tuple, 1, &major) !=
+				    0 ||
+				    tuple_field_u32(new_tuple, 2, &minor) != 0)
+					tnt_raise(ClientError,
+						  ER_WRONG_DD_VERSION);
+				/* Version can be major.minor with no patch. */
+				if (tuple_field_u32(new_tuple, 3, &patch) != 0)
+					patch = 0;
+				dd_version_id = version_id(major, minor, patch);
+			} else {
+				assert(old_tuple != NULL);
+				/*
+				 * _schema:delete({'version'}) for
+				 * example, for box.internal.bootstrap().
+				 */
+				dd_version_id = tarantool_version_id();
+			}
 		}
 	}
+	return;
+error:
+	struct user *user = user_find_xc(cr->uid);
+	tnt_raise(AccessDeniedError,
+		  priv_name(PRIV_W),
+		  schema_object_name(SC_SPACE),
+		  "_schema",
+		  user->def->name);
 }
 
 /**
@@ -2967,6 +3032,9 @@ 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);
+		/* When object is not created, we assume ADMIN owns it */
+		access_check_ddl(new_def->name, ADMIN, SC_SEQUENCE,
+				 PRIV_C, BOX_SEQUENCE_ID);
 		sequence_cache_replace(new_def);
 		alter->new_def = new_def;
 	} else if (old_tuple != NULL && new_tuple == NULL) {	/* DELETE */
@@ -2975,7 +3043,7 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event)
 		struct sequence *seq = sequence_by_id(id);
 		assert(seq != NULL);
 		access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE,
-				 PRIV_D, false);
+				 PRIV_D, BOX_SEQUENCE_ID);
 		if (space_has_data(BOX_SEQUENCE_DATA_ID, 0, id))
 			tnt_raise(ClientError, ER_DROP_SEQUENCE,
 				  seq->def->name, "the sequence has data");
@@ -2992,7 +3060,7 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event)
 		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);
+				 PRIV_A, BOX_SEQUENCE_ID);
 		alter->old_def = seq->def;
 		alter->new_def = new_def;
 	}
@@ -3072,10 +3140,10 @@ on_replace_dd_space_sequence(struct trigger * /* trigger */, void *event)
 
 	/* Check we have the correct access type on the sequence.  * */
 	access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE, priv_type,
-			 false);
+			 BOX_SEQUENCE_ID);
 	/** Check we have alter access on space. */
 	access_check_ddl(space->def->name, space->def->uid, SC_SPACE, PRIV_A,
-			 false);
+			 BOX_SPACE_ID);
 
 	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 677325c..6601337 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1977,10 +1977,7 @@ 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')
-    -- we have to grant global privileges from setuid function, since
-    -- only admin has the ownership over universe and we don't have
-    -- grant option
-    box.session.su('admin', box.schema.user.grant, uid, 'session,usage', 'universe',
+    box.schema.user.grant(uid, 'session,usage', 'universe',
                    nil, {if_not_exists=true})
 end
 
@@ -2098,12 +2095,9 @@ local function drop(uid, opts)
     for k, tuple in pairs(sequences) do
         box.schema.sequence.drop(tuple[1])
     end
-    -- xxx: hack, we have to revoke session and usage privileges
-    -- of a user using a setuid function in absence of create/drop
-    -- privileges and grant option
     if box.space._vuser:get{uid}[4] == 'user' then
-        box.session.su('admin', box.schema.user.revoke, uid,
-                       'session,usage', 'universe', nil, {if_exists = true})
+        box.schema.user.revoke(uid, 'session,usage', 'universe',
+                nil, {if_exists = true})
     end
     local privs = _vpriv.index.primary:select{uid}
     for k, tuple in pairs(privs) do
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 1b96f97..8ed2549 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -37,6 +37,7 @@
 #include "scoped_guard.h"
 #include "version.h"
 #include "user.h"
+#include "session.h"
 #include <stdio.h>
 /**
  * @module Data Dictionary
@@ -520,6 +521,70 @@ sequence_cache_delete(uint32_t id)
 	}
 }
 
+
+/**
+ * Runs simple read, usage checks.
+ * All other checks are postponed.
+ */
+static int
+sys_read_access_check(struct space *space, user_access_t access)
+{
+	credentials *cr = effective_user();
+	if (~cr->universal_access & PRIV_U) {
+		struct user *user = user_find(cr->uid);
+		if (user != NULL)
+			diag_set(AccessDeniedError,
+				 priv_name(PRIV_U),
+				 schema_object_name(SC_UNIVERSE),
+				 "",
+				 user->def->name);
+		return -1;
+	}
+	if (access == PRIV_R) {
+		uint32_t access = cr->universal_access |
+			space->access[cr->auth_token].effective;
+		if (~access & PRIV_R) {
+			struct user *user = user_find(cr->uid);
+			if (user != NULL)
+				diag_set(AccessDeniedError,
+					 priv_name(PRIV_R),
+					 schema_object_name(SC_SPACE),
+					 space->def->name,
+					 user->def->name);
+			return -1;
+		}
+	}
+	/* Other checks are postponed to trigger*/
+	return 0;
+}
+
+/**
+ * Runs simple read, usage, write checks.
+ * All other checks are postponed.
+ */
+static int
+sys_read_write_access_check(struct space *space, user_access_t access)
+{
+	credentials *cr = effective_user();
+	if (access == PRIV_W) {
+		uint32_t has_access = space->access[cr->auth_token].effective
+				      | cr->universal_access;
+		if (~has_access & access) {
+			struct user *user = user_find(cr->uid);
+			if (user != NULL) {
+				diag_set(AccessDeniedError,
+					 priv_name(access),
+					 schema_object_name(SC_SPACE),
+					 space->def->name,
+					 user->def->name);
+			}
+			return -1;
+		}
+		return 0;
+	}
+	return sys_read_access_check(space, access);
+}
+
 const char *
 schema_find_name(enum schema_object_type type, uint32_t object_id)
 {
@@ -562,3 +627,43 @@ schema_find_name(enum schema_object_type type, uint32_t object_id)
 	return "(nil)";
 }
 
+uint32_t
+schema_find_access(enum schema_object_type type, uint32_t object_id,
+		   struct user *grantor)
+{
+	struct priv_def def;
+	def.object_type = type;
+	def.object_id = object_id;
+	def.grantor_id = grantor->def->uid;
+	uint32_t access = universe.access[grantor->auth_token].effective;
+	struct access *obj_access = access_find(&def);
+	if (obj_access != NULL)
+		access |= obj_access->effective;
+	return access;
+}
+
+access_check_func_t
+get_access_check_func(uint32_t space_id)
+{
+	switch (space_id) {
+	case BOX_CLUSTER_ID:
+	case BOX_COLLATION_ID:
+		return sys_read_write_access_check;
+	case BOX_SCHEMA_ID:
+	case BOX_SPACE_ID:
+	case BOX_TRUNCATE_ID:
+	case BOX_INDEX_ID:
+	case BOX_FUNC_ID:
+	case BOX_USER_ID:
+	case BOX_SEQUENCE_ID:
+	case BOX_SEQUENCE_DATA_ID:
+	case BOX_SPACE_SEQUENCE_ID:
+	case BOX_PRIV_ID:
+		/*
+		 * Specialized access checks will be performed in trigger.
+		 */
+		return sys_read_access_check;
+	default:
+		return access_check_user_space;
+	}
+}
diff --git a/src/box/schema.h b/src/box/schema.h
index 2b87f5f..5b85e39 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -36,6 +36,7 @@
 #include "error.h"
 #include "space.h"
 #include "latch.h"
+#include "user.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -103,6 +104,13 @@ schema_find_name(enum schema_object_type type, uint32_t object_id);
  */
 struct sequence *
 sequence_by_id(uint32_t id);
+
+uint32_t
+schema_find_access(enum schema_object_type type, uint32_t object_id,
+		   struct user *grantor);
+
+int
+access_check_user_space(struct space *space, user_access_t access);
 #if defined(__cplusplus)
 } /* extern "C" */
 
@@ -205,6 +213,12 @@ sequence_cache_delete(uint32_t id);
 #endif /* defined(__cplusplus) */
 
 /**
+ * Utility function used to specify access_check function for system space.
+ */
+access_check_func_t
+get_access_check_func(uint32_t space_id);
+
+/**
  * Triggers fired after committing a change in space definition.
  * The space is passed to the trigger callback in the event
  * argument. It is the new space in case of create/update or
diff --git a/src/box/space.c b/src/box/space.c
index 02a9792..d72ce29 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -44,7 +44,7 @@
 #include "iproto_constants.h"
 
 int
-access_check_space(struct space *space, user_access_t access)
+access_check_user_space(struct space *space, user_access_t access)
 {
 	struct credentials *cr = effective_user();
 	/* Any space access also requires global USAGE privilege. */
@@ -132,6 +132,7 @@ space_create(struct space *space, struct engine *engine,
 	rlist_create(&space->on_replace);
 	rlist_create(&space->on_stmt_begin);
 	space->run_triggers = true;
+	space->access_check = get_access_check_func(def->id);
 
 	space->format = format;
 	if (format != NULL)
diff --git a/src/box/space.h b/src/box/space.h
index a024fdc..5139a76 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -50,6 +50,8 @@ struct port;
 struct tuple;
 struct tuple_format;
 
+typedef int (*access_check_func_t)(struct space *space, user_access_t access);
+
 struct space_vtab {
 	/** Free a space instance. */
 	void (*destroy)(struct space *);
@@ -127,6 +129,7 @@ struct space_vtab {
 struct space {
 	/** Virtual function table. */
 	const struct space_vtab *vtab;
+	access_check_func_t access_check;
 	/** Cached runtime access information. */
 	struct access access[BOX_USER_MAX];
 	/** Engine used by this space. */
@@ -273,8 +276,11 @@ index_name_by_id(struct space *space, uint32_t id);
  * Check whether or not the current user can be granted
  * the requested access to the space.
  */
-int
-access_check_space(struct space *space, user_access_t access);
+static inline int
+access_check_space(struct space *space, user_access_t access)
+{
+	return space->access_check(space, access);
+}
 
 static inline int
 space_apply_initial_join_row(struct space *space, struct request *request)
@@ -390,6 +396,12 @@ space_dump_def(const struct space *space, struct rlist *key_list);
 void
 space_fill_index_map(struct space *space);
 
+/*
+ * Utility function used to specify access_check function for system space.
+ */
+access_check_func_t
+get_access_check_func(uint32_t space_id);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 
@@ -499,4 +511,5 @@ space_prepare_alter_xc(struct space *old_space, struct space *new_space)
 
 #endif /* defined(__cplusplus) */
 
+
 #endif /* TARANTOOL_BOX_SPACE_H_INCLUDED */
diff --git a/src/box/sysview_engine.c b/src/box/sysview_engine.c
index 5aea87e..f77ce99 100644
--- a/src/box/sysview_engine.c
+++ b/src/box/sysview_engine.c
@@ -205,6 +205,7 @@ sysview_engine_create_space(struct engine *engine, struct space_def *def,
 		free(space);
 		return NULL;
 	}
+	space->access_check = access_check_user_space;
 	return space;
 }
 
diff --git a/src/box/user.h b/src/box/user.h
index 07c4dc5..254f0c9 100644
--- a/src/box/user.h
+++ b/src/box/user.h
@@ -199,6 +199,13 @@ priv_grant(struct user *grantee, struct priv_def *priv);
 void
 priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple);
 
+/**
+ * Find the corresponding access structure
+ * given object type and object id.
+ */
+struct access *
+access_find(struct priv_def *priv);
+
 /* }}} */
 
 #endif /* defined(__cplusplus) */
diff --git a/test/box/access.result b/test/box/access.result
index 476e594..8918cf7 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -378,7 +378,6 @@ box.schema.user.create('grantee')
 ...
 box.schema.user.grant('grantee', 'read, write, execute', 'universe')
 ---
-- error: Grant access to universe '' is denied for user 'grantor'
 ...
 session.su('grantee')
 ---
@@ -1177,11 +1176,11 @@ box.schema.space.create('test')
 ...
 box.schema.user.create('test')
 ---
-- error: Write access to space '_user' is denied for user 'guest'
+- error: Create access to user 'test' is denied for user 'guest'
 ...
 box.schema.func.create('test')
 ---
-- error: Write access to space '_func' is denied for user 'guest'
+- error: Create access to function 'test' is denied for user 'guest'
 ...
 box.session.su('admin')
 ---
@@ -1395,10 +1394,32 @@ box.schema.func.create('test_func')
 box.session.su("admin")
 ---
 ...
+-- failed create because of auto_increment
+box.session.su("tester")
+---
+...
+box.schema.space.create("test_space")
+---
+- error: Write access to space '_schema' is denied for user 'tester'
+...
+box.schema.user.create('test_user')
+---
+- error: Read access to space '_user' is denied for user 'tester'
+...
+box.schema.func.create('test_func')
+---
+- error: Read access to space '_func' is denied for user 'tester'
+...
+box.schema.sequence.create('test_seq')
+---
+- error: Read access to space '_sequence' is denied for user 'tester'
+...
+box.session.su("admin")
+---
+...
 box.schema.user.grant("tester", "read", "universe")
 ---
 ...
--- failed create
 box.session.su("tester")
 ---
 ...
@@ -1408,27 +1429,26 @@ box.schema.space.create("test_space")
 ...
 box.schema.user.create('test_user')
 ---
-- error: Write access to space '_user' is denied for user 'tester'
+- error: Create access to user 'test_user' is denied for user 'tester'
 ...
 box.schema.func.create('test_func')
 ---
-- error: Write access to space '_func' is denied for user 'tester'
+- error: Create access to function 'test_func' is denied for user 'tester'
+...
+box.schema.sequence.create('test_seq')
+---
+- error: Create access to sequence 'test_seq' is denied for user 'tester'
 ...
 box.session.su("admin")
 ---
 ...
---
--- FIXME 2.0: we still need to grant 'write' on universe
--- explicitly since we still use process_rw to write to system
--- tables from ddl
---
-box.schema.user.grant("tester", "create,write", "universe")
+box.schema.user.grant("tester", "create", "universe")
 ---
 ...
 box.session.su("tester")
 ---
 ...
--- successful create
+-- successful create. Note user still needs read privilege because of auto_increment.
 s1 = box.schema.space.create("test_space")
 ---
 ...
@@ -1464,29 +1484,45 @@ box.schema.func.drop('test_func')
 -- box.session.su("tester", s.format, s, {name="id", type="unsigned"})
 -- failed drop
 -- box.session.su("tester", s.drop, s)
+box.session.su("admin")
+---
+...
+-- revoke read
+box.schema.user.revoke("tester", "read", "universe")
+---
+...
+box.session.su("tester")
+---
+...
 s:drop()
 ---
 - error: Drop access to space 'test' is denied for user 'tester'
 ...
 seq:drop()
 ---
-- error: Drop access to sequence 'test' is denied for user 'tester'
+- error: Sequence '1' does not exist
 ...
 box.schema.user.drop("test")
 ---
-- error: Revoke access to role 'public' is denied for user 'tester'
+- error: User 'test' is not found
 ...
 box.schema.func.drop("test")
 ---
-- error: Drop access to function 'test' is denied for user 'tester'
+- error: Function 'test' does not exist
 ...
 box.session.su("admin")
 ---
 ...
+box.schema.user.revoke("tester", "create", "universe")
+---
+...
 box.schema.user.grant("tester", "drop", "universe")
 ---
 ...
--- successful drop
+-- successful truncate, drop
+box.session.su("tester", s.truncate, s)
+---
+...
 box.session.su("tester", s.drop, s)
 ---
 ...
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index 81f5ed1..d01e8c6 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -522,22 +522,25 @@ box.schema.space.create("test_space")
 box.schema.user.create('test_user')
 box.schema.func.create('test_func')
 box.session.su("admin")
-box.schema.user.grant("tester", "read", "universe")
--- failed create
+-- failed create because of auto_increment
 box.session.su("tester")
 box.schema.space.create("test_space")
 box.schema.user.create('test_user')
 box.schema.func.create('test_func')
+box.schema.sequence.create('test_seq')
+
 box.session.su("admin")
+box.schema.user.grant("tester", "read", "universe")
+box.session.su("tester")
 
---
--- FIXME 2.0: we still need to grant 'write' on universe
--- explicitly since we still use process_rw to write to system
--- tables from ddl
---
-box.schema.user.grant("tester", "create,write", "universe")
+box.schema.space.create("test_space")
+box.schema.user.create('test_user')
+box.schema.func.create('test_func')
+box.schema.sequence.create('test_seq')
+box.session.su("admin")
+box.schema.user.grant("tester", "create", "universe")
 box.session.su("tester")
--- successful create
+-- successful create. Note user still needs read privilege because of auto_increment.
 s1 = box.schema.space.create("test_space")
 _ = s1:create_index("primary")
 _ = box.schema.user.create('test_user')
@@ -559,14 +562,22 @@ box.schema.func.drop('test_func')
 
 -- failed drop
 -- box.session.su("tester", s.drop, s)
+box.session.su("admin")
+-- revoke read
+box.schema.user.revoke("tester", "read", "universe")
+box.session.su("tester")
+
 s:drop()
 seq:drop()
 box.schema.user.drop("test")
 box.schema.func.drop("test")
-
 box.session.su("admin")
+
+
+box.schema.user.revoke("tester", "create", "universe")
 box.schema.user.grant("tester", "drop", "universe")
--- successful drop
+-- successful truncate, drop
+box.session.su("tester", s.truncate, s)
 box.session.su("tester", s.drop, s)
 box.session.su("tester", seq.drop, seq)
 box.session.su("tester", box.schema.user.drop, "test")
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index 8bf99f2..f6ba483 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -61,6 +61,9 @@ box.schema.user.grant('testus', 'read', 'space', 'admin_space')
 ---
 - error: User 'testus' already has read access on space 'admin_space'
 ...
+box.schema.user.grant('testus', 'read', 'universe')
+---
+...
 session.su('testus')
 ---
 ...
@@ -78,7 +81,7 @@ s:delete(1)
 ...
 s:drop()
 ---
-- error: Write access to space '_space_sequence' is denied for user 'testus'
+- error: Drop access to space 'admin_space' is denied for user 'testus'
 ...
 --
 -- Check double revoke
@@ -93,6 +96,9 @@ box.schema.user.revoke('testus', 'read', 'space', 'admin_space')
 ---
 - error: User 'testus' does not have read access on space 'admin_space'
 ...
+box.schema.user.revoke('testus', 'read', 'universe')
+---
+...
 session.su('testus')
 ---
 ...
@@ -124,9 +130,18 @@ s:insert({3})
 ---
 - [3]
 ...
+session.su('admin')
+---
+...
+box.schema.user.grant('testus', 'read', 'universe')
+---
+...
+session.su('testus')
+---
+...
 s:drop()
 ---
-- error: Write access to space '_space_sequence' is denied for user 'testus'
+- error: Drop access to space 'admin_space' is denied for user 'testus'
 ...
 session.su('admin')
 ---
@@ -167,28 +182,26 @@ s:delete({3})
 ---
 - error: Write access to space 'admin_space' is denied for user 'guest'
 ...
-s:drop()
+session.su('admin')
 ---
-- error: Write access to space '_space_sequence' is denied for user 'guest'
 ...
-gs = box.schema.space.create('guest_space')
+box.schema.user.grant('guest', 'read', 'universe')
 ---
-- error: Write access to space '_schema' is denied for user 'guest'
 ...
---
--- FIXME: object create calls system space auto_increment, which requires
--- read and write privileges. Create privilege must solve this.
---
-box.schema.func.create('guest_func')
+session.su('guest')
 ---
-- error: Read access to space '_func' is denied for user 'guest'
 ...
-session.su('admin', box.schema.user.grant, "guest", "read", "universe")
+s:drop()
 ---
+- error: Drop access to space 'admin_space' is denied for user 'guest'
+...
+gs = box.schema.space.create('guest_space')
+---
+- error: Write access to space '_schema' is denied for user 'guest'
 ...
 box.schema.func.create('guest_func')
 ---
-- error: Read access to space '_func' is denied for user 'guest'
+- error: Create access to function 'guest_func' is denied for user 'guest'
 ...
 session.su('admin')
 ---
@@ -291,7 +304,7 @@ session.su('admin')
 box.schema.user.create('someuser')
 ---
 ...
-box.schema.user.grant('someuser', 'read, write, execute', 'universe')
+box.schema.user.grant('someuser', 'read', 'universe')
 ---
 ...
 session.su('someuser')
@@ -358,7 +371,7 @@ testuser_uid = session.uid()
 ...
 _ = box.space._user:delete(2)
 ---
-- error: Drop access to user 'public' is denied for user 'testuser'
+- error: 'Failed to drop user or role ''public'': the user or the role is a system'
 ...
 box.space._user:select(1)
 ---
@@ -395,7 +408,7 @@ session.su('testuser')
 ...
 _  = box.space._user:delete(2)
 ---
-- error: Write access to space '_user' is denied for user 'testuser'
+- error: Drop access to user 'public' is denied for user 'testuser'
 ...
 box.space._user:select(1)
 ---
@@ -403,7 +416,8 @@ box.space._user:select(1)
 ...
 box.space._user:insert{uid, session.uid(), 'someone2', 'user'}
 ---
-- error: Write access to space '_user' is denied for user 'testuser'
+- error: Tuple field count 4 is less than required by space format or defined indexes
+    (expected at least 5)
 ...
 session.su('admin')
 ---
@@ -423,7 +437,7 @@ box.space._index:select(272)
 ...
 box.space._index:insert{512, 1,'owner','tree', 1, 1, 0,'unsigned'}
 ---
-- error: Write access to space '_index' is denied for user 'testuser'
+- error: 'Tuple field 5 type does not match one required by operation: expected map'
 ...
 session.su('admin')
 ---
diff --git a/test/box/access_misc.test.lua b/test/box/access_misc.test.lua
index 27064c4..470f730 100644
--- a/test/box/access_misc.test.lua
+++ b/test/box/access_misc.test.lua
@@ -27,6 +27,7 @@ s:insert({2})
 --
 box.schema.user.grant('testus', 'read', 'space', 'admin_space')
 box.schema.user.grant('testus', 'read', 'space', 'admin_space')
+box.schema.user.grant('testus', 'read', 'universe')
 
 session.su('testus')
 s:select(1)
@@ -39,6 +40,7 @@ s:drop()
 session.su('admin')
 box.schema.user.revoke('testus', 'read', 'space', 'admin_space')
 box.schema.user.revoke('testus', 'read', 'space', 'admin_space')
+box.schema.user.revoke('testus', 'read', 'universe')
 
 session.su('testus')
 s:select(1)
@@ -52,6 +54,9 @@ session.su('testus')
 s:select(1)
 s:delete(1)
 s:insert({3})
+session.su('admin')
+box.schema.user.grant('testus', 'read', 'universe')
+session.su('testus')
 s:drop()
 session.su('admin')
 --
@@ -68,14 +73,12 @@ box.space._user:select(1)
 s:select(1)
 s:insert({4})
 s:delete({3})
+session.su('admin')
+box.schema.user.grant('guest', 'read', 'universe')
+session.su('guest')
 s:drop()
 gs = box.schema.space.create('guest_space')
---
--- FIXME: object create calls system space auto_increment, which requires
--- read and write privileges. Create privilege must solve this.
---
-box.schema.func.create('guest_func')
-session.su('admin', box.schema.user.grant, "guest", "read", "universe")
+
 box.schema.func.create('guest_func')
 session.su('admin')
 box.schema.user.revoke("guest", "read", "universe")
@@ -123,7 +126,7 @@ box.schema.func.create('uniuser_func')
 
 session.su('admin')
 box.schema.user.create('someuser')
-box.schema.user.grant('someuser', 'read, write, execute', 'universe')
+box.schema.user.grant('someuser', 'read', 'universe')
 session.su('someuser')
 --
 -- Check drop objects of another user
diff --git a/test/box/net.box.result b/test/box/net.box.result
index 1674c27..7861b4d 100644
--- a/test/box/net.box.result
+++ b/test/box/net.box.result
@@ -2146,6 +2146,9 @@ box.schema.user.grant('guest', 'write', 'space', '_space')
 box.schema.user.grant('guest', 'write', 'space', '_schema')
 ---
 ...
+box.schema.user.grant('guest', 'create', 'universe')
+---
+...
 count = 0
 ---
 ...
@@ -2194,6 +2197,9 @@ box.schema.user.revoke('guest', 'write', 'space', '_space')
 box.schema.user.revoke('guest', 'write', 'space', '_schema')
 ---
 ...
+box.schema.user.revoke('guest', 'create', 'universe')
+---
+...
 c:close()
 ---
 ...
diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua
index c34616a..3e87350 100644
--- a/test/box/net.box.test.lua
+++ b/test/box/net.box.test.lua
@@ -877,6 +877,7 @@ box.session.on_disconnect(nil, on_disconnect)
 --
 box.schema.user.grant('guest', 'write', 'space', '_space')
 box.schema.user.grant('guest', 'write', 'space', '_schema')
+box.schema.user.grant('guest', 'create', 'universe')
 count = 0
 function create_space(name) count = count + 1 box.schema.create_space(name) return true end
 c = net.connect(box.cfg.listen)
@@ -891,6 +892,7 @@ box.space.test2:drop()
 box.space.test3:drop()
 box.schema.user.revoke('guest', 'write', 'space', '_space')
 box.schema.user.revoke('guest', 'write', 'space', '_schema')
+box.schema.user.revoke('guest', 'create', 'universe')
 c:close()
 
 --
diff --git a/test/box/role.result b/test/box/role.result
index 806cea9..38dc43e 100644
--- a/test/box/role.result
+++ b/test/box/role.result
@@ -671,7 +671,7 @@ box.session.su('admin')
 _ = box.schema.space.create('test')
 ---
 ...
-box.schema.user.grant('john', 'read,write,execute', 'universe')
+box.schema.user.grant('john', 'read', 'universe')
 ---
 ...
 box.session.su('john')
@@ -695,14 +695,33 @@ box.schema.user.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,
+-- revoking role 'public' is another deal:
+-- the superuser or creator of user can do that, and even that would be useless,
 -- since one can still re-grant it back to oneself.
 --
 box.schema.user.revoke('grantee', 'public')
 ---
 - error: Revoke access to role 'public' is denied for user 'john'
 ...
+box.session.su("admin")
+---
+...
+box.schema.user.grant("john", "create", 'universe')
+---
+...
+box.session.su('john')
+---
+...
+box.schema.user.create("grantee2")
+---
+...
+-- must be ok
+box.schema.user.revoke('grantee2', 'public')
+---
+...
+box.schema.user.drop('grantee2')
+---
+...
 box.session.su('admin')
 ---
 ...
diff --git a/test/box/role.test.lua b/test/box/role.test.lua
index e97339f..abdd1b1 100644
--- a/test/box/role.test.lua
+++ b/test/box/role.test.lua
@@ -261,7 +261,7 @@ box.schema.user.grant('grantee', 'role')
 --
 box.session.su('admin')
 _ = box.schema.space.create('test')
-box.schema.user.grant('john', 'read,write,execute', 'universe')
+box.schema.user.grant('john', 'read', 'universe')
 box.session.su('john')
 box.schema.user.grant('grantee', 'role')
 box.schema.user.grant('grantee', 'read', 'space', 'test')
@@ -272,11 +272,18 @@ box.schema.user.grant('grantee', 'read', 'space', 'test')
 --
 box.schema.user.grant('grantee', 'public')
 --
--- revoking role 'public' is another deal - only the
--- superuser can do that, and even that would be useless,
+-- revoking role 'public' is another deal:
+-- the superuser or creator of user can do that, and even that would be useless,
 -- since one can still re-grant it back to oneself.
 --
 box.schema.user.revoke('grantee', 'public')
+box.session.su("admin")
+box.schema.user.grant("john", "create", 'universe')
+box.session.su('john')
+box.schema.user.create("grantee2")
+-- must be ok
+box.schema.user.revoke('grantee2', 'public')
+box.schema.user.drop('grantee2')
 
 box.session.su('admin')
 box.schema.user.drop('john')
diff --git a/test/box/sequence.result b/test/box/sequence.result
index f0164b6..91cd1a9 100644
--- a/test/box/sequence.result
+++ b/test/box/sequence.result
@@ -1451,7 +1451,7 @@ box.session.su('admin')
 ---
 ...
 -- A user cannot alter sequences created by other users.
-box.schema.user.grant('user', 'read,write', 'universe')
+box.schema.user.grant('user', 'read', 'universe')
 ---
 ...
 box.session.su('user')
@@ -1471,6 +1471,9 @@ box.session.su('admin')
 sq:drop()
 ---
 ...
+box.schema.user.grant('user', 'create', 'universe')
+---
+...
 -- A user can alter/use sequences that he owns.
 box.session.su('user')
 ---
@@ -1522,7 +1525,7 @@ s1 = box.schema.space.create('space1')
 _ = s1:create_index('pk')
 ---
 ...
-box.schema.user.grant('user', 'read,write', 'universe')
+box.schema.user.grant('user', 'read, create', 'universe')
 ---
 ...
 box.session.su('user')
@@ -1536,15 +1539,14 @@ s2 = box.schema.space.create('space2')
 ...
 _ = s2:create_index('pk', {sequence = 'seq1'}) -- error
 ---
-- error: Create access to sequence 'seq1' is denied for user 'user'
 ...
 s1.index.pk:alter({sequence = 'seq1'}) -- error
 ---
-- error: Create access to sequence 'seq1' is denied for user 'user'
+- error: Alter access to space 'space1' is denied for user 'user'
 ...
 box.space._space_sequence:replace{s1.id, sq1.id, false} -- error
 ---
-- error: Create access to sequence 'seq1' is denied for user 'user'
+- error: Alter access to space 'space1' is denied for user 'user'
 ...
 box.space._space_sequence:replace{s1.id, sq2.id, false} -- error
 ---
@@ -1552,7 +1554,7 @@ box.space._space_sequence:replace{s1.id, sq2.id, false} -- error
 ...
 box.space._space_sequence:replace{s2.id, sq1.id, false} -- error
 ---
-- error: Create access to sequence 'seq1' is denied for user 'user'
+- error: Alter access to sequence 'seq1' is denied for user 'user'
 ...
 s2.index.pk:alter({sequence = 'seq2'}) -- ok
 ---
@@ -1563,7 +1565,7 @@ box.session.su('admin')
 -- If the user owns a sequence attached to a space,
 -- it can use it for auto increment, otherwise it
 -- needs privileges.
-box.schema.user.revoke('user', 'read,write', 'universe')
+box.schema.user.revoke('user', 'read,create', 'universe')
 ---
 ...
 box.session.su('user')
@@ -1701,10 +1703,10 @@ box.schema.user.create('user1')
 box.schema.user.create('user2')
 ---
 ...
-box.schema.user.grant('user1', 'read,write', 'universe')
+box.schema.user.grant('user1', 'read, create', 'universe')
 ---
 ...
-box.schema.user.grant('user2', 'read,write', 'universe')
+box.schema.user.grant('user2', 'read', 'universe')
 ---
 ...
 box.session.su('user1')
diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua
index af3432f..e0c0fb6 100644
--- a/test/box/sequence.test.lua
+++ b/test/box/sequence.test.lua
@@ -482,12 +482,13 @@ sq:reset() -- error
 box.session.su('admin')
 
 -- A user cannot alter sequences created by other users.
-box.schema.user.grant('user', 'read,write', 'universe')
+box.schema.user.grant('user', 'read', 'universe')
 box.session.su('user')
 sq:alter{step = 2} -- error
 sq:drop() -- error
 box.session.su('admin')
 sq:drop()
+box.schema.user.grant('user', 'create', 'universe')
 
 -- A user can alter/use sequences that he owns.
 box.session.su('user')
@@ -508,7 +509,7 @@ sq:drop()
 sq1 = box.schema.sequence.create('seq1')
 s1 = box.schema.space.create('space1')
 _ = s1:create_index('pk')
-box.schema.user.grant('user', 'read,write', 'universe')
+box.schema.user.grant('user', 'read, create', 'universe')
 box.session.su('user')
 sq2 = box.schema.sequence.create('seq2')
 s2 = box.schema.space.create('space2')
@@ -523,7 +524,7 @@ box.session.su('admin')
 -- If the user owns a sequence attached to a space,
 -- it can use it for auto increment, otherwise it
 -- needs privileges.
-box.schema.user.revoke('user', 'read,write', 'universe')
+box.schema.user.revoke('user', 'read,create', 'universe')
 box.session.su('user')
 s2:insert{nil, 1} -- ok: {1, 1}
 box.session.su('admin')
@@ -571,8 +572,8 @@ box.sequence
 -- to a sequence.
 box.schema.user.create('user1')
 box.schema.user.create('user2')
-box.schema.user.grant('user1', 'read,write', 'universe')
-box.schema.user.grant('user2', 'read,write', 'universe')
+box.schema.user.grant('user1', 'read, create', 'universe')
+box.schema.user.grant('user2', 'read', 'universe')
 box.session.su('user1')
 sq = box.schema.sequence.create('test')
 box.session.su('user2')
diff --git a/test/engine/truncate.result b/test/engine/truncate.result
index 3ad400e..33ff70a 100644
--- a/test/engine/truncate.result
+++ b/test/engine/truncate.result
@@ -506,7 +506,7 @@ con = require('net.box').connect(box.cfg.listen)
 ...
 con:eval([[box.space.access_truncate:truncate()]])
 ---
-- error: Write access to space 'access_truncate' is denied for user 'guest'
+- error: Drop access to space 'access_truncate' is denied for user 'guest'
 ...
 con.space.access_truncate:select()
 ---
-- 
2.7.4





More information about the Tarantool-patches mailing list