Skip to content

Rework the Access control topic #4024

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Feb 12, 2024
Merged
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
1,293 changes: 994 additions & 299 deletions doc/book/admin/access_control.rst

Large diffs are not rendered by default.

104 changes: 104 additions & 0 deletions doc/code_snippets/test/access_control/grant_roles_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
local fio = require('fio')
local server = require('luatest.server')
local t = require('luatest')
local g = t.group()
g.before_all(function(cg)
cg.server = server:new {
box_cfg = {},
workdir = fio.cwd() .. '/tmp'
}
cg.server:start()
cg.server:exec(function()
box.schema.space.create('writers')
box.space.writers:format({
{ name = 'id', type = 'unsigned' },
{ name = 'name', type = 'string' }
})
box.space.writers:create_index('primary', { parts = { 'id' } })

box.schema.space.create('books')
box.space.books:format({
{ name = 'id', type = 'unsigned' },
{ name = 'title', type = 'string' },
{ name = 'author_id', foreign_key = { space = 'writers', field = 'id' } },
})
box.space.books:create_index('primary', { parts = { 'id' } })

box.space.writers:insert { 1, 'Leo Tolstoy' }
box.space.writers:insert { 2, 'Fyodor Dostoevsky' }
box.space.writers:insert { 3, 'Alexander Pushkin' }

box.space.books:insert { 1, 'War and Peace', 1 }
box.space.books:insert { 2, 'Anna Karenina', 1 }
box.space.books:insert { 3, 'Resurrection', 1 }
box.space.books:insert { 4, 'Crime and Punishment', 2 }
box.space.books:insert { 5, 'The Idiot', 2 }
box.space.books:insert { 6, 'The Brothers Karamazov', 2 }
box.space.books:insert { 7, 'Eugene Onegin', 3 }
box.space.books:insert { 8, 'The Captain\'s Daughter', 3 }
box.space.books:insert { 9, 'Boris Godunov', 3 }
box.space.books:insert { 10, 'Ruslan and Ludmila', 3 }
end)
end)

g.after_all(function(cg)
cg.server:drop()
fio.rmtree(cg.server.workdir)
end)

