You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ja...@apache.org on 2019/08/10 07:57:50 UTC

[couchdb] 02/02: feat: implement _users role handling

This is an automated email from the ASF dual-hosted git repository.

jan pushed a commit to branch access
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 5ca0d46acb7aedc18472e98202fd54eba9a76df3
Author: Jan Lehnardt <ja...@apache.org>
AuthorDate: Sat Aug 10 09:57:39 2019 +0200

    feat: implement _users role handling
---
 src/couch/src/couch_db.erl              |  2 +-
 src/couch/src/couch_db_updater.erl      | 21 ++++++++++++++-------
 src/couch/src/couch_httpd_auth.erl      |  6 +++---
 src/couch/test/couchdb_access_tests.erl | 21 ++++++++++++++++-----
 src/couch_mrview/src/couch_mrview.erl   |  3 ++-
 5 files changed, 36 insertions(+), 17 deletions(-)

diff --git a/src/couch/src/couch_db.erl b/src/couch/src/couch_db.erl
index 9231800..42a7176 100644
--- a/src/couch/src/couch_db.erl
+++ b/src/couch/src/couch_db.erl
@@ -765,7 +765,7 @@ check_name(UserName, Access) ->
 
 check_roles(Roles, Access) ->    
     UserRolesSet = ordsets:from_list(Roles),
-    RolesSet = ordsets:from_list(Access),
+    RolesSet = ordsets:from_list(Access ++ ["_users"]),
     not ordsets:is_disjoint(UserRolesSet, RolesSet).
 
 get_admins(#db{security=SecProps}) ->
diff --git a/src/couch/src/couch_db_updater.erl b/src/couch/src/couch_db_updater.erl
index ff6d366..47f1ff1 100644
--- a/src/couch/src/couch_db_updater.erl
+++ b/src/couch/src/couch_db_updater.erl
@@ -21,12 +21,15 @@
 -include("couch_db_int.hrl").
 
 -define(IDLE_LIMIT_DEFAULT, 61000).
-
+-define(DEFAULT_SECURITY_OBJECT, [
+ {<<"members">>,{[{<<"roles">>,[<<"_admin">>]}]}},
+ {<<"admins">>, {[{<<"roles">>,[<<"_admin">>]}]}}
+]).
 
 init({Engine, DbName, FilePath, Options0}) ->
     erlang:put(io_priority, {db_update, DbName}),
     update_idle_limit_from_config(),
-    DefaultSecObj = default_security_object(DbName),
+    DefaultSecObj = default_security_object(DbName, Options0),
     Options = [{default_security_object, DefaultSecObj} | Options0],
     try
         {ok, EngineState} = couch_db_engine:init(Engine, FilePath, Options),
@@ -770,20 +773,24 @@ get_meta_body_size(Meta) ->
     {ejson_size, ExternalSize} = lists:keyfind(ejson_size, 1, Meta),
     ExternalSize.
 
-
+default_security_object(DbName, []) ->
+    default_security_object(DbName);
+default_security_object(DbName, Options) ->
+    case lists:member({access, true}, Options) of
+        false -> default_security_object(DbName);
+        true -> ?DEFAULT_SECURITY_OBJECT
+    end.
 default_security_object(<<"shards/", _/binary>>) ->
     case config:get("couchdb", "default_security", "everyone") of
         "admin_only" ->
-            [{<<"members">>,{[{<<"roles">>,[<<"_admin">>]}]}},
-             {<<"admins">>,{[{<<"roles">>,[<<"_admin">>]}]}}];
+            ?DEFAULT_SECURITY_OBJECT;
         Everyone when Everyone == "everyone"; Everyone == "admin_local" ->
             []
     end;
 default_security_object(_DbName) ->
     case config:get("couchdb", "default_security", "everyone") of
         Admin when Admin == "admin_only"; Admin == "admin_local" ->
-            [{<<"members">>,{[{<<"roles">>,[<<"_admin">>]}]}},
-             {<<"admins">>,{[{<<"roles">>,[<<"_admin">>]}]}}];
+            ?DEFAULT_SECURITY_OBJECT;
         "everyone" ->
             []
     end.
diff --git a/src/couch/src/couch_httpd_auth.erl b/src/couch/src/couch_httpd_auth.erl
index b519534..9aa26ef 100644
--- a/src/couch/src/couch_httpd_auth.erl
+++ b/src/couch/src/couch_httpd_auth.erl
@@ -102,7 +102,7 @@ default_authentication_handler(Req, AuthModule) ->
                     true ->
                         Req#httpd{user_ctx=#user_ctx{
                             name=UserName,
-                            roles=couch_util:get_value(<<"roles">>, UserProps, [])
+                            roles=couch_util:get_value(<<"roles">>, UserProps, []) ++ [<<"_users">>]
                         }};
                     false ->
                         authentication_warning(Req, UserName),
@@ -165,7 +165,7 @@ proxy_auth_user(Req) ->
             Roles = case header_value(Req, XHeaderRoles) of
                 undefined -> [];
                 Else ->
-                    [?l2b(R) || R <- string:tokens(Else, ",")]
+                    [?l2b(R) || R <- string:tokens(Else, ",")] ++ [<<"_users">>]
             end,
             case config:get("couch_httpd_auth", "proxy_use_secret", "false") of
                 "true" ->
@@ -231,7 +231,7 @@ cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req, AuthModule) ->
                                                 [User]),
                                 Req#httpd{user_ctx=#user_ctx{
                                     name=?l2b(User),
