Tarantool development patches archive
 help / color / mirror / Atom feed
* [PATCH v2 0/4] Finish implementation of privileges.
@ 2018-08-22 13:39 Serge Petrenko
  2018-08-22 13:39 ` [PATCH v2 1/4] Introduce separate entity object types for entity privileges Serge Petrenko
                   ` (3 more replies)
  0 siblings, 4 replies; 13+ messages in thread
From: Serge Petrenko @ 2018-08-22 13:39 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: kostja, tarantool-patches, Serge Petrenko

First patch introduces a separate schema_object_type for entity privileges
and adds '' (empty string) in object_id field to indicate grants on an entire
entity.
https://github.com/tarantool/tarantool/issues/3574

Second patch adds previously non-existent entities to access control: user
and role.
https://github.com/tarantool/tarantool/issues/3524

Third patch introduces single object access checks to access_checks_ddl.
Theese checks were previously ignored and in some cases granting privileges
on a single object had no effect.
https://github.com/tarantool/tarantool/issues/3530

Fourth patch adds an upgrade script for 1.10.2 to grant alter, create and
drop privileges to users with read and write privileges on objects.
https://github.com/tarantool/tarantool/issues/3539

Branch: https://github.com/tarantool/tarantool/tree/sergepetrenko/gh-3539-1.10-upgrade-script 
Changes in v2:
  - fix indentation
  - move introduction of SC_ENTITY_USER and
    SC_ENTITY_ROLE to 2nd patch from 1st
  - move new code handling entity grants
    in priv_def_check() from 2nd patch to 1st
  - remove extraneous test changes from patches 2 and 3

Serge Petrenko (4):
  Introduce separate entity object types for entity privileges.
  Add entities user, role to access control.
  Add single object privilege checks to access_check_ddl.
  Add a privilege upgrade script and update tests.

 src/box/alter.cc                            | 157 +++++++++++-----
 src/box/bootstrap.snap                      | Bin 1540 -> 1555 bytes
 src/box/lua/schema.lua                      |  88 +++++----
 src/box/lua/upgrade.lua                     |  46 +++++
 src/box/schema.cc                           |  11 +-
 src/box/schema.h                            |  31 ++--
 src/box/schema_def.c                        |  21 +++
 src/box/schema_def.h                        |  18 +-
 src/box/user.cc                             |  68 ++++---
 src/box/user.h                              |   5 +
 test/box-py/bootstrap.result                |  14 +-
 test/box-tap/auth.test.lua                  |   5 -
 test/box-tap/session.test.lua               |  15 +-
 test/box/access.result                      | 213 ++++++++++++++++++++--
 test/box/access.test.lua                    |  72 ++++++--
 test/box/access_bin.result                  |   4 +-
 test/box/access_bin.test.lua                |   4 +-
 test/box/access_escalation.result           |  18 +-
 test/box/access_escalation.test.lua         |  11 +-
 test/box/access_misc.result                 |  14 +-
 test/box/access_misc.test.lua               |   4 +-
 test/box/alter.result                       |   8 +-
 test/box/call.result                        |   4 +-
 test/box/call.test.lua                      |   4 +-
 test/box/errinj.result                      |  39 +++-
 test/box/errinj.test.lua                    |  25 ++-
 test/box/net.box.result                     | 265 ++++++++++++++++++++++++++--
 test/box/net.box.test.lua                   | 121 +++++++++++--
 test/box/net_msg_max.result                 |  13 +-
 test/box/net_msg_max.test.lua               |   9 +-
 test/box/on_replace.result                  |   2 +-
 test/box/on_replace.test.lua                |   2 +-
 test/box/protocol.result                    |   9 +-
 test/box/protocol.test.lua                  |   5 +-
 test/box/push.result                        |  52 +++++-
 test/box/push.test.lua                      |  27 ++-
 test/box/role.result                        |   9 +
 test/box/schema_reload.result               |  32 +++-
 test/box/schema_reload.test.lua             |  16 +-
 test/box/sequence.result                    |   3 +
 test/box/sql.result                         |   9 +-
 test/box/sql.test.lua                       |   5 +-
 test/box/stat_net.result                    |   7 +-
 test/box/stat_net.test.lua                  |   5 +-
 test/engine/params.result                   |   6 -
 test/engine/params.test.lua                 |   2 -
 test/engine/replica_join.result             |   6 -
 test/engine/replica_join.test.lua           |   2 -
 test/replication/autobootstrap.result       |  23 ++-
 test/replication/autobootstrap.test.lua     |  10 +-
 test/replication/catch.result               |   6 -
 test/replication/catch.test.lua             |   2 -
 test/replication/errinj.result              |   3 -
 test/replication/errinj.test.lua            |   1 -
 test/replication/gc.result                  |   6 -
 test/replication/gc.test.lua                |   2 -
 test/replication/join_vclock.result         |   6 -
 test/replication/join_vclock.test.lua       |   2 -
 test/replication/skip_conflict_row.result   |   6 -
 test/replication/skip_conflict_row.test.lua |   2 -
 test/vinyl/replica_quota.result             |   6 -
 test/vinyl/replica_quota.test.lua           |   2 -
 test/wal_off/func_max.result                |  25 +--
 test/wal_off/func_max.test.lua              |  19 +-
 test/xlog/errinj.result                     |   9 +-
 test/xlog/errinj.test.lua                   |   4 +-
 test/xlog/misc.result                       |   9 +-
 test/xlog/misc.test.lua                     |   5 +-
 test/xlog/upgrade.result                    |  18 +-
 69 files changed, 1294 insertions(+), 378 deletions(-)

-- 
2.15.2 (Apple Git-101.1)

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 1/4] Introduce separate entity object types for entity privileges.
  2018-08-22 13:39 [PATCH v2 0/4] Finish implementation of privileges Serge Petrenko
@ 2018-08-22 13:39 ` Serge Petrenko
  2018-08-22 15:42   ` Serge Petrenko
  2018-08-22 16:22   ` Vladimir Davydov
  2018-08-22 13:39 ` [PATCH v2 2/4] Add entities user, role to access control Serge Petrenko
                   ` (2 subsequent siblings)
  3 siblings, 2 replies; 13+ messages in thread
From: Serge Petrenko @ 2018-08-22 13:39 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: kostja, tarantool-patches, Serge Petrenko

When granting or revoking a privilege on an entire entity, id 0 was used
to indicate the fact that we don't grant a privilege on a single object,
but on a whole entity. This caused confusion, because for entity USER,
for example, id 0 is a valid object id (user 'guest' uses it).
Any non-zero id dedicated to this cause obviously may be confused as well.
Fix this by creating separate schema_object_types for entities:
SC_ENTITY_SPACE, SC_ENTITY_SEQUENCE, etc.

Closes: #3574
Prerequisite: #3524
---
 src/box/alter.cc             |  39 ++++++++++++++++++++++++++++-
 src/box/bootstrap.snap       | Bin 1540 -> 1556 bytes
 src/box/lua/schema.lua       |  58 ++++++++++++++++++++++++++-----------------
 src/box/lua/upgrade.lua      |  23 +++++++++++++++++
 src/box/schema.cc            |   9 +++----
 src/box/schema.h             |  23 +++++++++--------
 src/box/schema_def.c         |  17 +++++++++++++
 src/box/schema_def.h         |  16 +++++++++++-
 src/box/user.cc              |  27 +++++++++++---------
 test/box-py/bootstrap.result |  14 +++++------
 test/box/access.result       |   6 ++---
 test/box/access_misc.result  |   8 +++---
 test/box/alter.result        |   8 +++---
 test/xlog/upgrade.result     |  14 +++++------
 14 files changed, 184 insertions(+), 78 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 3007a131d..8eba21a51 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -2537,10 +2537,35 @@ priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple)
 {
 	priv->grantor_id = tuple_field_u32_xc(tuple, BOX_PRIV_FIELD_ID);
 	priv->grantee_id = tuple_field_u32_xc(tuple, BOX_PRIV_FIELD_UID);
+
 	const char *object_type =
 		tuple_field_cstr_xc(tuple, BOX_PRIV_FIELD_OBJECT_TYPE);
-	priv->object_id = tuple_field_u32_xc(tuple, BOX_PRIV_FIELD_OBJECT_ID);
 	priv->object_type = schema_object_type(object_type);
+
+	const char *data = tuple_field(tuple, BOX_PRIV_FIELD_OBJECT_ID);
+	if (data == NULL) {
+		tnt_raise(ClientError, ER_NO_SUCH_FIELD,
+			  BOX_PRIV_FIELD_OBJECT_ID + TUPLE_INDEX_BASE);
+	}
+	/*
+	 * When granting or revoking privileges on a whole entity
+	 * we pass empty string ('') to object_id to indicate
+	 * grant on every object of that entity.
+	 * So check for that first.
+	 */
+	switch (mp_typeof(*data)) {
+	case MP_STR:
+		if (mp_decode_strl(&data) == 0) {
+			/* Entity-wide privilege. */
+			priv->object_id = 0;
+			priv->object_type = schema_entity_type(priv->object_type);
+			break;
+		}
+		FALLTHROUGH;
+	default:
+		priv->object_id = tuple_field_u32_xc(tuple,
+						     BOX_PRIV_FIELD_OBJECT_ID);
+	}
 	if (priv->object_type == SC_UNKNOWN) {
 		tnt_raise(ClientError, ER_UNKNOWN_SCHEMA_OBJECT,
 			  object_type);
@@ -2645,6 +2670,18 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type)
 		}
 		/* Not necessary to do during revoke, but who cares. */
 		role_check(grantee, role);
+		break;
+	}
+	case SC_ENTITY_SPACE:
+	case SC_ENTITY_FUNCTION:
+	case SC_ENTITY_SEQUENCE:
+	{
+		/* Only admin may grant privileges on an entire entity. */
+		if (grantor->def->uid != ADMIN) {
+			tnt_raise(AccessDeniedError, priv_name(priv_type),
+				  schema_object_name(priv->object_type), name,
+				  grantor->def->name);
+		}
 	}
 	default:
 		break;
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index b610828c9c9ae9a22acdd8c150c16c6838b7a273..44992b050e9d73d69608fb92782d1e965f6cdc51 100644
GIT binary patch
delta 1551
zcmV+q2JrcW43rFz7=JM>GB!9ZXE|m$WMeloGhzx!ZgX^DZewLSAT%>EG&o@}VJ$c{
zGcheRV>UP~VPZ03EipATVq!NoIX7cCWeQe9Y;R+0Iv{&}3JTS_3%bn(nE=k-$Bkd5
z0000004TLD{QyvfEC41w*h<hAaRLBeJb=_J{}F0FAgJ*mVt>V;#?6&k&og$Aul<dv
zv3Dv(N-{sS_S_R$=If$P=IFJ-Qc5dSF3)wMv;i7#PyNg>UnEmX0mA^f0L=io5sT!g
zD{Fnf#vxPKYgy*n?1AMVIy{=p9gia^hC=L0fWLuz8rC_P&bxEnm$jbL+0jvRI_+sG
zru|Ih;m)d9Ab%fU2!MAhTHBbGce$H&nf~2*&yFmHLT-w8E2;yCj1eht_F<%$wYjrA
z--_qmirxZNlbP>7cRjpY(I5*1%V7?t>8y7vDuOB;TVebwm5d3mRG{g<l`_3ftd)rc
zn_;zHClt(+{hgbmxU+pKw$>&pI!j<g$w35)6B;Hdw}0vc|76#A%XVkpWtdu!!EHY`
zQ473V(OEa~Z9g}|)Sgs&h}w*uq0W{Rc(<Zvf8ImXW-LrCDFM%iVB<2)IG&t`=^X0p
z%Yh$fD8!Boc(<bFh|kp4F)S=%TMBr$q8k55lCM#${Y=DS+Bfbz&A~d;aQkK7b>5ZV
zdG3dP6Mx6^(8sb6!1;Xg8sDAa&WZ-$y>W_Zn~dpXc#Nf8o1_@R#?P@Ion`uUK3>En
z4|O&qz`GSKF%8FiNe275855I3zK&RX`$!^s><!J0t0shXfpC3jd1!TLaj@ww*s37t
z-HLiP9v};~La9)x)67Zcgesj%8+J-DBoj%4cYiCoL(1@BuzjT$lI`Wf$ik??2xi9d
ztc3#01eTBGMdTtHiCXRru~^$q5*v~bieRnCyA@ptRojk|H*sWb>UR9^o~tSri06BO
z0Q-*j>z9Q*4+(#G7_m1GyjxLQ$D%fO4}ENn1MgPUw1qv^wQ}ZNwi<Z1qOu?JnRn@f
zM}I7Goln@sxA7u>0uPF;){6CdrBo=>OXh`5;7UxHP%|pfgijbup=MGrPps{8(Quhz
zWw^YXTN9%N1NEZh;%hNtVMQxeEKqt?SWV_GYBSbz?d=0?f`WpY*c$IvG*qAOKeIM_
zQh2wbDYx3&2cp<tnmn~GC&WCKzkjS};(teS=fpg|%u;9X?K_pwnFhnDwIsY-(GRlN
ztgP{o#>Rx0;YE$dsj=v%(gx?x*@*;&XWUfcndq})KR0uozbQ*?y?v(<-mNH&3>7#_
z#&nJefg`BVSY%8M%m4s@04M+v21hAoRSyz?&^U^NFbZH021E!rh)xs$fZ#w_L4OfM
zEf@qq49vQeA))6t$8JsxhygJm2AP2;6jR1Ytzq>yuq)+j_YBuXSm^>oOc44oRR2+r
zfKn$8@9xdDJsnXgj8fvJX-OOEnOz2N82f7Vr6^tAcX*WFIPM{0Ntj!pLMS~2mv!oE
zRrM)J%oMN{r_N{V(s7_ah4$kjihs$6<OnC-Xy|vNdloebHTnit@EUCcD|n6OjV)em
zPC$*u2HNnREZ$rv)082si4@Jx$$z3_Ifq*d0kp^Vv;;5qa1a|JkDvz!Y7ZzgZLtTG
znKqel_G}Nk_kmTD8UY2Zw|;97AY4LmR2{?0eUDLb#Sq+kdJS#-4;^$U>3_%%=)mhn
z2W|IQc+vMG<^-d?>WOsJ?wF5DwFGHVZ;5{eV6!P9un202v<JSzI-4XFWp2pfJ=}&9
z|B3!R7$^i;Ur3=7lANu}q6eAjK|>w!Z8R4d19B91M{)$#!E>qp7jG6HO>MyhU^@4T
z^f(MLmIX$FanP|HqB(=3bAPgI29$n(?koqvkkh_+Jdt&9kemV%zz>2&MdX*2g^(0B
zb3fr-G#O-Ux`l`P)LF58aj(U8%fMRlqE2MOof#G*F;Vmq(Um!gfT;c#$##_N2vs0b
zGn`~?u8)RY;W>4D58f)EMzh}yokR<1H8AM^(7KO=Ygngs1QB1+=m<%o!R6Hut?jEW
B)aL*I

delta 1534
zcmV<a1p)e$41^4j7=JJ=G%zh^V>dT4F*z~{Np5p=VQyn(Iv_b=V_`LAF*YqTV`gS8
zG-hTvEnzrjFfCy?W;Qf4VP!dGGGq!?Lu_wjYdRo%F*+bOeF_TIx(m9^1&9F7A|rpb
zr2qf`001bpFZ}>e{VM<lJ=RLl7I6XqUp$Ch%_7lYJ_Lj_@PFoXP~+we(A76EO7~<&
z)F3;RA|;uhw(`4WgTRw>I&<{eU?~Ai$VOI~v629dx2JaIm@kx4N&&zCxB$xlBK}6v
zM#}nreKVfmYggvF>;bcooE<&pjzw|&ydgCd;B)Xi4C|Up=iRyONm;Mu=<J|49rmyj
z!~XSg?^aVP(0>n31mId~$~K1OP2OJJrGIzc!y|v*5Q}mxHFpG|K|%#jeQ^A;E^l>*
z3*Bp}NefgxX1+gm^KmUTgDMc1Jsb?P)oZC4!mL!?OgGcvU!7uNP_0u4V%4c@$$Tde
z<JJuBKo=+bJ1@oWHsew%rA$(CR4!bhCq$aiEJ3j}Cx2ttc)M<~yh-n@9Ru2aUZxgY
zOU+j=F>OCDy|bcHe0;hLW1g*=6kJQqvOo9n=`!@rT1v3PA=sEaGtUmrgF1V*+HwF$
z%o|ck2Ck)MDdN~U>lpNk)Rh9)Qd8p>$FU`TvVVQ}JNw3+pEy`&7|(v!cb#wKcU}Xc
zztr*E^M9!-1a3aZxQyRo-mQ)X;JxvQVS5a1E-Z#;*X8(mp!hlb(b=Wn_TxWHa?e&p
z0<NVdiD5jvlVdHR&M*6U`SOA@wvN<F<4PlXstt_|t42g+g<yeLeOP%|b(q;~x1wEl
zEj90~M>ms_NyVg8C=MtXRLX=hZO}2jlujiL*MCw|r<8%iFyl-urCG}bk_A!)lFN(Z
z*9rxd2`ZdQ%ZOz}8m(9>QmHeYBvm9q6rtA9wbTrW=uAh+mpZa8Z9M*WuhEnWw8OPP
z0Ken?`duE+M1Fc0sWuO;rRJ>TPnWxUKDEZdwbU$UUQcC(VtJFR2G>&aT`%TX-lUJ$
zzkkQk8h`!+Ak42Gb5O~I9X_GL4xhR(RGq@`<8YEmeGLBa%Ed{YaV#4wH7X6(wXrob
zRx(mCOfa|?Bo}qm;#z9jPz5?Ii>k-mKV61Bw$iw6MNm)>lUj2vHAVFK{#e#!kBV!l
ziDGG`ab1f2WXaFUg5t{}`TM8d6EBXt_J3uuRg$)9X&lOg&M*+4wU)S+njl=^i<EJZ
zX2ppwLyQ}LBXiMF<cgb*y<$jG_{C2oetkYX_VcpV`IoM<QW}RMaV<4zXt3a^V}T&3
z(OhIq3CsWhfB+}}AqGb&W<?JYfWSD4qc93!7zRWLI0_9I0RX{)ke~>l77Rj$ynhqj
zxeG%W18Ui|uoin^FOJH&6BE><l!R4e54%@5i^3tJ2=RjuhoRy}$qP!PWxP;CvBe2+
zB`AQ$m7GU4)NS@llAFhZ>1-;h#Us!zr2@yPWOP}$xk=>TS*Ej6Q8ZgBx&}sXh}Wym
z;Yvs=Xm6s&oTIkS5VXc28)t^2+kc4qR4r1#;*gdpU~^0s$pG8pm?G)gVGer7p5*j@
zrQSOro_cGm&fR}<@b8h^=iXW!%4reE=RqJFW<g443Z&4$8`^`Azzc12c=p(Su(w)T
z1)jmCmtK6UOn@l~=HxoFllhK1;>sqt`1BcS0HC@As3ba#z^Ko9Pi8avYk$Oa!!S#&
zSx3_!A8xyrpfT#3Y0zk_Hi}Y>p%$pI61F+5M3N&%2xKJAC;$CDD}<6?=$#5uoTZ5Z
zCNj}EgB|fKv=$L@ag>MEJ_76DnN*W1Z`O`MZ9ptwx|K1yxMV1V1(Ltx%(4y7obk4E
zW?=^8GIQ=kv4A0`wS7N1*>`Z!opukPx!_g@c{8|RJi<`!QS!z#<&+X#nnV8V6uo^%
zFL^|hfwSZ-+|LAZ&d5i?Zt>-$tMH_1UnT!Yb{@SussOv@WfJ50JyfLCQd9Bw_N-dh
k2=V>Mm|$S01}gnuSl5<E4okR>OyKJnJxL~`9Mur5?adb1g8%>k

diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index b9b8c9004..d347fd1e6 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1801,16 +1801,16 @@ local function privilege_name(privilege)
 end
 
 local function object_resolve(object_type, object_name)
+    if object_name ~= nil and type(object_name) ~= 'string'
+            and type(object_name) ~= 'number' then
+        box.error(box.error.ILLEGAL_PARAMS, "wrong object name type")
+    end
     if object_type == 'universe' then
-        if object_name ~= nil and type(object_name) ~= 'string'
-                and type(object_name) ~= 'number' then
-            box.error(box.error.ILLEGAL_PARAMS, "wrong object name type")
-        end
         return 0
     end
     if object_type == 'space' then
-        if object_name == nil or object_name == 0 then
-            return 0
+        if object_name == '' then
+            return ''
         end
         local space = box.space[object_name]
         if  space == nil then
@@ -1819,8 +1819,8 @@ local function object_resolve(object_type, object_name)
         return space.id
     end
     if object_type == 'function' then
-        if object_name == nil or object_name == 0 then
-            return 0
+        if object_name == '' then
+            return ''
         end
         local _vfunc = box.space[box.schema.VFUNC_ID]
         local func
@@ -1836,8 +1836,8 @@ local function object_resolve(object_type, object_name)
         end
     end
     if object_type == 'sequence' then
-        if object_name == nil or object_name == 0 then
-            return 0
+        if object_name == '' then
+            return ''
         end
         local seq = sequence_resolve(object_name)
         if seq == nil then
@@ -1864,7 +1864,7 @@ local function object_resolve(object_type, object_name)
 end
 
 local function object_name(object_type, object_id)
-    if object_type == 'universe' then
+    if object_type == 'universe' or object_id == '' then
         return ""
     end
     local space
@@ -2072,12 +2072,18 @@ local function grant(uid, name, privilege, object_type,
                      object_name, options)
     -- From user point of view, role is the same thing
     -- as a privilege. Allow syntax grant(user, role).
-    if object_name == nil and object_type == nil then
-        -- sic: avoid recursion, to not bother with roles
-        -- named 'execute'
-        object_type = 'role'
-        object_name = privilege
-        privilege = 'execute'
+    if object_name == nil then
+        if object_type == nil then
+            -- sic: avoid recursion, to not bother with roles
+            -- named 'execute'
+            object_type = 'role'
+            object_name = privilege
+            privilege = 'execute'
+        else
+            -- Allow syntax grant(user, priv, entity)
+            -- for entity grants.
+            object_name = ''
+        end
     end
     local privilege_hex = privilege_check(privilege, object_type)
 
@@ -2117,10 +2123,16 @@ end
 local function revoke(uid, name, privilege, object_type, object_name, options)
     -- From user point of view, role is the same thing
     -- as a privilege. Allow syntax revoke(user, role).
-    if object_name == nil and object_type == nil then
-        object_type = 'role'
-        object_name = privilege
-        privilege = 'execute'
+    if object_name == nil then
+        if object_type == nil then
+            object_type = 'role'
+            object_name = privilege
+            privilege = 'execute'
+        else
+            -- Allow syntax revoke(user, privilege, entity)
+            -- to revoke entity privileges.
+            object_name = ''
+        end
     end
     local privilege_hex = privilege_check(privilege, object_type)
     options = options or {}
@@ -2192,8 +2204,8 @@ local function drop(uid, opts)
     local privs = _vpriv.index.primary:select{uid}
 
     for k, tuple in pairs(privs) do
-	-- we need an additional box.session.su() here, because of
-	-- unnecessary check for privilege PRIV_REVOKE in priv_def_check()
+        -- we need an additional box.session.su() here, because of
+        -- unnecessary check for privilege PRIV_REVOKE in priv_def_check()
         box.session.su("admin", revoke, uid, uid, tuple[5], tuple[3], tuple[4])
     end
     box.space[box.schema.USER_ID]:delete{uid}
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index 0293f6ef8..ec3826399 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -964,6 +964,28 @@ local function upgrade_to_1_10_0()
     create_vsequence_space()
 end
 
+--------------------------------------------------------------------------------
+--- Tarantool 1.10.2
+--------------------------------------------------------------------------------
+local function upgrade_priv_to_1_10_2()
+    local _priv = box.space._priv
+    local _vpriv = box.space._vpriv
+    local format = _priv:format()
+
+    format[4].type = 'scalar'
+    _priv:format(format)
+    format = _vpriv:format()
+    format[4].type = 'scalar'
+    _vpriv:format(format)
+    _priv.index.primary:alter{parts={2, 'unsigned', 3, 'string', 4, 'scalar'}}
+    _vpriv.index.primary:alter{parts={2, 'unsigned', 3, 'string', 4, 'scalar'}}
+    _priv.index.object:alter{parts={3, 'string', 4, 'scalar'}}
+    _vpriv.index.object:alter{parts={3, 'string', 4, 'scalar'}}
+end
+
+local function upgrade_to_1_10_2()
+    upgrade_priv_to_1_10_2()
+end
 
 local function get_version()
     local version = box.space._schema:get{'version'}
@@ -991,6 +1013,7 @@ local function upgrade(options)
         {version = mkversion(1, 7, 6), func = upgrade_to_1_7_6, auto = false},
         {version = mkversion(1, 7, 7), func = upgrade_to_1_7_7, auto = true},
         {version = mkversion(1, 10, 0), func = upgrade_to_1_10_0, auto = true},
+        {version = mkversion(1, 10, 2), func = upgrade_to_1_10_2, auto = true},
     }
 
     for _, handler in ipairs(handlers) do
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 433f52c08..aaa63a083 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -535,11 +535,12 @@ schema_find_name(enum schema_object_type type, uint32_t object_id)
 {
 	switch (type) {
 	case SC_UNIVERSE:
+	case SC_ENTITY_SPACE:
+	case SC_ENTITY_FUNCTION:
+	case SC_ENTITY_SEQUENCE:
 		return "";
 	case SC_SPACE:
 		{
-			if (object_id == 0)
-				return "SPACE";
 			struct space *space = space_by_id(object_id);
 			if (space == NULL)
 				break;
@@ -547,8 +548,6 @@ schema_find_name(enum schema_object_type type, uint32_t object_id)
 		}
 	case SC_FUNCTION:
 		{
-			if (object_id == 0)
-				return "FUNCTION";
 			struct func *func = func_by_id(object_id);
 			if (func == NULL)
 				break;
@@ -556,8 +555,6 @@ schema_find_name(enum schema_object_type type, uint32_t object_id)
 		}
 	case SC_SEQUENCE:
 		{
-			if (object_id == 0)
-				return "SEQUENCE";
 			struct sequence *seq = sequence_by_id(object_id);
 			if (seq == NULL)
 				break;
diff --git a/src/box/schema.h b/src/box/schema.h
index 0822262d0..f1735ff34 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -250,16 +250,19 @@ static inline
 struct access *
 entity_access_get(enum schema_object_type type)
 {
-       switch (type) {
-       case SC_SPACE:
-               return entity_access.space;
-       case SC_FUNCTION:
-               return entity_access.function;
-       case SC_SEQUENCE:
-               return entity_access.sequence;
-       default:
-               return NULL;
-       }
+	switch (type) {
+	case SC_SPACE:
+	case SC_ENTITY_SPACE:
+		return entity_access.space;
+	case SC_FUNCTION:
+	case SC_ENTITY_FUNCTION:
+		return entity_access.function;
+	case SC_SEQUENCE:
+	case SC_ENTITY_SEQUENCE:
+		return entity_access.sequence;
+	default:
+		return NULL;
+	}
 }
 
 #endif /* INCLUDES_TARANTOOL_BOX_SCHEMA_H */
diff --git a/src/box/schema_def.c b/src/box/schema_def.c
index 97c074ab2..9091af054 100644
--- a/src/box/schema_def.c
+++ b/src/box/schema_def.c
@@ -41,6 +41,23 @@ static const char *object_type_strs[] = {
 	/* [SC_COLLATION]       = */ "collation",
 };
 
+enum schema_object_type
+schema_entity_type(enum schema_object_type type)
+{
+	switch (type) {
+	case SC_SPACE:
+		return SC_ENTITY_SPACE;
+	case SC_FUNCTION:
+		return SC_ENTITY_FUNCTION;
+	case SC_SEQUENCE:
+		return SC_ENTITY_SEQUENCE;
+	case SC_COLLATION:
+		return SC_ENTITY_COLLATION;
+	default:
+		return SC_UNKNOWN;
+	}
+}
+
 enum schema_object_type
 schema_object_type(const char *name)
 {
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index 2edb8d37f..d2fc39b76 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -228,9 +228,23 @@ enum schema_object_type {
 	SC_ROLE = 5,
 	SC_SEQUENCE = 6,
 	SC_COLLATION = 7,
-	schema_object_type_MAX = 8
+	/*
+	 * All object types are supposed to be above this point,
+	 * all entity types - below.
+	 */
+	schema_object_type_MAX = 8,
+	SC_ENTITY_SPACE,
+	SC_ENTITY_FUNCTION,
+	SC_ENTITY_SEQUENCE,
+	SC_ENTITY_COLLATION
 };
 
+/**
+ * Given a object type, return an entity type it belongs to.
+ */
+enum schema_object_type
+schema_entity_type(enum schema_object_type type);
+
 enum schema_object_type
 schema_object_type(const char *name);
 
diff --git a/src/box/user.cc b/src/box/user.cc
index fbf06566a..eec785652 100644
--- a/src/box/user.cc
+++ b/src/box/user.cc
@@ -207,12 +207,23 @@ access_find(struct priv_def *priv)
 		access = universe.access;
 		break;
 	}
+	case SC_ENTITY_SPACE:
+	{
+		access = entity_access.space;
+		break;
+	}
+	case SC_ENTITY_FUNCTION:
+	{
+		access = entity_access.function;
+		break;
+	}
+	case SC_ENTITY_SEQUENCE:
+	{
+		access = entity_access.sequence;
+		break;
+	}
 	case SC_SPACE:
 	{
-		if (priv->object_id == 0) {
-			access = entity_access.space;
-			break;
-		}
 		struct space *space = space_by_id(priv->object_id);
 		if (space)
 			access = space->access;
@@ -220,10 +231,6 @@ access_find(struct priv_def *priv)
 	}
 	case SC_FUNCTION:
 	{
-		if (priv->object_id == 0) {
-			access = entity_access.function;
-			break;
-		}
 		struct func *func = func_by_id(priv->object_id);
 		if (func)
 			access = func->access;
@@ -231,10 +238,6 @@ access_find(struct priv_def *priv)
 	}
 	case SC_SEQUENCE:
 	{
-		if (priv->object_id == 0) {
-			access = entity_access.sequence;
-			break;
-		}
 		struct sequence *seq = sequence_by_id(priv->object_id);
 		if (seq)
 			access = seq->access;
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index 16c2027cf..cf8242de5 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -5,7 +5,7 @@ box.space._schema:select{}
 ---
 - - ['cluster', '<cluster uuid>']
   - ['max_id', 511]
-  - ['version', 1, 10, 0]
+  - ['version', 1, 10, 2]
 ...
 box.space._cluster:select{}
 ---
@@ -58,10 +58,10 @@ box.space._space:select{}
         'type': 'string'}, {'name': 'auth', 'type': 'map'}]]
   - [312, 1, '_priv', 'memtx', 0, {}, [{'name': 'grantor', 'type': 'unsigned'}, {
         'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
-      {'name': 'object_id', 'type': 'unsigned'}, {'name': 'privilege', 'type': 'unsigned'}]]
+      {'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
   - [313, 1, '_vpriv', 'sysview', 0, {}, [{'name': 'grantor', 'type': 'unsigned'},
       {'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
-      {'name': 'object_id', 'type': 'unsigned'}, {'name': 'privilege', 'type': 'unsigned'}]]
+      {'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
   - [320, 1, '_cluster', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'uuid',
         'type': 'string'}]]
   - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count',
@@ -104,13 +104,13 @@ box.space._index:select{}
   - [305, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
   - [305, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
   - [312, 0, 'primary', 'tree', {'unique': true}, [[1, 'unsigned'], [2, 'string'],
-      [3, 'unsigned']]]
+      [3, 'scalar']]]
   - [312, 1, 'owner', 'tree', {'unique': false}, [[0, 'unsigned']]]
-  - [312, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'unsigned']]]
+  - [312, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'scalar']]]
   - [313, 0, 'primary', 'tree', {'unique': true}, [[1, 'unsigned'], [2, 'string'],
-      [3, 'unsigned']]]
+      [3, 'scalar']]]
   - [313, 1, 'owner', 'tree', {'unique': false}, [[0, 'unsigned']]]
-  - [313, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'unsigned']]]
+  - [313, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'scalar']]]
   - [320, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
   - [320, 1, 'uuid', 'tree', {'unique': true}, [[1, 'string']]]
   - [330, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
diff --git a/test/box/access.result b/test/box/access.result
index f4669a4a3..599500633 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -504,7 +504,7 @@ box.space._priv:select{id}
 ...
 box.schema.user.grant('user', 'read', 'universe')
 ---
-- error: User 'user' already has read access on universe 'nil'
+- error: User 'user' already has read access on universe ''
 ...
 box.space._priv:select{id}
 ---
@@ -690,7 +690,7 @@ box.schema.user.grant('guest', 'read,write,execute', 'universe')
 ...
 box.schema.user.grant('guest', 'read,write,execute', 'universe')
 ---
-- error: User 'guest' already has read,write,execute access on universe 'nil'
+- error: User 'guest' already has read,write,execute access on universe ''
 ...
 box.schema.user.grant('guest', 'read,write,execute', 'universe', '', { if_not_exists = true })
 ---
@@ -703,7 +703,7 @@ box.schema.user.revoke('guest', 'usage,session', 'universe')
 ...
 box.schema.user.revoke('guest', 'read,write,execute', 'universe')
 ---
-- error: User 'guest' does not have read,write,execute access on universe 'nil'
+- error: User 'guest' does not have read,write,execute access on universe ''
 ...
 box.schema.user.revoke('guest', 'read,write,execute', 'universe', '', { if_exists = true })
 ---
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index 2d87fa2d5..3061f1181 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -436,7 +436,7 @@ box.schema.user.revoke('testuser', 'usage,session', 'universe')
 ...
 box.schema.user.revoke('testuser', 'read, write, execute', 'universe')
 ---
-- error: User 'testuser' does not have read, write, execute access on universe 'nil'
+- error: User 'testuser' does not have read, write, execute access on universe ''
 ...
 box.schema.user.revoke('testuser', 'create', 'universe')
 ---
@@ -797,10 +797,10 @@ box.space._space:select()
         'type': 'string'}, {'name': 'auth', 'type': 'map'}]]
   - [312, 1, '_priv', 'memtx', 0, {}, [{'name': 'grantor', 'type': 'unsigned'}, {
         'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
-      {'name': 'object_id', 'type': 'unsigned'}, {'name': 'privilege', 'type': 'unsigned'}]]
+      {'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
   - [313, 1, '_vpriv', 'sysview', 0, {}, [{'name': 'grantor', 'type': 'unsigned'},
       {'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
-      {'name': 'object_id', 'type': 'unsigned'}, {'name': 'privilege', 'type': 'unsigned'}]]
+      {'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
   - [320, 1, '_cluster', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'uuid',
         'type': 'string'}]]
   - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count',
@@ -853,7 +853,7 @@ box.schema.user.grant('tester', 'read', 'universe')
 -- error: the privilege is not granted
 box.schema.user.revoke('tester', 'create', 'universe')
 ---
-- error: User 'tester' does not have create access on universe 'nil'
+- error: User 'tester' does not have create access on universe ''
 ...
 -- no error: if_exists clause
 box.schema.user.revoke('tester', 'create', 'universe', nil, { if_exists = true })
diff --git a/test/box/alter.result b/test/box/alter.result
index eb7014d8b..36bdb5fd3 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -214,13 +214,13 @@ _index:select{}
   - [305, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
   - [305, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
   - [312, 0, 'primary', 'tree', {'unique': true}, [[1, 'unsigned'], [2, 'string'],
-      [3, 'unsigned']]]
+      [3, 'scalar']]]
   - [312, 1, 'owner', 'tree', {'unique': false}, [[0, 'unsigned']]]
-  - [312, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'unsigned']]]
+  - [312, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'scalar']]]
   - [313, 0, 'primary', 'tree', {'unique': true}, [[1, 'unsigned'], [2, 'string'],
-      [3, 'unsigned']]]
+      [3, 'scalar']]]
   - [313, 1, 'owner', 'tree', {'unique': false}, [[0, 'unsigned']]]
-  - [313, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'unsigned']]]
+  - [313, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'scalar']]]
   - [320, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
   - [320, 1, 'uuid', 'tree', {'unique': true}, [[1, 'string']]]
   - [330, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
diff --git a/test/xlog/upgrade.result b/test/xlog/upgrade.result
index f02996bba..76467baf1 100644
--- a/test/xlog/upgrade.result
+++ b/test/xlog/upgrade.result
@@ -36,7 +36,7 @@ box.space._schema:select()
 ---
 - - ['cluster', '<server_uuid>']
   - ['max_id', 513]
-  - ['version', 1, 10, 0]
+  - ['version', 1, 10, 2]
 ...
 box.space._space:select()
 ---
@@ -85,10 +85,10 @@ box.space._space:select()
         'type': 'string'}, {'name': 'auth', 'type': 'map'}]]
   - [312, 1, '_priv', 'memtx', 0, {}, [{'name': 'grantor', 'type': 'unsigned'}, {
         'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
-      {'name': 'object_id', 'type': 'unsigned'}, {'name': 'privilege', 'type': 'unsigned'}]]
+      {'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
   - [313, 1, '_vpriv', 'sysview', 0, {}, [{'name': 'grantor', 'type': 'unsigned'},
       {'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
-      {'name': 'object_id', 'type': 'unsigned'}, {'name': 'privilege', 'type': 'unsigned'}]]
+      {'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
   - [320, 1, '_cluster', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'uuid',
         'type': 'string'}]]
   - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count',
@@ -134,13 +134,13 @@ box.space._index:select()
   - [305, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
   - [305, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
   - [312, 0, 'primary', 'tree', {'unique': true}, [[1, 'unsigned'], [2, 'string'],
-      [3, 'unsigned']]]
+      [3, 'scalar']]]
   - [312, 1, 'owner', 'tree', {'unique': false}, [[0, 'unsigned']]]
-  - [312, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'unsigned']]]
+  - [312, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'scalar']]]
   - [313, 0, 'primary', 'tree', {'unique': true}, [[1, 'unsigned'], [2, 'string'],
-      [3, 'unsigned']]]
+      [3, 'scalar']]]
   - [313, 1, 'owner', 'tree', {'unique': false}, [[0, 'unsigned']]]
-  - [313, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'unsigned']]]
+  - [313, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'scalar']]]
   - [320, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
   - [320, 1, 'uuid', 'tree', {'unique': true}, [[1, 'string']]]
   - [330, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
-- 
2.15.2 (Apple Git-101.1)

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 2/4] Add entities user, role to access control.
  2018-08-22 13:39 [PATCH v2 0/4] Finish implementation of privileges Serge Petrenko
  2018-08-22 13:39 ` [PATCH v2 1/4] Introduce separate entity object types for entity privileges Serge Petrenko