g.test_role_granted_revoked = function(cg)
cg.server:exec(function()
box.schema.user.create('testuser', { password = 'foobar' })

-- Create roles --
box.schema.role.create('books_space_manager')
box.schema.role.create('writers_space_reader')
-- End: Create roles --

-- Grant read/write privileges to a role --
box.schema.role.grant('books_space_manager', 'read,write', 'space', 'books')
-- Grant write privileges to a role --
box.schema.role.grant('writers_space_reader', 'read', 'space', 'writers')
-- End: Grant privileges to roles --

-- Grant a role to a role --
box.schema.role.create('all_spaces_manager')
box.schema.role.grant('all_spaces_manager', 'books_space_manager')
box.schema.role.grant('all_spaces_manager', 'writers_space_reader')
-- End: Grant a role to a role --

-- Grant a role to a user --
box.schema.user.grant('testuser', 'books_space_manager')
box.schema.user.grant('testuser', 'writers_space_reader')
-- End: Grant a role to a user --

-- Test removing a tuple from 'writers' --
box.session.su('testuser')
local _, delete_writer_error = pcall(function()
box.space.writers:delete(3)
end)
t.assert_equals(delete_writer_error:unpack().message, "Write access to space 'writers' is denied for user 'testuser'")
box.session.su('admin')

-- Revoking a role from a user --
box.schema.user.revoke('testuser', 'execute', 'role', 'writers_space_reader')
-- End: Revoking a role from a user --

-- Test selecting data from 'writers' --
box.session.su('testuser')
local _, select_writer_error = pcall(function()
box.space.writers:select(3)
end)
t.assert_equals(select_writer_error:unpack().message, "Read access to space 'writers' is denied for user 'testuser'")
box.session.su('admin')

-- Dropping a role --
box.schema.role.drop('writers_space_reader')
-- End: Dropping a role --

-- Test roles exist --
t.assert_equals(box.schema.role.exists('books_space_manager'), true)
t.assert_equals(box.schema.role.exists('all_spaces_manager'), true)
t.assert_equals(box.schema.role.exists('writers_space_reader'), false)
end)
end
138 changes: 138 additions & 0 deletions doc/code_snippets/test/access_control/grant_user_privileges_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
local fio = require('fio')
local server = require('luatest.server')
local t = require('luatest')
local g = t.group()
g.before_all(function(cg)
cg.server = server:new {
box_cfg = {},
workdir = fio.cwd() .. '/tmp'
}
cg.server:start()
cg.server:exec(function()
box.schema.space.create('writers')
box.space.writers:format({
{ name = 'id', type = 'unsigned' },
{ name = 'name', type = 'string' }
})
box.space.writers:create_index('primary', { parts = { 'id' } })

box.schema.space.create('books')
box.space.books:format({
{ name = 'id', type = 'unsigned' },
{ name = 'title', type = 'string' },
{ name = 'author_id', foreign_key = { space = 'writers', field = 'id' } },
})
box.space.books:create_index('primary', { parts = { 'id' } })

box.space.writers:insert { 1, 'Leo Tolstoy' }
box.space.writers:insert { 2, 'Fyodor Dostoevsky' }
box.space.writers:insert { 3, 'Alexander Pushkin' }

box.space.books:insert { 1, 'War and Peace', 1 }
box.space.books:insert { 2, 'Anna Karenina', 1 }
box.space.books:insert { 3, 'Resurrection', 1 }
box.space.books:insert { 4, 'Crime and Punishment', 2 }
box.space.books:insert { 5, 'The Idiot', 2 }
box.space.books:insert { 6, 'The Brothers Karamazov', 2 }
box.space.books:insert { 7, 'Eugene Onegin', 3 }
box.space.books:insert { 8, 'The Captain\'s Daughter', 3 }
box.space.books:insert { 9, 'Boris Godunov', 3 }
box.space.books:insert { 10, 'Ruslan and Ludmila', 3 }
end)
end)

g.after_each(function(cg)
cg.server:exec(function()
if box.schema.user.exists('testuser') then
box.schema.user.drop('testuser')
end
end)
end)

g.after_all(function(cg)
cg.server:drop()
fio.rmtree(cg.server.workdir)
end)

g.test_user_without_password_created = function(cg)
cg.server:exec(function()
-- Create a user without a password --
box.schema.user.create('testuser')
-- End: Create a user without a password --
t.assert_equals(box.space._user.index.name:select { 'testuser' }[1][5]['chap-sha1'], nil)
end)
end

g.test_user_with_password_created = function(cg)
cg.server:exec(function()
-- Create a user with a password --
box.schema.user.create('testuser', { password = 'foobar' })
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: most style guides recommend avoiding foo/bar anywhere in examples.

Copy link

Choose a reason for hiding this comment

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

Usually password is "secret", or something similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Usually password is "secret", or something similar.

Thanks for your suggestion. Honestly, I try not to use meaningful words as password examples in docs because they make a reader think about the word meaning. IMO, passwords should look like some gibberish, well-known weak passwords (123456), or smth like P@$$word) to allow a reader quickly scan it und understand it is a password, not smth else. Not sure I'm right here :)

-- End: Create a user with a password --
t.assert_equals(box.space._user.index.name:select { 'testuser' }[1][5]['chap-sha1'], 'm1ADQ7xS4pERcutSrlz0hHYExuU=')
end)
end

g.test_current_user_password_set = function(cg)
cg.server:exec(function()
box.session.su('admin')
-- Set a password for the current user --
box.schema.user.passwd('foobar')
-- End: Set a password for the current user --
t.assert_equals(box.space._user.index.name:select { 'admin' }[1][5]['chap-sha1'], 'm1ADQ7xS4pERcutSrlz0hHYExuU=')
end)
end

g.test_specified_user_password_set = function(cg)
cg.server:exec(function()
box.schema.user.create('testuser')
-- Set a password for the specified user --
box.schema.user.passwd('testuser', 'foobar')
-- End: Set a password for the specified user --
t.assert_equals(box.space._user.index.name:select { 'testuser' }[1][5]['chap-sha1'], 'm1ADQ7xS4pERcutSrlz0hHYExuU=')
end)
end

