Skip to content

Commit

Permalink
box: add auth_history and last_modified fields to _user space
Browse files Browse the repository at this point in the history
See the doc bot request for the description of the new fields.

Note that we only store the value of the 'last_modified' field
in struct user_def, because 'auth_history' will be used only in
Lua code.

Needed for tarantool/tarantool-ee#298
Needed for tarantool/tarantool-ee#299

NO_CHANGELOG=no user-visible effects in CE; will be added to EE

@TarantoolBot document
Title: Document auth_history and last_modified _user space fields

Field name: auth_history. Field no: 6. Type: array.
Description: The field stores an array of previous authentication data:
when a user password is changed, the last value of the 'auth' field is
appended to 'auth_history'.  The length of the history is configured by
the `box.cfg.password_history_length` option, which is available only
in Tarantool EE, where it's used to prevent users from reusing old
passwords. In Tarantool CE, the array is always empty.

Field name: last_modified. Field no: 7. Type: unsigned.
Description: The field stores the timestamp (seconds since Unix epoch)
of the last user password update. It's never used in Tarantool CE.
In Tarantool EE, it's used to disable users that haven't changed the
password for more than `box.cfg.password_lifetime_days`.

`box.schema.upgrade()` sets the new field values to an empty array
and 0 for users that haven't updated them yet.
  • Loading branch information
locker committed Dec 22, 2022
1 parent 85ebbcc commit 1c33484
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 88 deletions.
4 changes: 4 additions & 0 deletions src/box/alter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3001,6 +3001,10 @@ user_def_new_from_tuple(struct tuple *tuple)
if (user_def_fill_auth_data(user, auth_data) != 0)
return NULL;
}
if (tuple_field_count(tuple) > BOX_USER_FIELD_LAST_MODIFIED &&
tuple_field_u64(tuple, BOX_USER_FIELD_LAST_MODIFIED,
&user->last_modified) != 0)
return NULL;
def_guard.is_active = false;
return user;
}
Expand Down
Binary file modified src/box/bootstrap.snap
Binary file not shown.
11 changes: 8 additions & 3 deletions src/box/lua/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local msgpack = require('msgpack')
local fun = require('fun')
local log = require('log')
local buffer = require('buffer')
local fiber = require('fiber')
local session = box.session
local internal = require('box.internal')
local utf8 = require('utf8')
Expand Down Expand Up @@ -3391,7 +3392,9 @@ end
local function chpasswd(uid, new_password)
local _user = box.space[box.schema.USER_ID]
check_password(new_password)
_user:update({uid}, {{"=", 5, prepare_auth_list(new_password)}})
_user:update({uid}, {{'=', 5, prepare_auth_list(new_password)},
{'=', 6, {}},
{'=', 7, math.floor(fiber.time())}})
end

box.schema.user.passwd = function(name, new_password)
Expand Down Expand Up @@ -3430,7 +3433,8 @@ box.schema.user.create = function(name, opts)
auth_list = setmap({})
end
local _user = box.space[box.schema.USER_ID]
uid = _user:auto_increment{session.euid(), name, 'user', auth_list}.id
uid = _user:auto_increment{session.euid(), name, 'user', auth_list, {},
math.floor(fiber.time())}.id
-- grant role 'public' to the user
box.schema.user.grant(uid, 'public')
-- Grant privilege 'alter' on itself, so that it can
Expand Down Expand Up @@ -3697,7 +3701,8 @@ box.schema.role.create = function(name, opts)
return
end
local _user = box.space[box.schema.USER_ID]
_user:auto_increment{session.euid(), name, 'role', setmap({})}
_user:auto_increment{session.euid(), name, 'role', setmap({}), {},
math.floor(fiber.time())}
end

box.schema.role.drop = function(name, opts)
Expand Down
19 changes: 19 additions & 0 deletions src/box/lua/upgrade.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1333,9 +1333,28 @@ local function convert_sql_constraints_to_tuple_constraints()
end
end