@ 2018-08-22 13:39 ` Serge Petrenko
  2018-08-22 16:36   ` Vladimir Davydov
  2018-08-22 13:39 ` [PATCH v2 3/4] Add single object privilege checks to access_check_ddl Serge Petrenko
  2018-08-22 13:39 ` [PATCH v2 4/4] Add a privilege upgrade script and update tests Serge Petrenko
  3 siblings, 1 reply; 13+ messages in thread
From: Serge Petrenko @ 2018-08-22 13:39 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: kostja, tarantool-patches, Serge Petrenko

Previously the only existing entities in access control were space,
funciton and sequence. Added user and role entities, so it is now
possible to create users or roles without create privilege on universe.
Also added all the needed checks and modified tests accordingly.

Closes #3524
Prerequisite #3530
---
 src/box/alter.cc            | 33 ++++++++++++++++++++++++++++-----
 src/box/lua/schema.lua      | 27 ++++++++++++++++++---------
 src/box/schema.cc           |  2 ++
 src/box/schema.h            |  8 ++++++++
 src/box/schema_def.c        |  4 ++++
 src/box/schema_def.h        |  2 ++
 src/box/user.cc             | 22 +++++++++++++++++++++-
 test/box/access.result      | 18 ++++++++++--------
 test/box/access.test.lua    | 12 ++++--------
 test/box/access_misc.result |  2 +-
 10 files changed, 98 insertions(+), 32 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 8eba21a51..8cf0f44bd 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -2170,7 +2170,7 @@ 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);
+		access_check_ddl(user->name, user->owner, user->type, PRIV_C, true);
 		auto def_guard = make_scoped_guard([=] { free(user); });
 		(void) user_cache_replace(user);
 		def_guard.is_active = false;
