diff --git a/extra/schema_fill.lua b/extra/schema_fill.lua index 493d5a68af7ffa67cfb45744cc778305996c5e6d..a18adb532a2e1c1a7335b95a2c2ed60c986244df 100644 --- a/extra/schema_fill.lua +++ b/extra/schema_fill.lua @@ -48,8 +48,8 @@ _index:insert{_func.id, 2, 'name', 'tree', 1, 1, 2, 'str'} -- space schema is: grantor id, user id, object_type, object_id, privilege -- primary key: user id, object type, object id _index:insert{_priv.id, 0, 'primary', 'tree', 1, 3, 1, 'num', 2, 'str', 3, 'num'} --- owner index - to quickly find all privileges granted to a user -_index:insert{_priv.id, 1, 'owner', 'tree', 0, 1, 1, 'num'} +-- owner index - to quickly find all privileges granted by a user +_index:insert{_priv.id, 1, 'owner', 'tree', 0, 1, 0, 'num'} -- object index - to quickly find all grants on a given object _index:insert{_priv.id, 2, 'object', 'tree', 0, 2, 2, 'str', 3, 'num'} -- primary key: node id diff --git a/src/box/access.h b/src/box/access.h index 6fb011de4edac1cd5caa7ed4f99ac001033cdce5..94c5b1d4851f4bd1717f50c5439e64d62261892e 100644 --- a/src/box/access.h +++ b/src/box/access.h @@ -66,7 +66,7 @@ struct user { /** User name - for error messages and debugging */ char name[BOX_NAME_MAX + 1]; /** Global privileges this user has on the universe. */ - uint8_t universal_access; + struct access universal_access; /** An id in users[] array to quickly find user */ uint8_t auth_token; }; diff --git a/src/box/alter.cc b/src/box/alter.cc index cbc61d4ec90b4d2adf2ce5e5113e403dd6207a5b..e638aa250d44cbd25038f179f3e42bd764b98c05 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -1467,27 +1467,30 @@ grant_or_revoke(struct priv_def *priv) struct user *grantee = user_cache_find(priv->grantee_id); if (grantee == NULL) return; + struct access *access; switch (priv->object_type) { case SC_UNIVERSE: - grantee->universal_access = priv->access; + access = &grantee->universal_access; break; case SC_SPACE: { struct space *space = space_by_id(priv->object_id); if (space) - space->access[grantee->auth_token] = priv->access; + access = &space->access[grantee->auth_token]; break; } case SC_FUNCTION: { struct func_def *func = func_by_id(priv->object_id); if (func) - func->access[grantee->auth_token] = priv->access; + access = &func->access[grantee->auth_token]; break; } default: break; } + if (access) + access->granted = access->effective = priv->access; } /** A trigger called on rollback of grant, or on commit of revoke. */ diff --git a/src/box/key_def.h b/src/box/key_def.h index 6ead7f7f8d038e9b3bf4b3400f0e470b1f2b023c..152e4a08b079cd4643b58c9ae83f175e837cbe21 100644 --- a/src/box/key_def.h +++ b/src/box/key_def.h @@ -271,6 +271,26 @@ key_mp_type_validate(enum field_type key_type, enum mp_type mp_type, field_type_strs[key_type]); } +/** + * Encapsulates privileges of a user on an object. + * I.e. "space" object has an instance of this + * structure for each user. + */ +struct access { + /** + * Granted access has been given to a user explicitly + * via some form of a grant. + */ + uint8_t granted; + /** + * Effective access is a sum of granted access and + * all privileges inherited by a user on this object + * via some role. Since roles may be granted to other + * roles, this may include indirect grants. + */ + uint8_t effective; +}; + /** * Definition of a function. Function body is not stored * or replicated (yet). @@ -288,7 +308,7 @@ struct func_def { * to func def but belongs to func cache entry. * Kept here for simplicity. */ - uint8_t access[BOX_USER_MAX]; + struct access access[BOX_USER_MAX]; }; /** @@ -303,7 +323,10 @@ struct priv_def { uint32_t object_id; /* Object type - function, space, universe */ enum schema_object_type object_type; - /** What is being or has been granted. */ + /** + * What is being granted, has been granted, or is being + * revoked. + */ uint8_t access; }; diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc index 9c3ab1c83086da95f8ced73b3e1a9af4df543189..69c26af4d32ce70e4c88f1527d775f88a11ae250 100644 --- a/src/box/lua/call.cc +++ b/src/box/lua/call.cc @@ -463,16 +463,16 @@ static inline void access_check_func(const char *name, uint32_t name_len, struct user *user, uint8_t access) { + access &= ~user->universal_access.effective; /* * No special check for ADMIN user is necessary * since ADMIN has universal access. */ if (access == 0) return; - struct func_def *func = func_by_name(name, name_len); if (func == NULL || (func->uid != user->uid && - access & ~func->access[user->auth_token])) { + access & ~func->access[user->auth_token].effective)) { char name_buf[BOX_NAME_MAX + 1]; snprintf(name_buf, sizeof(name_buf), "%.*s", name_len, name); @@ -494,8 +494,6 @@ box_lua_call(struct request *request, struct port *port) const char *name = request->key; uint32_t name_len = mp_decode_strl(&name); - uint8_t access = PRIV_X & ~user->universal_access; - /* Try to find a function by name */ int oc = box_lua_find(L, name, name + name_len); /** @@ -504,7 +502,7 @@ box_lua_call(struct request *request, struct port *port) * https://github.com/tarantool/tarantool/issues/300 * - if a function does not exist, say it first. */ - access_check_func(name, name_len, user, access); + access_check_func(name, name_len, user, PRIV_X); /* Push the rest of args (a tuple). */ const char *args = request->tuple; uint32_t arg_count = mp_decode_array(&args); diff --git a/src/box/lua/index.cc b/src/box/lua/index.cc index 543647fadf87779b634a498d114d7ae6ecbdcfd7..813a83ead102161c201dd8671809110560f04365 100644 --- a/src/box/lua/index.cc +++ b/src/box/lua/index.cc @@ -43,7 +43,7 @@ static inline Index * check_index(uint32_t space_id, uint32_t index_id) { struct space *space = space_cache_find(space_id); - space_check_access(space, PRIV_R); + access_check_space(space, PRIV_R); return index_find(space, index_id); } diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index f594eaf0e8f6a3dbc2d71760c970ee76eaf45f49..c163e470ad0101e6c8d35411c4d3e0d7dc5991ed 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -64,9 +64,9 @@ local function user_resolve(user) local _user = box.space[box.schema.USER_ID] local tuple if type(user) == 'string' then - tuple = _user.index['name']:get{user} + tuple = _user.index.name:get{user} else - tuple = _user.index['primary']:get{user} + tuple = _user:get{user} end if tuple == nil then return nil @@ -167,8 +167,16 @@ local function update_param_table(table, defaults) return table end -box.begin = function() if ffi.C.boxffi_txn_begin() == -1 then box.error() end end -box.commit = function() if ffi.C.boxffi_txn_commit() == -1 then box.error() end end +box.begin = function() + if ffi.C.boxffi_txn_begin() == -1 then + box.error() + end +end +box.commit = function() + if ffi.C.boxffi_txn_commit() == -1 then + box.error() + end +end box.rollback = ffi.C.boxffi_txn_rollback; box.schema.space = {} @@ -828,6 +836,20 @@ local function privilege_resolve(privilege) return numeric end +local function privilege_name(privilege) + local names = {} + if bit.band(privilege, 1) ~= 0 then + table.insert(names, "read") + end + if bit.band(privilege, 2) ~= 0 then + table.insert(names, "write") + end + if bit.band(privilege, 4) ~= 0 then + table.insert(names, "execute") + end + return table.concat(names, ",") +end + local function object_resolve(object_type, object_name) if object_type == 'universe' then return 0 @@ -843,9 +865,9 @@ local function object_resolve(object_type, object_name) local _func = box.space[box.schema.FUNC_ID] local func if type(object_name) == 'string' then - func = _func.index['name']:get{object_name} + func = _func.index.name:get{object_name} else - func = _func.index['primary']:get{object_name} + func = _func:get{object_name} end if func then return func[1] @@ -857,24 +879,41 @@ local function object_resolve(object_type, object_name) local _user = box.space[box.schema.USER_ID] local role if type(object_name) == 'string' then - role = _user.index['name']:get{object_name} + role = _user.index.name:get{object_name} else - role = _user.index['primary']:get{object_name} + role = _user:get{object_name} end - if role then + if role and role[4] == 'role' then return role[1] else - box.error(box.error.NO_SUCH_USER, object_name) + box.error(box.error.NO_SUCH_ROLE, object_name) end end box.error(box.error.UNKNOWN_SCHEMA_OBJECT, object_type) end +local function object_name(object_type, object_id) + if object_type == 'universe' then + return "" + end + local space + if object_type == 'space' then + space = box.space._space + elseif object_type == 'function' then + space = box.space._func + elseif object_type == 'role' or object_type == 'user' then + space = box.space._user + else + box.error(box.error.UNKNOWN_SCHEMA_OBJECT, object_type) + end + return space:get{object_id}[3] +end + box.schema.func = {} box.schema.func.create = function(name) local _func = box.space[box.schema.FUNC_ID] - local func = _func.index['name']:get{name} + local func = _func.index.name:get{name} if func then box.error(box.error.FUNCTION_EXISTS, name) end @@ -936,15 +975,15 @@ box.schema.user.drop = function(name) end -- recursive delete of user data local _priv = box.space[box.schema.PRIV_ID] - local privs = _priv.index['owner']:select{uid} + local privs = _priv.index.primary:select{uid} for k, tuple in pairs(privs) do box.schema.user.revoke(uid, tuple[5], tuple[3], tuple[4]) end - local spaces = box.space[box.schema.SPACE_ID].index['owner']:select{uid} + local spaces = box.space[box.schema.SPACE_ID].index.owner:select{uid} for k, tuple in pairs(spaces) do box.space[tuple[1]]:drop() end - local funcs = box.space[box.schema.FUNC_ID].index['owner']:select{uid} + local funcs = box.space[box.schema.FUNC_ID].index.owner:select{uid} for k, tuple in pairs(funcs) do box.schema.func.drop(tuple[1]) end @@ -995,7 +1034,7 @@ box.schema.user.revoke = function(user_name, privilege, object_type, object_name end local old_privilege = tuple[5] local grantor = tuple[1] - -- XXX bug: the privilege may be removed by someone who did + -- XXX gh-449: the privilege may be removed by someone who did -- not grant it if privilege ~= old_privilege then privilege = bit.band(old_privilege, bit.bnot(privilege)) @@ -1005,18 +1044,44 @@ box.schema.user.revoke = function(user_name, privilege, object_type, object_name end end +box.schema.user.info = function(user_name) + local uid + if user_name == nil then + uid = box.session.uid() + else + uid = user_resolve(user_name) + if uid == nil then + box.error(box.error.NO_SUCH_USER, user_name) + end + end + local _priv = box.space._priv + local _user = box.space._priv + local privs = {} + for _, v in pairs(_priv:select{uid}) do + table.insert(privs, + {privilege_name(v[5]), v[3], object_name(v[3], v[4])}) + end + return privs +end + box.schema.role = {} box.schema.role.create = function(name) local uid = user_resolve(name) if uid then - box.error(box.error.USER_EXISTS, name) + box.error(box.error.ROLE_EXISTS, name) end local _user = box.space[box.schema.USER_ID] _user:auto_increment{session.uid(), name, 'role'} end box.schema.role.drop = function(name) + local uid = user_resolve(name) + if uid == nil then + box.error(box.error.NO_SUCH_ROLE, name) + end return box.schema.user.drop(name) end - +box.schema.role.grant = box.schema.user.grant +box.schema.role.revoke = box.schema.user.revoke +box.schema.role .info = box.schema.user.info diff --git a/src/box/request.cc b/src/box/request.cc index 289a6ba3b4cb0c3b6d95e98af72d9f25ceb11ca1..200c6493963e161603dbeecc9566e56e822ed053 100644 --- a/src/box/request.cc +++ b/src/box/request.cc @@ -53,7 +53,7 @@ execute_replace(struct request *request, struct port *port) struct txn *txn = txn_begin_stmt(request); struct space *space = space_cache_find(request->space_id); - space_check_access(space, PRIV_W); + access_check_space(space, PRIV_W); struct tuple *new_tuple = tuple_new(space->format, request->tuple, request->tuple_end); TupleGuard guard(new_tuple); @@ -73,7 +73,7 @@ execute_update(struct request *request, struct port *port) /** Search key and key part count. */ struct space *space = space_cache_find(request->space_id); - space_check_access(space, PRIV_W); + access_check_space(space, PRIV_W); Index *pk = index_find(space, 0); /* Try to find the tuple by primary key. */ const char *key = request->key; @@ -105,7 +105,7 @@ execute_delete(struct request *request, struct port *port) struct txn *txn = txn_begin_stmt(request); (void) port; struct space *space = space_cache_find(request->space_id); - space_check_access(space, PRIV_W); + access_check_space(space, PRIV_W); /* Try to find tuple by primary key */ Index *pk = index_find(space, 0); @@ -124,7 +124,7 @@ static void execute_select(struct request *request, struct port *port) { struct space *space = space_cache_find(request->space_id); - space_check_access(space, PRIV_R); + access_check_space(space, PRIV_R); Index *index = index_find(space, request->index_id); ERROR_INJECT_EXCEPTION(ERRINJ_TESTING); diff --git a/src/box/space.cc b/src/box/space.cc index 5e722cd9941879e4f9f70713b66df2009e3c531d..7947cd3b0b5f5c9e2eac4ee7ab36e5ff4f5e152a 100644 --- a/src/box/space.cc +++ b/src/box/space.cc @@ -36,7 +36,7 @@ #include "access.h" void -space_check_access(struct space *space, uint8_t access) +access_check_space(struct space *space, uint8_t access) { struct user *user = user(); /* @@ -46,9 +46,9 @@ space_check_access(struct space *space, uint8_t access) * No special check for ADMIN user is necessary * since ADMIN has universal access. */ - access &= ~user->universal_access; + access &= ~user->universal_access.effective; if (access && space->def.uid != user->uid && - access & ~space->access[user->auth_token]) { + access & ~space->access[user->auth_token].effective) { tnt_raise(ClientError, ER_SPACE_ACCESS_DENIED, priv_name(access), user->name, space->def.name); } diff --git a/src/box/space.h b/src/box/space.h index 74aa1d5a45a41d6bde53b00a4c9df632e141df04..f171e68ca8d49f6920ebf41be45bac91036b0875 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -35,7 +35,7 @@ #include <exception.h> struct space { - uint8_t access[BOX_USER_MAX]; + struct access access[BOX_USER_MAX]; /** * Reflects the current space state and is also a vtab * with methods. Unlike a C++ vtab, changes during space @@ -85,7 +85,7 @@ struct space { * the requested access to the space. */ void -space_check_access(struct space *space, uint8_t access); +access_check_space(struct space *space, uint8_t access); /** Get space ordinal number. */ static inline uint32_t diff --git a/src/errcode.h b/src/errcode.h index d6754b9e197ec0997371b43d9d96ef6478b936ae..86c1def5698786175672078ad166eb50acc6f379 100644 --- a/src/errcode.h +++ b/src/errcode.h @@ -131,6 +131,8 @@ enum { TNT_ERRMSG_MAX = 512 }; /* 79 */_(ER_ACTIVE_TRANSACTION, 2, "Operation is not permitted when there is an active transaction ") \ /* 80 */_(ER_NO_ACTIVE_TRANSACTION, 2, "Operation is not permitted when there is no active transaction ") \ /* 81 */_(ER_CROSS_ENGINE_TRANSACTION, 2, "A multi-statement transaction can not use multiple storage engines") \ + /* 82 */_(ER_NO_SUCH_ROLE, 2, "Role '%s' is not found") \ + /* 46 */_(ER_ROLE_EXISTS, 2, "Role '%s' already exists") \ /* * !IMPORTANT! Please follow instructions at start of the file diff --git a/test/box/access.result b/test/box/access.result index 2976824d007c22e7acf1415344908202da16bc3d..1fa7aacaf58f598682e3784b7332d1b0198cadad 100644 --- a/test/box/access.result +++ b/test/box/access.result @@ -343,49 +343,49 @@ id = box.space._user.index.name:get{'user'}[1] box.schema.user.grant('user', 'read,write', 'universe') --- ... -box.space._priv.index.owner:select{id} +box.space._priv:select{id} --- - - [1, 3, 'universe', 0, 3] ... box.schema.user.grant('user', 'read', 'universe') --- ... -box.space._priv.index.owner:select{id} +box.space._priv:select{id} --- - - [1, 3, 'universe', 0, 3] ... box.schema.user.revoke('user', 'write', 'universe') --- ... -box.space._priv.index.owner:select{id} +box.space._priv:select{id} --- - - [1, 3, 'universe', 0, 1] ... box.schema.user.revoke('user', 'read', 'universe') --- ... -box.space._priv.index.owner:select{id} +box.space._priv:select{id} --- - [] ... box.schema.user.grant('user', 'write', 'universe') --- ... -box.space._priv.index.owner:select{id} +box.space._priv:select{id} --- - - [1, 3, 'universe', 0, 2] ... box.schema.user.grant('user', 'read', 'universe') --- ... -box.space._priv.index.owner:select{id} +box.space._priv:select{id} --- - - [1, 3, 'universe', 0, 3] ... box.schema.user.drop('user') --- ... -box.space._priv.index.owner:select{id} +box.space._priv:select{id} --- - [] ... diff --git a/test/box/access.test.lua b/test/box/access.test.lua index c8319f1a3f8acdfdfad9e3674cdd7617c846eed5..e7c1e7b14ede33f13784c621a59cc95b22c4ad92 100644 --- a/test/box/access.test.lua +++ b/test/box/access.test.lua @@ -154,17 +154,17 @@ box.space._user.index.name:select{'user1'} box.schema.user.create('user') id = box.space._user.index.name:get{'user'}[1] box.schema.user.grant('user', 'read,write', 'universe') -box.space._priv.index.owner:select{id} +box.space._priv:select{id} box.schema.user.grant('user', 'read', 'universe') -box.space._priv.index.owner:select{id} +box.space._priv:select{id} box.schema.user.revoke('user', 'write', 'universe') -box.space._priv.index.owner:select{id} +box.space._priv:select{id} box.schema.user.revoke('user', 'read', 'universe') -box.space._priv.index.owner:select{id} +box.space._priv:select{id} box.schema.user.grant('user', 'write', 'universe') -box.space._priv.index.owner:select{id} +box.space._priv:select{id} box.schema.user.grant('user', 'read', 'universe') -box.space._priv.index.owner:select{id} +box.space._priv:select{id} box.schema.user.drop('user') -box.space._priv.index.owner:select{id} +box.space._priv:select{id} session = nil diff --git a/test/box/misc.result b/test/box/misc.result index bea2b767b878b0df1cc20b092ebffa6235ae7ec6..833adfe903a07a074d19638250160c07ba757ee4 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -193,6 +193,8 @@ t; - 'box.error.CREATE_USER : 43' - 'box.error.CREATE_SPACE : 9' - 'box.error.UNKNOWN_SCHEMA_OBJECT : 49' + - 'box.error.ROLE_EXISTS : 83' + - 'box.error.NO_SUCH_ROLE : 82' - 'box.error.NO_ACTIVE_TRANSACTION : 80' - 'box.error.SPLICE : 25' - 'box.error.FIELD_TYPE_MISMATCH : 24' diff --git a/test/box/role.result b/test/box/role.result new file mode 100644 index 0000000000000000000000000000000000000000..4ca69d3f8b6ecbebcdf2e4587eaa262c4aaef612 --- /dev/null +++ b/test/box/role.result @@ -0,0 +1,56 @@ +box.schema.role.create('iddqd') +--- +... +box.schema.role.create('iddqd') +--- +- error: Role 'iddqd' already exists +... +box.schema.role.drop('iddqd') +--- +... +box.schema.role.drop('iddqd') +--- +- error: Role 'iddqd' is not found +... +box.schema.role.create('iddqd') +--- +... +-- impossible to su to a role +box.session.su('iddqd') +--- +- error: User 'iddqd' is not found +... +-- test granting privilege to a role +box.schema.role.grant('iddqd', 'execute', 'universe') +--- +... +box.schema.role.info('iddqd') +--- +- - - execute + - universe + - +... +box.schema.role.revoke('iddqd', 'execute', 'universe') +--- +... +box.schema.role.info('iddqd') +--- +- [] +... +-- test granting a role to a user +box.schema.user.create('tester') +--- +... +box.schema.user.info('tester') +--- +- [] +... +box.schema.user.grant('tester', 'execute', 'role', 'iddqd') +--- +... +box.schema.user.info('tester') +--- +- - - execute + - role + - iddqd +... diff --git a/test/box/role.test.lua b/test/box/role.test.lua new file mode 100644 index 0000000000000000000000000000000000000000..d60b7a6e9a85b2fde4e19737e371e179e0a19f58 --- /dev/null +++ b/test/box/role.test.lua @@ -0,0 +1,17 @@ +box.schema.role.create('iddqd') +box.schema.role.create('iddqd') +box.schema.role.drop('iddqd') +box.schema.role.drop('iddqd') +box.schema.role.create('iddqd') +-- impossible to su to a role +box.session.su('iddqd') +-- test granting privilege to a role +box.schema.role.grant('iddqd', 'execute', 'universe') +box.schema.role.info('iddqd') +box.schema.role.revoke('iddqd', 'execute', 'universe') +box.schema.role.info('iddqd') +-- test granting a role to a user +box.schema.user.create('tester') +box.schema.user.info('tester') +box.schema.user.grant('tester', 'execute', 'role', 'iddqd') +box.schema.user.info('tester')