g.test_grant_revoke_privileges_user = function(cg)
cg.server:exec(function()
box.schema.user.create('testuser', { password = 'foobar' })
box.schema.user.grant('testuser', 'execute', 'universe')
-- Grant privileges to the specified user --
box.schema.user.grant('testuser', 'read', 'space', 'writers')
box.schema.user.grant('testuser', 'read,write', 'space', 'books')
-- End: Grant privileges to the specified user --
box.session.su('testuser')
local _, delete_writer_error = pcall(function()
box.space.writers:delete(3)
end)
t.assert_equals(delete_writer_error:unpack().message, "Write access to space 'writers' is denied for user 'testuser'")

box.session.su('admin')
-- Revoke space reading --
box.schema.user.revoke('testuser', 'write', 'space', 'books')
-- End: Revoke space reading --
box.session.su('testuser')
local _, delete_book_error = pcall(function()
box.space.books:delete(10)
end)
t.assert_equals(delete_book_error:unpack().message, "Write access to space 'books' is denied for user 'testuser'")

box.session.su('admin')
-- Revoke session --
box.schema.user.revoke('testuser', 'session', 'universe')
-- End: Revoke session --
local _, change_user_error = pcall(function()
box.session.su('testuser')
end)
t.assert_equals(change_user_error:unpack().message, "Session access to universe '' is denied for user 'testuser'")
end)
end

g.test_user_dropped = function(cg)
cg.server:exec(function()
box.schema.user.create('testuser')
-- Drop a user --
box.schema.user.drop('testuser')
-- End: Drop a user --
t.assert_equals(box.schema.user.exists('testuser'), false)
end)
end
9 changes: 6 additions & 3 deletions doc/reference/reference_lua/box_schema/role_create.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ box.schema.role.create()

**Example:**

.. code-block:: lua
.. literalinclude:: /code_snippets/test/access_control/grant_roles_test.lua
:language: lua
:start-after: Create roles
:end-before: End: Create roles
:dedent:

box.schema.role.create('Accountant')
box.schema.role.create('Accountant', {if_not_exists = false})
See also: :ref:`access_control_roles`.
8 changes: 6 additions & 2 deletions doc/reference/reference_lua/box_schema/role_drop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ box.schema.role.drop()

**Example:**

.. code-block:: lua
.. literalinclude:: /code_snippets/test/access_control/grant_roles_test.lua
:language: lua
:start-after: Dropping a role
:end-before: End: Dropping a role
:dedent:

box.schema.role.drop('Accountant')
See also: :ref:`access_control_roles`.
6 changes: 1 addition & 5 deletions doc/reference/reference_lua/box_schema/role_exists.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,4 @@ box.schema.role.exists()
:param string role-name: the name of the role
:rtype: bool

**Example:**

.. code-block:: lua

box.schema.role.exists('Accountant')
See also: :ref:`access_control_roles_info`.
25 changes: 12 additions & 13 deletions doc/reference/reference_lua/box_schema/role_grant.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,28 @@ box.schema.role.grant()

Grant :ref:`privileges <authentication-owners_privileges>` to a role.

:param string role-name: the name of the role.
:param string privilege: 'read' or 'write' or 'execute' or 'create' or
'alter' or 'drop' or a combination.
:param string object-type: 'space' or 'function' or 'sequence' or 'role'.
:param string object-name: the name of a function or space or sequence or role.
:param string role-name: the name of the role
:param string privilege: one or more :ref:`privileges <access_control_list_privileges>` to grant to the role (for example, ``read`` or ``read,write``)
:param string object-type: a database :ref:`object type <access_control_list_objects>` to grant privileges to (for example, ``space``, ``role``, or ``function``)
:param string object-name: the name of a function or space or sequence or role
:param table option: ``if_not_exists`` = ``true|false`` (default = ``false``) - boolean;
``true`` means there should be no error if the role already
has the privilege.
has the privilege