@@ -2179,7 +2179,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);
+				 old_user->def->type, PRIV_D, true);
 		/* Can't drop guest or super user */
 		if (uid <= (uint32_t) BOX_SYSTEM_USER_ID_MAX || uid == SUPER) {
 			tnt_raise(ClientError, ER_DROP_USER,
@@ -2205,7 +2205,7 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 		 * correct.
 		 */
 		struct user_def *user = user_def_new_from_tuple(new_tuple);
-		access_check_ddl(user->name, user->uid, SC_USER, PRIV_A,
+		access_check_ddl(user->name, user->uid, old_user->def->type, PRIV_A,
 				 true);
 		auto def_guard = make_scoped_guard([=] { free(user); });
 		struct trigger *on_commit =
@@ -2672,11 +2672,30 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type)
 		role_check(grantee, role);
 		break;
 	}
+	case SC_USER:
+	{
+		struct user *user = user_by_id(priv->object_id);
+		if (user == NULL || user->def->type != SC_USER) {
+			tnt_raise(ClientError, ER_NO_SUCH_USER,
+				  user ? user->def->name :
+				  int2str(priv->object_id));
+		}
+		if (user->def->owner != grantor->def->uid &&
+		    grantor->def->uid != ADMIN) {
+			tnt_raise(AccessDeniedError,
+				  priv_name(priv_type),
+				  schema_object_name(SC_USER), name,
+				  grantor->def->name);
+		}
+		break;
+	}
 	case SC_ENTITY_SPACE:
 	case SC_ENTITY_FUNCTION:
 	case SC_ENTITY_SEQUENCE:
+	case SC_ENTITY_ROLE:
+	case SC_ENTITY_USER:
 	{
-		/* Only admin may grant privileges on an entire entity. */
+		/* Only amdin may grant privileges on an entire entity. */
 		if (grantor->def->uid != ADMIN) {
 			tnt_raise(AccessDeniedError, priv_name(priv_type),
 				  schema_object_name(priv->object_type), name,
@@ -2702,7 +2721,11 @@ grant_or_revoke(struct priv_def *priv)
 	struct user *grantee = user_by_id(priv->grantee_id);
 	if (grantee == NULL)
 		return;
-	if (priv->object_type == SC_ROLE) {
+	/*
+	 * Grant a role to a user only when privilege type is 'execute'
+	 * and the role is specified.
+	 */
+	if (priv->object_type == SC_ROLE && !(priv->access & ~PRIV_X)) {
 		struct user *role = user_by_id(priv->object_id);
 		if (role == NULL || role->def->type != SC_ROLE)
 			return;
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index d347fd1e6..6fc0cc7f6 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1739,6 +1739,8 @@ local priv_object_combo = {
                            box.priv.C, box.priv.D),
     ["role"]     = bit.bor(box.priv.X, box.priv.U,
                            box.priv.C, box.priv.D),
+    ["user"]     = bit.bor(box.priv.C, box.priv.A,
+                           box.priv.D),
 }
 
 --
@@ -1845,18 +1847,23 @@ local function object_resolve(object_type, object_name)
         end
         return seq
     end
-    if object_type == 'role' then
+    if object_type == 'role' or object_type == 'user' then
+        if object_name == '' then
+            return ''
+        end
         local _vuser = box.space[box.schema.VUSER_ID]
-        local role
+        local role_or_user
         if type(object_name) == 'string' then
-            role = _vuser.index.name:get{object_name}
+            role_or_user = _vuser.index.name:get{object_name}
         else
-            role = _vuser:get{object_name}
+            role_or_user = _vuser:get{object_name}
         end
-        if role and role[4] == 'role' then
-            return role[1]
-        else
+        if role_or_user and role_or_user[4] == object_type then
+            return role_or_user[1]
+        elseif object_type == 'role' then
             box.error(box.error.NO_SUCH_ROLE, object_name)
+        else
+            box.error(box.error.NO_SUCH_USER, object_name)
         end
     end
 
@@ -2111,7 +2118,8 @@ local function grant(uid, name, privilege, object_type,
     if privilege_hex ~= old_privilege then
         _priv:replace{options.grantor, uid, object_type, oid, privilege_hex}
     elseif not options.if_not_exists then
-            if object_type == 'role' then
+            if object_type == 'role' and object_name ~= '' and
+               privilege == 'execute' then
                 box.error(box.error.ROLE_GRANTED, name, object_name)
             else
                 box.error(box.error.PRIV_GRANTED, name, privilege,
@@ -2145,7 +2153,8 @@ local function revoke(uid, name, privilege, object_type, object_name, options)
         if options.if_exists then
             return
         end
-        if object_type == 'role' then
+        if object_type == 'role' and object_name ~= '' and
+           privilege == 'execute' then
             box.error(box.error.ROLE_NOT_GRANTED, name, object_name)
         else
             box.error(box.error.PRIV_NOT_GRANTED, name, privilege,
diff --git a/src/box/schema.cc b/src/box/schema.cc
index aaa63a083..4502ca6dc 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -538,6 +538,8 @@ schema_find_name(enum schema_object_type type, uint32_t object_id)
 	case SC_ENTITY_SPACE:
 	case SC_ENTITY_FUNCTION:
 	case SC_ENTITY_SEQUENCE:
+	case SC_ENTITY_ROLE:
+	case SC_ENTITY_USER:
 		return "";
 	case SC_SPACE:
 		{
diff --git a/src/box/schema.h b/src/box/schema.h
index f1735ff34..c343650de 100644
--- a/src/box/schema.h
+++ b/src/box/schema.h
@@ -240,6 +240,8 @@ struct on_access_denied_ctx {
 struct entity_access {
        struct access space[BOX_USER_MAX];
        struct access function[BOX_USER_MAX];
+       struct access user[BOX_USER_MAX];
+       struct access role[BOX_USER_MAX];
        struct access sequence[BOX_USER_MAX];
 };
 
@@ -257,6 +259,12 @@ entity_access_get(enum schema_object_type type)
 	case SC_FUNCTION:
 	case SC_ENTITY_FUNCTION:
 		return entity_access.function;
+	case SC_USER:
+	case SC_ENTITY_USER:
+		return entity_access.user;
+	case SC_ROLE:
+	case SC_ENTITY_ROLE:
+		return entity_access.role;
 	case SC_SEQUENCE:
 	case SC_ENTITY_SEQUENCE:
 		return entity_access.sequence;
diff --git a/src/box/schema_def.c b/src/box/schema_def.c
index 9091af054..199b26183 100644
--- a/src/box/schema_def.c
+++ b/src/box/schema_def.c
@@ -49,6 +49,10 @@ schema_entity_type(enum schema_object_type type)
 		return SC_ENTITY_SPACE;
 	case SC_FUNCTION:
 		return SC_ENTITY_FUNCTION;
+	case SC_USER:
+		return SC_ENTITY_USER;
+	case SC_ROLE:
+		return SC_ENTITY_ROLE;
 	case SC_SEQUENCE:
 		return SC_ENTITY_SEQUENCE;
 	case SC_COLLATION:
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index d2fc39b76..c0444cd11 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -235,6 +235,8 @@ enum schema_object_type {
 	schema_object_type_MAX = 8,
 	SC_ENTITY_SPACE,
 	SC_ENTITY_FUNCTION,
+	SC_ENTITY_ROLE,
+	SC_ENTITY_USER,
 	SC_ENTITY_SEQUENCE,
 	SC_ENTITY_COLLATION
 };
diff --git a/src/box/user.cc b/src/box/user.cc
index eec785652..b4fb65a59 100644
--- a/src/box/user.cc
+++ b/src/box/user.cc
@@ -217,6 +217,16 @@ access_find(struct priv_def *priv)
 		access = entity_access.function;
 		break;
 	}
+	case SC_ENTITY_USER:
+	{
+		access = entity_access.user;
+		break;
+	}
+	case SC_ENTITY_ROLE:
+	{
+		access = entity_access.role;
+		break;
+	}
 	case SC_ENTITY_SEQUENCE:
 	{
 		access = entity_access.sequence;
@@ -236,6 +246,16 @@ access_find(struct priv_def *priv)
 			access = func->access;
 		break;
 	}
+	case SC_USER:
+	{
+		/* No grants on a single object user yet. */
+		break;
+	}
+	case SC_ROLE:
+	{
+		/* No grants on a single object role yet. */
+		break;
+	}
 	case SC_SEQUENCE:
 	{
 		struct sequence *seq = sequence_by_id(priv->object_id);
@@ -318,7 +338,7 @@ user_reload_privs(struct user *user)
 			 * Skip role grants, we're only
 			 * interested in real objects.
 			 */
-			if (priv.object_type != SC_ROLE)
+			if (priv.object_type != SC_ROLE || !(priv.access & PRIV_X))
 				user_grant_priv(user, &priv);
 		}
 	}
diff --git a/test/box/access.result b/test/box/access.result
index 599500633..377a8fa66 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -1427,16 +1427,18 @@ box.session.su("admin")
 box.schema.user.grant('tester', 'write', 'universe')
 ---
 ...
--- no entity user currently, so have to grant create
--- on universe in order to create a user.
-box.schema.user.grant('tester', 'create', 'universe')
+box.schema.user.grant('tester', 'create', 'user')
+---
+...
+box.schema.user.grant('tester', 'create', 'space')
+---
+...
+box.schema.user.grant('tester', 'create', 'function')
+---
+...
+box.schema.user.grant('tester', 'create' , 'sequence')
 ---
 ...
--- this should work instead:
---box.schema.user.grant('tester', 'create', 'user')
---box.schema.user.grant('tester', 'create', 'space')
---box.schema.user.grant('tester', 'create', 'function')
---box.schema.user.grant('tester', 'create' , 'sequence')
 box.schema.user.grant('tester', 'read', 'space', '_sequence')
 ---
 ...
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index 9ae0e1114..c90fe0a1a 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -538,14 +538,10 @@ box.session.su("admin")
 -- tables from ddl
 --
 box.schema.user.grant('tester', 'write', 'universe')
--- no entity user currently, so have to grant create
--- on universe in order to create a user.
-box.schema.user.grant('tester', 'create', 'universe')
--- this should work instead:
---box.schema.user.grant('tester', 'create', 'user')
---box.schema.user.grant('tester', 'create', 'space')
---box.schema.user.grant('tester', 'create', 'function')
---box.schema.user.grant('tester', 'create' , 'sequence')
+box.schema.user.grant('tester', 'create', 'user')
+box.schema.user.grant('tester', 'create', 'space')
+box.schema.user.grant('tester', 'create', 'function')
+box.schema.user.grant('tester', 'create' , 'sequence')
 box.schema.user.grant('tester', 'read', 'space', '_sequence')
 box.session.su("tester")
 -- successful create
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index 3061f1181..c1809d69a 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -361,7 +361,7 @@ testuser_uid = session.uid()
 ...
 _ = box.space._user:delete(2)
 ---
-- error: Drop access to user 'public' is denied for user 'testuser'
+- error: Drop access to role 'public' is denied for user 'testuser'
 ...
 box.space._user:select(1)
 ---
-- 
2.15.2 (Apple Git-101.1)

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 3/4] Add single object privilege checks to access_check_ddl.
  2018-08-22 13:39 [PATCH v2 0/4] Finish implementation of privileges Serge Petrenko
  2018-08-22 13:39 ` [PATCH v2 1/4] Introduce separate entity object types for entity privileges Serge Petrenko
  2018-08-22 13:39 ` [PATCH v2 2/4] Add entities user, role to access control Serge Petrenko
@ 2018-08-22 13:39 ` Serge Petrenko
  2018-08-22 16:47   ` Vladimir Davydov
  2018-08-23  8:57   ` Vladimir Davydov
  2018-08-22 13:39 ` [PATCH v2 4/4] Add a privilege upgrade script and update tests Serge Petrenko
  3 siblings, 2 replies; 13+ messages in thread
From: Serge Petrenko @ 2018-08-22 13:39 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: kostja, tarantool-patches, Serge Petrenko

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

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

Closes #3530
Prerequisite #3539
---
 src/box/alter.cc         |  91 +++++++++++++----------
 src/box/lua/schema.lua   |   3 +
 src/box/user.cc          |  23 +++---
 src/box/user.h           |   5 ++
 test/box/access.result   | 185 +++++++++++++++++++++++++++++++++++++++++++++++
 test/box/access.test.lua |  56 ++++++++++++++
 test/box/role.result     |   9 +++
 test/box/sequence.result |   3 +
 8 files changed, 328 insertions(+), 47 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 8cf0f44bd..c0db96e07 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -62,9 +62,8 @@
 /* {{{ Auxiliary functions and methods. */
 
 static void
-access_check_ddl(const char *name, uint32_t owner_uid,
-		 enum schema_object_type type,
-		 enum priv_type priv_type,
+access_check_ddl(const char *name, uint32_t object_id, uint32_t owner_uid,
+		 enum schema_object_type type, enum priv_type priv_type,
 		 bool is_17_compat_mode)
 {
 	struct credentials *cr = effective_user();
@@ -103,7 +102,17 @@ access_check_ddl(const char *name, uint32_t owner_uid,
 	 */
 	if (access == 0 || (is_owner && !(access & (PRIV_U | PRIV_C))))
 		return; /* Access granted. */
-
+	/*
+	 * USAGE can be granted only globally.
+	 */
+	if (!(access & (PRIV_U))) {
+		/* Check for privileges on a single object. */
+		struct access *access_obj = access_find(type, object_id);
+		if (access_obj != NULL)
+			access &= ~access_obj[cr->auth_token].effective;
+		if (access == 0)
+			return; /* Access granted. */
+	}
 	/* Create a meaningful error message. */
 	struct user *user = user_find_xc(cr->uid);
 	const char *object_name;
@@ -1590,7 +1599,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		struct space_def *def =
 			space_def_new_from_tuple(new_tuple, ER_CREATE_SPACE,
 						 region);
-		access_check_ddl(def->name, def->uid, SC_SPACE, PRIV_C, true);
+		access_check_ddl(def->name, def->id, def->uid, SC_SPACE,
+				 PRIV_C, true);
 		auto def_guard =
 			make_scoped_guard([=] { space_def_delete(def); });
 		RLIST_HEAD(empty_list);
@@ -1623,8 +1633,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 			txn_alter_trigger_new(on_create_space_rollback, space);
 		txn_on_rollback(txn, on_rollback);
 	} else if (new_tuple == NULL) { /* DELETE */
-		access_check_ddl(old_space->def->name, old_space->def->uid,
-				 SC_SPACE, PRIV_D, true);
+		access_check_ddl(old_space->def->name, old_space->def->id,
+				 old_space->def->uid, SC_SPACE, PRIV_D, true);
 		/* Verify that the space is empty (has no indexes) */
 		if (old_space->index_count) {
 			tnt_raise(ClientError, ER_DROP_SPACE,
@@ -1669,7 +1679,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		struct space_def *def =
 			space_def_new_from_tuple(new_tuple, ER_ALTER_SPACE,
 						 region);
-		access_check_ddl(def->name, def->uid, SC_SPACE, PRIV_A, true);
+		access_check_ddl(def->name, def->id, def->uid, SC_SPACE,
+				 PRIV_A, true);
 		auto def_guard =
 			make_scoped_guard([=] { space_def_delete(def); });
 		if (def->id != space_id(old_space))
@@ -1774,8 +1785,8 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	enum priv_type priv_type = new_tuple ? PRIV_C : PRIV_D;
 	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);
+	access_check_ddl(old_space->def->name, old_space->def->id,
+			 old_space->def->uid, SC_SPACE, priv_type, true);
 	struct index *old_index = space_index(old_space, iid);
 
 	/*
@@ -2170,7 +2181,8 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 	struct user *old_user = user_by_id(uid);
 	if (new_tuple != NULL && old_user == NULL) { /* INSERT */
 		struct user_def *user = user_def_new_from_tuple(new_tuple);
-		access_check_ddl(user->name, user->owner, user->type, PRIV_C, true);
+		access_check_ddl(user->name, user->uid, user->owner, user->type,
+				 PRIV_C, true);
 		auto def_guard = make_scoped_guard([=] { free(user); });
 		(void) user_cache_replace(user);
 		def_guard.is_active = false;
@@ -2178,7 +2190,8 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 			txn_alter_trigger_new(user_cache_remove_user, NULL);
 		txn_on_rollback(txn, on_rollback);
 	} else if (new_tuple == NULL) { /* DELETE */
-		access_check_ddl(old_user->def->name, old_user->def->owner,
+		access_check_ddl(old_user->def->name, old_user->def->uid,
+				 old_user->def->owner,
 				 old_user->def->type, PRIV_D, true);
 		/* Can't drop guest or super user */
 		if (uid <= (uint32_t) BOX_SYSTEM_USER_ID_MAX || uid == SUPER) {
@@ -2205,8 +2218,8 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event)
 		 * correct.
 		 */
 		struct user_def *user = user_def_new_from_tuple(new_tuple);
-		access_check_ddl(user->name, user->uid, old_user->def->type, PRIV_A,
-				 true);
+		access_check_ddl(user->name, user->uid, user->uid,
+			         old_user->def->type, PRIV_A, true);
 		auto def_guard = make_scoped_guard([=] { free(user); });
 		struct trigger *on_commit =
 			txn_alter_trigger_new(user_cache_alter_user, NULL);
@@ -2308,7 +2321,8 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 	struct func *old_func = func_by_id(fid);
 	if (new_tuple != NULL && old_func == NULL) { /* INSERT */
 		struct func_def *def = func_def_new_from_tuple(new_tuple);
-		access_check_ddl(def->name, def->uid, SC_FUNCTION, PRIV_C, true);
+		access_check_ddl(def->name, def->fid, def->uid, SC_FUNCTION,
+				 PRIV_C, true);
 		auto def_guard = make_scoped_guard([=] { free(def); });
 		func_cache_replace(def);
 		def_guard.is_active = false;
@@ -2322,7 +2336,7 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 		 * Can only delete func if you're the one
 		 * who created it or a superuser.
 		 */
-		access_check_ddl(old_func->def->name, uid, SC_FUNCTION,
+		access_check_ddl(old_func->def->name, fid, uid, SC_FUNCTION,
 				 PRIV_D, true);
 		/* Can only delete func if it has no grants. */
 		if (schema_find_grants("function", old_func->def->fid)) {
@@ -2336,8 +2350,8 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 	} else {                                /* UPDATE, REPLACE */
 		struct func_def *def = func_def_new_from_tuple(new_tuple);
 		auto def_guard = make_scoped_guard([=] { free(def); });
-		access_check_ddl(def->name, def->uid, SC_FUNCTION, PRIV_A,
-				 true);
+		access_check_ddl(def->name, def->fid, def->uid,
+				 SC_FUNCTION, PRIV_A, true);
 		struct trigger *on_commit =
 			txn_alter_trigger_new(func_cache_replace_func, NULL);
 		txn_on_commit(txn, on_commit);
@@ -2493,8 +2507,9 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event)
 						    BOX_COLLATION_FIELD_ID);
 		struct coll_id *old_coll_id = coll_by_id(old_id);
 		assert(old_coll_id != NULL);
-		access_check_ddl(old_coll_id->name, old_coll_id->owner_id,
-				 SC_COLLATION, PRIV_D, false);
+		access_check_ddl(old_coll_id->name, old_id,
+				 old_coll_id->owner_id, SC_COLLATION, PRIV_D,
+				 false);
 		/*
 		 * Set on_commit/on_rollback triggers after
 		 * deletion from the cache to make trigger logic
@@ -2509,8 +2524,8 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event)
 		/* INSERT */
 		struct coll_id_def new_def;
 		coll_id_def_new_from_tuple(new_tuple, &new_def);
-		access_check_ddl(new_def.name, new_def.owner_id, SC_COLLATION,
-				 PRIV_C, false);
+		access_check_ddl(new_def.name, new_def.id, new_def.owner_id,
+				 SC_COLLATION, PRIV_C, false);
 		struct coll_id *new_coll_id = coll_id_new(&new_def);
 		if (new_coll_id == NULL)
 			diag_raise();
@@ -2594,8 +2609,8 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type)
 			  int2str(priv->grantee_id));
 	}
 	const char *name = schema_find_name(priv->object_type, priv->object_id);
-	access_check_ddl(name, grantor->def->uid, priv->object_type, priv_type,
-			 false);
+	access_check_ddl(name, priv->object_id, grantor->def->uid,
+			 priv->object_type, priv_type, false);
 	switch (priv->object_type) {
 	case SC_UNIVERSE:
 		if (grantor->def->uid != ADMIN) {
@@ -3098,8 +3113,8 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event)
 		new_def = sequence_def_new_from_tuple(new_tuple,
 						      ER_CREATE_SEQUENCE);
 		assert(sequence_by_id(new_def->id) == NULL);
-		access_check_ddl(new_def->name, new_def->uid, SC_SEQUENCE,
-				 PRIV_C, false);
+		access_check_ddl(new_def->name, new_def->id, new_def->uid,
+				 SC_SEQUENCE, PRIV_C, false);
 		sequence_cache_replace(new_def);
 		alter->new_def = new_def;
 	} else if (old_tuple != NULL && new_tuple == NULL) {	/* DELETE */
@@ -3107,8 +3122,8 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event)
 						 BOX_SEQUENCE_DATA_FIELD_ID);
 		struct sequence *seq = sequence_by_id(id);
 		assert(seq != NULL);
-		access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE,
-				 PRIV_D, false);
+		access_check_ddl(seq->def->name, seq->def->id, seq->def->uid,
+				 SC_SEQUENCE, PRIV_D, false);
 		if (space_has_data(BOX_SEQUENCE_DATA_ID, 0, id))
 			tnt_raise(ClientError, ER_DROP_SEQUENCE,
 				  seq->def->name, "the sequence has data");
@@ -3124,8 +3139,8 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event)
 						      ER_ALTER_SEQUENCE);
 		struct sequence *seq = sequence_by_id(new_def->id);
 		assert(seq != NULL);
-		access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE,
-				 PRIV_A, false);
+		access_check_ddl(seq->def->name, seq->def->id, seq->def->uid,
+				 SC_SEQUENCE, PRIV_A, false);
 		alter->old_def = seq->def;
 		alter->new_def = new_def;
 	}
@@ -3205,21 +3220,21 @@ on_replace_dd_space_sequence(struct trigger * /* trigger */, void *event)
 
 	/* Check we have the correct access type on the sequence.  * */
 	if (is_generated || !stmt->new_tuple) {
-		access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE,
-				 priv_type, false);
+		access_check_ddl(seq->def->name, seq->def->id, seq->def->uid,
+				 SC_SEQUENCE, priv_type, false);
 	} else {
 		/*
 		 * In case user wants to attach an existing sequence,
 		 * check that it has read and write access.
 		 */
-		access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE,
-				 PRIV_R, false);
-		access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE,
-				 PRIV_W, false);
+		access_check_ddl(seq->def->name, seq->def->id, seq->def->uid,
+				 SC_SEQUENCE, PRIV_R, false);
+		access_check_ddl(seq->def->name, seq->def->id, seq->def->uid,
+				 SC_SEQUENCE, PRIV_W, false);
 	}
 	/** Check we have alter access on space. */
-	access_check_ddl(space->def->name, space->def->uid, SC_SPACE, PRIV_A,
-			 false);
+	access_check_ddl(space->def->name, space->def->id, space->def->uid,
+			 SC_SPACE, PRIV_A, false);
 
 	struct trigger *on_commit =
 		txn_alter_trigger_new(on_commit_dd_space_sequence, space);
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 6fc0cc7f6..540a2a5fd 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -2060,6 +2060,9 @@ box.schema.user.create = function(name, opts)
     uid = _user:auto_increment{session.euid(), name, 'user', auth_mech_list}[1]
     -- grant role 'public' to the user
     box.schema.user.grant(uid, 'public')
+    -- Grant privilege 'alter' on itself, so that it can
+    -- change its password or username.
+    box.schema.user.grant(uid, 'alter', 'user', uid)
     -- we have to grant global privileges from setuid function, since
     -- only admin has the ownership over universe and we don't have
     -- grant option
diff --git a/src/box/user.cc b/src/box/user.cc
index b4fb65a59..7185c0234 100644
--- a/src/box/user.cc
+++ b/src/box/user.cc
@@ -198,10 +198,10 @@ user_grant_priv(struct user *user, struct priv_def *def)
  * given object type and object id.
  */
 struct access *
-access_find(struct priv_def *priv)
+access_find(enum schema_object_type object_type, uint32_t object_id)
 {
 	struct access *access = NULL;
-	switch (priv->object_type) {
+	switch (object_type) {
 	case SC_UNIVERSE:
 	{
 		access = universe.access;
@@ -234,31 +234,35 @@ access_find(struct priv_def *priv)
 	}
 	case SC_SPACE:
 	{
-		struct space *space = space_by_id(priv->object_id);
+		struct space *space = space_by_id(object_id);
 		if (space)
 			access = space->access;
 		break;
 	}
 	case SC_FUNCTION:
 	{
-		struct func *func = func_by_id(priv->object_id);
+		struct func *func = func_by_id(object_id);
 		if (func)
 			access = func->access;
 		break;
 	}
 	case SC_USER:
 	{
-		/* No grants on a single object user yet. */
+		struct user *user = user_by_id(object_id);
+		if (user)
+			access = user->access;
 		break;
 	}
 	case SC_ROLE:
 	{
-		/* No grants on a single object role yet. */
+		struct user *role = user_by_id(object_id);
+		if (role)
+			access = role->access;
 		break;
 	}
 	case SC_SEQUENCE:
 	{
-		struct sequence *seq = sequence_by_id(priv->object_id);
+		struct sequence *seq = sequence_by_id(object_id);
 		if (seq)
 			access = seq->access;
 		break;
@@ -282,7 +286,8 @@ user_set_effective_access(struct user *user)
 	privset_ifirst(&user->privs, &it);
 	struct priv_def *priv;
 	while ((priv = privset_inext(&it)) != NULL) {
-		struct access *object = access_find(priv);
+		struct access *object = access_find(priv->object_type,
+						    priv->object_id);
 		 /* Protect against a concurrent drop. */
 		if (object == NULL)
 			continue;
@@ -697,7 +702,7 @@ role_revoke(struct user *grantee, struct user *role)
 void
 priv_grant(struct user *grantee, struct priv_def *priv)
 {
-	struct access *object = access_find(priv);
+	struct access *object = access_find(priv->object_type, priv->object_id);
 	if (object == NULL)
 		return;
 	struct access *access = &object[grantee->auth_token];
diff --git a/src/box/user.h b/src/box/user.h
index 07c4dc504..527fb2e7c 100644
--- a/src/box/user.h
+++ b/src/box/user.h
@@ -88,8 +88,13 @@ struct user
 	bool is_dirty;
 	/** Memory pool for privs */
 	struct region pool;
+	/** Cached runtime access imformation. */
+	struct access access[BOX_USER_MAX];
 };
 
+struct access *
+access_find(enum schema_object_type object_type, uint32_t object_id);
+
 /** Find user by id. */
 struct user *
 user_by_id(uint32_t uid);
diff --git a/test/box/access.result b/test/box/access.result
index 377a8fa66..4f0607471 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -136,6 +136,9 @@ box.schema.user.revoke('rich', 'read,write', 'universe')
 box.schema.user.revoke('rich', 'public')
 ---
 ...
+box.schema.user.revoke('rich', 'alter', 'user', 'rich')
+---
+...
 box.schema.user.disable("rich")
 ---
 ...
@@ -501,6 +504,7 @@ box.space._priv:select{id}
 ---
 - - [1, 32, 'role', 2, 4]
   - [1, 32, 'universe', 0, 27]
+  - [1, 32, 'user', 32, 128]
 ...
 box.schema.user.grant('user', 'read', 'universe')
 ---
@@ -510,6 +514,7 @@ box.space._priv:select{id}
 ---
 - - [1, 32, 'role', 2, 4]
   - [1, 32, 'universe', 0, 27]
+  - [1, 32, 'user', 32, 128]
 ...
 box.schema.user.revoke('user', 'write', 'universe')
 ---
@@ -518,6 +523,7 @@ box.space._priv:select{id}
 ---
 - - [1, 32, 'role', 2, 4]
   - [1, 32, 'universe', 0, 25]
+  - [1, 32, 'user', 32, 128]
 ...
 box.schema.user.revoke('user', 'read', 'universe')
 ---
@@ -526,6 +532,7 @@ box.space._priv:select{id}
 ---
 - - [1, 32, 'role', 2, 4]
   - [1, 32, 'universe', 0, 24]
+  - [1, 32, 'user', 32, 128]
 ...
 box.schema.user.grant('user', 'write', 'universe')
 ---
@@ -534,6 +541,7 @@ box.space._priv:select{id}
 ---
 - - [1, 32, 'role', 2, 4]
   - [1, 32, 'universe', 0, 26]
+  - [1, 32, 'user', 32, 128]
 ...
 box.schema.user.grant('user', 'read', 'universe')
 ---
@@ -542,6 +550,7 @@ box.space._priv:select{id}
 ---
 - - [1, 32, 'role', 2, 4]
   - [1, 32, 'universe', 0, 27]
+  - [1, 32, 'user', 32, 128]
 ...
 box.schema.user.drop('user')
 ---
@@ -967,6 +976,9 @@ box.schema.user.info('test_user')
   - - session,usage
     - universe
     - 
+  - - alter
+    - user
+    - test_user
 ...
 box.schema.role.info('test_role')
 ---
@@ -997,6 +1009,9 @@ box.schema.user.info('test_user')
   - - session,usage
     - universe
     - 
+  - - alter
+    - user
+    - test_user
 ...
 box.schema.role.info('test_role')
 ---
@@ -1862,3 +1877,173 @@ box.session.su('admin')
 box.schema.user.drop('tester')
 ---
 ...
+--
+-- test case for 3530: do not ignore single object privileges
+--
+box.schema.user.create("test")
+---
+...
+_ = box.schema.space.create("space1")
+---
+...
+box.schema.user.grant("test", "read", "space", "space1")
+---
+...
+box.schema.user.grant("test", "write", "space", "_index")
+---
+...
+box.session.su("test")
+---
+...
+box.space.space1:create_index("pk")
+---
+- error: Create access to space 'space1' is denied for user 'test'
+...
+box.session.su("admin")
+---
+...
+box.space.space1.index[0] == nil
+---
+- true
+...
+-- fixme: cannot grant create on a single space
+-- this is because when checking for create
+-- access_check_ddl ignores space privileges,
+-- assuming that there is no space yet.
+box.schema.user.grant("test", "create", "space")
+---
+...
+box.session.su("test")
+---
+...
+_ = box.space.space1:create_index("pk")
+---
+...
+box.space.space1:insert{5}
+---
+- error: Write access to space 'space1' is denied for user 'test'
+...
+box.session.su("admin")
+---
+...
+box.space.space1.index[0] ~= nil
+---
+- true
+...
+box.space.space1:select{}
+---
+- []
+...
+box.schema.user.grant("test", "write", "space", "space1")
+---
+...
+box.session.su("test")
+---
+...
+box.space.space1:insert{5}
+---
+- [5]
+...
+box.session.su("admin")
+---
+...
+box.space.space1:select{}
+---
+- - [5]
+...
+box.schema.user.drop("test")
+---
+...
+box.space.space1:drop()
+---
+...
+--
+-- test that it is possible to grant privileges on a single user.
+box.schema.user.create("user1")
+---
+...
+box.schema.user.create("user2")
+---
+...
+box.schema.user.create("user3")
+---
+...
+box.schema.user.grant("user1", "write", "space", "_user")
+---
+...
+box.schema.user.grant("user1", "read", "space", "_user")
+---
+...
+box.space._user:select{}
+---
+- - [0, 1, 'guest', 'user', {'chap-sha1': 'vhvewKp0tNyweZQ+cFKAlsyphfg='}]
+  - [1, 1, 'admin', 'user', {}]
+  - [2, 1, 'public', 'role', {}]
+  - [3, 1, 'replication', 'role', {}]
+  - [31, 1, 'super', 'role', {}]
+  - [32, 1, 'user1', 'user', {}]
+  - [33, 1, 'user2', 'user', {}]
+  - [34, 1, 'user3', 'user', {}]
+...
+box.session.su("user1")
+---
+...
+-- can alter itself, but can't alter others without privileges.
+box.schema.user.passwd("user1", "abcd")
+---
+...
+box.schema.user.passwd("user2", "abcd")
+---
+- error: Alter access to user 'user2' is denied for user 'user1'
+...
+box.session.su("admin")
+---
+...
+box.space._user:select{}
+---
+- - [0, 1, 'guest', 'user', {'chap-sha1': 'vhvewKp0tNyweZQ+cFKAlsyphfg='}]
+  - [1, 1, 'admin', 'user', {}]
+  - [2, 1, 'public', 'role', {}]
+  - [3, 1, 'replication', 'role', {}]
+  - [31, 1, 'super', 'role', {}]
+  - [32, 1, 'user1', 'user', {'chap-sha1': 'oVTFJWXp5/lL/Aih/nAmJO2O/9o='}]
+  - [33, 1, 'user2', 'user', {}]
+  - [34, 1, 'user3', 'user', {}]
+...
+box.schema.user.grant("user1", "alter", "user", "user2")
+---
+...
+box.session.su("user1")
+---
+...
+box.schema.user.passwd("user2", "abcd")
+---
+...
+-- still fails
+box.schema.user.passwd("user3", "qewr")
+---
+- error: Alter access to user 'user3' is denied for user 'user1'
+...
+box.session.su("admin")
+---
+...
+box.space._user:select{}
+---
+- - [0, 1, 'guest', 'user', {'chap-sha1': 'vhvewKp0tNyweZQ+cFKAlsyphfg='}]
+  - [1, 1, 'admin', 'user', {}]
+  - [2, 1, 'public', 'role', {}]
+  - [3, 1, 'replication', 'role', {}]
+  - [31, 1, 'super', 'role', {}]
+  - [32, 1, 'user1', 'user', {'chap-sha1': 'oVTFJWXp5/lL/Aih/nAmJO2O/9o='}]
+  - [33, 1, 'user2', 'user', {'chap-sha1': 'oVTFJWXp5/lL/Aih/nAmJO2O/9o='}]
+  - [34, 1, 'user3', 'user', {}]
+...
+box.schema.user.drop("user1")
+---
+...
+box.schema.user.drop("user2")
+---
+...
+box.schema.user.drop("user3")
+---
+...
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index c90fe0a1a..d3e2aab98 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -60,6 +60,7 @@ box.schema.func.drop('dummy')
 box.space['_user']:delete{uid}
 box.schema.user.revoke('rich', 'read,write', 'universe')
 box.schema.user.revoke('rich', 'public')
+box.schema.user.revoke('rich', 'alter', 'user', 'rich')
 box.schema.user.disable("rich")
 -- test double disable is a no op
 box.schema.user.disable("rich")
@@ -727,3 +728,58 @@ _ = box.schema.sequence.create('test_sequence')
 box.session.su('admin')
 box.schema.user.drop('tester')
 
+
+--
+-- test case for 3530: do not ignore single object privileges
+--
+box.schema.user.create("test")
+_ = box.schema.space.create("space1")
+box.schema.user.grant("test", "read", "space", "space1")
+box.schema.user.grant("test", "write", "space", "_index")
+box.session.su("test")
+box.space.space1:create_index("pk")
+box.session.su("admin")
+box.space.space1.index[0] == nil
+-- fixme: cannot grant create on a single space
+-- this is because when checking for create
+-- access_check_ddl ignores space privileges,
+-- assuming that there is no space yet.
+box.schema.user.grant("test", "create", "space")
+box.session.su("test")
+_ = box.space.space1:create_index("pk")
+box.space.space1:insert{5}
+box.session.su("admin")
+box.space.space1.index[0] ~= nil
+box.space.space1:select{}
+box.schema.user.grant("test", "write", "space", "space1")
+box.session.su("test")
+box.space.space1:insert{5}
+box.session.su("admin")
+box.space.space1:select{}
+box.schema.user.drop("test")
+box.space.space1:drop()
+
+--
+-- test that it is possible to grant privileges on a single user.
+box.schema.user.create("user1")
+box.schema.user.create("user2")
+box.schema.user.create("user3")
+box.schema.user.grant("user1", "write", "space", "_user")
+box.schema.user.grant("user1", "read", "space", "_user")
+box.space._user:select{}
+box.session.su("user1")
+-- can alter itself, but can't alter others without privileges.
+box.schema.user.passwd("user1", "abcd")
+box.schema.user.passwd("user2", "abcd")
+box.session.su("admin")
+box.space._user:select{}
+box.schema.user.grant("user1", "alter", "user", "user2")
+box.session.su("user1")
+box.schema.user.passwd("user2", "abcd")
+-- still fails
+box.schema.user.passwd("user3", "qewr")
+box.session.su("admin")
+box.space._user:select{}
+box.schema.user.drop("user1")
+box.schema.user.drop("user2")
+box.schema.user.drop("user3")
diff --git a/test/box/role.result b/test/box/role.result
index 806cea90b..3a54e2460 100644
--- a/test/box/role.result
+++ b/test/box/role.result
@@ -49,6 +49,9 @@ box.schema.user.info('tester')
   - - session,usage
     - universe
     - 
+  - - alter
+    - user
+    - tester
 ...
 box.schema.user.grant('tester', 'execute', 'role', 'iddqd')
 ---
@@ -64,6 +67,9 @@ box.schema.user.info('tester')
   - - session,usage
     - universe
     - 
+  - - alter
+    - user
+    - tester
 ...
 -- test granting user to a user
 box.schema.user.grant('tester', 'execute', 'role', 'tester')
@@ -935,6 +941,9 @@ box.schema.user.info('test_user')
   - - session,usage
     - universe
     - 
+  - - alter
+    - user
+    - test_user
 ...
 box.schema.role.info('test_user')
 ---
diff --git a/test/box/sequence.result b/test/box/sequence.result
index a2a1a60ea..b3907659f 100644
--- a/test/box/sequence.result
+++ b/test/box/sequence.result
@@ -1362,6 +1362,9 @@ box.schema.user.info()
   - - session,usage
     - universe
     - 
+  - - alter
+    - user
+    - user
 ...
 sq:set(100) -- ok
 ---
-- 
2.15.2 (Apple Git-101.1)

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 4/4] Add a privilege upgrade script and update tests.
  2018-08-22 13:39 [PATCH v2 0/4] Finish implementation of privileges Serge Petrenko
                   ` (2 preceding siblings ...)
  2018-08-22 13:39 ` [PATCH v2 3/4] Add single object privilege checks to access_check_ddl Serge Petrenko
@ 2018-08-22 13:39 ` Serge Petrenko
  2018-08-22 16:48   ` Vladimir Davydov
  3 siblings, 1 reply; 13+ messages in thread
From: Serge Petrenko @ 2018-08-22 13:39 UTC (permalink / raw)
  To: vdavydov.dev; +Cc: kostja, tarantool-patches, Serge Petrenko

This patch adds a privilege upgrade script, which runs on upgrade to
1.10.2 and automatically grants CREATE,ALTER,DROP on objects and entities
to all users, who have READ and WRITE access on them.
Also all tests are rewritten to grant only necessary privileges, not
privileges to universe.

Closes #3539
---
 src/box/bootstrap.snap                      | Bin 1556 -> 1555 bytes
 src/box/lua/upgrade.lua                     |  23 +++
 test/box-tap/auth.test.lua                  |   5 -
 test/box-tap/session.test.lua               |  15 +-
 test/box/access.result                      |   4 +-
 test/box/access.test.lua                    |   4 +-
 test/box/access_bin.result                  |   4 +-
 test/box/access_bin.test.lua                |   4 +-
 test/box/access_escalation.result           |  18 +-
 test/box/access_escalation.test.lua         |  11 +-
 test/box/access_misc.result                 |   4 +-
 test/box/access_misc.test.lua               |   4 +-
 test/box/call.result                        |   4 +-
 test/box/call.test.lua                      |   4 +-
 test/box/errinj.result                      |  39 +++-
 test/box/errinj.test.lua                    |  25 ++-
 test/box/net.box.result                     | 265 ++++++++++++++++++++++++++--
 test/box/net.box.test.lua                   | 121 +++++++++++--
 test/box/net_msg_max.result                 |  13 +-
 test/box/net_msg_max.test.lua               |   9 +-
 test/box/on_replace.result                  |   2 +-
 test/box/on_replace.test.lua                |   2 +-
 test/box/protocol.result                    |   9 +-
 test/box/protocol.test.lua                  |   5 +-
 test/box/push.result                        |  52 +++++-
 test/box/push.test.lua                      |  27 ++-
 test/box/schema_reload.result               |  32 +++-
 test/box/schema_reload.test.lua             |  16 +-
 test/box/sql.result                         |   9 +-
 test/box/sql.test.lua                       |   5 +-
 test/box/stat_net.result                    |   7 +-
 test/box/stat_net.test.lua                  |   5 +-
 test/engine/params.result                   |   6 -
 test/engine/params.test.lua                 |   2 -
 test/engine/replica_join.result             |   6 -
 test/engine/replica_join.test.lua           |   2 -
 test/replication/autobootstrap.result       |  23 ++-
 test/replication/autobootstrap.test.lua     |  10 +-
 test/replication/catch.result               |   6 -
 test/replication/catch.test.lua             |   2 -
 test/replication/errinj.result              |   3 -
 test/replication/errinj.test.lua            |   1 -
 test/replication/gc.result                  |   6 -
 test/replication/gc.test.lua                |   2 -
 test/replication/join_vclock.result         |   6 -
 test/replication/join_vclock.test.lua       |   2 -
 test/replication/skip_conflict_row.result   |   6 -
 test/replication/skip_conflict_row.test.lua |   2 -
 test/vinyl/replica_quota.result             |   6 -
 test/vinyl/replica_quota.test.lua           |   2 -
 test/wal_off/func_max.result                |  25 +--
 test/wal_off/func_max.test.lua              |  19 +-
 test/xlog/errinj.result                     |   9 +-
 test/xlog/errinj.test.lua                   |   4 +-
 test/xlog/misc.result                       |   9 +-
 test/xlog/misc.test.lua                     |   5 +-
 test/xlog/upgrade.result                    |   4 +-
 57 files changed, 689 insertions(+), 226 deletions(-)

diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index 44992b050e9d73d69608fb92782d1e965f6cdc51..7f214e019d316a58689d6c0e6f40d27a812b3a5e 100644
GIT binary patch
delta 1430
zcmV;H1!?+}43i9y8-F=5EoU__HZWo|VP<0rNp5p=VQyn(Iv_PQVK-$pWMeHdW@ctB
zG&yB9En#C}W-U1|I5}oHVqr5dIbsS{Lu_wjYdRo%eF_TIx(m9^1(*QN^Y_tyr2qf`
z001bpFZ}>eeJlVbz1K<57I6arUp&AU-y+dqH(0h4*l!hp|9@?Au~44{l+5aDRdrXC
zTZ%99zp_%;Z$4xEWR9K})E4?c8<3Sf)i+QjABdtA)&V7D3d8`p0L=ik5N9#8jj`V~
z*AdT8bJzBq{Q33}9v?35kHb*R9+A2dK;xik80Iw>-~D@=9%H}8)1!m#e3*uv7^Yv@
zd$+1mg??}%0Dsp~GuAO|_wn}nF0T9cKRb5zh}e{Csi{NA3=%46RexgMoV-;ZZslG}
zO<SOHar>^zn~!U$IaGzd?d@QgtzJt_+>)6|eIM#mrD{$!p9-I7WLh()Po)A+tr;4b
zTtm~rK<WB>GR!v_r&1|xqQVox$q<hqQJ&Z^NwJhCT7Qe5<L<hZyN}*kR}5xddos1)
zT57&NiDzAV(mN|k#Yg5OSbDZ<QgAIb$*;YS%t_EYYbk*bhoIx}+%%sZ{qgPD>dOHj
zrAMTa3|vc1GQ{O6`}p&W)RqF*Qj=pHief9wG5yLopTFbp&mGJ+jMlYFf4*;{{=No;
zYchxS9)D0}A)upo>0|y@dbc_nfP2R$hIJ9Nxo`-cpOa$tzEE#4h;J9ywjVm<(R;Qk
z5^yawM+~F+KZ*fedom_wM{FUfb;gxO{L~zq9M+A9$_l{((S7JVbRA?iyXxp_buBfo
ztjDS*MUzscW>7LH4ycp~W!lhViYc8+8m^^gPk$)`he5`fUP`i;3nUAq3M819$FCL&
zEE8BbmY0#sh%{=kR>V?eJxZ!bf+#|*qid<TBBHXMC7R5!IceLWb^kT0QiXiD7YLvJ
zxNELmp5H~hpdLqR&Vy^IDf>8cvVRY#-Z;3HnxxG8sjQW9_pxhmEj3*_+~w{g5Dszn
zHh&=RXMLk31_iASC4*ZX*sW0A3B(znXmmGo0zr_&DSs+mgH<XRC{@O>YPis-Ggvpr
z-pJ@;q+pm{a4twK>WIZk6*}#T%EkRNC&3?EXWR;BXlSTOy}6c}BC37Y<>us%ifgHv
zVriXms}x<2q(7?*ioC;7_n>-D=uqsnyiLOvIohh7aVQgf!#;f0TH;!2f^dPaG3G-X
zoD(?%3>#V}*5ac^935T0$|NcL@~08MGSH5$Jz4u+ldiMU8HXZqEj4Xste{m9<U1yl
z;{$kq<&yA*v9DHNiqhqMhe!F1;~pZGgt-MOgwj)RS*N~MRiBc?OaWVQ>U_2?9S8bT
zXg@BZn0!c%aMF#2emA;jQIk-kZ(s$l(KfJx*J$3@;>G3!)M#v=4e!a~&2=(O8N!-K
z(fpkJCpwmMxU~>Kdu&fj@L~@Ku_5vZdT^kB_JA_e7JERMX_E<O&-So;A6PZ15m3;2
z>$e60!X*?()iJEx_ZSsd48gsp*U-lQ&_Rcij{JZQyl!;Rc8`S@eLrGOFxso0NJs6C
z`M6X|kQVip_*Vcnn-T(xpq5B`;47@NNm5bfh8*6*Z8-6t=--2ZLXh=^6gnZv*}5!$
zdXSkOG}IB_MstxdAV+a`Bu8K!JeTT!@n-SS)D}ztrgN`IkHZjSSzsg>2OZlXnlm^$
zC(C9)>G$W(au5tT?Tg0~SqBHnDIfv-AXrpHepy)vNntbh6W&FWLB^(Ac(_lU726m0
zT5PurtR*k%L?+ysVKEXDMK2LunUe@Ti0XflY)8qCPz542!%5cW`e@h{o>Ry7;H?5`
kH2dAqNwknw1B3n#t@}v0hILv;5b+g_o+KJv4%HB??ay<dv;Y7A

delta 1431
zcmV;I1!(${43rFz8-F%9EoV7qIb>ruGc#feNp5p=VQyn(Iv_MNF*G<~Fkvk?H8U|S
zG-Eb6En#9ZVl6Q>G-6^mHaRzAIAsb}Lu_wjYdRo%eF_TIx(m9^1(^WO-p7q!r2qf`
z001bpFZ}>eg)9IjJ=jXn7I6XqUp#=+EdLQ|J|L*^AY#R!#(&L~S<f?ekgxrXsIhk{
zMM^S1wf5W-S?24aPUh&f!BR>qR4&hTqO<`TZ%_TqF<&H8N&&+Fxd6=oxe<%xs4HuI
zzs4a`*lStl+U$YlAUZsn%pH#-DTYGqN`SwCdm7d`na;a&-IukV)7jBcb2{y5DW?5Q
z<l)Y$SRfx?2!DWgD_Yx_mUp?Eb(#L%dC!h4hC*(NcPpv`h>Q^_aQ0!On6<gHJl~4v
z-HP4<R+E|UKX*O6ThSm31j}I#rs=GAD=LC299v=hE0v51uT-Gvzm+n*POO!Q1)E{D
zUMCdHll`5WqqwtuDz?@pDmqJGM9Dz}iW3?pDYxnb|9@oHc*}NY-es6tkil&~H&F|`
zThUoJ@ohgh!_=NsdWhPLouST_6nM9yW`Ev8)MhM9Ehz!dhhXC}%{ZQ%hv^*Z?8|{4
zXDGyu40yMq=7`VK)-fzBVp|G$x1t*VNRqEnto=;HV%j(EJk7y6({TG`-*w)V-+Atb
zeiO&@(0|9W5Wx9-@*3Zr;m(Q%;JtB*X`77cWO$6FU7MsB!p6_BADw0Tbv|CiB@cBr
zB*42BEinzpdr1cSxfv6aL%xn!d;3Ttdh89&jjJYvb%AhwXnAOLXmPOVF4(Fd=-rBX
zHXa}gwL+;-sng6!=7cJpN*i`cF(ea7gLf;sLx0NfVX%Fr7n1Ge!pOp?!U$%@@vMac
z%LJB><wfKo8i`u&4Y63;P7)iE5Q<=}$h#F?302#Uk~eW=ZR&RX@1CnF7KrD2fdKoC
z_v@F1JP!$fco?xa54>AZTgRd{cMpARjRWsi)U<^?*0plxUA7u{x1zEi^O<+)gGVfK
zoqtc*#kcVye*zDRtk#P4dZkn-(@W-sP2fsQnNTw-(1cGIOrd5{Fi))QbJ1{_VP&|y
zn_Cm31q1b><l<{FVqrxqRxD6@Rai~tE^0H@bM5T|ZGwV=n%EleRy0(f??1CPds29}
zqA9o9+Xtf9Uz$9%E+@o1mcM_jXW~b4=TF2uzRXf*@9jI4(3u9qskJ1$ThR})*sQGa
zk;cY^nBhf@$EmUCr_u)J&)JCtg=gGU;+g2PV?Q@@oxdqdZM}V`5#FsRjSLkyOU87L
z36tRicz<06Zy5V(^`$6X-gkJE-#G3eVo8`=ph74;1($W|YgP3rNz4?m6{pT;>(X(c
zKZW+=B8tg}<OnC-Xy|vNdloebHTnit@EUCcD|n6OjV)emPC$*u2HNnREZ$rv)082s
zi4@Jx$$z3_Ifq*d0kp^Vv;;5qa1a|JkDvz!YJU$XGi|X4l$kb}aQ18uyZ3=rlNtd9
zt+#$_5FlJaaa0|{%6*Sfam5hadwLCR{0|*;DCx)#=)mhn2W|IQc+vMG<^-d?>WOsJ
z?wF5DwFGHVZ;5{eV6!P9un202v<JSzI-4XFWp2pfJ=}&9|B3!R7$^i;Ur3=7lANu}
zqJIaO=|Mvs@oh9083S?@cSmvr*1>bB{ugf+A5Cq+1YkP%iu5=PF_r~Jf^pEX9ilmd
zqjR!s29$n(?koqvkkh_+Jdt&9kemV%zz>2&MdX*2g^(0Bb3fr-G#O-Ux`l`P)LF58
zaj(U8%fMRlqE2MOof#G*F;Vmq(Um!gfIO)F7s+;%><CpLQZt-nZLW`oUEw)(d=K6#
lphmOb4V^>_X*DqD|IoURglkx*bp#P#(dbE{!R6Hut?gN!sVe{g

diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index ec3826399..6e8263389 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -983,8 +983,31 @@ local function upgrade_priv_to_1_10_2()
     _vpriv.index.object:alter{parts={3, 'string', 4, 'scalar'}}
 end
 
+local function upgrade_users_to_1_10_2()
+    local _priv = box.space[box.schema.PRIV_ID]
+    local _user = box.space[box.schema.USER_ID]
+
+    for _, user in _user:pairs() do
+        if user[0] ~= ADMIN and user[0] ~= SUPER then
+            for _, priv in _priv:pairs(user[0]) do
+                if bit.band(priv[5], box.priv.W) ~= 0 and
+                bit.band(priv[5], box.priv.R) ~= 0 then
+                    local new_privs = bit.bor(box.priv.A, box.priv.D)
+                    -- for universal grants
+                    if priv[3] == 'universe' then
+                        new_privs = bit.bor(new_privs, box.priv.C)
+                    end
+                    _priv:update({priv[2], priv[3], priv[4]},
+                                 {{ "|", 5, new_privs}})
+                end
+            end
+        end
+    end
+end
+
 local function upgrade_to_1_10_2()
     upgrade_priv_to_1_10_2()
+    upgrade_users_to_1_10_2()
 end
 
 local function get_version()
diff --git a/test/box-tap/auth.test.lua b/test/box-tap/auth.test.lua
index 272bd97dc..4e9879408 100755
--- a/test/box-tap/auth.test.lua
+++ b/test/box-tap/auth.test.lua
@@ -20,10 +20,7 @@ test:plan(42)
 local space = box.schema.space.create('tweedledum')
 local index = space:create_index('primary', { type = 'hash' })
 box.schema.user.create('test', {password='pass'})
-box.schema.user.grant('test', 'read,write,execute', 'universe')
 box.schema.user.create('test2', {password=''})
-box.schema.user.grant('test2', 'read,write,execute', 'universe')
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 
 -- check how authentication trigger work
 local msg, counter, succeeded
@@ -163,8 +160,6 @@ test:is(session.sync(), 0, "box.session.sync()")
 
 -- cleanup
 space:drop()
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
-box.schema.user.revoke('test', 'read,write,execute', 'universe')
 box.schema.user.drop('test', { if_exists = true})
 box.schema.user.drop("test2", { if_exists = true})
 
diff --git a/test/box-tap/session.test.lua b/test/box-tap/session.test.lua
index c3c07a67c..857bc643b 100755
--- a/test/box-tap/session.test.lua
+++ b/test/box-tap/session.test.lua
@@ -101,7 +101,8 @@ function audit_disconnect() box.space['tweedledum']:delete{session.id()} end
 test:is(type(session.on_connect(audit_connect)), "function", "type of trigger audit_connect on_connect")
 test:is(type(session.on_disconnect(audit_disconnect)), "function", "type of trigger audit_connect on_disconnect")
 
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.user.grant('guest', 'read,write', 'space', 'tweedledum')
+box.schema.user.grant('guest', 'execute', 'universe')
 a = net.box.connect(HOST, PORT)
 test:ok(a:eval('return space:get{box.session.id()}[1] == session.id()'), "eval get_id")
 test:ok(a:eval('return session.sync() ~= 0'), "eval sync")
@@ -112,12 +113,12 @@ session.on_connect(nil, audit_connect)
 session.on_disconnect(nil, audit_disconnect)
 test:is(active_connections, 0, "active connections after other triggers")
 
-space:drop()
+space:drop() -- tweedledum
 
 test:is(session.uid(), 1, "uid == 1")
 test:is(session.user(), "admin", "user is admin")
 test:is(session.sync(), 0, "sync constant")
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'execute', 'universe')
 
 -- audit permission in on_connect/on_disconnect triggers
 box.schema.user.create('tester', { password = 'tester' })
@@ -199,7 +200,10 @@ function f2()
 	sync2 = box.session.sync()
 	cond:signal()
 end
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.func.create('f1')
+box.schema.func.create('f2')
+box.schema.user.grant('guest', 'execute', 'function', 'f1')
+box.schema.user.grant('guest', 'execute', 'function', 'f2')
 conn = net.box.connect(box.cfg.listen)
 test:ok(conn:ping(), 'connect to self')
 _ = fiber.create(function() conn:call('f1') end)
@@ -208,7 +212,8 @@ _ = fiber.create(function() conn:call('f2') end)
 while started ~= 2 do fiber.sleep(0.01) end
 test:isnt(sync1, sync2, 'session.sync() is request local')
 conn:close()
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'execute', 'function', 'f1')
+box.schema.user.revoke('guest', 'execute', 'function', 'f2')
 
 inspector:cmd('stop server session with cleanup=1')
 session = nil
diff --git a/test/box/access.result b/test/box/access.result
index 4f0607471..2ca8944c6 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -370,7 +370,7 @@ box.schema.user.drop('uniuser')
 box.schema.user.create('grantor')
 ---
 ...
-box.schema.user.grant('grantor', 'read, write, execute', 'universe')
+box.schema.user.grant('grantor', 'read, write, execute, create, alter, drop', 'universe')
 ---
 ...
 session.su('grantor')
@@ -379,7 +379,7 @@ session.su('grantor')
 box.schema.user.create('grantee')
 ---
 ...
-box.schema.user.grant('grantee', 'read, write, execute', 'universe')
+box.schema.user.grant('grantee', 'read, write, execute, create, alter, drop', 'universe')
 ---
 - error: Grant access to universe '' is denied for user 'grantor'
 ...
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index d3e2aab98..6ebb45c1f 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -167,10 +167,10 @@ box.schema.user.drop('uniuser')
 -- only by its creator at the moment
 -- ------------------------------------------------------------
 box.schema.user.create('grantor')
-box.schema.user.grant('grantor', 'read, write, execute', 'universe')
+box.schema.user.grant('grantor', 'read, write, execute, create, alter, drop', 'universe')
 session.su('grantor')
 box.schema.user.create('grantee')
-box.schema.user.grant('grantee', 'read, write, execute', 'universe')
+box.schema.user.grant('grantee', 'read, write, execute, create, alter, drop', 'universe')
 session.su('grantee')
 -- fails - can't suicide - ask the creator to kill you
 box.schema.user.drop('grantee')
diff --git a/test/box/access_bin.result b/test/box/access_bin.result
index 7b30d11f2..df8ef8dee 100644
--- a/test/box/access_bin.result
+++ b/test/box/access_bin.result
@@ -8,7 +8,7 @@ test_run = env.new()
 -- Access control tests which require a binary protocol
 -- connection to the server
 --
-box.schema.user.grant('guest','read,write,execute','universe')
+box.schema.user.grant('guest','execute','universe')
 ---
 ...
 session = box.session
@@ -30,7 +30,7 @@ c:eval("return session.user()")
 c:close()
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'execute', 'universe')
 ---
 ...
 -- gh-488 suid functions
diff --git a/test/box/access_bin.test.lua b/test/box/access_bin.test.lua
index 4c7a6d08f..e77d8c0a8 100644
--- a/test/box/access_bin.test.lua
+++ b/test/box/access_bin.test.lua
@@ -4,14 +4,14 @@ test_run = env.new()
 -- Access control tests which require a binary protocol
 -- connection to the server
 --
-box.schema.user.grant('guest','read,write,execute','universe')
+box.schema.user.grant('guest','execute','universe')
 session = box.session
 remote = require('net.box')
 c = remote.connect(box.cfg.listen)
 c:eval("session.su('admin')")
 c:eval("return session.user()")
 c:close()
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'execute', 'universe')
 
 -- gh-488 suid functions
 --
