Feature/user db security obj readonly (#2395)
* Allow to set the user db security object to readonly
- Add the default config
- Deny update on _security if the database is the user db and if the config is to false
- Add unit test
* Allow edits on _users security for the JS test
Co-authored-by: Alexis Côté <popojargo@users.noreply.github.com>
diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index a0c2617..7bfbbe9 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -80,6 +80,9 @@
; document. Default is 24 hours.
;index_lag_warn_seconds = 86400
+; Allow edits on the _security object in the user db. By default, it's disabled.
+users_db_security_editable = false
+
[couchdb_engines]
; The keys in this section are the filename extension that
; the specified engine module will use. This is important so
diff --git a/src/chttpd/src/chttpd_db.erl b/src/chttpd/src/chttpd_db.erl
index 1787e39..6a3df6d 100644
--- a/src/chttpd/src/chttpd_db.erl
+++ b/src/chttpd/src/chttpd_db.erl
@@ -781,6 +781,8 @@
db_req(#httpd{method='PUT',path_parts=[_,<<"_security">>],user_ctx=Ctx}=Req,
Db) ->
+ DbName = ?b2l(couch_db:name(Db)),
+ validate_security_can_be_edited(DbName),
SecObj = chttpd:json_body(Req),
case fabric:set_security(Db, SecObj, [{user_ctx, Ctx}]) of
ok ->
@@ -1886,6 +1888,15 @@
throw({bad_request, "Document rev and etag have different values"})
end.
+validate_security_can_be_edited(DbName) ->
+ UserDbName = config:get("chttpd_auth", "authentication_db", "_users"),
+ CanEditUserSecurityObject = config:get("couchdb","users_db_security_editable","false"),
+ case {DbName,CanEditUserSecurityObject} of
+ {UserDbName,"false"} ->
+ Msg = "You can't edit the security object of the user database.",
+ throw({forbidden, Msg});
+ {_,_} -> ok
+ end.
validate_attachment_names(Doc) ->
lists:foreach(fun(Att) ->
diff --git a/src/chttpd/test/eunit/chttpd_security_tests.erl b/src/chttpd/test/eunit/chttpd_security_tests.erl
index 955b4ff..0bea9db 100644
--- a/src/chttpd/test/eunit/chttpd_security_tests.erl
+++ b/src/chttpd/test/eunit/chttpd_security_tests.erl
@@ -137,7 +137,8 @@
fun should_return_ok_for_sec_obj_with_roles_and_names/1,
fun should_return_error_for_sec_obj_with_incorrect_roles_and_names/1,
fun should_return_error_for_sec_obj_with_incorrect_roles/1,
- fun should_return_error_for_sec_obj_with_incorrect_names/1
+ fun should_return_error_for_sec_obj_with_incorrect_names/1,
+ fun should_return_error_for_sec_obj_in_user_db/1
]
}
}
@@ -382,3 +383,24 @@
{<<"reason">>,<<"no_majority">>}
]}, ResultJson)
].
+
+should_return_error_for_sec_obj_in_user_db([_,_UsersUrl]) ->
+ SecurityUrl = lists:concat([_UsersUrl, "/_security"]),
+ SecurityProperties = [
+ {<<"admins">>, {[{<<"names">>,[<<?TEST_ADMIN>>]},
+ {<<"roles">>,[<<?TEST_ADMIN>>]}]}},
+ {<<"members">>,{[{<<"names">>,[<<?TEST_MEMBER>>]},
+ {<<"roles">>,[<<?TEST_MEMBER>>]}]}}
+ ],
+
+ Body = jiffy:encode({SecurityProperties}),
+ {ok, Status, _, RespBody} = test_request:put(SecurityUrl,
+ [?CONTENT_JSON, ?AUTH], Body),
+ ResultJson = ?JSON_DECODE(RespBody),
+ [
+ ?_assertEqual(403, Status),
+ ?_assertEqual({[
+ {<<"error">>,<<"forbidden">>},
+ {<<"reason">>,<<"You can't edit the security object of the user database.">>}
+ ]}, ResultJson)
+ ].
diff --git a/test/javascript/tests/users_db_security.js b/test/javascript/tests/users_db_security.js
index 1db6c14..faffd8c 100644
--- a/test/javascript/tests/users_db_security.js
+++ b/test/javascript/tests/users_db_security.js
@@ -374,10 +374,22 @@
};
run_on_modified_server(
- [{section: "couch_httpd_auth",
- key: "iterations", value: "1"},
- {section: "admins",
- key: "jan", value: "apple"}],
+ [
+ {
+ section:"couchdb",
+ key:"users_db_security_editable",
+ value:"true"
+ },
+ {
+ section: "couch_httpd_auth",
+ key: "iterations",
+ value: "1"
+ },
+ {
+ section: "admins",
+ key: "jan",
+ value: "apple"
+ }],
function() {
try {
testFun();