Tarantool development patches archive
 help / color / mirror / Atom feed
From: Ilya Markov <imarkov@tarantool.org>
To: georgy@tarantool.org
Cc: tarantool-patches@freelists.org
Subject: [tarantool-patches] [security 3/3] security: Refactor system space access checks
Date: Thu, 29 Mar 2018 10:37:01 +0300	[thread overview]
Message-ID: <b6deb6f960c861695fa151e87ddd643264da3558.1522308989.git.imarkov@tarantool.org> (raw)
In-Reply-To: <cover.1522308989.git.imarkov@tarantool.org>
In-Reply-To: <cover.1522308989.git.imarkov@tarantool.org>

From: imarkov <imarkov@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              | 283 ++++++++++++++++++++++++++----------------
 src/box/index.cc              |   1 +
 src/box/lua/schema.lua        |  12 +-
 src/box/schema.cc             | 119 +++++++++++++++++-
 src/box/schema.h              |  15 +++
 src/box/space.c               |   3 +-
 src/box/space.h               |  17 ++-
 src/box/sysview_engine.c      |   4 +
 src/box/user.cc               |   4 -
 src/box/user.h                |   7 ++
 test/box/access.result        |  30 ++---
 test/box/access.test.lua      |  14 +--
 test/box/access_misc.result   |  46 +++++--
 test/box/access_misc.test.lua |  11 +-
 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      |  17 ++-
 test/box/sequence.test.lua    |  10 +-
 test/engine/truncate.result   |   2 +-
 21 files changed, 461 insertions(+), 180 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 8455373..4a3b1d8 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);
 }
 
 /**
@@ -1496,7 +1483,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);
@@ -1523,7 +1512,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,
@@ -1562,7 +1551,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); });
 		/*
@@ -1660,7 +1650,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);
 
 	/*
@@ -1894,10 +1884,16 @@ 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);
+	}
 	/*
 	 * Truncate counter is updated - truncate the space.
 	 */
@@ -2133,7 +2129,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;
@@ -2142,7 +2140,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,
@@ -2169,7 +2167,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);
@@ -2271,7 +2269,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;
@@ -2285,8 +2285,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,
@@ -2300,7 +2300,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);
@@ -2436,17 +2436,21 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event)
 	struct tuple *new_tuple = stmt->new_tuple;
 	txn_check_singlestatement_xc(txn, "Space _collation");
 	struct coll *old_coll = NULL;
+	struct credentials *cr = effective_user();
 	if (old_tuple != NULL) {
 		/* TODO: Check that no index uses the collation */
 		uint32_t old_id = tuple_field_u32_xc(old_tuple,
 						     BOX_COLLATION_FIELD_ID);
 		old_coll = coll_by_id(old_id);
 		assert(old_coll != NULL);
-		access_check_ddl(old_coll->name, old_coll->owner_id,
-				 SC_COLLATION,
-				 new_tuple == NULL ? PRIV_D: PRIV_A,
-				 false);
-
+		if (cr->uid != ADMIN && old_coll->owner_id != cr->uid) {
+			struct user *user = user_find_xc(cr->uid);
+			tnt_raise(AccessDeniedError,
+				  priv_name(new_tuple == NULL ? PRIV_D : PRIV_A),
+				  schema_object_name(SC_COLLATION),
+				  old_coll->name,
+				  user->def->name);
+		}
 		struct trigger *on_commit =
 			txn_alter_trigger_new(coll_cache_delete_coll, old_coll);
 		txn_on_commit(txn, on_commit);
@@ -2465,8 +2469,14 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event)
 
 	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,
-			 old_tuple == NULL ? PRIV_C : PRIV_A, false);
+	if (cr->uid != ADMIN && new_def.owner_id != cr->uid) {
+		struct user *user = user_find_xc(new_def.owner_id);
+		tnt_raise(AccessDeniedError,
+			  priv_name(old_tuple == NULL ? PRIV_C : PRIV_A),
+			  schema_object_name(SC_COLLATION),
+			  new_def.name,
+			  user->def->name);
+	}
 	struct coll *new_coll = coll_new(&new_def);
 	if (new_coll == NULL)
 		diag_raise();