diff --git a/test/box/access_escalation.result b/test/box/access_escalation.result
index a83f1ee8a..1992ba46d 100644
--- a/test/box/access_escalation.result
+++ b/test/box/access_escalation.result
@@ -84,7 +84,7 @@ box.schema.user.create('underprivileged')
 box.schema.user.grant('underprivileged', 'read,write', 'space', '_func')
 ---
 ...
-box.schema.user.grant('underprivileged', 'create', 'universe')
+box.schema.user.grant('underprivileged', 'create', 'function')
 ---
 ...
 box.session.su('underprivileged')
@@ -99,7 +99,16 @@ box.session.su('admin')
 --
 -- create a deprived function
 --
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.func.create('escalation')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'setuid')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'escalation')
+---
+...
+box.schema.user.grant('guest', 'read', 'space', '_space')
 ---
 ...
 connection = net:connect(os.getenv("LISTEN"))
@@ -119,7 +128,10 @@ fiber.cancel(background)
 box.schema.user.drop('underprivileged')
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.func.drop('escalation')
+---
+...
+box.schema.user.revoke('guest', 'read', 'space', '_space')
 ---
 ...
 connection:close()
diff --git a/test/box/access_escalation.test.lua b/test/box/access_escalation.test.lua
index 29b14c8ea..b333eccf4 100644
--- a/test/box/access_escalation.test.lua
+++ b/test/box/access_escalation.test.lua
@@ -61,7 +61,7 @@ connection:close()
 
 box.schema.user.create('underprivileged')
 box.schema.user.grant('underprivileged', 'read,write', 'space', '_func')
-box.schema.user.grant('underprivileged', 'create', 'universe')
+box.schema.user.grant('underprivileged', 'create', 'function')
 box.session.su('underprivileged')
 box.schema.func.create('setuid', {setuid=true})
 box.session.su('admin')
@@ -69,7 +69,10 @@ box.session.su('admin')
 -- create a deprived function
 --
 
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.func.create('escalation')
+box.schema.user.grant('guest', 'execute', 'function', 'setuid')
+box.schema.user.grant('guest', 'execute', 'function', 'escalation')
+box.schema.user.grant('guest', 'read', 'space', '_space')
 
 connection = net:connect(os.getenv("LISTEN"))
 
@@ -80,6 +83,6 @@ fiber.cancel(background)
 -- tear down
 
 box.schema.user.drop('underprivileged')
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
-
+box.schema.func.drop('escalation')
+box.schema.user.revoke('guest', 'read', 'space', '_space')
 connection:close()
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index c1809d69a..af1a82c20 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -208,7 +208,7 @@ s:select()
 box.schema.user.create('uniuser')
 ---
 ...
-box.schema.user.grant('uniuser', 'read, write, execute', 'universe')
+box.schema.user.grant('uniuser', 'read, write, execute, create', 'universe')
 ---
 ...
 session.su('uniuser')
@@ -291,7 +291,7 @@ session.su('admin')
 box.schema.user.create('someuser')
 ---
 ...
-box.schema.user.grant('someuser', 'read, write, execute', 'universe')
+box.schema.user.grant('someuser', 'read, write, execute, create', 'universe')
 ---
 ...
 session.su('someuser')
diff --git a/test/box/access_misc.test.lua b/test/box/access_misc.test.lua
index 35234898d..7783dc131 100644
--- a/test/box/access_misc.test.lua
+++ b/test/box/access_misc.test.lua
@@ -86,7 +86,7 @@ s:select()
 -- and create this user session
 --
 box.schema.user.create('uniuser')
-box.schema.user.grant('uniuser', 'read, write, execute', 'universe')
+box.schema.user.grant('uniuser', 'read, write, execute, create', 'universe')
 session.su('uniuser')
 uid = session.uid()
 --
@@ -123,7 +123,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, write, execute, create', 'universe')
 session.su('someuser')
 --
 -- Check drop objects of another user
diff --git a/test/box/call.result b/test/box/call.result
index 40d7ef952..349e5ce40 100644
--- a/test/box/call.result
+++ b/test/box/call.result
@@ -1,4 +1,4 @@
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.user.grant('guest', 'execute', 'universe')
 ---
 ...
 conn = require('net.box').connect(box.cfg.listen)
@@ -748,6 +748,6 @@ conn:close()
 require('msgpack').cfg { encode_sparse_safe = sparse_safe }
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'execute', 'universe')
 ---
 ...
diff --git a/test/box/call.test.lua b/test/box/call.test.lua
index 0cc3b8fe6..a74aac4d1 100644
--- a/test/box/call.test.lua
+++ b/test/box/call.test.lua
@@ -1,4 +1,4 @@
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.user.grant('guest', 'execute', 'universe')
 
 conn = require('net.box').connect(box.cfg.listen)
 conn:ping()
@@ -240,4 +240,4 @@ conn:call_16("return_sparse4")
 conn:close()
 require('msgpack').cfg { encode_sparse_safe = sparse_safe }
 
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'execute', 'universe')
diff --git a/test/box/errinj.result b/test/box/errinj.result
index c7e4ce20b..3b7347bc9 100644
--- a/test/box/errinj.result
+++ b/test/box/errinj.result
@@ -786,7 +786,7 @@ test_run:cmd('setopt delimiter ""');
 - true
 ...
 -- Port_dump can fail.
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.user.grant('guest', 'read', 'space', '_space')
 ---
 ...
 cn = net_box.connect(box.cfg.listen)
@@ -818,7 +818,7 @@ errinj.set('ERRINJ_PORT_DUMP', false)
 cn:close()
 ---
 ...
-box.schema.user.revoke('guest', 'read, write, execute', 'universe')
+box.schema.user.revoke('guest', 'read', 'space', '_space')
 ---
 ...
 run()
@@ -1071,15 +1071,15 @@ s:drop()
 -- gh-3255: iproto can crash and discard responses, if a network
 -- is saturated, and DML yields too long on commit.
 --
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 s = box.schema.space.create('test')
 ---
 ...
 _ = s:create_index('pk')
 ---
 ...
+box.schema.user.grant('guest', 'read,write,alter', 'space', 'test')
+---
+...
 c = net_box.connect(box.cfg.listen)
 ---
 ...
@@ -1110,12 +1110,24 @@ s:drop()
 -- gh-3325: do not cancel already sent requests, when a schema
 -- change is detected.
 --
+box.schema.user.grant('guest', 'execute', 'universe')
+---
+...
 s = box.schema.create_space('test')
 ---
 ...
 pk = s:create_index('pk')
 ---
 ...
+box.schema.user.grant('guest', 'read,write,alter', 'space', 'test')
+---
+...
+box.schema.user.grant('guest', 'create', 'space')
+---
+...
+box.schema.user.grant('guest', 'write', 'space', '_index')
+---
+...
 s:replace{1, 1}
 ---
 - [1, 1]
@@ -1166,6 +1178,12 @@ cn:close()
 s:drop()
 ---
 ...
+box.schema.user.revoke('guest', 'execute', 'universe')
+---
+...
+box.schema.user.revoke('guest', 'create', 'space')
+---
+...
 --
 -- If message memory pool is used up, stop the connection, until
 -- the pool has free memory.
@@ -1191,6 +1209,12 @@ function long_poll_f()
 end;
 ---
 ...
+box.schema.func.create('long_poll_f');
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'long_poll_f');
+---
+...
 test_run:cmd('setopt delimiter ""');
 ---
 - true
@@ -1252,7 +1276,10 @@ while finished ~= 2 do fiber.sleep(0.01) end
 cn:close()
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute','universe')
+box.schema.user.revoke('guest', 'execute', 'function', 'long_poll_f')
+---
+...
+box.schema.func.drop('long_poll_f')
 ---
 ...
 --
diff --git a/test/box/errinj.test.lua b/test/box/errinj.test.lua
index a3ea659aa..1c1149e7d 100644
--- a/test/box/errinj.test.lua
+++ b/test/box/errinj.test.lua
@@ -245,7 +245,7 @@ test_run:cmd('setopt delimiter ""');
 
 -- Port_dump can fail.
 
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.user.grant('guest', 'read', 'space', '_space')
 
 cn = net_box.connect(box.cfg.listen)
 cn:ping()
@@ -255,7 +255,7 @@ assert(not ok)
 assert(string.match(tostring(ret), 'Failed to allocate'))
 errinj.set('ERRINJ_PORT_DUMP', false)
 cn:close()
-box.schema.user.revoke('guest', 'read, write, execute', 'universe')
+box.schema.user.revoke('guest', 'read', 'space', '_space')
 
 run()
 ch:get()
@@ -350,10 +350,9 @@ s:drop()
 -- is saturated, and DML yields too long on commit.
 --
 
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 s = box.schema.space.create('test')
 _ = s:create_index('pk')
-
+box.schema.user.grant('guest', 'read,write,alter', 'space', 'test')
 c = net_box.connect(box.cfg.listen)
 
 ch = fiber.channel(200)
@@ -369,8 +368,15 @@ s:drop()
 -- gh-3325: do not cancel already sent requests, when a schema
 -- change is detected.
 --
+
+box.schema.user.grant('guest', 'execute', 'universe')
+
 s = box.schema.create_space('test')
 pk = s:create_index('pk')
+
+box.schema.user.grant('guest', 'read,write,alter', 'space', 'test')
+box.schema.user.grant('guest', 'create', 'space')
+box.schema.user.grant('guest', 'write', 'space', '_index')
 s:replace{1, 1}
 cn = net_box.connect(box.cfg.listen)
 errinj.set("ERRINJ_WAL_DELAY", true)
@@ -388,7 +394,8 @@ while ok == nil do fiber.sleep(0.01) end
 ok, err
 cn:close()
 s:drop()
-
+box.schema.user.revoke('guest', 'execute', 'universe')
+box.schema.user.revoke('guest', 'create', 'space')
 --
 -- If message memory pool is used up, stop the connection, until
 -- the pool has free memory.
@@ -403,6 +410,10 @@ function long_poll_f()
     while not continue do fiber.sleep(0.01) end
     finished = finished + 1
 end;
+
+box.schema.func.create('long_poll_f');
+box.schema.user.grant('guest', 'execute', 'function', 'long_poll_f');
+
 test_run:cmd('setopt delimiter ""');
 cn = net_box.connect(box.cfg.listen)
 function long_poll() cn:call('long_poll_f') end
@@ -428,8 +439,8 @@ errinj.set("ERRINJ_TESTING", false)
 while finished ~= 2 do fiber.sleep(0.01) end
 cn:close()
 
-box.schema.user.revoke('guest', 'read,write,execute','universe')
-
+box.schema.user.revoke('guest', 'execute', 'function', 'long_poll_f')
+box.schema.func.drop('long_poll_f')
 --
 -- gh-3289: drop/truncate leaves the space in inconsistent
 -- state if WAL write fails.
diff --git a/test/box/net.box.result b/test/box/net.box.result
index 37ac54658..bd91f63c2 100644
--- a/test/box/net.box.result
+++ b/test/box/net.box.result
@@ -203,6 +203,9 @@ remote.self:eval('!invalid expression')
 ---
 - error: '[string "return !invalid expression"]:1: unexpected symbol near ''!'''
 ...
+box.schema.user.revoke('guest', 'execute', 'universe')
+---
+...
 --
 -- gh-822: net.box.call should roll back local transaction on error
 --
@@ -265,10 +268,10 @@ test_run:cmd("setopt delimiter ''");
 box.space.gh822:drop()
 ---
 ...
-box.schema.user.revoke('guest','execute','universe')
+box.schema.user.grant('guest', 'read,write', 'space', 'net_box_test_space')
 ---
 ...
-box.schema.user.grant('guest','read,write,execute','universe')
+box.schema.user.grant('guest', 'execute', 'universe')
 ---
 ...
 cn:close()
@@ -496,6 +499,15 @@ cn.space.net_box_test_space:get(354)
 - [354, 1, 2, 4]
 ...
 -- reconnects after errors
+box.schema.user.revoke('guest', 'execute', 'universe')
+---
+...
+box.schema.func.create('test_foo')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'test_foo')
+---
+...
 -- -- 1. no reconnect
 x_fatal(cn)
 ---
@@ -593,6 +605,12 @@ type(fiber.create(function() fiber.sleep(.5) x_fatal(cn) end))
 function pause() fiber.sleep(10) return true end
 ---
 ...
+box.schema.func.create('pause')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'pause')
+---
+...
 cn:call('pause')
 ---
 - error: Peer closed
@@ -601,6 +619,9 @@ cn:call('test_foo', {'a', 'b', 'c'})
 ---
 - [[{'a': 1}], [{'b': 2}], 'c']
 ...
+box.schema.func.drop('pause')
+---
+...
 -- call
 remote.self:call('test_foo', {'a', 'b', 'c'})
 ---
@@ -612,6 +633,15 @@ cn:call('test_foo', {'a', 'b', 'c'})
 ---
 - [[{'a': 1}], [{'b': 2}], 'c']
 ...
+box.schema.func.drop('test_foo')
+---
+...
+box.schema.func.create('long_rep')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'long_rep')
+---
+...
 -- long replies
 function long_rep() return { 1,  string.rep('a', 5000) } end
 ---
@@ -641,6 +671,9 @@ res[2] == string.rep('a', 50000)
 ---
 - true
 ...
+box.schema.func.drop('long_rep')
+---
+...
 -- a.b.c.d
 u = '84F7BCFA-079C-46CC-98B4-F0C821BE833E'
 ---
@@ -654,6 +687,15 @@ X.X = X
 function X.fn(x,y) return y or x end
 ---
 ...
+box.schema.user.grant('guest', 'execute', 'universe')
+---
+...
+cn:close()
+---
+...
+cn = remote.connect(LISTEN.host, LISTEN.service)
+---
+...
 cn:call('X.fn', {u})
 ---
 - 84F7BCFA-079C-46CC-98B4-F0C821BE833E
@@ -666,6 +708,12 @@ cn:call('X.X.X.X:fn', {u})
 ---
 - 84F7BCFA-079C-46CC-98B4-F0C821BE833E
 ...
+box.schema.user.revoke('guest', 'execute', 'universe')
+---
+...
+cn:close()
+---
+...
 -- auth
 cn = remote.connect(LISTEN.host, LISTEN.service, { user = 'netbox', password = '123', wait_connected = true })
 ---
@@ -685,7 +733,10 @@ cn.state
 box.schema.user.create('netbox', { password  = 'test' })
 ---
 ...
