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();