@@ -2509,6 +2519,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
@@ -2522,35 +2561,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;
 	}
@@ -2559,22 +2596,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;
 	}
@@ -2587,16 +2621,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);
@@ -2713,6 +2754,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);
@@ -2742,31 +2784,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 (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();
+			}
 		}
 	}
+	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);
 }
 
 /**
@@ -3001,7 +3066,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");
@@ -3018,7 +3083,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;
 	}
@@ -3098,10 +3163,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/index.cc b/src/box/index.cc
index 69fc761..a58e172 100644
--- a/src/box/index.cc
+++ b/src/box/index.cc
@@ -38,6 +38,7 @@
 #include "txn.h"
 #include "rmean.h"
 #include "info.h"
+#include "xrow.h"
 
 /* {{{ Utilities. **********************************************/
 
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 1d245e3..c285ccf 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1934,10 +1934,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
 
@@ -2055,12 +2052,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 2e8533b..bef0f25 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -37,6 +37,9 @@
 #include "scoped_guard.h"
 #include "version.h"
 #include "user.h"
+#include "session.h"
+#include "xrow.h"
+#include "memtx_tuple.h"
 #include <stdio.h>
 /**
  * @module Data Dictionary
@@ -251,6 +254,15 @@ schema_find_id(uint32_t system_space_id, uint32_t index_id,
 }
 
 /**
+ * Access checks for system spaces, which can be modified only by admin
+ * _collation, _cluster
+ */
+static int
+sys_read_write_access_check(MAYBE_UNUSED struct space *space,
+			    user_access_t access);
+
+
+/**
  * Initialize a prototype for the two mandatory data
  * dictionary spaces and create a cache entry for them.
  * When restoring data from the snapshot these spaces
@@ -308,7 +320,9 @@ schema_init()
 	sc_space_new(BOX_SEQUENCE_DATA_ID, "_sequence_data", key_def,
 		     &on_replace_sequence_data, NULL);
 
-	/* _space_seq - association space <-> sequence. */
+	/* _space_seq - association space <-> sequence.
+	 *
+	 */
 	sc_space_new(BOX_SPACE_SEQUENCE_ID, "_space_sequence", key_def,
 		     &on_replace_space_sequence, NULL);
 
@@ -520,6 +534,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 +640,42 @@ 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..63b81f4 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -36,6 +36,8 @@
 #include "error.h"
 #include "space.h"
 #include "latch.h"
+#include "user.h"
+#include "iproto_constants.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -103,6 +105,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 +214,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 cc6cbed..a7a0930 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 6408eed..5335b31 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -49,6 +49,8 @@ struct request;
 struct port;
 struct tuple;
 
+typedef int (*access_check_func_t)(struct space *space, user_access_t access);
+
 struct space_vtab {
 	/** Free a space instance. */
 	void (*destroy)(struct space *);
@@ -139,6 +141,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. */
@@ -291,8 +294,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)
@@ -424,6 +430,12 @@ space_swap_index(struct space *lhs, struct space *rhs,
 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" */
 
@@ -540,4 +552,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 b478e78..0f126d2 100644
--- a/src/box/sysview_engine.c
+++ b/src/box/sysview_engine.c
@@ -32,6 +32,9 @@
 #include "sysview_index.h"
 #include "schema.h"
 #include "space.h"
+#include "session.h"
+#include "xrow.h"
+#include "iproto_constants.h"
 
 static void
 sysview_space_destroy(struct space *space)
@@ -230,6 +233,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.cc b/src/box/user.cc
index 7fa66da..3576efe 100644
--- a/src/box/user.cc
+++ b/src/box/user.cc
@@ -193,10 +193,6 @@ user_grant_priv(struct user *user, struct priv_def *def)
 	}
 }
 
