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/26 08:10:03 UTC

[couchdb] 06/06: feat: add _users ddocs to _all_docs

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 a4fea4870b619b5537813c06ad7657c4afd1a4de
Author: Jan Lehnardt <ja...@apache.org>
AuthorDate: Mon Aug 26 10:09:41 2019 +0200

    feat: add _users ddocs to _all_docs
---
 src/couch/src/couch_db_updater.erl      |  1 +
 src/couch/src/couch_doc.erl             |  4 +++
 src/couch/test/couchdb_access_tests.erl | 25 ++++++++++++++++++
 src/couch_mrview/src/couch_mrview.erl   | 47 ++++++++++++++++++++++++++++++---
 4 files changed, 73 insertions(+), 4 deletions(-)

diff --git a/src/couch/src/couch_db_updater.erl b/src/couch/src/couch_db_updater.erl
index 47f1ff1..245554b 100644
--- a/src/couch/src/couch_db_updater.erl
+++ b/src/couch/src/couch_db_updater.erl
@@ -338,6 +338,7 @@ refresh_validate_doc_funs(#db{name = <<"shards/", _/binary>> = Name} = Db) ->
 refresh_validate_doc_funs(Db0) ->
     Db = Db0#db{user_ctx=?ADMIN_USER},
     {ok, DesignDocs} = couch_db:get_design_docs(Db),
+    % TODO: filter out non-admin ddocs
     ProcessDocFuns = lists:flatmap(
         fun(DesignDocInfo) ->
             {ok, DesignDoc} = couch_db:open_doc_int(
diff --git a/src/couch/src/couch_doc.erl b/src/couch/src/couch_doc.erl
index 76692d3..1ec31e2 100644
--- a/src/couch/src/couch_doc.erl
+++ b/src/couch/src/couch_doc.erl
@@ -399,6 +399,10 @@ is_deleted(Tree) ->
         false
     end.
 
+get_access({Props}) ->
+    get_access(couch_doc:from_json_obj({Props}));
+get_access(#doc{access=Access}) ->
+    Access.
 
 get_validate_doc_fun({Props}) ->
     get_validate_doc_fun(couch_doc:from_json_obj({Props}));
diff --git a/src/couch/test/couchdb_access_tests.erl b/src/couch/test/couchdb_access_tests.erl
index f985528..fca6bb0 100644
--- a/src/couch/test/couchdb_access_tests.erl
+++ b/src/couch/test/couchdb_access_tests.erl
@@ -92,6 +92,7 @@ access_test_() ->
         % _all_docs with include_docs
         fun should_let_admin_fetch_all_docs/2,
         fun should_let_user_fetch_their_own_all_docs/2,
+        fun should_let_user_fetch_their_own_all_docs_plus_users_ddocs/2,
 
         % _changes
         fun should_let_admin_fetch_changes/2,
@@ -283,6 +284,30 @@ should_let_user_fetch_their_own_all_docs(_PortType, Url) ->
     ?_assertEqual(2, length(proplists:get_value(<<"rows">>, Json))).
     % TODO    ?_assertEqual(2, proplists:get_value(<<"total_rows">>, Json)).
 
+should_let_user_fetch_their_own_all_docs_plus_users_ddocs(_PortType, Url) ->
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/_design/foo",
+        ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"_users\"]}"),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/_design/bar",
+        ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"houdini\"]}"),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/b",
+        ?USERX_REQ_HEADERS, "{\"b\":2,\"_access\":[\"x\"]}"),
+
+    % % TODO: add allowing non-admin users adding non-admin ddocs
+    % {ok, 201, _, _} = test_request:put(Url ++ "/db/_design/x",
+    %     ?ADMIN_REQ_HEADERS, "{\"b\":2,\"_access\":[\"x\"]}"),
+
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/c",
+        ?ADMIN_REQ_HEADERS, "{\"c\":3,\"_access\":[\"y\"]}"),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/d",
+        ?USERY_REQ_HEADERS, "{\"d\":4,\"_access\":[\"y\"]}"),
+    {ok, 200, _, Body} = test_request:get(Url ++ "/db/_all_docs?include_docs=true",
+        ?USERX_REQ_HEADERS),
+    {Json} = jiffy:decode(Body),
+    ?debugFmt("~nHSOIN: ~p~n", [Json]),
+    ?_assertEqual(3, length(proplists:get_value(<<"rows">>, Json))).
+
 % _changes
 should_let_admin_fetch_changes(_PortType, Url) ->
     {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
diff --git a/src/couch_mrview/src/couch_mrview.erl b/src/couch_mrview/src/couch_mrview.erl
index 72fbb92..645041b 100644
--- a/src/couch_mrview/src/couch_mrview.erl
+++ b/src/couch_mrview/src/couch_mrview.erl
@@ -280,23 +280,62 @@ query_changes_access(Db, StartSeq, Fun, Options, Acc) ->
     VName = <<"_access_by_seq">>,
     query_view(Db, DDoc, VName, Args, Callback, Acc).
 
+get_design_docs(Db) ->
+    {ok, DDocs} = couch_db:get_design_docs(Db),
+    lists:filter(fun({DDoc}) ->
+        couch_util:get_value(<<"_access">>, DDoc) =:= [<<"_users">>]
+    end, DDocs).
+
 query_all_docs_access(Db, Args0, Callback0, Acc) ->
     % query our not yest existing, home-grown _access view.
     % use query_view for this.
     DDoc = access_ddoc(),
     UserCtx = couch_db:get_user_ctx(Db),
     UserName = UserCtx#user_ctx.name,
-    % TODO: add roles
     Args1 = prefix_startkey_endkey(UserName, Args0, Args0#mrargs.direction),
     Args = Args1#mrargs{reduce=false},
-    % filter out the user-prefix from the key, so _all_docs looks normal
-    % this isn’t a separate function because I’m binding Callback0 and I don’t
-    % know the Erlang equivalent of JS’s fun.bind(this, newarg)
+
+    % get all _users design docs so we can splice them into the result
+    % in the right places
+    DDocs = get_design_docs(Db),
+    couch_log:info("~nget_user_design_docs: DDocs ~p~n", [DDocs]),
+    DDIterator = couch_iter:start(length(DDocs)),
+    % couch_log:info("~nDDocs~p~n", [DDocs]),
+
     Callback = fun
         ({row, Props}, Acc0) ->
+
+        % filter out the user-prefix from the key, so _all_docs looks normal
+        % this isn’t a separate function because I’m binding Callback0 and I
+        % don’t know the Erlang equivalent of JS’s fun.bind(this, newarg)
+
             [_User, Key] = proplists:get_value(key, Props),
             Row0 = proplists:delete(key, Props),
             Row = [{key, Key} | Row0],
+
+            couch_log:info("~nRow~p~n", [Row]),
+            % if new row id is > than next ddocs id, call Callback0 with ddoc
+            % id first, move DDocs iterator one forward
+            DDNextIdx = couch_iter:next(DDIterator),
+            case DDNextIdx of
+                done -> done;
+                N ->
+                    {DDNext} = lists:nth(N, DDocs),
+                    DDID = couch_util:get_value(<<"_id">>, DDNext),
+                    DDRev = couch_util:get_value(<<"_rev">>, DDNext),
+                    case Key > DDID of
+                        true ->
+                            DDRow = [
+                                {key, DDID},
+                                {id, DDID},
+                                {value, DDRev},
+                                {doc, {DDNext}}
+                            ],
+                            Callback0({row, DDRow}, Acc0);
+                        _Else
+                            -> ok
+                    end
+            end,
             Callback0({row, Row}, Acc0);
         (Row, Acc0) ->
             Callback0(Row, Acc0)