summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@apache.org>2019-11-05 10:52:34 -0500
committerNick Vatamaniuc <nickva@users.noreply.github.com>2019-11-14 18:09:53 -0500
commitaaae5649a5a2cf38e97e23ac43b9bc6cc11a4fd3 (patch)
tree58d78d53173874745b45b34f7af2fa0e1e1cfc28
parent3db0ba7fae66bf4c817da38e9d508e5349a44b69 (diff)
downloadcouchdb-aaae5649a5a2cf38e97e23ac43b9bc6cc11a4fd3.tar.gz
Check security properties in the main transaction
Previously we checked security properties in a separate transaction, after opening the db or fetching it from the cache. To avoid running an extra transaction move the check inside the main transaction right after the metadata check runs. That ensure it will be consistent and it won't be accidentally missed as all operations run the `ensure_current` metadata check. Also remove the special `get_config/2` function in `fabric2_fdb` for getting revs limit and security properties and just read them directly from the db map.
-rw-r--r--src/chttpd/src/chttpd_auth_request.erl5
-rw-r--r--src/fabric/src/fabric2_db.erl59
-rw-r--r--src/fabric/src/fabric2_fdb.erl39
-rw-r--r--src/fabric/src/fabric2_server.erl3
-rw-r--r--src/fabric/test/fabric2_db_security_tests.erl21
5 files changed, 71 insertions, 56 deletions
diff --git a/src/chttpd/src/chttpd_auth_request.erl b/src/chttpd/src/chttpd_auth_request.erl
index 7210905f0..26fee17c3 100644
--- a/src/chttpd/src/chttpd_auth_request.erl
+++ b/src/chttpd/src/chttpd_auth_request.erl
@@ -102,9 +102,8 @@ server_authorization_check(#httpd{method=Method, path_parts=[<<"_utils">>|_]}=Re
server_authorization_check(#httpd{path_parts=[<<"_", _/binary>>|_]}=Req) ->
require_admin(Req).
-db_authorization_check(#httpd{path_parts=[DbName|_],user_ctx=Ctx}=Req) ->
- {ok, Db} = fabric2_db:open(DbName, [{user_ctx, Ctx}]),
- fabric2_db:check_is_member(Db),
+db_authorization_check(#httpd{path_parts=[_DbName|_]}=Req) ->
+ % Db authorization checks are performed in fabric before every FDB operation
Req.
require_admin(Req) ->
diff --git a/src/fabric/src/fabric2_db.erl b/src/fabric/src/fabric2_db.erl
index e2674a480..b5d68c087 100644
--- a/src/fabric/src/fabric2_db.erl
+++ b/src/fabric/src/fabric2_db.erl
@@ -176,19 +176,18 @@ open(DbName, Options) ->
case fabric2_server:fetch(DbName) of
#{} = Db ->
Db1 = maybe_set_user_ctx(Db, Options),
- ok = check_is_member(Db1),
- {ok, Db1};
+ {ok, require_member_check(Db1)};
undefined ->
Result = fabric2_fdb:transactional(DbName, Options, fun(TxDb) ->
fabric2_fdb:open(TxDb, Options)
end),
% Cache outside the transaction retry loop
case Result of
- #{security_doc := SecDoc} = Db0 ->
- ok = check_is_member(Db0, SecDoc),
+ #{} = Db0 ->
Db1 = maybe_add_sys_db_callbacks(Db0),
ok = fabric2_server:store(Db1),
- {ok, Db1#{tx := undefined}};
+ Db2 = Db1#{tx := undefined},
+ {ok, require_member_check(Db2)};
Error ->
Error
end
@@ -256,7 +255,11 @@ is_admin(Db, {SecProps}) when is_list(SecProps) ->
check_is_admin(Db) ->
- case is_admin(Db) of
+ check_is_admin(Db, get_security(Db)).
+
+
+check_is_admin(Db, SecDoc) ->
+ case is_admin(Db, SecDoc) of
true ->
ok;
false ->
@@ -280,6 +283,18 @@ check_is_member(Db, SecDoc) ->
end.
+require_admin_check(#{} = Db) ->
+ Db#{security_fun := fun check_is_admin/2}.
+
+
+require_member_check(#{} = Db) ->
+ Db#{security_fun := fun check_is_member/2}.
+
+
+no_security_check(#{} = Db) ->
+ Db#{security_fun := undefined}.
+
+
name(#{name := DbName}) ->
DbName.
@@ -359,17 +374,17 @@ get_pid(#{}) ->
get_revs_limit(#{} = Db) ->
- RevsLimitBin = fabric2_fdb:transactional(Db, fun(TxDb) ->
- fabric2_fdb:get_config(TxDb, <<"revs_limit">>)
+ #{revs_limit := RevsLimit} = fabric2_fdb:transactional(Db, fun(TxDb) ->
+ fabric2_fdb:ensure_current(TxDb)
end),
- ?bin2uint(RevsLimitBin).
+ RevsLimit.
get_security(#{} = Db) ->
- SecBin = fabric2_fdb:transactional(Db, fun(TxDb) ->
- fabric2_fdb:get_config(TxDb, <<"security_doc">>)
+ #{security_doc := SecDoc} = fabric2_fdb:transactional(Db, fun(TxDb) ->
+ fabric2_fdb:ensure_current(no_security_check(TxDb))
end),
- ?JSON_DECODE(SecBin).
+ SecDoc.
get_update_seq(#{} = Db) ->
@@ -440,26 +455,26 @@ is_users_db(DbName) when is_binary(DbName) ->
IsAuthCache orelse IsCfgUsersDb orelse IsGlobalUsersDb.
-set_revs_limit(#{} = Db, RevsLimit) ->
- check_is_admin(Db),
+set_revs_limit(#{} = Db0, RevsLimit) ->
+ Db1 = require_admin_check(Db0),
RevsLimBin = ?uint2bin(max(1, RevsLimit)),
- Resp = fabric2_fdb:transactional(Db, fun(TxDb) ->
- fabric2_fdb:set_config(TxDb, <<"revs_limit">>, RevsLimBin)
+ {Resp, Db2} = fabric2_fdb:transactional(Db1, fun(TxDb) ->
+ {fabric2_fdb:set_config(TxDb, <<"revs_limit">>, RevsLimBin), TxDb}
end),
if Resp /= ok -> Resp; true ->
- fabric2_server:store(Db#{revs_limit := RevsLimit})
+ fabric2_server:store(Db2#{revs_limit := RevsLimit})
end.
-set_security(#{} = Db, Security) ->
- check_is_admin(Db),
+set_security(#{} = Db0, Security) ->
+ Db1 = require_admin_check(Db0),
ok = fabric2_util:validate_security_object(Security),
SecBin = ?JSON_ENCODE(Security),
- Resp = fabric2_fdb:transactional(Db, fun(TxDb) ->
- fabric2_fdb:set_config(TxDb, <<"security_doc">>, SecBin)
+ {Resp, Db2} = fabric2_fdb:transactional(Db1, fun(TxDb) ->
+ {fabric2_fdb:set_config(TxDb, <<"security_doc">>, SecBin), TxDb}
end),
if Resp /= ok -> Resp; true ->
- fabric2_server:store(Db#{security_doc := Security})
+ fabric2_server:store(Db2#{security_doc := Security})
end.
diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl
index c59346ebd..1392ccd0f 100644
--- a/src/fabric/src/fabric2_fdb.erl
+++ b/src/fabric/src/fabric2_fdb.erl
@@ -20,7 +20,7 @@
create/2,
open/2,
- reopen/1,
+ ensure_current/1,
delete/1,
exists/1,
@@ -30,7 +30,6 @@
get_info/1,
get_config/1,
- get_config/2,
set_config/3,
get_stat/2,
@@ -246,10 +245,12 @@ reopen(#{} = OldDb) ->
tx := Tx,
name := DbName,
db_options := Options,
- user_ctx := UserCtx
+ user_ctx := UserCtx,
+ security_fun := SecurityFun
} = OldDb,
Options1 = lists:keystore(user_ctx, 1, Options, {user_ctx, UserCtx}),
- open(init_db(Tx, DbName, Options1), Options1).
+ NewDb = open(init_db(Tx, DbName, Options1), Options1),
+ NewDb#{security_fun := SecurityFun}.
delete(#{} = Db) ->
@@ -360,19 +361,6 @@ get_config(#{} = Db) ->
end, erlfdb:wait(Future)).
-get_config(#{} = Db, ConfigKey) ->
- #{
- tx := Tx,
- db_prefix := DbPrefix
- } = ensure_current(Db),
-
- Key = erlfdb_tuple:pack({?DB_CONFIG, ConfigKey}, DbPrefix),
- case erlfdb:wait(erlfdb:get(Tx, Key)) of
- % config values are expected to be set so we blow if not_found
- Val when Val =/= not_found -> Val
- end.
-
-
set_config(#{} = Db, ConfigKey, ConfigVal) ->
#{
tx := Tx,
@@ -835,6 +823,7 @@ init_db(Tx, DbName, Options) ->
layer_prefix => Prefix,
md_version => Version,
+ security_fun => undefined,
db_options => Options
}.
@@ -1276,12 +1265,20 @@ ensure_current(Db) ->
ensure_current(Db, true).
-ensure_current(#{} = Db, CheckDbVersion) ->
- require_transaction(Db),
- case check_metadata_version(Db) of
+ensure_current(#{} = Db0, CheckDbVersion) ->
+ require_transaction(Db0),
+ Db2 = case check_metadata_version(Db0) of
{current, Db1} -> Db1;
{stale, Db1} -> check_db_version(Db1, CheckDbVersion)
- end.
+ end,
+ case maps:get(security_fun, Db2) of
+ SecurityFun when is_function(SecurityFun, 2) ->
+ #{security_doc := SecDoc} = Db2,
+ ok = SecurityFun(Db2, SecDoc);
+ undefined ->
+ ok
+ end,
+ Db2.
is_transaction_applied(Tx) ->
diff --git a/src/fabric/src/fabric2_server.erl b/src/fabric/src/fabric2_server.erl
index f88ceb643..9dd0b7739 100644
--- a/src/fabric/src/fabric2_server.erl
+++ b/src/fabric/src/fabric2_server.erl
@@ -56,7 +56,8 @@ fetch(DbName) when is_binary(DbName) ->
store(#{name := DbName} = Db0) when is_binary(DbName) ->
Db1 = Db0#{
tx := undefined,
- user_ctx := #user_ctx{}
+ user_ctx := #user_ctx{},
+ security_fun := undefined
},
true = ets:insert(?MODULE, {DbName, Db1}),
ok.
diff --git a/src/fabric/test/fabric2_db_security_tests.erl b/src/fabric/test/fabric2_db_security_tests.erl
index 4a54083ac..e5f3ad2c0 100644
--- a/src/fabric/test/fabric2_db_security_tests.erl
+++ b/src/fabric/test/fabric2_db_security_tests.erl
@@ -39,9 +39,9 @@ security_test_() ->
fun check_admin_is_member/1,
fun check_is_member_of_public_db/1,
fun check_set_user_ctx/1,
- fun check_open_forbidden/1,
- fun check_fail_open_no_opts/1,
- fun check_fail_open_name_null/1
+ fun check_forbidden/1,
+ fun check_fail_no_opts/1,
+ fun check_fail_name_null/1
]}
}
}.
@@ -165,18 +165,21 @@ check_set_user_ctx({Db0, _, _}) ->
?assertEqual(UserCtx, fabric2_db:get_user_ctx(Db1)).
-check_open_forbidden({Db0, _, _}) ->
+check_forbidden({Db0, _, _}) ->
DbName = fabric2_db:name(Db0),
UserCtx = #user_ctx{name = <<"foo">>, roles = [<<"bar">>]},
- ?assertThrow({forbidden, _}, fabric2_db:open(DbName, [{user_ctx, UserCtx}])).
+ {ok, Db} = fabric2_db:open(DbName, [{user_ctx, UserCtx}]),
+ ?assertThrow({forbidden, _}, fabric2_db:get_db_info(Db)).
-check_fail_open_no_opts({Db0, _, _}) ->
+check_fail_no_opts({Db0, _, _}) ->
DbName = fabric2_db:name(Db0),
- ?assertThrow({unauthorized, _}, fabric2_db:open(DbName, [])).
+ {ok, Db} = fabric2_db:open(DbName, []),
+ ?assertThrow({unauthorized, _}, fabric2_db:get_db_info(Db)).
-check_fail_open_name_null({Db0, _, _}) ->
+check_fail_name_null({Db0, _, _}) ->
DbName = fabric2_db:name(Db0),
UserCtx = #user_ctx{name = null},
- ?assertThrow({unauthorized, _}, fabric2_db:open(DbName, [{user_ctx, UserCtx}])).
+ {ok, Db} = fabric2_db:open(DbName, [{user_ctx, UserCtx}]),
+ ?assertThrow({unauthorized, _}, fabric2_db:get_db_info(Db)).