-/**
- * Find the corresponding access structure
- * given object type and object id.
- */
 struct access *
 access_find(struct priv_def *priv)
 {
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 4354b05..8cd631a 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')
 ---
@@ -1174,11 +1173,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')
 ---
@@ -1380,21 +1379,16 @@ 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.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")
@@ -1442,23 +1436,29 @@ s:drop()
 ...
 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 e3ad687..581342c 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -521,12 +521,7 @@ box.schema.user.create('test_user')
 box.schema.func.create('test_func')
 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
 s1 = box.schema.space.create("test_space")
@@ -554,10 +549,13 @@ 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 abc0be7..2f94b63 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,9 +182,18 @@ s:delete({3})
 ---
 - error: Write access to space 'admin_space' is denied for user 'guest'
 ...
+session.su('admin')
+---
+...
+box.schema.user.grant('guest', 'read', 'universe')
+---
+...
+session.su('guest')
+---
+...
 s:drop()
 ---
-- error: Write access to space '_space_sequence' is denied for user 'guest'
+- error: Drop access to space 'admin_space' is denied for user 'guest'
 ...
 gs = box.schema.space.create('guest_space')
 ---
@@ -177,11 +201,14 @@ gs = box.schema.space.create('guest_space')
 ...
 box.schema.func.create('guest_func')
 ---
-- error: Write access to space '_func' is denied for user 'guest'
+- error: Create access to function 'guest_func' is denied for user 'guest'
 ...
 session.su('admin')
 ---
 ...
+box.schema.user.revoke('guest', 'read', 'universe')
+---
+...
 s:select()
 ---
 - - [2]
@@ -277,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')
@@ -344,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)
 ---
@@ -381,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)
 ---
@@ -389,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')
 ---
@@ -409,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 cf6447e..e78058a 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,11 +73,15 @@ 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')
 box.schema.func.create('guest_func')
 
 session.su('admin')
+box.schema.user.revoke('guest', 'read', 'universe')
 s:select()
 --
 -- Create user with universe read&write grants
@@ -116,7 +125,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 46d85b3..1b62584 100644
--- a/test/box/net.box.result
+++ b/test/box/net.box.result
@@ -2140,6 +2140,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
 ---
 ...
@@ -2188,6 +2191,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 87e26f8..f9272f2 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..b8055ff 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..a1d6808 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 a509207..631a8dd 100644
--- a/test/box/sequence.result
+++ b/test/box/sequence.result
@@ -1447,7 +1447,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')
@@ -1518,7 +1518,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')
@@ -1532,15 +1532,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
 ---
@@ -1548,7 +1547,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
 ---
@@ -1559,7 +1558,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')
@@ -1697,10 +1696,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 26147bb..f6d4c29 100644
--- a/test/box/sequence.test.lua
+++ b/test/box/sequence.test.lua
@@ -481,7 +481,7 @@ 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
@@ -507,7 +507,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')
@@ -522,7 +522,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')
@@ -570,8 +570,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 b6e1a99..1547b8c 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

  parent reply	other threads:[~2018-03-29  7:37 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-03-29  7:36 [tarantool-patches] [security 0/3] System space access check lists Ilya Markov
2018-03-29  7:36 ` [tarantool-patches] [security 1/3] box: Add system view for _sequence system space Ilya Markov
2018-03-29  7:37 ` [tarantool-patches] [security 2/3] security: Refactor reads from systems spaces Ilya Markov
2018-05-13 12:49   ` [tarantool-patches] " Konstantin Osipov
2018-03-29  7:37 ` Ilya Markov [this message]
2018-04-03 14:01 ` [tarantool-patches] Re: [security 0/3] System space access check lists Georgy Kirichenko
  -- strict thread matches above, loose matches on Subject: below --
2018-03-28  8:09 [tarantool-patches] " Ilya Markov
2018-03-28  8:09 ` [tarantool-patches] [security 3/3] security: Refactor system space access checks Ilya Markov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=b6deb6f960c861695fa151e87ddd643264da3558.1522308989.git.imarkov@tarantool.org \
    --to=imarkov@tarantool.org \
    --cc=georgy@tarantool.org \
    --cc=tarantool-patches@freelists.org \
    --subject='Re: [tarantool-patches] [security 3/3] security: Refactor system space access checks' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox