Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

- Ability to grant tubes to roles. All default tube drivers are supported (#249).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, add an example of grantee in the CHANGELOG.md.


### Changed

### Fixed
Expand Down
90 changes: 59 additions & 31 deletions queue/abstract.lua
Original file line number Diff line number Diff line change
Expand Up @@ -324,51 +324,79 @@ function tube.on_task_change(self, cb)
return old_cb
end

function tube.grant(self, user, args)
if not check_state("grant") then
return
local function tube_grant_space(grantor, grantee, name, tp)
grantor.grant(grantee, tp or 'read,write', 'space', name, {
if_not_exists = true,
})
end

local function tube_grant_func(grantor, grantee, name)
box.schema.func.create(name, { if_not_exists = true })
grantor.grant(grantee, 'execute', 'function', name, {
if_not_exists = true
})
end

local function grant_system_spaces(grantor, grantee)
tube_grant_space(grantor, grantee, '_queue', 'read')
tube_grant_space(grantor, grantee, '_queue_consumers')
tube_grant_space(grantor, grantee, '_queue_taken_2')
end

local function grant_args(grantor, grantee, args, name)
if args.call then
tube_grant_func(grantor, grantee, 'queue.identify')
tube_grant_func(grantor, grantee, 'queue.statistics')
local prefix = (args.prefix or 'queue.tube') .. ('.%s:'):format(name)
tube_grant_func(grantor, grantee, prefix .. 'put')
tube_grant_func(grantor, grantee, prefix .. 'take')
tube_grant_func(grantor, grantee, prefix .. 'touch')
tube_grant_func(grantor, grantee, prefix .. 'ack')
tube_grant_func(grantor, grantee, prefix .. 'release')
tube_grant_func(grantor, grantee, prefix .. 'peek')
tube_grant_func(grantor, grantee, prefix .. 'bury')
tube_grant_func(grantor, grantee, prefix .. 'kick')
tube_grant_func(grantor, grantee, prefix .. 'delete')
end
local function tube_grant_space(user, name, tp)
box.schema.user.grant(user, tp or 'read,write', 'space', name, {
if_not_exists = true,
})

if args.truncate then
local prefix = (args.prefix or 'queue.tube') .. ('.%s:'):format(name)
tube_grant_func(grantor, grantee, prefix .. 'truncate')
end
end

local function tube_grant_func(user, name)
box.schema.func.create(name, { if_not_exists = true })
box.schema.user.grant(user, 'execute', 'function', name, {
if_not_exists = true
})
function tube.grant(self, user, args)
if not check_state("grant") then
return
end

args = args or {}

tube_grant_space(user, '_queue', 'read')
tube_grant_space(user, '_queue_consumers')
tube_grant_space(user, '_queue_taken_2')
grant_system_spaces(box.schema.user, user)

self.raw:grant(user, {if_not_exists = true})
session.grant(user)

if args.call then
tube_grant_func(user, 'queue.identify')
tube_grant_func(user, 'queue.statistics')
local prefix = (args.prefix or 'queue.tube') .. ('.%s:'):format(self.name)
tube_grant_func(user, prefix .. 'put')
tube_grant_func(user, prefix .. 'take')
tube_grant_func(user, prefix .. 'touch')
tube_grant_func(user, prefix .. 'ack')
tube_grant_func(user, prefix .. 'release')
tube_grant_func(user, prefix .. 'peek')
tube_grant_func(user, prefix .. 'bury')
tube_grant_func(user, prefix .. 'kick')
tube_grant_func(user, prefix .. 'delete')
grant_args(box.schema.user, user, args, self.name)
end

function tube.grant_role(self, role, args)
if not check_state("grant") then
return
end

if args.truncate then
local prefix = (args.prefix or 'queue.tube') .. ('.%s:'):format(self.name)
tube_grant_func(user, prefix .. 'truncate')
args = args or {}

if self.raw.grant_role ~= nil then
self.raw:grant_role(role, { if_not_exists = true })
else
error(('Tube %s driver does not support grant_role()'):format(self.name))
end

grant_system_spaces(box.schema.role, role)
session.grant_role(role)

grant_args(box.schema.role, role, args, self.name)
end

-- methods
Expand Down
4 changes: 4 additions & 0 deletions queue/abstract/driver/fifo.lua
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ function method.grant(self, user, opts)
box.schema.user.grant(user, 'read,write', 'space', self.space.name, opts)
end

function method.grant_role(self, role, opts)
box.schema.role.grant(role, 'read,write', 'space', self.space.name, opts)
end

-- normalize task: cleanup all internal fields
function method.normalize_task(self, task)
return task
Expand Down
4 changes: 4 additions & 0 deletions queue/abstract/driver/fifottl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ function method.grant(self, user, opts)
box.schema.user.grant(user, 'read,write', 'space', self.space.name, opts)
end

function method.grant_role(self, role, opts)
box.schema.role.grant(role, 'read,write', 'space', self.space.name, opts)
end

-- cleanup internal fields in task
function method.normalize_task(self, task)
return task and task:transform(3, 5)
Expand Down
8 changes: 8 additions & 0 deletions queue/abstract/driver/utube.lua
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ function method.grant(self, user, opts)
end
end

function method.grant_role(self, role, opts)
box.schema.role.grant(role, 'read,write', 'space', self.space.name, opts)
if self.space_ready_buffer ~= nil then
box.schema.role.grant(role, 'read,write', 'space',
self.space_ready_buffer.name, opts)
end
end

-- normalize task: cleanup all internal fields
function method.normalize_task(self, task)
return task and task:transform(3, 1)
Expand Down
8 changes: 8 additions & 0 deletions queue/abstract/driver/utubettl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,14 @@ function method.grant(self, user, opts)
end
end

function method.grant_role(self, role, opts)
box.schema.role.grant(role, 'read,write', 'space', self.space.name, opts)
if self.space_ready_buffer ~= nil then
box.schema.role.grant(role, 'read,write', 'space',
self.space_ready_buffer.name, opts)
end
end

-- cleanup internal fields in task
function method.normalize_task(self, task)
return task and task:transform(i_next_event, i_data - i_next_event)
Expand Down
8 changes: 8 additions & 0 deletions queue/abstract/queue_session.lua
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,13 @@ local function grant(user)
{ if_not_exists = true })
end