-box.schema.user.grant('netbox', 'read, write, execute', 'universe');
+box.schema.user.grant('netbox', 'read,write', 'space', 'net_box_test_space')
+---
+...
+box.schema.user.grant('netbox', 'execute', 'universe')
 ---
 ...
 cn = remote.connect(LISTEN.host, LISTEN.service, { user = 'netbox', password = 'test' })
@@ -1119,6 +1170,15 @@ cn:close()
 cn = remote.connect(LISTEN.host, LISTEN.service)
 ---
 ...
+box.schema.user.grant('guest', 'execute', 'universe')
+---
+...
+cn:close()
+---
+...
+cn = remote.connect(LISTEN.host, LISTEN.service)
+---
+...
 cn:eval('return true')
 ---
 - true
@@ -1142,6 +1202,9 @@ remote.self.eval('return true')
 ---
 - error: 'Use remote:eval(...) instead of remote.eval(...):'
 ...
+box.schema.user.revoke('guest', 'execute', 'universe')
+---
+...
 -- uri as the first argument
 uri = string.format('%s:%s@%s:%s', 'netbox', 'test', LISTEN.host, LISTEN.service)
 ---
@@ -1186,13 +1249,16 @@ cn:ping()
 cn:close()
 ---
 ...
-box.schema.user.revoke('netbox', 'read, write, execute', 'universe');
----
-...
 box.schema.user.drop('netbox')
 ---
 ...
 -- #594: bad argument #1 to 'setmetatable' (table expected, got number)
+box.schema.func.create('dostring')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'dostring')
+---
+...
 test_run:cmd("setopt delimiter ';'")
 ---
 - true
@@ -1212,6 +1278,9 @@ test_run:cmd("setopt delimiter ''");
 gh594()
 ---
 ...
+box.schema.func.drop('dostring')
+---
+...
 -- #636: Reload schema on demand
 sp = box.schema.space.create('test_old')
 ---
@@ -1223,6 +1292,9 @@ sp:insert{1, 2, 3}
 ---
 - [1, 2, 3]
 ...
+box.schema.user.grant('guest', 'read', 'space', 'test_old')
+---
+...
 con = remote.new(box.cfg.listen)
 ---
 ...
@@ -1248,6 +1320,9 @@ sp:insert{2, 3, 4}
 ---
 - [2, 3, 4]
 ...
+box.schema.user.grant('guest', 'read', 'space', 'test')
+---
+...
 con.space.test == nil
 ---
 - true
@@ -1278,6 +1353,9 @@ file_log:seek(0, 'SEEK_END') ~= 0
 ---
 - true
 ...
+box.schema.user.grant('guest', 'execute', 'universe')
+---
+...
 test_run:cmd("setopt delimiter ';'")
 ---
 - true
@@ -1299,6 +1377,9 @@ test_run:grep_log("default", "ER_NO_SUCH_PROC")
 ---
 - ER_NO_SUCH_PROC
 ...
+box.schema.user.revoke('guest', 'execute', 'universe')
+---
+...
 -- gh-983 selecting a lot of data crashes the server or hangs the
 -- connection
 -- gh-983 test case: iproto connection selecting a lot of data
@@ -1314,6 +1395,9 @@ data1k = "aaaabbbbccccddddeeeeffffgggghhhhaaaabbbbccccddddeeeeffffgggghhhhaaaabb
 for i = 0,10000 do box.space.test:insert{i, data1k} end
 ---
 ...
+box.schema.user.grant('guest', 'read', 'space', 'test')
+---
+...
 net = require('net.box')
 ---
 ...
@@ -1339,6 +1423,9 @@ _ = box.space.test:create_index('covering', {type = 'TREE', parts = {1,'unsigned
 _ = box.space.test:insert{1, 2, "string"}
 ---
 ...
+box.schema.user.grant('guest', 'read,write', 'space', 'test')
+---
+...
 c = net:connect(box.cfg.listen)
 ---
 ...
@@ -1395,6 +1482,9 @@ box.space.test:drop()
 function echo(...) return ... end
 ---
 ...
+box.schema.user.grant('guest', 'execute', 'universe')
+---
+...
 c = net.connect(box.cfg.listen)
 ---
 ...
@@ -1434,6 +1524,9 @@ c:eval('return echo(...)', 42)
 c:close()
 ---
 ...
+box.schema.user.revoke('guest', 'execute', 'universe')
+---
+...
 --
 -- gh-2195 export pure msgpack from net.box
 --
@@ -1443,6 +1536,12 @@ space = box.schema.space.create('test')
 _ = box.space.test:create_index('primary')
 ---
 ...
+box.schema.user.grant('guest', 'read,write', 'space', 'test')
+---
+...
+box.schema.user.grant('guest', 'execute', 'universe')
+---
+...
 c = net.connect(box.cfg.listen)
 ---
 ...
@@ -1678,6 +1777,9 @@ c:close()
 space:drop()
 ---
 ...
+box.schema.user.revoke('guest', 'execute', 'universe')
+---
+...
 -- gh-1904 net.box hangs in :close() if a fiber was cancelled
 -- while blocked in :_wait_state() in :_request()
 options = {user = 'netbox', password = 'badpass', wait_connected = false, reconnect_after = 0.01}
@@ -1695,6 +1797,9 @@ fiber.sleep(0.01)
 f:cancel(); c:close()
 ---
 ...
+box.schema.user.grant('guest', 'read', 'space', '_schema')
+---
+...
 -- check for on_schema_reload callback
 test_run:cmd("setopt delimiter ';'")
 ---
@@ -1747,7 +1852,7 @@ test_run:cmd("setopt delimiter ''");
 ---
 - true
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'read', 'space', '_schema')
 ---
 ...
 -- Tarantool < 1.7.1 compatibility (gh-1533)
@@ -1836,7 +1941,7 @@ space ~= nil
 _ = box.space.test:create_index('primary')
 ---
 ...
-box.schema.user.grant('guest','read,write,execute','space', 'test')
+box.schema.user.grant('guest', 'read', 'space', 'test')
 ---
 ...
 c = net.connect(box.cfg.listen)
@@ -1915,7 +2020,7 @@ c:close()
 --
 -- gh-2642: box.session.type()
 --
-box.schema.user.grant('guest','read,write,execute','universe')
+box.schema.user.grant('guest','execute','universe')
 ---
 ...
 c = net.connect(box.cfg.listen)
@@ -1928,6 +2033,9 @@ c:call("box.session.type")
 c:close()
 ---
 ...
+box.schema.user.revoke('guest', 'execute', 'universe')
+---
+...
 --
 -- On_connect/disconnect triggers.
 --
@@ -2007,6 +2115,9 @@ space:drop()
 space = box.schema.space.create('test')
 ---
 ...
+box.schema.user.grant('guest', 'read', 'space', 'test')
+---
+...
 c = net.connect(box.cfg.listen)
 ---
 ...
@@ -2040,16 +2151,28 @@ c.space.test.index.test_index ~= nil
 - true
 ...
 -- cleanup
-box.schema.user.revoke('guest','read,write,execute','universe')
----
-...
 space:drop()
 ---
 ...
 --
 -- gh-946: long polling CALL blocks input
 --
-box.schema.user.grant('guest', 'execute', 'universe')
+box.schema.func.create('fast_call')
+---
+...
+box.schema.func.create('long_call')
+---
+...
+box.schema.func.create('wait_signal')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'fast_call')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'long_call')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'wait_signal')
 ---
 ...
 c = net.connect(box.cfg.listen)
@@ -2166,6 +2289,15 @@ disconnected -- true
 box.session.on_disconnect(nil, on_disconnect)
 ---
 ...
+box.schema.func.drop('long_call')
+---
+...
+box.schema.func.drop('fast_call')
+---
+...
+box.schema.func.drop('wait_signal')
+---
+...
 --
 -- gh-2666: check that netbox.call is not repeated on schema
 -- change.
@@ -2185,6 +2317,12 @@ count = 0
 function create_space(name) count = count + 1 box.schema.create_space(name) return true end
 ---
 ...
+box.schema.func.create('create_space')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'create_space')
+---
+...
 c = net.connect(box.cfg.listen)
 ---
 ...
@@ -2233,6 +2371,9 @@ box.schema.user.revoke('guest', 'create', 'universe')
 c:close()
 ---
 ...
+box.schema.func.drop('create_space')
+---
+...
 --
 -- gh-3164: netbox connection is not closed and garbage collected
 -- ever, if reconnect_after is set.
@@ -2343,7 +2484,7 @@ weak.c
 -- binary or text protocol, and netbox could not be created from
 -- existing socket.
 --
-box.schema.user.grant('guest','read,write,execute','universe')
+box.schema.user.grant('guest', 'execute', 'universe')
 ---
 ...
 urilib = require('uri')
@@ -2382,6 +2523,9 @@ c:call('kek', {300})
 s = box.schema.create_space('test')
 ---
 ...
+box.schema.user.grant('guest', 'read,write', 'space', 'test')
+---
+...
 pk = s:create_index('pk')
 ---
 ...
@@ -2436,7 +2580,7 @@ while c.state ~= 'error_reconnect' do fiber.sleep(0.01) end
 c:close()
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'execute', 'universe')
 ---
 ...
 c.state
@@ -2452,7 +2596,7 @@ c = nil
 space = box.schema.create_space('test')
 ---
 ...
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.user.grant('guest', 'read', 'space', 'test')
 ---
 ...
 _ = space:create_index('pk')
@@ -2476,6 +2620,15 @@ space:drop()
 space = box.schema.create_space('test')
 ---
 ...
+c:close()
+---
+...
+box.schema.user.grant('guest', 'read', 'space', 'test')
+---
+...
+c = net:connect(box.cfg.listen)
+---
+...
 box.internal.collation.create('test', 'ICU', 'ru-RU')
 ---
 ...
@@ -2514,6 +2667,12 @@ c = nil
 cond = nil
 ---
 ...
+box.schema.func.create('long_function')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'long_function')
+---
+...
 function long_function(...) cond = fiber.cond() cond:wait() return ... end
 ---
 ...
@@ -2621,6 +2780,15 @@ ret
 ---
 - [1, 2, 3]
 ...
+c:close()
+---
+...
+box.schema.user.grant('guest', 'execute', 'universe')
+---
+...
+c = net:connect(box.cfg.listen)
+---
+...
 future = c:eval('return long_function(...)', {1, 2, 3}, {is_async = true})
 ---
 ...
@@ -2641,6 +2809,15 @@ future:wait_result(100)
 ---
 - [1, 2, 3]
 ...
+c:close()
+---
+...
+box.schema.user.revoke('guest', 'execute', 'universe')
+---
+...
+c = net:connect(box.cfg.listen)
+---
+...
 --
 -- Ensure the request is garbage collected both if is not used and
 -- if is.
@@ -2748,6 +2925,15 @@ ret
 --
 -- Test space methods.
 --
+c:close()
+---
+...
+box.schema.user.grant('guest', 'read,write', 'space', 'test')
+---
+...
+c = net:connect(box.cfg.listen)
+---
+...
 future = c.space.test:select({1}, {is_async = true})
 ---
 ...
@@ -2854,6 +3040,15 @@ future:wait_result(100)
 ---
 - [5, 6]
 ...
+c:close()
+---
+...
+box.schema.user.grant('guest', 'execute', 'universe')
+---
+...
+c = net:connect(box.cfg.listen)
+---
+...
 future = c.space.test.index.pk:count({3}, {is_async = true})
 ---
 ...
@@ -2861,6 +3056,15 @@ future:wait_result(100)
 ---
 - 1
 ...
+c:close()
+---
+...
+box.schema.user.revoke('guest', 'execute', 'universe')
+---
+...
+c = net:connect(box.cfg.listen)
+---
+...
 future = c.space.test.index.pk:delete({3}, {is_async = true})
 ---
 ...
@@ -3029,12 +3233,30 @@ result
 ---
 - {48: [1, 2, 3]}
 ...
+box.schema.func.drop('long_function')
+---
+...
 --
 -- Test async schema version change.
 --
 function change_schema(i) local tmp = box.schema.create_space('test'..i) return 'ok' end
 ---
 ...
+box.schema.func.create('change_schema')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'change_schema')
+---
+...
+box.schema.user.grant('guest', 'write', 'space', '_schema')
+---
+...
+box.schema.user.grant('guest', 'read,write', 'space', '_space')
+---
+...
+box.schema.user.grant('guest', 'create', 'space')
+---
+...
 future1 = c:call('change_schema', {'1'}, {is_async = true})
 ---
 ...
@@ -3071,6 +3293,9 @@ box.space.test2:drop()
 box.space.test3:drop()
 ---
 ...
+box.schema.func.drop('change_schema')
+---
+...
 --
 -- gh-3400: long-poll input discard must not touch event loop of
 -- a closed connection.
@@ -3122,6 +3347,12 @@ test_run:grep_log('default', 'too big packet size in the header') ~= nil
 ---
 - true
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'write', 'space', '_schema')
+---
+...
+box.schema.user.revoke('guest', 'read,write', 'space', '_space')
+---
+...
+box.schema.user.revoke('guest', 'create', 'space')
 ---
 ...
diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua
index 56b656cc5..08fe60fd5 100644
--- a/test/box/net.box.test.lua
+++ b/test/box/net.box.test.lua
@@ -31,7 +31,6 @@ log.info("ping is done")
 
 cn:ping()
 
-
 -- check permissions
 cn:call('unexists_procedure')
 function test_foo(a,b,c) return { {{ [a] = 1 }}, {{ [b] = 2 }}, c } end
@@ -73,6 +72,8 @@ remote.self:eval('error("exception")')
 remote.self:eval('box.error(0)')
 remote.self:eval('!invalid expression')
 
+box.schema.user.revoke('guest', 'execute', 'universe')
+
 --
 -- gh-822: net.box.call should roll back local transaction on error
 --
@@ -113,8 +114,9 @@ rollback_on_eval_error();
 test_run:cmd("setopt delimiter ''");
 box.space.gh822:drop()
 
-box.schema.user.revoke('guest','execute','universe')
-box.schema.user.grant('guest','read,write,execute','universe')
+box.schema.user.grant('guest', 'read,write', 'space', 'net_box_test_space')
+box.schema.user.grant('guest', 'execute', 'universe')
+
 cn:close()
 cn = remote.connect(box.cfg.listen)
 
@@ -189,6 +191,10 @@ cn.space.net_box_test_space:get(354)
 
 -- reconnects after errors
 
+box.schema.user.revoke('guest', 'execute', 'universe')
+box.schema.func.create('test_foo')
+box.schema.user.grant('guest', 'execute', 'function', 'test_foo')
+
 -- -- 1. no reconnect
 x_fatal(cn)
 cn.state
@@ -224,13 +230,19 @@ cn1:close()
 type(fiber.create(function() fiber.sleep(.5) x_fatal(cn) end))
 function pause() fiber.sleep(10) return true end
 
+box.schema.func.create('pause')
+box.schema.user.grant('guest', 'execute', 'function', 'pause')
 cn:call('pause')
 cn:call('test_foo', {'a', 'b', 'c'})
-
+box.schema.func.drop('pause')
 
 -- call
 remote.self:call('test_foo', {'a', 'b', 'c'})
 cn:call('test_foo', {'a', 'b', 'c'})
+box.schema.func.drop('test_foo')
+
+box.schema.func.create('long_rep')
+box.schema.user.grant('guest', 'execute', 'function', 'long_rep')
 
 -- long replies
 function long_rep() return { 1,  string.rep('a', 5000) } end
@@ -243,14 +255,21 @@ res = cn:call('long_rep')
 res[1] == 1
 res[2] == string.rep('a', 50000)
 
+box.schema.func.drop('long_rep')
+
 -- a.b.c.d
 u = '84F7BCFA-079C-46CC-98B4-F0C821BE833E'
 X = {}
 X.X = X
 function X.fn(x,y) return y or x end
+box.schema.user.grant('guest', 'execute', 'universe')
+cn:close()
+cn = remote.connect(LISTEN.host, LISTEN.service)
 cn:call('X.fn', {u})
 cn:call('X.X.X.X.X.X.X.fn', {u})
 cn:call('X.X.X.X:fn', {u})
+box.schema.user.revoke('guest', 'execute', 'universe')
+cn:close()
 
 -- auth
 
@@ -259,9 +278,10 @@ cn:is_connected()
 cn.error
 cn.state
 
-box.schema.user.create('netbox', { password  = 'test' })
-box.schema.user.grant('netbox', 'read, write, execute', 'universe');
 
+box.schema.user.create('netbox', { password  = 'test' })
+box.schema.user.grant('netbox', 'read,write', 'space', 'net_box_test_space')
+box.schema.user.grant('netbox', 'execute', 'universe')
 cn = remote.connect(LISTEN.host, LISTEN.service, { user = 'netbox', password = 'test' })
 cn.state
 cn.error
@@ -427,6 +447,9 @@ cn:close()
 -- #544 usage for remote[point]method
 cn = remote.connect(LISTEN.host, LISTEN.service)
 
+box.schema.user.grant('guest', 'execute', 'universe')
+cn:close()
+cn = remote.connect(LISTEN.host, LISTEN.service)
 cn:eval('return true')
 cn.eval('return true')
 
@@ -436,7 +459,7 @@ cn:close()
 
 remote.self:eval('return true')
 remote.self.eval('return true')
-
+box.schema.user.revoke('guest', 'execute', 'universe')
 
 -- uri as the first argument
 uri = string.format('%s:%s@%s:%s', 'netbox', 'test', LISTEN.host, LISTEN.service)
@@ -455,10 +478,11 @@ cn = remote.new(uri, { user = 'netbox', password = 'test' })
 cn:ping()
 cn:close()
 
-box.schema.user.revoke('netbox', 'read, write, execute', 'universe');
 box.schema.user.drop('netbox')
 
 -- #594: bad argument #1 to 'setmetatable' (table expected, got number)
+box.schema.func.create('dostring')
+box.schema.user.grant('guest', 'execute', 'function', 'dostring')
 test_run:cmd("setopt delimiter ';'")
 function gh594()
     local cn = remote.connect(box.cfg.listen)
@@ -468,12 +492,15 @@ function gh594()
 end;
 test_run:cmd("setopt delimiter ''");
 gh594()
+box.schema.func.drop('dostring')
+
 
 -- #636: Reload schema on demand
 sp = box.schema.space.create('test_old')
 _ = sp:create_index('primary')
 sp:insert{1, 2, 3}
 
+box.schema.user.grant('guest', 'read', 'space', 'test_old')
 con = remote.new(box.cfg.listen)
 con:ping()
 con.space.test_old:select{}
@@ -483,6 +510,8 @@ sp = box.schema.space.create('test')
 _ = sp:create_index('primary')
 sp:insert{2, 3, 4}
 
+box.schema.user.grant('guest', 'read', 'space', 'test')
+
 con.space.test == nil
 con:reload_schema()
 con.space.test:select{}
@@ -495,6 +524,7 @@ name = string.match(arg[0], "([^,]+)%.lua")
 file_log = require('fio').open(name .. '.log', {'O_RDONLY', 'O_NONBLOCK'})
 file_log:seek(0, 'SEEK_END') ~= 0
 
+box.schema.user.grant('guest', 'execute', 'universe')
 test_run:cmd("setopt delimiter ';'")
 
 _ = fiber.create(
@@ -506,6 +536,7 @@ _ = fiber.create(
 );
 test_run:cmd("setopt delimiter ''");
 test_run:grep_log("default", "ER_NO_SUCH_PROC")
+box.schema.user.revoke('guest', 'execute', 'universe')
 
 -- gh-983 selecting a lot of data crashes the server or hangs the
 -- connection
@@ -518,6 +549,7 @@ data1k = "aaaabbbbccccddddeeeeffffgggghhhhaaaabbbbccccddddeeeeffffgggghhhhaaaabb
 
 for i = 0,10000 do box.space.test:insert{i, data1k} end
 
+box.schema.user.grant('guest', 'read', 'space', 'test')
 net = require('net.box')
 c = net:connect(box.cfg.listen)
 r = c.space.test:select(nil, {limit=5000})
@@ -528,6 +560,7 @@ _ = box.schema.space.create('test')
 _ = box.space.test:create_index('primary', {type = 'TREE', parts = {1,'unsigned'}})
 _ = box.space.test:create_index('covering', {type = 'TREE', parts = {1,'unsigned',3,'string',2,'unsigned'}})
 _ = box.space.test:insert{1, 2, "string"}
+box.schema.user.grant('guest', 'read,write', 'space', 'test')
 c = net:connect(box.cfg.listen)
 c.space.test:select{}
 c.space.test:upsert({1, 2, 'nothing'}, {{'+', 2, 1}}) -- common update
@@ -545,6 +578,7 @@ box.space.test:drop()
 
 -- CALL vs CALL_16 in connect options
 function echo(...) return ... end
+box.schema.user.grant('guest', 'execute', 'universe')
 c = net.connect(box.cfg.listen)
 c:call('echo', {42})
 c:eval('return echo(...)', {42})
@@ -556,6 +590,7 @@ c = net.connect(box.cfg.listen, {call_16 = true})
 c:call('echo', 42)
 c:eval('return echo(...)', 42)
 c:close()
+box.schema.user.revoke('guest', 'execute', 'universe')
 
 --
 -- gh-2195 export pure msgpack from net.box
@@ -563,6 +598,8 @@ c:close()
 
 space = box.schema.space.create('test')
 _ = box.space.test:create_index('primary')
+box.schema.user.grant('guest', 'read,write', 'space', 'test')
+box.schema.user.grant('guest', 'execute', 'universe')
 c = net.connect(box.cfg.listen)
 ibuf = require('buffer').ibuf()
 
@@ -649,6 +686,7 @@ ibuf.rpos == rpos, ibuf.wpos == wpos
 ibuf = nil
 c:close()
 space:drop()
+box.schema.user.revoke('guest', 'execute', 'universe')
 
 -- gh-1904 net.box hangs in :close() if a fiber was cancelled
 -- while blocked in :_wait_state() in :_request()
@@ -658,6 +696,8 @@ f = fiber.create(function() c:call("") end)
 fiber.sleep(0.01)
 f:cancel(); c:close()
 
+box.schema.user.grant('guest', 'read', 'space', '_schema')
+
 -- check for on_schema_reload callback
 test_run:cmd("setopt delimiter ';'")
 do
@@ -699,7 +739,7 @@ do
 end;
 test_run:cmd("setopt delimiter ''");
 
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'read', 'space', '_schema')
 
 -- Tarantool < 1.7.1 compatibility (gh-1533)
 c = net.new(box.cfg.listen)
@@ -745,7 +785,7 @@ test_run:cmd("clear filter")
 space = box.schema.space.create('test', {format={{name="id", type="unsigned"}}})
 space ~= nil
 _ = box.space.test:create_index('primary')
-box.schema.user.grant('guest','read,write,execute','space', 'test')
+box.schema.user.grant('guest', 'read', 'space', 'test')
 
 c = net.connect(box.cfg.listen)
 
@@ -785,10 +825,12 @@ c:close()
 -- gh-2642: box.session.type()
 --
 
-box.schema.user.grant('guest','read,write,execute','universe')
+box.schema.user.grant('guest','execute','universe')
 c = net.connect(box.cfg.listen)
 c:call("box.session.type")
 c:close()
+box.schema.user.revoke('guest', 'execute', 'universe')
+
 
 --
 -- On_connect/disconnect triggers.
@@ -818,6 +860,7 @@ test_run:cmd('stop server connecter')
 --
 space:drop()
 space = box.schema.space.create('test')
+box.schema.user.grant('guest', 'read', 'space', 'test')
 c = net.connect(box.cfg.listen)
 cspace = c.space.test
 space.index.test_index == nil
@@ -829,15 +872,18 @@ cspace.index.test_index ~= nil
 c.space.test.index.test_index ~= nil
 
 -- cleanup
-box.schema.user.revoke('guest','read,write,execute','universe')
 
 space:drop()
 
 --
 -- gh-946: long polling CALL blocks input
 --
-box.schema.user.grant('guest', 'execute', 'universe')
-
+box.schema.func.create('fast_call')
+box.schema.func.create('long_call')
+box.schema.func.create('wait_signal')
+box.schema.user.grant('guest', 'execute', 'function', 'fast_call')
+box.schema.user.grant('guest', 'execute', 'function', 'long_call')
+box.schema.user.grant('guest', 'execute', 'function', 'wait_signal')
 c = net.connect(box.cfg.listen)
 
 N = 100
@@ -894,6 +940,9 @@ disconnected -- true
 
 box.session.on_disconnect(nil, on_disconnect)
 
+box.schema.func.drop('long_call')
+box.schema.func.drop('fast_call')
+box.schema.func.drop('wait_signal')
 --
 -- gh-2666: check that netbox.call is not repeated on schema
 -- change.
@@ -903,6 +952,8 @@ 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
+box.schema.func.create('create_space')
+box.schema.user.grant('guest', 'execute', 'function', 'create_space')
 c = net.connect(box.cfg.listen)
 c:call('create_space', {'test1'})
 count
@@ -917,6 +968,7 @@ 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()
+box.schema.func.drop('create_space')
 
 --
 -- gh-3164: netbox connection is not closed and garbage collected
@@ -971,7 +1023,7 @@ weak.c
 -- binary or text protocol, and netbox could not be created from
 -- existing socket.
 --
-box.schema.user.grant('guest','read,write,execute','universe')
+box.schema.user.grant('guest', 'execute', 'universe')
 urilib = require('uri')
 uri = urilib.parse(tostring(box.cfg.listen))
 s, greeting = net.establish_connection(uri.host, uri.service)
@@ -984,6 +1036,7 @@ c:eval('a = 200')
 a
 c:call('kek', {300})
 s = box.schema.create_space('test')
+box.schema.user.grant('guest', 'read,write', 'space', 'test')
 pk = s:create_index('pk')
 c:reload_schema()
 c.space.test:replace{1}