The role must exist, and the object must exist.

**Variation:** instead of ``object-type, object-name`` say 'universe'
**Variation:** instead of ``object-type, object-name`` say ``universe``
which means 'all object-types and all objects'. In this case, object name is omitted.

**Variation:** instead of ``privilege, object-type, object-name`` say
``role-name`` -- to grant a role to a role.

**Example:**

.. code-block:: lua
.. literalinclude:: /code_snippets/test/access_control/grant_roles_test.lua
:language: lua
:start-after: Grant read/write privileges to a role
:end-before: Grant write privileges to a role
:dedent:

box.schema.role.grant('Accountant', 'read', 'space', 'tester')
box.schema.role.grant('Accountant', 'execute', 'function', 'f')
box.schema.role.grant('Accountant', 'read,write', 'universe')
box.schema.role.grant('public', 'Accountant')
box.schema.role.grant('role1', 'role2', nil, nil, {if_not_exists=false})
See also: :ref:`access_control_roles`.
6 changes: 1 addition & 5 deletions doc/reference/reference_lua/box_schema/role_info.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,4 @@ box.schema.role.info()

:param string role-name: the name of the role.

**Example:**

.. code-block:: lua

box.schema.role.info('Accountant')
See also: :ref:`access_control_roles_info`.
20 changes: 6 additions & 14 deletions doc/reference/reference_lua/box_schema/role_revoke.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,18 @@ box.schema.role.revoke()

Revoke :ref:`privileges <authentication-owners_privileges>` from a role.

:param string role-name: the name of the role.
:param string privilege: 'read' or 'write' or 'execute' or 'create' or
'alter' or 'drop' or a combination.
:param string object-type: 'space' or 'function' or 'sequence' or 'role'.
:param string object-name: the name of a function or space or sequence or role.
:param string role-name: the name of the role
:param string privilege: one or more :ref:`privileges <access_control_list_privileges>` to revoke from the role (for example, ``read`` or ``read,write``)
:param string object-type: a database :ref:`object type <access_control_list_objects>` to revoke privileges from (for example, ``space``, ``role``, or ``function``)
:param string object-name: the name of a database object to revoke privileges from

The role must exist, and the object must exist,
but it is not an error if the role does not have the privilege.

**Variation:** instead of ``object-type, object-name`` say 'universe'
**Variation:** instead of ``object-type, object-name`` say ``universe``
which means 'all object-types and all objects'.

**Variation:** instead of ``privilege, object-type, object-name`` say
``role-name``.

**Example:**

.. code-block:: lua

box.schema.role.revoke('Accountant', 'read', 'space', 'tester')
box.schema.role.revoke('Accountant', 'execute', 'function', 'f')
box.schema.role.revoke('Accountant', 'read,write', 'universe')
box.schema.role.revoke('public', 'Accountant')
See also: :ref:`access_control_roles`.
12 changes: 7 additions & 5 deletions doc/reference/reference_lua/box_schema/user_create.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ box.schema.user.create()
* ``password`` (default = '') - string; the ``password`` = *password*
specification is good because in a :ref:`URI <index-uri>`
(Uniform Resource Identifier) it is usually illegal to include a
user-name without a password.
username without a password.

.. NOTE::
Copy link
Contributor

Choose a reason for hiding this comment

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

Wow, this surprised me a lot!
Isn't it important enough to mention in the Users section of Access control?

image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is also described in Limitations: https://www.tarantool.io/en/doc/latest/book/box/limitations/.
I'd keep it as is because it might be hard to maintain such reference information is multiple places.


Expand All @@ -34,8 +34,10 @@ box.schema.user.create()

**Examples:**

.. code-block:: lua
.. literalinclude:: /code_snippets/test/access_control/grant_user_privileges_test.lua
:language: lua
:start-after: Create a user with a password
:end-before: End: Create a user with a password
:dedent:

box.schema.user.create('testuser')
box.schema.user.create('testuser', {password = 'foobar'})
box.schema.user.create('testuser', {if_not_exists = false})
See also: :ref:`access_control_users`.
Loading