local function add_user_auth_history_and_last_modified()
log.info("add auth_history and last_modified fields to space _user")
local _space = box.space[box.schema.SPACE_ID]
local _user = box.space[box.schema.USER_ID]
local _vuser = box.space[box.schema.VUSER_ID]
for _, v in _user:pairs() do
if #v == 5 then
_user:update(v[1], {{'=', 6, {}}, {'=', 7, 0}})
end
end
local ops = {
{'=', '[7][6]', {name = 'auth_history', type = 'array'}},
{'=', '[7][7]', {name = 'last_modified', type = 'unsigned'}},
}
_space:update({_user.id}, ops)
_space:update({_vuser.id}, ops)
end

local function upgrade_to_2_11_0()
revoke_write_access_on__collation_from_role_public()
convert_sql_constraints_to_tuple_constraints()
add_user_auth_history_and_last_modified()
end
--------------------------------------------------------------------------------

Expand Down
2 changes: 2 additions & 0 deletions src/box/schema_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ enum {
BOX_USER_FIELD_NAME = 2,
BOX_USER_FIELD_TYPE = 3,
BOX_USER_FIELD_AUTH = 4,
BOX_USER_FIELD_AUTH_HISTORY = 5,
BOX_USER_FIELD_LAST_MODIFIED = 6,
};