@@ -1008,7 +1061,7 @@ c = net.connect('localhost:33333', {reconnect_after = 0.1, wait_connected = fals
 while c.state ~= 'error_reconnect' do fiber.sleep(0.01) end
 c:close()
 
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'execute', 'universe')
 c.state
 c = nil
 
@@ -1016,7 +1069,7 @@ c = nil
 -- gh-3256 net.box is_nullable and collation options output
 --
 space = box.schema.create_space('test')
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.user.grant('guest', 'read', 'space', 'test')
 _ = space:create_index('pk')
 _ = space:create_index('sk', {parts = {{2, 'unsigned', is_nullable = true}}})
 c = net:connect(box.cfg.listen)
@@ -1024,6 +1077,9 @@ c.space.test.index.sk.parts
 space:drop()
 
 space = box.schema.create_space('test')
+c:close()
+box.schema.user.grant('guest', 'read', 'space', 'test')
+c = net:connect(box.cfg.listen)
 box.internal.collation.create('test', 'ICU', 'ru-RU')
 _ = space:create_index('sk', { type = 'tree', parts = {{1, 'str', collation = 'test'}}, unique = true })
 c:reload_schema()
@@ -1038,6 +1094,8 @@ c = nil
 -- gh-3107: fiber-async netbox.
 --
 cond = nil
+box.schema.func.create('long_function')
+box.schema.user.grant('guest', 'execute', 'function', 'long_function')
 function long_function(...) cond = fiber.cond() cond:wait() return ... end
 function finalize_long() while not cond do fiber.sleep(0.01) end cond:signal() cond = nil end
 s = box.schema.create_space('test')
@@ -1075,13 +1133,19 @@ _ = fiber.create(function() ret = c:call('long_function', {1, 2, 3}, {is_async =
 finalize_long()
 while not ret do fiber.sleep(0.01) end
 ret
-
+c:close()
+box.schema.user.grant('guest', 'execute', 'universe')
+c = net:connect(box.cfg.listen)
 future = c:eval('return long_function(...)', {1, 2, 3}, {is_async = true})
 future:result()
 future:wait_result(0.01) -- Must fail on timeout.
 finalize_long()
 future:wait_result(100)
 
+c:close()
+box.schema.user.revoke('guest', 'execute', 'universe')
+c = net:connect(box.cfg.listen)
+
 --
 -- Ensure the request is garbage collected both if is not used and
 -- if is.
@@ -1120,6 +1184,9 @@ ret
 --
 -- Test space methods.
 --
+c:close()
+box.schema.user.grant('guest', 'read,write', 'space', 'test')
+c = net:connect(box.cfg.listen)
 future = c.space.test:select({1}, {is_async = true})
 ret = future:wait_result(100)
 ret
@@ -1153,8 +1220,14 @@ future = c.space.test.index.pk:min({}, {is_async = true})
 future:wait_result(100)
 future = c.space.test.index.pk:max({}, {is_async = true})
 future:wait_result(100)
+c:close()
+box.schema.user.grant('guest', 'execute', 'universe')
+c = net:connect(box.cfg.listen)
 future = c.space.test.index.pk:count({3}, {is_async = true})
 future:wait_result(100)
+c:close()
+box.schema.user.revoke('guest', 'execute', 'universe')
+c = net:connect(box.cfg.listen)
 future = c.space.test.index.pk:delete({3}, {is_async = true})
 future:wait_result(100)
 s:get{3}
@@ -1219,10 +1292,17 @@ future:wait_result(100)
 result, ibuf.rpos = msgpack.decode_unchecked(ibuf.rpos)
 result
 
+box.schema.func.drop('long_function')
+
 --
 -- Test async schema version change.
 --
 function change_schema(i) local tmp = box.schema.create_space('test'..i) return 'ok' end
+box.schema.func.create('change_schema')
+box.schema.user.grant('guest', 'execute', 'function', 'change_schema')
+box.schema.user.grant('guest', 'write', 'space', '_schema')
+box.schema.user.grant('guest', 'read,write', 'space', '_space')
+box.schema.user.grant('guest', 'create', 'space')
 future1 = c:call('change_schema', {'1'}, {is_async = true})
 future2 = c:call('change_schema', {'2'}, {is_async = true})
 future3 = c:call('change_schema', {'3'}, {is_async = true})
@@ -1235,6 +1315,7 @@ s:drop()
 box.space.test1:drop()
 box.space.test2:drop()
 box.space.test3:drop()
+box.schema.func.drop('change_schema')
 
 --
 -- gh-3400: long-poll input discard must not touch event loop of
@@ -1263,4 +1344,6 @@ c._transport.perform_request(nil, nil, 'inject', nil, nil, data)
 c:close()
 test_run:grep_log('default', 'too big packet size in the header') ~= nil
 
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'write', 'space', '_schema')
+box.schema.user.revoke('guest', 'read,write', 'space', '_space')
+box.schema.user.revoke('guest', 'create', 'space')
diff --git a/test/box/net_msg_max.result b/test/box/net_msg_max.result
index ccda2014e..85872b65f 100644
--- a/test/box/net_msg_max.result
+++ b/test/box/net_msg_max.result
@@ -7,7 +7,13 @@ fiber = require('fiber')
 net_box = require('net.box')
 ---
 ...
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.user.grant('guest', 'read', 'space', '_space')
+---
+...
+box.schema.func.create('do_long_f')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'do_long_f')
 ---
 ...
 conn = net_box.connect(box.cfg.listen)
@@ -252,7 +258,10 @@ conn2:close()
 conn:close()
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.func.drop('do_long_f')
+---
+...
+box.schema.user.revoke('guest', 'read', 'space', '_space')
 ---
 ...
 box.cfg{readahead = old_readahead, net_msg_max = limit}
diff --git a/test/box/net_msg_max.test.lua b/test/box/net_msg_max.test.lua
index 13f7050ed..c793b0f1c 100644
--- a/test/box/net_msg_max.test.lua
+++ b/test/box/net_msg_max.test.lua
@@ -3,7 +3,10 @@ test_run = require('test_run').new()
 fiber = require('fiber')
 net_box = require('net.box')
 
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.user.grant('guest', 'read', 'space', '_space')
+box.schema.func.create('do_long_f')
+box.schema.user.grant('guest', 'execute', 'function', 'do_long_f')
+
 conn = net_box.connect(box.cfg.listen)
 conn2 = net_box.connect(box.cfg.listen)
 active = 0
@@ -79,6 +82,7 @@ wait_finished(run_max)
 --
 -- Test minimal iproto msg count.
 --
+
 box.cfg{net_msg_max = 2}
 conn:ping()
 #conn.space._space:select{} > 0
@@ -136,5 +140,6 @@ wait_finished(110)
 conn2:close()
 conn:close()
 
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.func.drop('do_long_f')
+box.schema.user.revoke('guest', 'read', 'space', '_space')
 box.cfg{readahead = old_readahead, net_msg_max = limit}
diff --git a/test/box/on_replace.result b/test/box/on_replace.result
index f2de06f90..ac5a540c2 100644
--- a/test/box/on_replace.result
+++ b/test/box/on_replace.result
@@ -540,7 +540,7 @@ s:select()
 ---
 - []
 ...
-s:drop()
+s:drop() -- test_on_repl_ddl
 ---
 ...
 --
diff --git a/test/box/on_replace.test.lua b/test/box/on_replace.test.lua
index 802aaaf2a..8a9fd3678 100644
--- a/test/box/on_replace.test.lua
+++ b/test/box/on_replace.test.lua
@@ -202,7 +202,7 @@ s:replace({8, 9})
 t = s:on_replace(function () s.index.pk:rename('newname') end, t)
 s:replace({9, 10})
 s:select()
-s:drop()
+s:drop() -- test_on_repl_ddl
 
 --
 -- gh-3020: sub-statement rollback
diff --git a/test/box/protocol.result b/test/box/protocol.result
index 3e43663b1..e03186cc9 100644
--- a/test/box/protocol.result
+++ b/test/box/protocol.result
@@ -1,6 +1,3 @@
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 --------------------------------------------------------------------------------
 -- Test case for #273: IPROTO_ITERATOR ignored in network protocol
 --------------------------------------------------------------------------------
@@ -10,6 +7,9 @@ space = box.schema.space.create('tweedledum')
 index = space:create_index('primary', { type = 'tree'})
 ---
 ...
+box.schema.user.grant('guest', 'read', 'space', 'tweedledum')
+---
+...
 for i=1,5 do space:insert{i} end
 ---
 ...
@@ -51,6 +51,3 @@ conn:close()
 space:drop()
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
----
-...
diff --git a/test/box/protocol.test.lua b/test/box/protocol.test.lua
index 5026d585e..805a43721 100644
--- a/test/box/protocol.test.lua
+++ b/test/box/protocol.test.lua
@@ -1,4 +1,3 @@
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 
 --------------------------------------------------------------------------------
 -- Test case for #273: IPROTO_ITERATOR ignored in network protocol
@@ -6,6 +5,9 @@ box.schema.user.grant('guest', 'read,write,execute', 'universe')
 
 space = box.schema.space.create('tweedledum')
 index = space:create_index('primary', { type = 'tree'})
+
+box.schema.user.grant('guest', 'read', 'space', 'tweedledum')
+
 for i=1,5 do space:insert{i} end
 
 LISTEN = require('uri').parse(box.cfg.listen)
@@ -18,4 +20,3 @@ conn.space[space.id]:select(3, { iterator = 'LT' })
 conn:close()
 
 space:drop()
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
diff --git a/test/box/push.result b/test/box/push.result
index 4ec7c546c..af730c1a7 100644
--- a/test/box/push.result
+++ b/test/box/push.result
@@ -42,7 +42,10 @@ test_run:cmd("setopt delimiter ''");
 netbox = require('net.box')
 ---
 ...
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.schema.func.create('do_pushes')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'do_pushes')
 ---
 ...
 c = netbox.connect(box.cfg.listen)
@@ -78,6 +81,9 @@ finished = 0
 s = box.schema.create_space('test', {format = {{'field1', 'integer'}}})
 ---
 ...
+box.schema.user.grant('guest', 'write', 'space', 'test')
+---
+...
 pk = s:create_index('pk')
 ---
 ...
@@ -133,6 +139,12 @@ function dml_push_and_dml_f()
 end;
 ---
 ...
+box.schema.func.create('dml_push_and_dml');
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'dml_push_and_dml');
+---
+...
 -- At first check that a pushed message can be ignored in a binary
 -- protocol too.
 c:call('do_pushes', {300});
@@ -149,6 +161,8 @@ end;
 while finished ~= 400 do fiber.sleep(0.1) end;
 ---
 ...
+box.schema.func.drop('dml_push_and_dml')
+
 failed_catchers = {};
 ---
 ...
@@ -196,6 +210,12 @@ function push_null() box.session.push(box.NULL) end
 messages = {}
 ---
 ...
+box.schema.func.create('push_null')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'push_null')
+---
+...
 c:call('push_null', {}, {on_push = table.insert, on_push_ctx = messages})
 ---
 ...
@@ -203,6 +223,9 @@ messages
 ---
 - - null
 ...
+box.schema.func.drop('push_null')
+---
+...
 --
 -- Test binary pushes.
 --
@@ -272,6 +295,12 @@ t = setmetatable({100}, {__serialize = function() error('err in ser') end})
 function do_push() ok, err = box.session.push(t) end
 ---
 ...
+box.schema.func.create('do_push')
+---
+...
+box.schema.user.grant("guest", "execute", "function", "do_push")
+---
+...
 c:call('do_push', {}, {on_push = table.insert, on_push_ctx = messages})
 ---
 ...
@@ -284,6 +313,9 @@ messages
 ---
 - []
 ...
+box.schema.func.drop('do_push')
+---
+...
 --
 -- Test push from a non-call request.
 --
@@ -417,9 +449,18 @@ s:replace{1}
 ---
 - [1]
 ...
+box.schema.user.grant('guest', 'write', 'space', 'test')
+---
+...
 function do_push_and_duplicate() box.session.push(100) s:insert{1} end
 ---
 ...
+box.schema.func.create('do_push_and_duplicate')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'do_push_and_duplicate')
+---
+...
 future = c:call('do_push_and_duplicate', {}, {is_async = true})
 ---
 ...
@@ -447,12 +488,15 @@ keys
 - - 1
   - null
 ...
-s:drop()
+box.schema.func.drop('do_push_and_duplicate')
 ---
 ...
-c:close()
+box.schema.func.drop('do_pushes')
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+s:drop()
+---
+...
+c:close()
 ---
 ...
diff --git a/test/box/push.test.lua b/test/box/push.test.lua
index 36dc9eeb1..0d2bec3fe 100644
--- a/test/box/push.test.lua
+++ b/test/box/push.test.lua
@@ -23,7 +23,9 @@ end;
 test_run:cmd("setopt delimiter ''");
 
 netbox = require('net.box')
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
+
+box.schema.func.create('do_pushes')
+box.schema.user.grant('guest', 'execute', 'function', 'do_pushes')
 
 c = netbox.connect(box.cfg.listen)
 c:ping()
@@ -37,6 +39,7 @@ catchers = {}
 started = 0
 finished = 0
 s = box.schema.create_space('test', {format = {{'field1', 'integer'}}})
+box.schema.user.grant('guest', 'write', 'space', 'test')
 pk = s:create_index('pk')
 c:reload_schema()
 test_run:cmd("setopt delimiter ';'")
@@ -77,6 +80,9 @@ function dml_push_and_dml_f()
     table.insert(catchers, catcher)
     finished = finished + 1
 end;
+box.schema.func.create('dml_push_and_dml');
+box.schema.user.grant('guest', 'execute', 'function', 'dml_push_and_dml');
+
 -- At first check that a pushed message can be ignored in a binary
 -- protocol too.
 c:call('do_pushes', {300});
@@ -87,6 +93,8 @@ for i = 1, 200 do
 end;
 while finished ~= 400 do fiber.sleep(0.1) end;
 
+box.schema.func.drop('dml_push_and_dml')
+
 failed_catchers = {};
 
 for _, c in pairs(catchers) do
@@ -121,9 +129,11 @@ failed_catchers
 --
 function push_null() box.session.push(box.NULL) end
 messages = {}
+box.schema.func.create('push_null')
+box.schema.user.grant('guest', 'execute', 'function', 'push_null')
 c:call('push_null', {}, {on_push = table.insert, on_push_ctx = messages})
 messages
-
+box.schema.func.drop('push_null')
 --
 -- Test binary pushes.
 --
@@ -148,10 +158,12 @@ err = nil
 messages = {}
 t = setmetatable({100}, {__serialize = function() error('err in ser') end})
 function do_push() ok, err = box.session.push(t) end
+box.schema.func.create('do_push')
+box.schema.user.grant("guest", "execute", "function", "do_push")
 c:call('do_push', {}, {on_push = table.insert, on_push_ctx = messages})
 ok, err
 messages
-
+box.schema.func.drop('do_push')
 --
 -- Test push from a non-call request.
 --
@@ -187,7 +199,6 @@ function do_pushes()
     return true
 end;
 test_run:cmd("setopt delimiter ''");
-
 -- Can not combine callback and async mode.
 ok, err = pcall(c.call, c, 'do_pushes', {}, {is_async = true, on_push = function() end})
 ok
@@ -209,7 +220,11 @@ s = box.schema.create_space('test')
 pk = s:create_index('pk')
 s:replace{1}
 
+box.schema.user.grant('guest', 'write', 'space', 'test')
+
 function do_push_and_duplicate() box.session.push(100) s:insert{1} end
+box.schema.func.create('do_push_and_duplicate')
+box.schema.user.grant('guest', 'execute', 'function', 'do_push_and_duplicate')
 future = c:call('do_push_and_duplicate', {}, {is_async = true})
 future:wait_result(1000)
 messages = {}
@@ -218,7 +233,7 @@ for i, message in future:pairs() do table.insert(messages, message) table.insert
 messages
 keys
 
+box.schema.func.drop('do_push_and_duplicate')
+box.schema.func.drop('do_pushes')
 s:drop()
 c:close()
-
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
diff --git a/test/box/schema_reload.result b/test/box/schema_reload.result
index 31f215bc3..c927e4fcf 100644
--- a/test/box/schema_reload.result
+++ b/test/box/schema_reload.result
@@ -1,6 +1,3 @@
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 net_box = require('net.box')
 ---
 ...
@@ -17,6 +14,9 @@ s = box.schema.create_space('test')
 i = s:create_index('primary')
 ---
 ...
+box.schema.user.grant('guest', 'read', 'space', 'test')
+---
+...
 cn = net_box.connect(LISTEN.host, LISTEN.service)
 ---
 ...
@@ -35,6 +35,9 @@ s2 = box.schema.create_space('test2')
 i2 = s2:create_index('primary')
 ---
 ...
+box.schema.user.grant('guest', 'read', 'space', 'test2')
+---
+...
 ----------------------------------
 -- TEST #1 simple reload
 ----------------------------------
@@ -123,6 +126,9 @@ s:drop()
 s2:drop()
 ---
 ...
+cn:close()
+---
+...
 --------------------------------------------------------------------------------
 -- gh-1808: support schema_version in CALL, EVAL and PING
 --------------------------------------------------------------------------------
@@ -177,6 +183,12 @@ bump_schema_version()
 function somefunc() return true end
 ---
 ...
+box.schema.func.create('somefunc')
+---
+...
+box.schema.user.grant('guest', 'execute', 'function', 'somefunc')
+---
+...
 cn:call('somefunc')
 ---
 - true
@@ -209,6 +221,18 @@ cn.schema_version == schema_version + 1
 ---
 - true
 ...
+box.schema.func.drop('somefunc')
+---
+...
+cn:close()
+---
+...
+box.schema.user.grant('guest', 'execute', 'universe')
+---
+...
+cn = net_box.connect(box.cfg.listen)
+---
+...
 -- eval
 schema_version = cn.schema_version
 ---
@@ -271,6 +295,6 @@ box.internal.schema_version() == schema_version + 1
 if box.space.bump_schema_version ~= nil then box.space.bump_schema_version:drop() end
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'execute', 'universe')
 ---
 ...
diff --git a/test/box/schema_reload.test.lua b/test/box/schema_reload.test.lua
index 48ccb169c..cdfbfca75 100644
--- a/test/box/schema_reload.test.lua
+++ b/test/box/schema_reload.test.lua
@@ -1,4 +1,3 @@
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 net_box = require('net.box')
 fiber = require('fiber')
 LISTEN = require('uri').parse(box.cfg.listen)
@@ -6,6 +5,7 @@ LISTEN = require('uri').parse(box.cfg.listen)
 -- create first space
 s = box.schema.create_space('test')
 i = s:create_index('primary')
+box.schema.user.grant('guest', 'read', 'space', 'test')
 cn = net_box.connect(LISTEN.host, LISTEN.service)
 
 -- check that schema is correct
@@ -15,6 +15,7 @@ old_schema_version = cn.schema_version
 -- create one more space
 s2 = box.schema.create_space('test2')
 i2 = s2:create_index('primary')
+box.schema.user.grant('guest', 'read', 'space', 'test2')
 
 ----------------------------------
 -- TEST #1 simple reload
@@ -66,6 +67,7 @@ request_fiber:cancel()
 reload_fiber:cancel()
 s:drop()
 s2:drop()
+cn:close()
 
 --------------------------------------------------------------------------------
 -- gh-1808: support schema_version in CALL, EVAL and PING
@@ -95,6 +97,8 @@ cn.schema_version == schema_version + 1
 schema_version = cn.schema_version
 bump_schema_version()
 function somefunc() return true end
+box.schema.func.create('somefunc')
+box.schema.user.grant('guest', 'execute', 'function', 'somefunc')
 cn:call('somefunc')
 wait_new_schema()
 cn.schema_version == schema_version + 1
@@ -107,6 +111,12 @@ cn:call('somefunc')
 wait_new_schema()
 cn.schema_version == schema_version + 1
 
+box.schema.func.drop('somefunc')
+
+cn:close()
+box.schema.user.grant('guest', 'execute', 'universe')
+cn = net_box.connect(box.cfg.listen)
+
 -- eval
 schema_version = cn.schema_version
 bump_schema_version()
@@ -125,6 +135,7 @@ somefunc = nil
 
 cn:close()
 
+
 -- box.internal.schema_version()
 schema_version = box.internal.schema_version()
 schema_version > 0
@@ -132,5 +143,4 @@ bump_schema_version()
 box.internal.schema_version() == schema_version + 1
 
 if box.space.bump_schema_version ~= nil then box.space.bump_schema_version:drop() end
-
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+box.schema.user.revoke('guest', 'execute', 'universe')
diff --git a/test/box/sql.result b/test/box/sql.result
index 11a698850..a6c572f35 100644
--- a/test/box/sql.result
+++ b/test/box/sql.result
@@ -16,7 +16,10 @@ _ = box.schema.space.create('test1', { id = 555 })
 box.schema.user.create('test', { password = 'test' })
 ---
 ...
-box.schema.user.grant('test', 'execute,read,write', 'universe')
+box.schema.user.grant('test', 'read,write,alter', 'space', 'test1')
+---
+...
+box.schema.user.grant('test', 'read,write,alter', 'space', 'test')
 ---
 ...
 conn = net_box.connect('test:test@' .. box.cfg.listen)
@@ -228,10 +231,10 @@ net_box = require('net.box')
 box.schema.user.create('test', { password = 'test' })
 ---
 ...
-box.schema.user.grant('test', 'execute,read,write', 'universe')
+s = box.schema.space.create('tweedledum')
 ---
 ...
-s = box.schema.space.create('tweedledum')
+box.schema.user.grant('test', 'read,write,alter', 'space', 'tweedledum')
 ---
 ...
 index1 = s:create_index('primary', { type = 'tree', parts = { 1, 'string'} })
diff --git a/test/box/sql.test.lua b/test/box/sql.test.lua
index b1ba4168c..0523311ee 100644
--- a/test/box/sql.test.lua
+++ b/test/box/sql.test.lua
@@ -5,7 +5,8 @@ net_box = require('net.box')
 s = box.schema.space.create('test')
 _ = box.schema.space.create('test1', { id = 555 })
 box.schema.user.create('test', { password = 'test' })
-box.schema.user.grant('test', 'execute,read,write', 'universe')
+box.schema.user.grant('test', 'read,write,alter', 'space', 'test1')
+box.schema.user.grant('test', 'read,write,alter', 'space', 'test')
 
 conn = net_box.connect('test:test@' .. box.cfg.listen)
 space = conn.space.test
@@ -92,8 +93,8 @@ net_box = require('net.box')
 
 -- Prepare spaces
 box.schema.user.create('test', { password = 'test' })
-box.schema.user.grant('test', 'execute,read,write', 'universe')
 s = box.schema.space.create('tweedledum')
+box.schema.user.grant('test', 'read,write,alter', 'space', 'tweedledum')
 index1 = s:create_index('primary', { type = 'tree', parts = { 1, 'string'} })
 index2 = s:create_index('secondary', { type = 'tree', unique = false, parts = {2, 'string'}})
 function compare(a,b) return a[1] < b[1] end
diff --git a/test/box/stat_net.result b/test/box/stat_net.result
index fc1379920..b3e3db11f 100644
--- a/test/box/stat_net.result
+++ b/test/box/stat_net.result
@@ -19,7 +19,7 @@ box.stat.net.RECEIVED -- zero
 space = box.schema.space.create('tweedledum')
 ---
 ...
-box.schema.user.grant('guest','read,write,execute','universe')
+box.schema.user.grant('guest', 'read', 'space', 'tweedledum')
 ---
 ...
 index = space:create_index('primary', { type = 'hash' })
@@ -60,12 +60,9 @@ box.stat.net.RECEIVED.total
 ---
 - 0
 ...
-space:drop()
+space:drop() -- tweedledum
 ---
 ...
 cn:close()
 ---
 ...
-box.schema.user.revoke('guest','read,write,execute','universe')
----
-...
diff --git a/test/box/stat_net.test.lua b/test/box/stat_net.test.lua
index 9ddc55789..808bb71e7 100644
--- a/test/box/stat_net.test.lua
+++ b/test/box/stat_net.test.lua
@@ -7,7 +7,7 @@ box.stat.net.SENT -- zero
 box.stat.net.RECEIVED -- zero
 
 space = box.schema.space.create('tweedledum')
-box.schema.user.grant('guest','read,write,execute','universe')
+box.schema.user.grant('guest', 'read', 'space', 'tweedledum')
 index = space:create_index('primary', { type = 'hash' })
 remote = require 'net.box'
 
@@ -26,6 +26,5 @@ box.stat.reset()
 box.stat.net.SENT.total
 box.stat.net.RECEIVED.total
 
-space:drop()
+space:drop() -- tweedledum
 cn:close()
-box.schema.user.revoke('guest','read,write,execute','universe')
diff --git a/test/engine/params.result b/test/engine/params.result
index d5f4b3c82..debb40d40 100644
--- a/test/engine/params.result
+++ b/test/engine/params.result
@@ -8,9 +8,6 @@ inspector = test_run.new()
 engine = inspector:get_cfg('engine')
 ---
 ...
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 s = box.schema.create_space('engine', {engine=engine})
 ---
 ...
@@ -30,6 +27,3 @@ box.space.engine:select{}
 box.space.engine:drop()
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
----
-...
diff --git a/test/engine/params.test.lua b/test/engine/params.test.lua
index 3272835a1..a7cb499fd 100644
--- a/test/engine/params.test.lua
+++ b/test/engine/params.test.lua
@@ -3,7 +3,6 @@ test_run = require('test_run')
 inspector = test_run.new()
 engine = inspector:get_cfg('engine')
 
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 s = box.schema.create_space('engine', {engine=engine})
 i = s:create_index('primary')
 
@@ -14,4 +13,3 @@ box.space.engine:select{}
 
 -- cleanup
 box.space.engine:drop()
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
diff --git a/test/engine/replica_join.result b/test/engine/replica_join.result
index 39d857fef..7d6f50a19 100644
--- a/test/engine/replica_join.result
+++ b/test/engine/replica_join.result
@@ -10,9 +10,6 @@ engine = test_run:get_cfg('engine')
 index = test_run:get_cfg('index')
 ---
 ...
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 box.schema.user.grant('guest', 'replication')
 ---
 ...
@@ -535,6 +532,3 @@ box.snapshot()
 box.schema.user.revoke('guest', 'replication')
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
----
-...
diff --git a/test/engine/replica_join.test.lua b/test/engine/replica_join.test.lua
index 1792281e8..07320137b 100644
--- a/test/engine/replica_join.test.lua
+++ b/test/engine/replica_join.test.lua
@@ -2,7 +2,6 @@ env = require('test_run')
 test_run = env.new()
 engine = test_run:get_cfg('engine')
 index = test_run:get_cfg('index')
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 box.schema.user.grant('guest', 'replication')
 space = box.schema.space.create('test', { id = 99999, engine = engine })
 _ = space:create_index('primary', { type = index})
@@ -137,4 +136,3 @@ space:drop()
 box.snapshot()
 
 box.schema.user.revoke('guest', 'replication')
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
diff --git a/test/replication/autobootstrap.result b/test/replication/autobootstrap.result
index 91badc1f1..7b770a5ea 100644
--- a/test/replication/autobootstrap.result
+++ b/test/replication/autobootstrap.result
@@ -112,7 +112,16 @@ _ = test_run:cmd("switch autobootstrap1")
 u1 = box.schema.user.create('test_u')
 ---
 ...
-box.schema.user.grant('test_u', 'read,write,create', 'universe')
+box.schema.user.grant('test_u', 'create', 'space')
+---
+...
+box.schema.user.grant('test_u', 'read,write', 'space', '_space')
+---
+...
+box.schema.user.grant('test_u', 'write', 'space', '_schema')
+---
+...
+box.schema.user.grant('test_u', 'write', 'space', '_index')
 ---
 ...
 box.session.su('test_u')
@@ -131,6 +140,18 @@ box.space.test_u:select()
 ---
 - - [1, 2, 3, 4]
 ...
+box.schema.user.revoke('test_u', 'write', 'space', '_index')
+---
+...
+box.schema.user.revoke('test_u', 'write', 'space', '_schema')
+---
+...
+box.schema.user.revoke('test_u', 'read,write', 'space', '_space')
+---
+...
+box.schema.user.revoke('test_u', 'create', 'space')
+---
+...
 -- Synchronize
 vclock = test_run:get_vclock('autobootstrap1')
 ---
diff --git a/test/replication/autobootstrap.test.lua b/test/replication/autobootstrap.test.lua
index 752d5f317..3b1397eb5 100644
--- a/test/replication/autobootstrap.test.lua
+++ b/test/replication/autobootstrap.test.lua
@@ -55,13 +55,21 @@ _ = test_run:cmd("switch default")
 
 _ = test_run:cmd("switch autobootstrap1")
 u1 = box.schema.user.create('test_u')
-box.schema.user.grant('test_u', 'read,write,create', 'universe')
+box.schema.user.grant('test_u', 'create', 'space')
+box.schema.user.grant('test_u', 'read,write', 'space', '_space')
+box.schema.user.grant('test_u', 'write', 'space', '_schema')
+box.schema.user.grant('test_u', 'write', 'space', '_index')
 box.session.su('test_u')
 _ = box.schema.space.create('test_u'):create_index('pk')
 box.session.su('admin')
 _ = box.space.test_u:replace({1, 2, 3, 4})
 box.space.test_u:select()
 
+box.schema.user.revoke('test_u', 'write', 'space', '_index')
+box.schema.user.revoke('test_u', 'write', 'space', '_schema')
+box.schema.user.revoke('test_u', 'read,write', 'space', '_space')
+box.schema.user.revoke('test_u', 'create', 'space')
+
 -- Synchronize
 vclock = test_run:get_vclock('autobootstrap1')
 _ = test_run:wait_vclock("autobootstrap2", vclock)
diff --git a/test/replication/catch.result b/test/replication/catch.result
index 0f72e89e2..aebba819f 100644
--- a/test/replication/catch.result
+++ b/test/replication/catch.result
@@ -7,9 +7,6 @@ test_run = env.new()
 engine = test_run:get_cfg('engine')
 ---
 ...
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 net_box = require('net.box')
 ---
 ...
@@ -139,6 +136,3 @@ box.space.test:drop()
 box.schema.user.revoke('guest', 'replication')
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
----
-...
diff --git a/test/replication/catch.test.lua b/test/replication/catch.test.lua
index 457f910e9..8cc3242f7 100644
--- a/test/replication/catch.test.lua
+++ b/test/replication/catch.test.lua
@@ -2,7 +2,6 @@ env = require('test_run')
 test_run = env.new()
 engine = test_run:get_cfg('engine')
 
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 
 net_box = require('net.box')
 errinj = box.error.injection
@@ -62,5 +61,4 @@ test_run:cmd("stop server replica")
 test_run:cmd("cleanup server replica")
 box.space.test:drop()
 box.schema.user.revoke('guest', 'replication')
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
 
diff --git a/test/replication/errinj.result b/test/replication/errinj.result
index 3fc432010..ce6add8d4 100644
--- a/test/replication/errinj.result
+++ b/test/replication/errinj.result
@@ -7,9 +7,6 @@ test_run = env.new()
 engine = test_run:get_cfg('engine')
 ---
 ...
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 errinj = box.error.injection
 ---
 ...