-                                    roles=couch_util:get_value(<<"roles">>, UserProps, [])
+                                    roles=couch_util:get_value(<<"roles">>, UserProps, []) ++ [<<"_users">>]
                                 }, auth={FullSecret, TimeLeft < Timeout*0.9}};
                             _Else ->
                                 Req
diff --git a/src/couch/test/couchdb_access_tests.erl b/src/couch/test/couchdb_access_tests.erl
index bba0da2..74ee77c 100644
--- a/src/couch/test/couchdb_access_tests.erl
+++ b/src/couch/test/couchdb_access_tests.erl
@@ -18,17 +18,22 @@
 -define(ADMIN_REQ_HEADERS, [?CONTENT_JSON, {basic_auth, {"a", "a"}}]).
 -define(USERX_REQ_HEADERS, [?CONTENT_JSON, {basic_auth, {"x", "x"}}]).
 -define(USERY_REQ_HEADERS, [?CONTENT_JSON, {basic_auth, {"y", "y"}}]).
+-define(SECURITY_OBJECT, {[
+ {<<"members">>,{[{<<"roles">>,[<<"_admin">>, <<"_users">>]}]}},
+ {<<"admins">>, {[{<<"roles">>,[<<"_admin">>]}]}}
+]}).
 
 url() ->
     Addr = config:get("httpd", "bind_address", "127.0.0.1"),
     lists:concat(["http://", Addr, ":", port()]).
 
 before_each(_) ->
-    {ok, _, _, _} = test_request:put(url() ++ "/db?q=1&n=1&access=true", ?ADMIN_REQ_HEADERS, ""),
+    {ok, 201, _, _} = test_request:put(url() ++ "/db?q=1&n=1&access=true", ?ADMIN_REQ_HEADERS, ""),
+    {ok, _, _, _} = test_request:put(url() ++ "/db/_security", ?ADMIN_REQ_HEADERS, jiffy:encode(?SECURITY_OBJECT)),
     url().
 
 after_each(_, Url) ->
-    {ok, _, _, _} = test_request:delete(Url ++ "/db", ?ADMIN_REQ_HEADERS),
+    {ok, 200, _, _} = test_request:delete(Url ++ "/db", ?ADMIN_REQ_HEADERS),
     ok.
 
 before_all() ->
@@ -38,7 +43,7 @@ before_all() ->
 
     % cleanup and setup
     {ok, _, _, _} = test_request:delete(url() ++ "/db", ?ADMIN_REQ_HEADERS),
-    {ok, _, _, _} = test_request:put(url() ++ "/db?q=1&n=1&access=true", ?ADMIN_REQ_HEADERS, ""),
+    % {ok, _, _, _} = test_request:put(url() ++ "/db?q=1&n=1&access=true", ?ADMIN_REQ_HEADERS, ""),
 
     % create users
     UserDbUrl = url() ++ "/_users?q=1&n=1",
@@ -59,6 +64,7 @@ after_all(_) ->
 
 access_test_() ->
     Tests = [
+        fun should_not_let_anonymous_user_create_doc/2,
         fun should_let_admin_create_doc_with_access/2,
         fun should_let_user_create_doc_for_themselves/2,
         fun should_not_let_user_create_doc_for_someone_else/2,
@@ -91,8 +97,13 @@ make_test_cases(Mod, Funs) ->
     }.
 
 % Doc creation
-should_let_admin_create_doc_with_access(_PortType, Url) ->
+
+should_not_let_anonymous_user_create_doc(_PortType, Url) ->
     {ok, Code, _, _} = test_request:put(Url ++ "/db/a", "{\"a\":1,\"_access\":[\"x\"]}"),
+    ?_assertEqual(401, Code).
+
+should_let_admin_create_doc_with_access(_PortType, Url) ->
+    {ok, Code, _, _} = test_request:put(Url ++ "/db/a", ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
     ?_assertEqual(201, Code).
 
 should_let_user_create_doc_for_themselves(_PortType, Url) ->
@@ -154,7 +165,7 @@ should_let_user_fetch_their_own_all_docs(_PortType, Url) ->
     {ok, 200, _, Body} = test_request:get(Url ++ "/db/_all_docs?include_docs=true", ?USERX_REQ_HEADERS),
     {Json} = jiffy:decode(Body),
     ?_assertEqual(2, length(proplists:get_value(<<"rows">>, Json))).
-% TODO    ?_assertEqual(2, proplists:get_value(<<"total_rows">>, Json)).
+    % TODO    ?_assertEqual(2, proplists:get_value(<<"total_rows">>, Json)).
 
 % _changes
 should_let_admin_fetch_changes(_PortType, Url) ->
diff --git a/src/couch_mrview/src/couch_mrview.erl b/src/couch_mrview/src/couch_mrview.erl
index 8222dde..72fbb92 100644
--- a/src/couch_mrview/src/couch_mrview.erl
+++ b/src/couch_mrview/src/couch_mrview.erl
@@ -594,7 +594,8 @@ all_docs_fold(Db, #mrargs{direction=Dir, keys=Keys0}=Args, Callback, UAcc) ->
 map_fold(Db, View, Args, Callback, UAcc) ->
     {ok, Total} = case View#mrview.def of
         <<"_access/by-id-map">> ->
-            {ok, 0}; % TODO: couch_mrview_util:get_access_row_count(View, Args#mrargs.start_key);
+            % TODO: couch_mrview_util:get_access_row_count(View, [Args#mrargs.start_key]);
+            {ok, 0};
         _Else ->
             couch_mrview_util:get_row_count(View)
     end,