local function grant_role(role)
box.schema.role.grant(role, 'read, write', 'space', '_queue_session_ids',
{ if_not_exists = true })
box.schema.role.grant(role, 'read, write', 'space', '_queue_shared_sessions',
{ if_not_exists = true })
end

local function start()
identification_init()
queue_session.sync_chan = fiber.channel()
Expand Down Expand Up @@ -349,6 +356,7 @@ local method = {
identify = identify,
disconnect = disconnect,
grant = grant,
grant_role = grant_role,
on_session_remove = on_session_remove,
start = start,
stop = stop,
Expand Down
139 changes: 137 additions & 2 deletions t/090-grant-check.t
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#!/usr/bin/env tarantool
local test = require('tap').test()
test:plan(9)
test:plan(17)

local test_user = 'test'
local test_role = 'test_role'
local test_pass = '1234'
local test_host = 'localhost'
local test_port = '3388'
Expand Down Expand Up @@ -53,7 +54,7 @@ local test_drivers_grant_cases = {
}

for _, tc in pairs(test_drivers_grant_cases) do
test:test('test dirvers grant ' .. tc.name, function(test)
test:test('test drivers grant ' .. tc.name, function(test)
local queue = require('queue')
box.schema.user.create(test_user, { password = test_pass })

Expand Down Expand Up @@ -81,6 +82,38 @@ for _, tc in pairs(test_drivers_grant_cases) do
box.schema.user.drop(test_user)
tube:drop()
end)

test:test('test drivers grant_role ' .. tc.name, function(test)
local queue = require('queue')
box.schema.user.create(test_user, { password = test_pass })
box.schema.role.create(test_role)
box.schema.user.grant(test_user, test_role, nil, nil, { if_not_exists = true })

test:plan(2)
local tube_opts = { engine = engine }
if tc.storage_mode ~= nil and tc.storage_mode ~= 'default' then
tube_opts.storage_mode = tc.storage_mode
tube_opts.engine = 'memtx'
end

local tube_name = 'test_' .. tc.name .. '_role'
local tube = queue.create_tube(tube_name, tc.queue_type, tube_opts)
tube:put('help')

tube:grant_role(test_role)

box.session.su(test_user, function()
local a = tube:take()
test:is(a[1], 0, 'take works via role grants')

local c = tube:ack(a[1])
test:is(c[1], 0, 'ack works via role grants')
end)

tube:drop()
box.schema.user.drop(test_user)
box.schema.role.drop(test_role)
end)
end

test:test('check for space grants', function(test)
Expand Down Expand Up @@ -120,6 +153,43 @@ test:test('check for space grants', function(test)
tube:drop()
end)

test:test('check for space grants via role', function(test)
local queue = require('queue')
box.schema.user.create(test_user, { password = test_pass })
box.schema.role.create(test_role)
box.schema.user.grant(test_user, test_role, nil, nil, { if_not_exists = true })

test:plan(5)

local tube = queue.create_tube('test', 'fifo', { engine = engine })
tube:put('help')
local task = tube:take()
test:is(task[1], 0, 'admin can take record')
tube:release(task[1])

-- Without grants.
box.session.su(test_user)
local stat = pcall(tube.take, tube)
test:is(stat, false, 'no access without grants')
box.session.su('admin')

-- With role grants.
tube:grant_role(test_role)

box.session.su(test_user)
local a = tube:take()
test:is(a[1], 0, 'take works with role grants')
local b = tube:take(0.1)
test:isnil(b, 'take timeout works with role grants')
local c = tube:ack(a[1])
test:is(c[1], 0, 'ack works with role grants')
box.session.su('admin')

tube:drop()
box.schema.user.drop(test_user)
box.schema.role.drop(test_role)
end)

test:test('check for call grants', function(test)
-- prepare for tests
rawset(_G, 'queue', require('queue'))
Expand Down Expand Up @@ -203,6 +273,71 @@ test:test('check for call grants', function(test)

rawset(_G, 'queue', nil)
tube:drop()
box.schema.user.drop(test_user)
end)

test:test('check for call grants via role', function(test)
rawset(_G, 'queue', require('queue'))
box.schema.user.create(test_user, { password = test_pass })
box.schema.role.create(test_role)
box.schema.user.grant(test_user, test_role, nil, nil, { if_not_exists = true })

test:plan(9)

local tube = queue.create_tube('test', 'fifo', { engine = engine })
tube:put('help')
local task = tube:take()
test:is(task[1], 0, 'admin can take record')
tube:release(task[1])

-- Without call grants.
local nb_connect = netbox.connect or netbox.new
local con = nb_connect(('%s:%s@%s:%s'):format(test_user, test_pass, test_host, test_port))

local stat = pcall(con.call, con, 'queue.tube.test:take')
test:is(stat, false, 'no execute on tube function without call grants')

-- Grant call via role.
tube:grant_role(test_role, { call = true })

local id = con:call('queue.identify')
test:ok(id ~= nil, 'queue.identify via net.box works with role call grants')

local qc_arg_unpack = function(arg)
if qc.check_version({1, 7}) then
return arg
end
return arg and arg[1]
end

local a = con:call('queue.tube.test:take')
test:is(qc_arg_unpack(a[1]), 0, 'take via net.box works with role call grants')

local b = con:call('queue.tube.test:take', qc.pack_args(0.1))
test:isnil(qc_arg_unpack(qc_arg_unpack(b)), 'take timeout via net.box works')

local c = con:call('queue.tube.test:ack', qc.pack_args(qc_arg_unpack(a[1])))
test:is(qc_arg_unpack(c[1]), 0, 'ack via net.box works')

local d = con:call('queue.tube.test:put', {'help'})
test:is(qc_arg_unpack(d[1]) >= 0, true, 'put via net.box works')

local e = con:call('queue.statistics')
test:is(type(qc_arg_unpack(e)), 'table', 'queue.statistics via net.box works')

tube:grant_role(test_role, { truncate = true })
local f = con:call('queue.tube.test:truncate')
test:isnil(qc_arg_unpack(f), 'truncate via net.box works')

-- Check double grants.
tube:grant_role(test_role, { call = true })

con:close()
rawset(_G, 'queue', nil)

tube:drop()
box.schema.user.drop(test_user)
box.schema.role.drop(test_role)
end)

test:test('check tube existence', function(test)
Expand Down
Loading
Loading