diff --git a/test/replication/errinj.test.lua b/test/replication/errinj.test.lua
index 37375f45e..e1e96a0c4 100644
--- a/test/replication/errinj.test.lua
+++ b/test/replication/errinj.test.lua
@@ -1,7 +1,6 @@
 env = require('test_run')
 test_run = env.new()
 engine = test_run:get_cfg('engine')
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 
 errinj = box.error.injection
 
diff --git a/test/replication/gc.result b/test/replication/gc.result
index 3f9db26ce..83d0de293 100644
--- a/test/replication/gc.result
+++ b/test/replication/gc.result
@@ -27,9 +27,6 @@ function wait_gc(n) while #box.info.gc().checkpoints > n do fiber.sleep(0.01) en
 ---
 ...
 -- Grant permissions needed for replication.
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 box.schema.user.grant('guest', 'replication')
 ---
 ...
@@ -463,9 +460,6 @@ box.error.injection.set("ERRINJ_RELAY_REPORT_INTERVAL", 0)
 box.schema.user.revoke('guest', 'replication')
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
----
-...
 box.cfg{checkpoint_count = default_checkpoint_count}
 ---
 ...
diff --git a/test/replication/gc.test.lua b/test/replication/gc.test.lua
index 96f11f8d4..eed76850c 100644
--- a/test/replication/gc.test.lua
+++ b/test/replication/gc.test.lua
@@ -13,7 +13,6 @@ box.cfg{checkpoint_count = 1}
 function wait_gc(n) while #box.info.gc().checkpoints > n do fiber.sleep(0.01) end end
 
 -- Grant permissions needed for replication.
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 box.schema.user.grant('guest', 'replication')
 
 -- By default, relay thread reports status to tx once a second.
@@ -212,6 +211,5 @@ box.cfg{replication = {}}
 s:drop()
 box.error.injection.set("ERRINJ_RELAY_REPORT_INTERVAL", 0)
 box.schema.user.revoke('guest', 'replication')
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
 
 box.cfg{checkpoint_count = default_checkpoint_count}
diff --git a/test/replication/join_vclock.result b/test/replication/join_vclock.result
index 7c402dbf6..a9781073d 100644
--- a/test/replication/join_vclock.result
+++ b/test/replication/join_vclock.result
@@ -13,9 +13,6 @@ test_run = env.new()
 engine = test_run:get_cfg('engine')
 ---
 ...
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 errinj = box.error.injection
 ---
 ...
@@ -87,9 +84,6 @@ replica_set.drop_all(test_run)
 box.space.test:drop()
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
----
-...
 box.schema.user.revoke('guest', 'replication')
 ---
 ...
diff --git a/test/replication/join_vclock.test.lua b/test/replication/join_vclock.test.lua
index ac6eab75c..0b60dffc2 100644
--- a/test/replication/join_vclock.test.lua
+++ b/test/replication/join_vclock.test.lua
@@ -3,7 +3,6 @@ env = require('test_run')
 replica_set = require('fast_replica')
 test_run = env.new()
 engine = test_run:get_cfg('engine')
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 
 errinj = box.error.injection
 errinj.set("ERRINJ_RELAY_FINAL_SLEEP", true)
@@ -33,5 +32,4 @@ test_run:cmd("switch default")
 
 replica_set.drop_all(test_run)
 box.space.test:drop()
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
 box.schema.user.revoke('guest', 'replication')
diff --git a/test/replication/skip_conflict_row.result b/test/replication/skip_conflict_row.result
index bf794db5a..29963f56a 100644
--- a/test/replication/skip_conflict_row.result
+++ b/test/replication/skip_conflict_row.result
@@ -7,9 +7,6 @@ test_run = env.new()
 engine = test_run:get_cfg('engine')
 ---
 ...
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 box.schema.user.grant('guest', 'replication')
 ---
 ...
@@ -100,6 +97,3 @@ box.space.test:drop()
 box.schema.user.revoke('guest', 'replication')
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
----
-...
diff --git a/test/replication/skip_conflict_row.test.lua b/test/replication/skip_conflict_row.test.lua
index 695cce9db..5f7d6ead3 100644
--- a/test/replication/skip_conflict_row.test.lua
+++ b/test/replication/skip_conflict_row.test.lua
@@ -2,7 +2,6 @@ env = require('test_run')
 test_run = env.new()
 engine = test_run:get_cfg('engine')
 
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 box.schema.user.grant('guest', 'replication')
 
 space = box.schema.space.create('test', {engine = engine});
@@ -34,4 +33,3 @@ test_run:cmd("stop server replica")
 test_run:cmd("cleanup server replica")
 box.space.test:drop()
 box.schema.user.revoke('guest', 'replication')
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
diff --git a/test/vinyl/replica_quota.result b/test/vinyl/replica_quota.result
index 460cc1e61..50e397199 100644
--- a/test/vinyl/replica_quota.result
+++ b/test/vinyl/replica_quota.result
@@ -1,9 +1,6 @@
 test_run = require('test_run').new()
 ---
 ...
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 box.schema.user.grant('guest', 'replication')
 ---
 ...
@@ -94,6 +91,3 @@ s:drop()
 box.schema.user.revoke('guest', 'replication')
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
----
-...
diff --git a/test/vinyl/replica_quota.test.lua b/test/vinyl/replica_quota.test.lua
index eade6f2f7..e04abbc22 100644
--- a/test/vinyl/replica_quota.test.lua
+++ b/test/vinyl/replica_quota.test.lua
@@ -1,6 +1,5 @@
 test_run = require('test_run').new()
 
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 box.schema.user.grant('guest', 'replication')
 
 s = box.schema.space.create('test', { engine = 'vinyl' })
@@ -49,4 +48,3 @@ _ = test_run:cmd("cleanup server replica")
 s:drop()
 
 box.schema.user.revoke('guest', 'replication')
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
diff --git a/test/wal_off/func_max.result b/test/wal_off/func_max.result
index 9211c4310..5a43821b2 100644
--- a/test/wal_off/func_max.result
+++ b/test/wal_off/func_max.result
@@ -36,36 +36,41 @@ function drop_limit_func()
 end;
 ---
 ...
-func_limit();
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+func_limit()
 ---
 - error: 'A limit on the total number of functions has been reached: 32000'
 ...
-drop_limit_func();
+drop_limit_func()
 ---
 - error: Function 'func32000' does not exist
 ...
-box.schema.user.create('testuser');
+box.schema.user.create('testuser')
 ---
 ...
-box.schema.user.grant('testuser', 'read, write, execute,create', 'universe');
+box.schema.user.grant('testuser', 'read,write', 'space', '_func')
 ---
 ...
-session.su('testuser');
+box.schema.user.grant('testuser', 'create', 'function')
 ---
 ...
-func_limit();
+session.su('testuser')
+---
+...
+func_limit()
 ---
 - error: 'A limit on the total number of functions has been reached: 32000'
 ...
-drop_limit_func();
+drop_limit_func()
 ---
 - error: Function 'func32000' does not exist
 ...
 session.su('admin')
-box.schema.user.drop('testuser');
 ---
 ...
-test_run:cmd("setopt delimiter ''");
+box.schema.user.drop('testuser')
 ---
-- true
 ...
diff --git a/test/wal_off/func_max.test.lua b/test/wal_off/func_max.test.lua
index 00a095936..47fa6834b 100644
--- a/test/wal_off/func_max.test.lua
+++ b/test/wal_off/func_max.test.lua
@@ -21,13 +21,14 @@ function drop_limit_func()
         i = i + 1
     end
 end;
-func_limit();
-drop_limit_func();
-box.schema.user.create('testuser');
-box.schema.user.grant('testuser', 'read, write, execute,create', 'universe');
-session.su('testuser');
-func_limit();
-drop_limit_func();
-session.su('admin')
-box.schema.user.drop('testuser');
 test_run:cmd("setopt delimiter ''");
+func_limit()
+drop_limit_func()
+box.schema.user.create('testuser')
+box.schema.user.grant('testuser', 'read,write', 'space', '_func')
+box.schema.user.grant('testuser', 'create', 'function')
+session.su('testuser')
+func_limit()
+drop_limit_func()
+session.su('admin')
+box.schema.user.drop('testuser')
diff --git a/test/xlog/errinj.result b/test/xlog/errinj.result
index 262677f1d..6243ac701 100644
--- a/test/xlog/errinj.result
+++ b/test/xlog/errinj.result
@@ -50,15 +50,15 @@ test_run:cmd('restart server default with cleanup=1')
 errinj = box.error.injection
 ---
 ...
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 test = box.schema.create_space('test')
 ---
 ...
 _ = test:create_index('primary')
 ---
 ...
+box.schema.user.grant('guest', 'write', 'space', 'test')
+---
+...
 for i=1, box.cfg.rows_per_wal do test:insert{i, 'test'} end
 ---
 ...
@@ -85,6 +85,3 @@ test:drop()
 errinj = nil
 ---
 ...
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
----
-...
diff --git a/test/xlog/errinj.test.lua b/test/xlog/errinj.test.lua
index 0ea15123f..7a5a29cb6 100644
--- a/test/xlog/errinj.test.lua
+++ b/test/xlog/errinj.test.lua
@@ -25,10 +25,11 @@ test_run:cmd('restart server default with cleanup=1')
 -- gh-881 iproto request with wal IO error
 errinj = box.error.injection
 
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
 test = box.schema.create_space('test')
 _ = test:create_index('primary')
 
+box.schema.user.grant('guest', 'write', 'space', 'test')
+
 for i=1, box.cfg.rows_per_wal do test:insert{i, 'test'} end
 c = require('net.box').connect(box.cfg.listen)
 
@@ -40,4 +41,3 @@ errinj.set('ERRINJ_WAL_WRITE', false)
 -- Cleanup
 test:drop()
 errinj = nil
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
diff --git a/test/xlog/misc.result b/test/xlog/misc.result
index fd3362c4c..5df21aede 100644
--- a/test/xlog/misc.result
+++ b/test/xlog/misc.result
@@ -11,12 +11,12 @@ xlog = require('xlog')
 netbox = require('net.box')
 ---
 ...
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
----
-...
 --
 -- Check that xlogs doesn't contain IPROTO_SYNC
 --
+box.schema.user.grant('guest', 'write', 'space', '_schema')
+---
+...
 conn = netbox.connect(box.cfg.listen)
 ---
 ...
@@ -68,9 +68,6 @@ box.space._schema:delete('test')
 --
 -- Clean up
 --
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
----
-...
 netbox = nil
 ---
 ...
diff --git a/test/xlog/misc.test.lua b/test/xlog/misc.test.lua
index 6e67b86a9..44adfd942 100644
--- a/test/xlog/misc.test.lua
+++ b/test/xlog/misc.test.lua
@@ -5,12 +5,12 @@ fio = require('fio')
 xlog = require('xlog')
 netbox = require('net.box')
 
-box.schema.user.grant('guest', 'read,write,execute', 'universe')
-
 --
 -- Check that xlogs doesn't contain IPROTO_SYNC
 --
 
+box.schema.user.grant('guest', 'write', 'space', '_schema')
+
 conn = netbox.connect(box.cfg.listen)
 -- insert some row using the binary protocol
 conn.space._schema:insert({'test'})
@@ -31,7 +31,6 @@ box.space._schema:delete('test')
 --
 -- Clean up
 --
-box.schema.user.revoke('guest', 'read,write,execute', 'universe')
 netbox = nil
 xlog = nil
 fio = nil
diff --git a/test/xlog/upgrade.result b/test/xlog/upgrade.result
index 76467baf1..b4cf18c71 100644
--- a/test/xlog/upgrade.result
+++ b/test/xlog/upgrade.result
@@ -192,9 +192,9 @@ box.space._priv:select()
   - [1, 4, 'function', 3, 4]
   - [1, 4, 'role', 2, 4]
   - [1, 4, 'role', 5, 4]
-  - [1, 4, 'space', 513, 3]
+  - [1, 4, 'space', 513, 195]
   - [1, 4, 'universe', 0, 24]
-  - [1, 5, 'space', 512, 3]
+  - [1, 5, 'space', 512, 195]
   - [1, 31, 'universe', 0, 4294967295]
 ...
 box.space._vspace ~= nil