/** _priv fields. */
Expand Down
1 change: 1 addition & 0 deletions src/box/user_def.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ user_def_new(uint32_t uid, uint32_t owner, enum schema_object_type type,
def->owner = owner;
def->type = type;
def->auth = NULL;
def->last_modified = 0;
def->name = grp_alloc_create_str(&all, name, name_len);
assert(grp_alloc_size(&all) == 0);
return def;
Expand Down
5 changes: 5 additions & 0 deletions src/box/user_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ struct user_def {
* reside in the user struct.
*/
struct authenticator *auth;
/**
* Last modification timestamp (seconds since UNIX epoch)
* or 0 if unknown.
*/
uint64_t last_modified;
/** User name - for error messages and debugging */
char *name;
};
Expand Down
Binary file not shown.
99 changes: 99 additions & 0 deletions test/box-luatest/user_auth_history_last_modified_upgrade_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
local server = require('luatest.server')
local t = require('luatest')

local g = t.group()

g.before_all(function(cg)
t.tarantool.skip_if_not_debug()
cg.server = server:new({
alias = 'master',
datadir = 'test/box-luatest/upgrade/2.10.4',
env = {ERRINJ_AUTO_UPGRADE = 'true'},
})
cg.server:start()
end)

g.after_all(function(cg)
cg.server:drop()
end)

g.test_upgrade = function(cg)
cg.server:exec(function()
local t = require('luatest')
local fiber = require('fiber')
t.assert_equals(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, 'eve', 'user', {}},
{33, 1, 'bob', 'user',
{['chap-sha1'] = 'FOZVZ6vbUTXQz9mnCzAywXmknuc='}},
{34, 1, 'test', 'role', {}},
})
box.schema.user.create('alice')
box.schema.user.create('sarah', {password = 'SARAH'})
box.schema.user.passwd('bob', 'BOB')
box.schema.role.create('dev')
t.assert_equals(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, 'eve', 'user', {}},
{33, 1, 'bob', 'user',
{['chap-sha1'] = 'Ll5w6uuDmXlEaz2b8kmjHZu1SLg='}, {},
box.space._user:get(33)[7]},
{34, 1, 'test', 'role', {}},
{35, 0, 'alice', 'user', {}, {},
box.space._user:get(35)[7]},
{36, 0, 'sarah', 'user',
{['chap-sha1'] = '973rCIFsYhe7gupdgOPCSJoPRNU='}, {},
box.space._user:get(36)[7]},
{37, 0, 'dev', 'role', {}, {},
box.space._user:get(37)[7]},
})
local time = fiber.time()
local margin = 60
t.assert_almost_equals(box.space._user:get(33)[7], time, margin)
t.assert_almost_equals(box.space._user:get(35)[7], time, margin)
t.assert_almost_equals(box.space._user:get(36)[7], time, margin)
t.assert_almost_equals(box.space._user:get(37)[7], time, margin)
box.schema.upgrade()
local format = {
{name = "id", type = "unsigned"},
{name = "owner", type = "unsigned"},
{name = "name", type = "string"},
{name = "type", type = "string"},
{name = "auth", type = "map"},
{name = "auth_history", type = "array"},
{name = "last_modified", type = "unsigned"},
}
t.assert_equals(box.space._user:format(), format)
t.assert_equals(box.space._vuser:format(), format)
t.assert_equals(box.space._user:select(), {
{0, 1, 'guest', 'user',
{['chap-sha1'] = 'vhvewKp0tNyweZQ+cFKAlsyphfg='}, {}, 0},
{1, 1, 'admin', 'user', {}, {}, 0},
{2, 1, 'public', 'role', {}, {}, 0},
{3, 1, 'replication', 'role', {}, {}, 0},
{31, 1, 'super', 'role', {}, {}, 0},
{32, 1, 'eve', 'user', {}, {}, 0},
{33, 1, 'bob', 'user',
{['chap-sha1'] = 'Ll5w6uuDmXlEaz2b8kmjHZu1SLg='}, {},
box.space._user:get(33)[7]},
{34, 1, 'test', 'role', {}, {}, 0},
{35, 0, 'alice', 'user', {}, {},
box.space._user:get(35)[7]},
{36, 0, 'sarah', 'user',
{['chap-sha1'] = '973rCIFsYhe7gupdgOPCSJoPRNU='}, {},
box.space._user:get(36)[7]},
{37, 0, 'dev', 'role', {}, {},
box.space._user:get(37)[7]},
})
end)
end
16 changes: 9 additions & 7 deletions test/box-py/bootstrap.result
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,12 @@ box.space._space:select{}
'type': 'string'}, {'name': 'last_altered', 'type': 'string'}]]
- [304, 1, '_user', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'type',
'type': 'string'}, {'name': 'auth', 'type': 'map'}]]
'type': 'string'}, {'name': 'auth', 'type': 'map'}, {'name': 'auth_history',
'type': 'array'}, {'name': 'last_modified', 'type': 'unsigned'}]]
- [305, 1, '_vuser', 'sysview', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'owner',
'type': 'unsigned'}, {'name': 'name', 'type': 'string'}, {'name': 'type',
'type': 'string'}, {'name': 'auth', 'type': 'map'}]]
'type': 'string'}, {'name': 'auth', 'type': 'map'}, {'name': 'auth_history',
'type': 'array'}, {'name': 'last_modified', 'type': 'unsigned'}]]
- [312, 1, '_priv', 'memtx', 0, {}, [{'name': 'grantor', 'type': 'unsigned'}, {
'name': 'grantee', 'type': 'unsigned'}, {'name': 'object_type', 'type': 'string'},
{'name': 'object_id', 'type': 'scalar'}, {'name': 'privilege', 'type': 'unsigned'}]]
Expand Down Expand Up @@ -166,11 +168,11 @@ box.space._index:select{}
...
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', {}]
- - [0, 1, 'guest', 'user', {'chap-sha1': 'vhvewKp0tNyweZQ+cFKAlsyphfg='}, [], 0]
- [1, 1, 'admin', 'user', {}, [], 0]
- [2, 1, 'public', 'role', {}, [], 0]
- [3, 1, 'replication', 'role', {}, [], 0]
- [31, 1, 'super', 'role', {}, [], 0]
...
for _, v in box.space._func:pairs{} do r = {} table.insert(r, v:update({{"=", 18, ""}, {"=", 19, ""}})) return r end
---
Expand Down
Loading

0 comments on commit 1c33484

Please sign in to comment.