-- 
2.15.2 (Apple Git-101.1)

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/4] Introduce separate entity object types for entity privileges.
  2018-08-22 13:39 ` [PATCH v2 1/4] Introduce separate entity object types for entity privileges Serge Petrenko
@ 2018-08-22 15:42   ` Serge Petrenko
  2018-08-22 16:22   ` Vladimir Davydov
  1 sibling, 0 replies; 13+ messages in thread
From: Serge Petrenko @ 2018-08-22 15:42 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: kostja, tarantool-patches

Sorry, forgot one minor change in priv_def_check(). No need to check for object_id 0
here anymore.

Here’s everything that’s changed.

@@ -2583,10 +2623,8 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type)
                break;
        case SC_SPACE:
        {
-               struct space *space = NULL;
-               if (priv->object_id != 0)
-                       space = space_cache_find_xc(priv->object_id);
-               if ((space == NULL || space->def->uid != grantor->def->uid) &&
+               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),
@@ -2597,10 +2635,8 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type)
        }
        case SC_FUNCTION:
        {
-               struct func *func = NULL;
-               if (priv->object_id != 0)
-                       func = func_cache_find(priv->object_id);
-               if ((func == NULL || func->def->uid != grantor->def->uid) &&
+               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),
@@ -2611,10 +2647,8 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type)
        }
        case SC_SEQUENCE:
        {
-               struct sequence *seq = NULL;
-               if (priv->object_id != 0)
-                       seq = sequence_cache_find(priv->object_id);
-               if ((seq == NULL || seq->def->uid != grantor->def->uid) &&
+               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),


> 22 авг. 2018 г., в 16:39, Serge Petrenko <sergepetrenko@tarantool.org> написал(а):
> 
> When granting or revoking a privilege on an entire entity, id 0 was used
> to indicate the fact that we don't grant a privilege on a single object,
> but on a whole entity. This caused confusion, because for entity USER,
> for example, id 0 is a valid object id (user 'guest' uses it).
> Any non-zero id dedicated to this cause obviously may be confused as well.
> Fix this by creating separate schema_object_types for entities:
> SC_ENTITY_SPACE, SC_ENTITY_SEQUENCE, etc.
> 
> Closes: #3574
> Prerequisite: #3524
> ---
> src/box/alter.cc             |  39 ++++++++++++++++++++++++++++-
> src/box/bootstrap.snap       | Bin 1540 -> 1556 bytes
> src/box/lua/schema.lua       |  58 ++++++++++++++++++++++++++-----------------
> src/box/lua/upgrade.lua      |  23 +++++++++++++++++
> src/box/schema.cc            |   9 +++----
> src/box/schema.h             |  23 +++++++++--------
> src/box/schema_def.c         |  17 +++++++++++++
> src/box/schema_def.h         |  16 +++++++++++-
> src/box/user.cc              |  27 +++++++++++---------
> test/box-py/bootstrap.result |  14 +++++------
> test/box/access.result       |   6 ++---
> test/box/access_misc.result  |   8 +++---
> test/box/alter.result        |   8 +++---
> test/xlog/upgrade.result     |  14 +++++------
> 14 files changed, 184 insertions(+), 78 deletions(-)
> 
> diff --git a/src/box/alter.cc b/src/box/alter.cc
> index 3007a131d..8eba21a51 100644
> --- a/src/box/alter.cc
> +++ b/src/box/alter.cc
> @@ -2537,10 +2537,35 @@ priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple)
> {
> 	priv->grantor_id = tuple_field_u32_xc(tuple, BOX_PRIV_FIELD_ID);
> 	priv->grantee_id = tuple_field_u32_xc(tuple, BOX_PRIV_FIELD_UID);
> +
> 	const char *object_type =
> 		tuple_field_cstr_xc(tuple, BOX_PRIV_FIELD_OBJECT_TYPE);
> -	priv->object_id = tuple_field_u32_xc(tuple, BOX_PRIV_FIELD_OBJECT_ID);
> 	priv->object_type = schema_object_type(object_type);
> +
> +	const char *data = tuple_field(tuple, BOX_PRIV_FIELD_OBJECT_ID);
> +	if (data == NULL) {
> +		tnt_raise(ClientError, ER_NO_SUCH_FIELD,
> +			  BOX_PRIV_FIELD_OBJECT_ID + TUPLE_INDEX_BASE);
> +	}
> +	/*
> +	 * When granting or revoking privileges on a whole entity
> +	 * we pass empty string ('') to object_id to indicate
> +	 * grant on every object of that entity.
> +	 * So check for that first.
> +	 */
> +	switch (mp_typeof(*data)) {
> +	case MP_STR:
> +		if (mp_decode_strl(&data) == 0) {
> +			/* Entity-wide privilege. */
> +			priv->object_id = 0;
> +			priv->object_type = schema_entity_type(priv->object_type);
> +			break;
> +		}
> +		FALLTHROUGH;
> +	default:
> +		priv->object_id = tuple_field_u32_xc(tuple,
> +						     BOX_PRIV_FIELD_OBJECT_ID);
> +	}
> 	if (priv->object_type == SC_UNKNOWN) {
> 		tnt_raise(ClientError, ER_UNKNOWN_SCHEMA_OBJECT,
> 			  object_type);
> @@ -2645,6 +2670,18 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type)
> 		}
> 		/* Not necessary to do during revoke, but who cares. */
> 		role_check(grantee, role);
> +		break;
> +	}
> +	case SC_ENTITY_SPACE:
> +	case SC_ENTITY_FUNCTION:
> +	case SC_ENTITY_SEQUENCE:
> +	{
> +		/* Only admin may grant privileges on an entire entity. */
> +		if (grantor->def->uid != ADMIN) {
> +			tnt_raise(AccessDeniedError, priv_name(priv_type),
> +				  schema_object_name(priv->object_type), name,
> +				  grantor->def->name);
> +		}
> 	}
> 	default:
> 		break;
> diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
> index b610828c9c9ae9a22acdd8c150c16c6838b7a273..44992b050e9d73d69608fb92782d1e965f6cdc51 100644
> GIT binary patch
> delta 1551
> zcmV+q2JrcW43rFz7=JM>GB!9ZXE|m$WMeloGhzx!ZgX^DZewLSAT%>EG&o@}VJ$c{
> zGcheRV>UP~VPZ03EipATVq!NoIX7cCWeQe9Y;R+0Iv{&}3JTS_3%bn(nE=k-$Bkd5
> z0000004TLD{QyvfEC41w*h<hAaRLBeJb=_J{}F0FAgJ*mVt>V;#?6&k&og$Aul<dv
> zv3Dv(N-{sS_S_R$=If$P=IFJ-Qc5dSF3)wMv;i7#PyNg>UnEmX0mA^f0L=io5sT!g
> zD{Fnf#vxPKYgy*n?1AMVIy{=p9gia^hC=L0fWLuz8rC_P&bxEnm$jbL+0jvRI_+sG
> zru|Ih;m)d9Ab%fU2!MAhTHBbGce$H&nf~2*&yFmHLT-w8E2;yCj1eht_F<%$wYjrA
> z--_qmirxZNlbP>7cRjpY(I5*1%V7?t>8y7vDuOB;TVebwm5d3mRG{g<l`_3ftd)rc
> zn_;zHClt(+{hgbmxU+pKw$>&pI!j<g$w35)6B;Hdw}0vc|76#A%XVkpWtdu!!EHY`
> zQ473V(OEa~Z9g}|)Sgs&h}w*uq0W{Rc(<Zvf8ImXW-LrCDFM%iVB<2)IG&t`=^X0p
> z%Yh$fD8!Boc(<bFh|kp4F)S=%TMBr$q8k55lCM#${Y=DS+Bfbz&A~d;aQkK7b>5ZV
> zdG3dP6Mx6^(8sb6!1;Xg8sDAa&WZ-$y>W_Zn~dpXc#Nf8o1_@R#?P@Ion`uUK3>En
> z4|O&qz`GSKF%8FiNe275855I3zK&RX`$!^s><!J0t0shXfpC3jd1!TLaj@ww*s37t
> z-HLiP9v};~La9)x)67Zcgesj%8+J-DBoj%4cYiCoL(1@BuzjT$lI`Wf$ik??2xi9d
> ztc3#01eTBGMdTtHiCXRru~^$q5*v~bieRnCyA@ptRojk|H*sWb>UR9^o~tSri06BO
> z0Q-*j>z9Q*4+(#G7_m1GyjxLQ$D%fO4}ENn1MgPUw1qv^wQ}ZNwi<Z1qOu?JnRn@f
> zM}I7Goln@sxA7u>0uPF;){6CdrBo=>OXh`5;7UxHP%|pfgijbup=MGrPps{8(Quhz
> zWw^YXTN9%N1NEZh;%hNtVMQxeEKqt?SWV_GYBSbz?d=0?f`WpY*c$IvG*qAOKeIM_
> zQh2wbDYx3&2cp<tnmn~GC&WCKzkjS};(teS=fpg|%u;9X?K_pwnFhnDwIsY-(GRlN
> ztgP{o#>Rx0;YE$dsj=v%(gx?x*@*;&XWUfcndq})KR0uozbQ*?y?v(<-mNH&3>7#_
> z#&nJefg`BVSY%8M%m4s@04M+v21hAoRSyz?&^U^NFbZH021E!rh)xs$fZ#w_L4OfM
> zEf@qq49vQeA))6t$8JsxhygJm2AP2;6jR1Ytzq>yuq)+j_YBuXSm^>oOc44oRR2+r
> zfKn$8@9xdDJsnXgj8fvJX-OOEnOz2N82f7Vr6^tAcX*WFIPM{0Ntj!pLMS~2mv!oE
> zRrM)J%oMN{r_N{V(s7_ah4$kjihs$6<OnC-Xy|vNdloebHTnit@EUCcD|n6OjV)em
> zPC$*u2HNnREZ$rv)082si4@Jx$$z3_Ifq*d0kp^Vv;;5qa1a|JkDvz!Y7ZzgZLtTG
> znKqel_G}Nk_kmTD8UY2Zw|;97AY4LmR2{?0eUDLb#Sq+kdJS#-4;^$U>3_%%=)mhn
> z2W|IQc+vMG<^-d?>WOsJ?wF5DwFGHVZ;5{eV6!P9un202v<JSzI-4XFWp2pfJ=}&9
> z|B3!R7$^i;Ur3=7lANu}q6eAjK|>w!Z8R4d19B91M{)$#!E>qp7jG6HO>MyhU^@4T
> z^f(MLmIX$FanP|HqB(=3bAPgI29$n(?koqvkkh_+Jdt&9kemV%zz>2&MdX*2g^(0B
> zb3fr-G#O-Ux`l`P)LF58aj(U8%fMRlqE2MOof#G*F;Vmq(Um!gfT;c#$##_N2vs0b
> zGn`~?u8)RY;W>4D58f)EMzh}yokR<1H8AM^(7KO=Ygngs1QB1+=m<%o!R6Hut?jEW
> B)aL*I
> 
> delta 1534
> zcmV<a1p)e$41^4j7=JJ=G%zh^V>dT4F*z~{Np5p=VQyn(Iv_b=V_`LAF*YqTV`gS8
> zG-hTvEnzrjFfCy?W;Qf4VP!dGGGq!?Lu_wjYdRo%F*+bOeF_TIx(m9^1&9F7A|rpb
> zr2qf`001bpFZ}>e{VM<lJ=RLl7I6XqUp$Ch%_7lYJ_Lj_@PFoXP~+we(A76EO7~<&
> z)F3;RA|;uhw(`4WgTRw>I&<{eU?~Ai$VOI~v629dx2JaIm@kx4N&&zCxB$xlBK}6v
> zM#}nreKVfmYggvF>;bcooE<&pjzw|&ydgCd;B)Xi4C|Up=iRyONm;Mu=<J|49rmyj
> z!~XSg?^aVP(0>n31mId~$~K1OP2OJJrGIzc!y|v*5Q}mxHFpG|K|%#jeQ^A;E^l>*
> z3*Bp}NefgxX1+gm^KmUTgDMc1Jsb?P)oZC4!mL!?OgGcvU!7uNP_0u4V%4c@$$Tde
> z<JJuBKo=+bJ1@oWHsew%rA$(CR4!bhCq$aiEJ3j}Cx2ttc)M<~yh-n@9Ru2aUZxgY
> zOU+j=F>OCDy|bcHe0;hLW1g*=6kJQqvOo9n=`!@rT1v3PA=sEaGtUmrgF1V*+HwF$
> z%o|ck2Ck)MDdN~U>lpNk)Rh9)Qd8p>$FU`TvVVQ}JNw3+pEy`&7|(v!cb#wKcU}Xc
> zztr*E^M9!-1a3aZxQyRo-mQ)X;JxvQVS5a1E-Z#;*X8(mp!hlb(b=Wn_TxWHa?e&p
> z0<NVdiD5jvlVdHR&M*6U`SOA@wvN<F<4PlXstt_|t42g+g<yeLeOP%|b(q;~x1wEl
> zEj90~M>ms_NyVg8C=MtXRLX=hZO}2jlujiL*MCw|r<8%iFyl-urCG}bk_A!)lFN(Z
> z*9rxd2`ZdQ%ZOz}8m(9>QmHeYBvm9q6rtA9wbTrW=uAh+mpZa8Z9M*WuhEnWw8OPP
> z0Ken?`duE+M1Fc0sWuO;rRJ>TPnWxUKDEZdwbU$UUQcC(VtJFR2G>&aT`%TX-lUJ$
> zzkkQk8h`!+Ak42Gb5O~I9X_GL4xhR(RGq@`<8YEmeGLBa%Ed{YaV#4wH7X6(wXrob
> zRx(mCOfa|?Bo}qm;#z9jPz5?Ii>k-mKV61Bw$iw6MNm)>lUj2vHAVFK{#e#!kBV!l
> ziDGG`ab1f2WXaFUg5t{}`TM8d6EBXt_J3uuRg$)9X&lOg&M*+4wU)S+njl=^i<EJZ
> zX2ppwLyQ}LBXiMF<cgb*y<$jG_{C2oetkYX_VcpV`IoM<QW}RMaV<4zXt3a^V}T&3
> z(OhIq3CsWhfB+}}AqGb&W<?JYfWSD4qc93!7zRWLI0_9I0RX{)ke~>l77Rj$ynhqj
> zxeG%W18Ui|uoin^FOJH&6BE><l!R4e54%@5i^3tJ2=RjuhoRy}$qP!PWxP;CvBe2+
> zB`AQ$m7GU4)NS@llAFhZ>1-;h#Us!zr2@yPWOP}$xk=>TS*Ej6Q8ZgBx&}sXh}Wym
> z;Yvs=Xm6s&oTIkS5VXc28)t^2+kc4qR4r1#;*gdpU~^0s$pG8pm?G)gVGer7p5*j@
> zrQSOro_cGm&fR}<@b8h^=iXW!%4reE=RqJFW<g443Z&4$8`^`Azzc12c=p(Su(w)T
> z1)jmCmtK6UOn@l~=HxoFllhK1;>sqt`1BcS0HC@As3ba#z^Ko9Pi8avYk$Oa!!S#&
> zSx3_!A8xyrpfT#3Y0zk_Hi}Y>p%$pI61F+5M3N&%2xKJAC;$CDD}<6?=$#5uoTZ5Z
> zCNj}EgB|fKv=$L@ag>MEJ_76DnN*W1Z`O`MZ9ptwx|K1yxMV1V1(Ltx%(4y7obk4E
> zW?=^8GIQ=kv4A0`wS7N1*>`Z!opukPx!_g@c{8|RJi<`!QS!z#<&+X#nnV8V6uo^%
> zFL^|hfwSZ-+|LAZ&d5i?Zt>-$tMH_1UnT!Yb{@SussOv@WfJ50JyfLCQd9Bw_N-dh
> k2=V>Mm|$S01}gnuSl5<E4okR>OyKJnJxL~`9Mur5?adb1g8%>k
> 
> diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
> index b9b8c9004..d347fd1e6 100644
> --- a/src/box/lua/schema.lua
> +++ b/src/box/lua/schema.lua
> @@ -1801,16 +1801,16 @@ local function privilege_name(privilege)
> end
> 
> local function object_resolve(object_type, object_name)
> +    if object_name ~= nil and type(object_name) ~= 'string'
> +            and type(object_name) ~= 'number' then
> +        box.error(box.error.ILLEGAL_PARAMS, "wrong object name type")
> +    end
>     if object_type == 'universe' then
> -        if object_name ~= nil and type(object_name) ~= 'string'
> -                and type(object_name) ~= 'number' then
> -            box.error(box.error.ILLEGAL_PARAMS, "wrong object name type")
> -        end
>         return 0
>     end
>     if object_type == 'space' then
> -        if object_name == nil or object_name == 0 then
> -            return 0
> +        if object_name == '' then
> +            return ''
>         end
>         local space = box.space[object_name]
>         if  space == nil then
> @@ -1819,8 +1819,8 @@ local function object_resolve(object_type, object_name)
>         return space.id
>     end
>     if object_type == 'function' then
> -        if object_name == nil or object_name == 0 then
> -            return 0
> +        if object_name == '' then
> +            return ''
>         end
>         local _vfunc = box.space[box.schema.VFUNC_ID]
>         local func
> @@ -1836,8 +1836,8 @@ local function object_resolve(object_type, object_name)
>         end
>     end
>     if object_type == 'sequence' then
> -        if object_name == nil or object_name == 0 then
> -            return 0
> +        if object_name == '' then
> +            return ''
>         end
>         local seq = sequence_resolve(object_name)
>         if seq == nil then
> @@ -1864,7 +1864,7 @@ local function object_resolve(object_type, object_name)
> end
> 
> local function object_name(object_type, object_id)
> -    if object_type == 'universe' then
> +    if object_type == 'universe' or object_id == '' then
>         return ""
>     end
>     local space
> @@ -2072,12 +2072,18 @@ local function grant(uid, name, privilege, object_type,
>                      object_name, options)
>     -- From user point of view, role is the same thing
>     -- as a privilege. Allow syntax grant(user, role).
> -    if object_name == nil and object_type == nil then
> -        -- sic: avoid recursion, to not bother with roles
> -        -- named 'execute'
> -        object_type = 'role'
> -        object_name = privilege
> -        privilege = 'execute'
> +    if object_name == nil then
> +        if object_type == nil then
> +            -- sic: avoid recursion, to not bother with roles
> +            -- named 'execute'
> +            object_type = 'role'
> +            object_name = privilege
> +            privilege = 'execute'
> +        else
> +            -- Allow syntax grant(user, priv, entity)
> +            -- for entity grants.
> +            object_name = ''
> +        end
>     end
>     local privilege_hex = privilege_check(privilege, object_type)
> 
> @@ -2117,10 +2123,16 @@ end
> local function revoke(uid, name, privilege, object_type, object_name, options)
>     -- From user point of view, role is the same thing
>     -- as a privilege. Allow syntax revoke(user, role).
> -    if object_name == nil and object_type == nil then
> -        object_type = 'role'
> -        object_name = privilege
> -        privilege = 'execute'
> +    if object_name == nil then
> +        if object_type == nil then
> +            object_type = 'role'
> +            object_name = privilege
> +            privilege = 'execute'
> +        else
> +            -- Allow syntax revoke(user, privilege, entity)
> +            -- to revoke entity privileges.
> +            object_name = ''
> +        end
>     end
>     local privilege_hex = privilege_check(privilege, object_type)
>     options = options or {}
> @@ -2192,8 +2204,8 @@ local function drop(uid, opts)
>     local privs = _vpriv.index.primary:select{uid}
> 
>     for k, tuple in pairs(privs) do
> -	-- we need an additional box.session.su() here, because of
> -	-- unnecessary check for privilege PRIV_REVOKE in priv_def_check()
> +        -- we need an additional box.session.su() here, because of
> +        -- unnecessary check for privilege PRIV_REVOKE in priv_def_check()
>         box.session.su("admin", revoke, uid, uid, tuple[5], tuple[3], tuple[4])
>     end
>     box.space[box.schema.USER_ID]:delete{uid}
> diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
> index 0293f6ef8..ec3826399 100644
> --- a/src/box/lua/upgrade.lua
> +++ b/src/box/lua/upgrade.lua
> @@ -964,6 +964,28 @@ local function upgrade_to_1_10_0()
>     create_vsequence_space()
> end
> 
> +--------------------------------------------------------------------------------
> +--- Tarantool 1.10.2
> +--------------------------------------------------------------------------------
> +local function upgrade_priv_to_1_10_2()
> +    local _priv = box.space._priv
> +    local _vpriv = box.space._vpriv
> +    local format = _priv:format()
> +
> +    format[4].type = 'scalar'
> +    _priv:format(format)
> +    format = _vpriv:format()
> +    format[4].type = 'scalar'
> +    _vpriv:format(format)
> +    _priv.index.primary:alter{parts={2, 'unsigned', 3, 'string', 4, 'scalar'}}
> +    _vpriv.index.primary:alter{parts={2, 'unsigned', 3, 'string', 4, 'scalar'}}
> +    _priv.index.object:alter{parts={3, 'string', 4, 'scalar'}}
> +    _vpriv.index.object:alter{parts={3, 'string', 4, 'scalar'}}
> +end
> +
> +local function upgrade_to_1_10_2()
> +    upgrade_priv_to_1_10_2()
> +end
> 
> local function get_version()
>     local version = box.space._schema:get{'version'}
> @@ -991,6 +1013,7 @@ local function upgrade(options)
>         {version = mkversion(1, 7, 6), func = upgrade_to_1_7_6, auto = false},
>         {version = mkversion(1, 7, 7), func = upgrade_to_1_7_7, auto = true},
>         {version = mkversion(1, 10, 0), func = upgrade_to_1_10_0, auto = true},
> +        {version = mkversion(1, 10, 2), func = upgrade_to_1_10_2, auto = true},
>     }
> 
>     for _, handler in ipairs(handlers) do
> diff --git a/src/box/schema.cc b/src/box/schema.cc
> index 433f52c08..aaa63a083 100644
> --- a/src/box/schema.cc
> +++ b/src/box/schema.cc
> @@ -535,11 +535,12 @@ schema_find_name(enum schema_object_type type, uint32_t object_id)
> {
> 	switch (type) {
> 	case SC_UNIVERSE:
> +	case SC_ENTITY_SPACE:
> +	case SC_ENTITY_FUNCTION:
> +	case SC_ENTITY_SEQUENCE:
> 		return "";
> 	case SC_SPACE:
> 		{
> -			if (object_id == 0)
> -				return "SPACE";
> 			struct space *space = space_by_id(object_id);
> 			if (space == NULL)
> 				break;
> @@ -547,8 +548,6 @@ schema_find_name(enum schema_object_type type, uint32_t object_id)
> 		}
> 	case SC_FUNCTION:
> 		{
> -			if (object_id == 0)
> -				return "FUNCTION";
> 			struct func *func = func_by_id(object_id);
> 			if (func == NULL)
> 				break;
> @@ -556,8 +555,6 @@ schema_find_name(enum schema_object_type type, uint32_t object_id)
> 		}
> 	case SC_SEQUENCE:
> 		{
> -			if (object_id == 0)
> -				return "SEQUENCE";
> 			struct sequence *seq = sequence_by_id(object_id);
> 			if (seq == NULL)
> 				break;
> diff --git a/src/box/schema.h b/src/box/schema.h
> index 0822262d0..f1735ff34 100644
> --- a/src/box/schema.h
> +++ b/src/box/schema.h
> @@ -250,16 +250,19 @@ static inline
> struct access *
> entity_access_get(enum schema_object_type type)
> {
> -       switch (type) {
> -       case SC_SPACE:
> -               return entity_access.space;
> -       case SC_FUNCTION:
> -               return entity_access.function;
> -       case SC_SEQUENCE:
> -               return entity_access.sequence;
> -       default:
> -               return NULL;
> -       }
> +	switch (type) {
> +	case SC_SPACE:
> +	case SC_ENTITY_SPACE:
> +		return entity_access.space;
> +	case SC_FUNCTION:
> +	case SC_ENTITY_FUNCTION:
> +		return entity_access.function;
> +	case SC_SEQUENCE:
> +	case SC_ENTITY_SEQUENCE:
> +		return entity_access.sequence;
> +	default:
> +		return NULL;
> +	}
> }
> 
> #endif /* INCLUDES_TARANTOOL_BOX_SCHEMA_H */
> diff --git a/src/box/schema_def.c b/src/box/schema_def.c
> index 97c074ab2..9091af054 100644
> --- a/src/box/schema_def.c
> +++ b/src/box/schema_def.c
> @@ -41,6 +41,23 @@ static const char *object_type_strs[] = {
> 	/* [SC_COLLATION]       = */ "collation",
> };
> 
> +enum schema_object_type
> +schema_entity_type(enum schema_object_type type)
> +{
> +	switch (type) {
> +	case SC_SPACE:
> +		return SC_ENTITY_SPACE;
> +	case SC_FUNCTION:
> +		return SC_ENTITY_FUNCTION;
> +	case SC_SEQUENCE:
> +		return SC_ENTITY_SEQUENCE;
> +	case SC_COLLATION:
> +		return SC_ENTITY_COLLATION;
> +	default:
> +		return SC_UNKNOWN;
> +	}
> +}
> +
> enum schema_object_type
> schema_object_type(const char *name)
> {
> diff --git a/src/box/schema_def.h b/src/box/schema_def.h
> index 2edb8d37f..d2fc39b76 100644
> --- a/src/box/schema_def.h
> +++ b/src/box/schema_def.h
> @@ -228,9 +228,23 @@ enum schema_object_type {
> 	SC_ROLE = 5,
> 	SC_SEQUENCE = 6,
> 	SC_COLLATION = 7,
> -	schema_object_type_MAX = 8
> +	/*
> +	 * All object types are supposed to be above this point,
> +	 * all entity types - below.
> +	 */
> +	schema_object_type_MAX = 8,
> +	SC_ENTITY_SPACE,
> +	SC_ENTITY_FUNCTION,
> +	SC_ENTITY_SEQUENCE,
> +	SC_ENTITY_COLLATION
> };
> 
> +/**
> + * Given a object type, return an entity type it belongs to.
> + */
> +enum schema_object_type
> +schema_entity_type(enum schema_object_type type);
> +
> enum schema_object_type
> schema_object_type(const char *name);
> 
> diff --git a/src/box/user.cc b/src/box/user.cc
> index fbf06566a..eec785652 100644
> --- a/src/box/user.cc
> +++ b/src/box/user.cc
> @@ -207,12 +207,23 @@ access_find(struct priv_def *priv)
> 		access = universe.access;
> 		break;
> 	}
> +	case SC_ENTITY_SPACE:
> +	{
> +		access = entity_access.space;
> +		break;
> +	}
> +	case SC_ENTITY_FUNCTION:
> +	{
> +		access = entity_access.function;
> +		break;
> +	}
> +	case SC_ENTITY_SEQUENCE:
> +	{
> +		access = entity_access.sequence;
> +		break;
> +	}
> 	case SC_SPACE:
> 	{
> -		if (priv->object_id == 0) {
> -			access = entity_access.space;
> -			break;
> -		}
> 		struct space *space = space_by_id(priv->object_id);
> 		if (space)
> 			access = space->access;
> @@ -220,10 +231,6 @@ access_find(struct priv_def *priv)
> 	}
> 	case SC_FUNCTION:
> 	{
> -		if (priv->object_id == 0) {
> -			access = entity_access.function;
> -			break;
> -		}
> 		struct func *func = func_by_id(priv->object_id);
> 		if (func)
> 			access = func->access;
> @@ -231,10 +238,6 @@ access_find(struct priv_def *priv)
> 	}
> 	case SC_SEQUENCE:
> 	{
> -		if (priv->object_id == 0) {
> -			access = entity_access.sequence;
> -			break;
> -		}
> 		struct sequence *seq = sequence_by_id(priv->object_id);
> 		if (seq)
> 			access = seq->access;
> diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
> index 16c2027cf..cf8242de5 100644
> --- a/test/box-py/bootstrap.result
> +++ b/test/box-py/bootstrap.result
> @@ -5,7 +5,7 @@ box.space._schema:select{}
> ---
> - - ['cluster', '<cluster uuid>']
>   - ['max_id', 511]
> -  - ['version', 1, 10, 0]
> +  - ['version', 1, 10, 2]
> ...
> box.space._cluster:select{}
> ---
> @@ -58,10 +58,10 @@ box.space._space:select{}
>         'type': 'string'}, {'name': 'auth', 'type': 'map'}]]
>   - [312, 1, '_priv', 'memtx', 0, {}, [{'name': 'grantor', 'type': 'unsigned'}, {
>         'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
> -      {'name': 'object_id', 'type': 'unsigned'}, {'name': 'privilege', 'type': 'unsigned'}]]
> +      {'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
>   - [313, 1, '_vpriv', 'sysview', 0, {}, [{'name': 'grantor', 'type': 'unsigned'},
>       {'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
> -      {'name': 'object_id', 'type': 'unsigned'}, {'name': 'privilege', 'type': 'unsigned'}]]
> +      {'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
>   - [320, 1, '_cluster', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'uuid',
>         'type': 'string'}]]
>   - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count',
> @@ -104,13 +104,13 @@ box.space._index:select{}
>   - [305, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
>   - [305, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
>   - [312, 0, 'primary', 'tree', {'unique': true}, [[1, 'unsigned'], [2, 'string'],
> -      [3, 'unsigned']]]
> +      [3, 'scalar']]]
>   - [312, 1, 'owner', 'tree', {'unique': false}, [[0, 'unsigned']]]
> -  - [312, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'unsigned']]]
> +  - [312, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'scalar']]]
>   - [313, 0, 'primary', 'tree', {'unique': true}, [[1, 'unsigned'], [2, 'string'],
> -      [3, 'unsigned']]]
> +      [3, 'scalar']]]
>   - [313, 1, 'owner', 'tree', {'unique': false}, [[0, 'unsigned']]]
> -  - [313, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'unsigned']]]
> +  - [313, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'scalar']]]
>   - [320, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
>   - [320, 1, 'uuid', 'tree', {'unique': true}, [[1, 'string']]]
>   - [330, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
> diff --git a/test/box/access.result b/test/box/access.result
> index f4669a4a3..599500633 100644
> --- a/test/box/access.result
> +++ b/test/box/access.result
> @@ -504,7 +504,7 @@ box.space._priv:select{id}
> ...
> box.schema.user.grant('user', 'read', 'universe')
> ---
> -- error: User 'user' already has read access on universe 'nil'
> +- error: User 'user' already has read access on universe ''
> ...
> box.space._priv:select{id}
> ---
> @@ -690,7 +690,7 @@ box.schema.user.grant('guest', 'read,write,execute', 'universe')
> ...
> box.schema.user.grant('guest', 'read,write,execute', 'universe')
> ---
> -- error: User 'guest' already has read,write,execute access on universe 'nil'
> +- error: User 'guest' already has read,write,execute access on universe ''
> ...
> box.schema.user.grant('guest', 'read,write,execute', 'universe', '', { if_not_exists = true })
> ---
> @@ -703,7 +703,7 @@ box.schema.user.revoke('guest', 'usage,session', 'universe')
> ...
> box.schema.user.revoke('guest', 'read,write,execute', 'universe')
> ---
> -- error: User 'guest' does not have read,write,execute access on universe 'nil'
> +- error: User 'guest' does not have read,write,execute access on universe ''
> ...
> box.schema.user.revoke('guest', 'read,write,execute', 'universe', '', { if_exists = true })
> ---
> diff --git a/test/box/access_misc.result b/test/box/access_misc.result
> index 2d87fa2d5..3061f1181 100644
> --- a/test/box/access_misc.result
> +++ b/test/box/access_misc.result
> @@ -436,7 +436,7 @@ box.schema.user.revoke('testuser', 'usage,session', 'universe')
> ...
> box.schema.user.revoke('testuser', 'read, write, execute', 'universe')
> ---
> -- error: User 'testuser' does not have read, write, execute access on universe 'nil'
> +- error: User 'testuser' does not have read, write, execute access on universe ''
> ...
> box.schema.user.revoke('testuser', 'create', 'universe')
> ---
> @@ -797,10 +797,10 @@ box.space._space:select()
>         'type': 'string'}, {'name': 'auth', 'type': 'map'}]]
>   - [312, 1, '_priv', 'memtx', 0, {}, [{'name': 'grantor', 'type': 'unsigned'}, {
>         'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
> -      {'name': 'object_id', 'type': 'unsigned'}, {'name': 'privilege', 'type': 'unsigned'}]]
> +      {'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
>   - [313, 1, '_vpriv', 'sysview', 0, {}, [{'name': 'grantor', 'type': 'unsigned'},
>       {'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
> -      {'name': 'object_id', 'type': 'unsigned'}, {'name': 'privilege', 'type': 'unsigned'}]]
> +      {'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
>   - [320, 1, '_cluster', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'uuid',
>         'type': 'string'}]]
>   - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count',
> @@ -853,7 +853,7 @@ box.schema.user.grant('tester', 'read', 'universe')
> -- error: the privilege is not granted
> box.schema.user.revoke('tester', 'create', 'universe')
> ---
> -- error: User 'tester' does not have create access on universe 'nil'
> +- error: User 'tester' does not have create access on universe ''
> ...
> -- no error: if_exists clause
> box.schema.user.revoke('tester', 'create', 'universe', nil, { if_exists = true })
> diff --git a/test/box/alter.result b/test/box/alter.result
> index eb7014d8b..36bdb5fd3 100644
> --- a/test/box/alter.result
> +++ b/test/box/alter.result
> @@ -214,13 +214,13 @@ _index:select{}
>   - [305, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
>   - [305, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
>   - [312, 0, 'primary', 'tree', {'unique': true}, [[1, 'unsigned'], [2, 'string'],
> -      [3, 'unsigned']]]
> +      [3, 'scalar']]]
>   - [312, 1, 'owner', 'tree', {'unique': false}, [[0, 'unsigned']]]
> -  - [312, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'unsigned']]]
> +  - [312, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'scalar']]]
>   - [313, 0, 'primary', 'tree', {'unique': true}, [[1, 'unsigned'], [2, 'string'],
> -      [3, 'unsigned']]]
> +      [3, 'scalar']]]
>   - [313, 1, 'owner', 'tree', {'unique': false}, [[0, 'unsigned']]]
> -  - [313, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'unsigned']]]
> +  - [313, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'scalar']]]
>   - [320, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
>   - [320, 1, 'uuid', 'tree', {'unique': true}, [[1, 'string']]]
>   - [330, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
> diff --git a/test/xlog/upgrade.result b/test/xlog/upgrade.result
> index f02996bba..76467baf1 100644
> --- a/test/xlog/upgrade.result
> +++ b/test/xlog/upgrade.result
> @@ -36,7 +36,7 @@ box.space._schema:select()
> ---
> - - ['cluster', '<server_uuid>']
>   - ['max_id', 513]
> -  - ['version', 1, 10, 0]
> +  - ['version', 1, 10, 2]
> ...
> box.space._space:select()
> ---
> @@ -85,10 +85,10 @@ box.space._space:select()
>         'type': 'string'}, {'name': 'auth', 'type': 'map'}]]
>   - [312, 1, '_priv', 'memtx', 0, {}, [{'name': 'grantor', 'type': 'unsigned'}, {
>         'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
> -      {'name': 'object_id', 'type': 'unsigned'}, {'name': 'privilege', 'type': 'unsigned'}]]
> +      {'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
>   - [313, 1, '_vpriv', 'sysview', 0, {}, [{'name': 'grantor', 'type': 'unsigned'},
>       {'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
> -      {'name': 'object_id', 'type': 'unsigned'}, {'name': 'privilege', 'type': 'unsigned'}]]
> +      {'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
>   - [320, 1, '_cluster', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'uuid',
>         'type': 'string'}]]
>   - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count',
> @@ -134,13 +134,13 @@ box.space._index:select()
>   - [305, 1, 'owner', 'tree', {'unique': false}, [[1, 'unsigned']]]
>   - [305, 2, 'name', 'tree', {'unique': true}, [[2, 'string']]]
>   - [312, 0, 'primary', 'tree', {'unique': true}, [[1, 'unsigned'], [2, 'string'],
> -      [3, 'unsigned']]]
> +      [3, 'scalar']]]
>   - [312, 1, 'owner', 'tree', {'unique': false}, [[0, 'unsigned']]]
> -  - [312, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'unsigned']]]
> +  - [312, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'scalar']]]
>   - [313, 0, 'primary', 'tree', {'unique': true}, [[1, 'unsigned'], [2, 'string'],
> -      [3, 'unsigned']]]
> +      [3, 'scalar']]]
>   - [313, 1, 'owner', 'tree', {'unique': false}, [[0, 'unsigned']]]
> -  - [313, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'unsigned']]]
> +  - [313, 2, 'object', 'tree', {'unique': false}, [[2, 'string'], [3, 'scalar']]]
>   - [320, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
>   - [320, 1, 'uuid', 'tree', {'unique': true}, [[1, 'string']]]
>   - [330, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned']]]
> -- 
> 2.15.2 (Apple Git-101.1)
> 

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/4] Introduce separate entity object types for entity privileges.
  2018-08-22 13:39 ` [PATCH v2 1/4] Introduce separate entity object types for entity privileges Serge Petrenko
  2018-08-22 15:42   ` Serge Petrenko
@ 2018-08-22 16:22   ` Vladimir Davydov
  1 sibling, 0 replies; 13+ messages in thread
From: Vladimir Davydov @ 2018-08-22 16:22 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: kostja, tarantool-patches

Pushed to 1.10

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 2/4] Add entities user, role to access control.
  2018-08-22 13:39 ` [PATCH v2 2/4] Add entities user, role to access control Serge Petrenko
@ 2018-08-22 16:36   ` Vladimir Davydov
  0 siblings, 0 replies; 13+ messages in thread
From: Vladimir Davydov @ 2018-08-22 16:36 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: kostja, tarantool-patches

Pushed to 1.10

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 3/4] Add single object privilege checks to access_check_ddl.
  2018-08-22 13:39 ` [PATCH v2 3/4] Add single object privilege checks to access_check_ddl Serge Petrenko
@ 2018-08-22 16:47   ` Vladimir Davydov
  2018-08-23  7:51     ` Serge Petrenko
  2018-08-23  8:57   ` Vladimir Davydov
  1 sibling, 1 reply; 13+ messages in thread
From: Vladimir Davydov @ 2018-08-22 16:47 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: kostja, tarantool-patches

On Wed, Aug 22, 2018 at 04:39:04PM +0300, Serge Petrenko wrote:
> @@ -1862,3 +1877,173 @@ box.session.su('admin')
>  box.schema.user.drop('tester')
>  ---
>  ...
> +--
> +-- test case for 3530: do not ignore single object privileges
> +--
> +box.schema.user.create("test")
> +---
> +...
> +_ = box.schema.space.create("space1")
> +---
> +...
> +box.schema.user.grant("test", "read", "space", "space1")
> +---
> +...
> +box.schema.user.grant("test", "write", "space", "_index")
> +---
> +...
> +box.session.su("test")
> +---
> +...
> +box.space.space1:create_index("pk")
> +---
> +- error: Create access to space 'space1' is denied for user 'test'
> +...
> +box.session.su("admin")
> +---
> +...
> +box.space.space1.index[0] == nil
> +---
> +- true
> +...
> +-- fixme: cannot grant create on a single space
> +-- this is because when checking for create
> +-- access_check_ddl ignores space privileges,

Please don't use code function names in tests - if they change (and they
can), it'll be difficult to understand what this test is about.

> +-- assuming that there is no space yet.

I thought you fixed that in v2 by dropping PRIV_C check from
access_check_ddl, no?

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 4/4] Add a privilege upgrade script and update tests.
  2018-08-22 13:39 ` [PATCH v2 4/4] Add a privilege upgrade script and update tests Serge Petrenko
@ 2018-08-22 16:48   ` Vladimir Davydov
  2018-08-23  7:54     ` Serge Petrenko
  0 siblings, 1 reply; 13+ messages in thread
From: Vladimir Davydov @ 2018-08-22 16:48 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: kostja, tarantool-patches

You haven't addressed my comments to v1:

https://www.freelists.org/post/tarantool-patches/PATCH-44-Add-a-privilege-upgrade-script-and-update-tests,2

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 3/4] Add single object privilege checks to access_check_ddl.
  2018-08-22 16:47   ` Vladimir Davydov
@ 2018-08-23  7:51     ` Serge Petrenko
  0 siblings, 0 replies; 13+ messages in thread
From: Serge Petrenko @ 2018-08-23  7:51 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: kostja, tarantool-patches



> 22 авг. 2018 г., в 19:47, Vladimir Davydov <vdavydov.dev@gmail.com> написал(а):
> 
>> 
>> +---
>> +- true
>> +...
>> +-- fixme: cannot grant create on a single space
>> +-- this is because when checking for create
>> +-- access_check_ddl ignores space privileges,
> 
> Please don't use code function names in tests - if they change (and they
> can), it'll be difficult to understand what this test is about.

Sorry, fixed.

> 
>> +-- assuming that there is no space yet.
> 
> I thought you fixed that in v2 by dropping PRIV_C check from
> access_check_ddl, no?

Yes. This was fixed in v2, sorry. Changed the test accordingly.

I also rebased the patch on top of 1.10 and pushed it on a separate branch, since previous 2 patches
are already pushed to 1.10 and we decided not to push the fourth patch yet.
The branch is https://github.com/tarantool/tarantool/tree/sergepetrenko/gh-3530-object-access-checks

Here’s everything that's changed:

diff --git a/test/box/access.result b/test/box/access.result
index 4f0607471..933564a2b 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -1906,11 +1906,7 @@ box.space.space1.index[0] == nil
 ---
 - true
 ...
--- fixme: cannot grant create on a single space
--- this is because when checking for create
--- access_check_ddl ignores space privileges,
--- assuming that there is no space yet.
-box.schema.user.grant("test", "create", "space")
+box.schema.user.grant("test", "create", "space", "space1")
 ---
 ...
 box.session.su("test")
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index d3e2aab98..b252b4bd9 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -740,11 +740,7 @@ box.session.su("test")
 box.space.space1:create_index("pk")
 box.session.su("admin")
 box.space.space1.index[0] == nil
--- fixme: cannot grant create on a single space
--- this is because when checking for create
--- access_check_ddl ignores space privileges,
--- assuming that there is no space yet.
-box.schema.user.grant("test", "create", "space")
+box.schema.user.grant("test", "create", "space", "space1")
 box.session.su("test")
 _ = box.space.space1:create_index("pk")
 box.space.space1:insert{5}

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 4/4] Add a privilege upgrade script and update tests.
  2018-08-22 16:48   ` Vladimir Davydov
@ 2018-08-23  7:54     ` Serge Petrenko
  0 siblings, 0 replies; 13+ messages in thread
From: Serge Petrenko @ 2018-08-23  7:54 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: kostja, tarantool-patches



> 22 авг. 2018 г., в 19:48, Vladimir Davydov <vdavydov.dev@gmail.com> написал(а):
> 
> You haven't addressed my comments to v1:
> 
> https://www.freelists.org/post/tarantool-patches/PATCH-44-Add-a-privilege-upgrade-script-and-update-tests,2

Yes, I’ll fix it when we decide whether to push it to 1.10 or 2.0

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 3/4] Add single object privilege checks to access_check_ddl.
  2018-08-22 13:39 ` [PATCH v2 3/4] Add single object privilege checks to access_check_ddl Serge Petrenko
  2018-08-22 16:47   ` Vladimir Davydov
@ 2018-08-23  8:57   ` Vladimir Davydov
  1 sibling, 0 replies; 13+ messages in thread
From: Vladimir Davydov @ 2018-08-23  8:57 UTC (permalink / raw)
  To: Serge Petrenko; +Cc: kostja, tarantool-patches

Pushed to 1.10

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2018-08-23  8:57 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-08-22 13:39 [PATCH v2 0/4] Finish implementation of privileges Serge Petrenko
2018-08-22 13:39 ` [PATCH v2 1/4] Introduce separate entity object types for entity privileges Serge Petrenko
2018-08-22 15:42   ` Serge Petrenko
2018-08-22 16:22   ` Vladimir Davydov
2018-08-22 13:39 ` [PATCH v2 2/4] Add entities user, role to access control Serge Petrenko
2018-08-22 16:36   ` Vladimir Davydov
2018-08-22 13:39 ` [PATCH v2 3/4] Add single object privilege checks to access_check_ddl Serge Petrenko
2018-08-22 16:47   ` Vladimir Davydov
2018-08-23  7:51     ` Serge Petrenko
2018-08-23  8:57   ` Vladimir Davydov
2018-08-22 13:39 ` [PATCH v2 4/4] Add a privilege upgrade script and update tests Serge Petrenko
2018-08-22 16:48   ` Vladimir Davydov
2018-08-23  7:54     ` Serge Petrenko

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