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:09:57 UTC

[couchdb] branch access updated (5ca0d46 -> a4fea48)

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

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


    from 5ca0d46  feat: implement _users role handling
     new dade59f  test: add two more tests and reformat for eadability
     new 7f26e04  feat: conflicted docs are admin-only
     new 2a5f506  feat: forbid querying non _users ddocs
     new 73acc3c  feat: allow querying _users ddocs
     new bde3a72  test: doc updates
     new a4fea48  feat: add _users ddocs to _all_docs

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 rel/overlay/etc/default.ini                |   2 +-
 src/chttpd/src/chttpd_view.erl             |   9 +
 src/couch/src/couch_db.erl                 |  29 +++-
 src/couch/src/couch_db_updater.erl         |   1 +
 src/couch/src/couch_doc.erl                |   4 +
 src/couch/test/couchdb_access_tests.erl    | 262 +++++++++++++++++++++++++----
 src/couch_mrview/src/couch_mrview.erl      |  47 +++++-
 src/couch_mrview/src/couch_mrview_http.erl |   4 +-
 8 files changed, 307 insertions(+), 51 deletions(-)


[couchdb] 01/06: test: add two more tests and reformat for eadability

Posted by ja...@apache.org.
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 dade59f92177943b679fc269945757f90909939f
Author: Jan Lehnardt <ja...@apache.org>
AuthorDate: Sun Aug 25 14:29:21 2019 +0200

    test: add two more tests and reformat for eadability
---
 src/couch/test/couchdb_access_tests.erl | 125 +++++++++++++++++++++++---------
 1 file changed, 89 insertions(+), 36 deletions(-)

diff --git a/src/couch/test/couchdb_access_tests.erl b/src/couch/test/couchdb_access_tests.erl
index 74ee77c..e386f60 100644
--- a/src/couch/test/couchdb_access_tests.erl
+++ b/src/couch/test/couchdb_access_tests.erl
@@ -66,11 +66,13 @@ access_test_() ->
     Tests = [
         fun should_not_let_anonymous_user_create_doc/2,
         fun should_let_admin_create_doc_with_access/2,
+        fun should_let_admin_create_doc_without_access/2,
         fun should_let_user_create_doc_for_themselves/2,
         fun should_not_let_user_create_doc_for_someone_else/2,
         fun should_let_admin_read_doc_with_access/2,
         fun user_with_access_can_read_doc/2,
         fun user_without_access_can_not_read_doc/2,
+        fun user_can_not_read_doc_without_access/2,
         fun should_let_admin_delete_doc_with_access/2,
         fun should_let_user_delete_doc_for_themselves/2,
         fun should_not_let_user_delete_doc_for_someone_else/2,
@@ -78,6 +80,9 @@ access_test_() ->
         fun should_let_user_fetch_their_own_all_docs/2,
         fun should_let_admin_fetch_changes/2,
         fun should_let_user_fetch_their_own_changes/2
+        % TODO: create test db with role and not _users in _security.members
+        % and make sure a user in that group can access while a user not
+        % in that group cant
     ],
     {
         "Access tests",
@@ -103,87 +108,135 @@ should_not_let_anonymous_user_create_doc(_PortType, Url) ->
     ?_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\"]}"),
+    {ok, Code, _, _} = test_request:put(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    ?_assertEqual(201, Code).
+
+should_let_admin_create_doc_without_access(_PortType, Url) ->
+    {ok, Code, _, _} = test_request:put(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS, "{\"a\":1}"),
     ?_assertEqual(201, Code).
 
 should_let_user_create_doc_for_themselves(_PortType, Url) ->
-    {ok, Code, _, _} = test_request:put(Url ++ "/db/b", ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, Code, _, _} = test_request:put(Url ++ "/db/b",
+        ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
     ?_assertEqual(201, Code).
 
 should_not_let_user_create_doc_for_someone_else(_PortType, Url) ->
-    {ok, Code, _, _} = test_request:put(Url ++ "/db/c", ?USERY_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, Code, _, _} = test_request:put(Url ++ "/db/c",
+        ?USERY_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
     ?_assertEqual(403, Code).
 
 % Doc reads
 should_let_admin_read_doc_with_access(_PortType, Url) ->
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/a", ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
-    {ok, Code, _, _} = test_request:get(Url ++ "/db/a", ?ADMIN_REQ_HEADERS),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, Code, _, _} = test_request:get(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS),
     ?_assertEqual(200, Code).
 
 user_with_access_can_read_doc(_PortType, Url) ->
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/a", ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
-    {ok, Code, _, _} = test_request:get(Url ++ "/db/a", ?USERX_REQ_HEADERS),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, Code, _, _} = test_request:get(Url ++ "/db/a",
+        ?USERX_REQ_HEADERS),
     ?_assertEqual(200, Code).
 
 user_without_access_can_not_read_doc(_PortType, Url) ->
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/a", ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
-    {ok, Code, _, _} = test_request:get(Url ++ "/db/a", ?USERY_REQ_HEADERS),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, Code, _, _} = test_request:get(Url ++ "/db/a",
+        ?USERY_REQ_HEADERS),
+    ?_assertEqual(403, Code).
+
+user_can_not_read_doc_without_access(_PortType, Url) ->
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS, "{\"a\":1}"),
+    {ok, Code, _, _} = test_request:get(Url ++ "/db/a",
+        ?USERX_REQ_HEADERS),
     ?_assertEqual(403, Code).
 
 % Doc deletes
 should_let_admin_delete_doc_with_access(_PortType, Url) ->
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/a", ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
-    {ok, Code, _, _} = test_request:delete(Url ++ "/db/a?rev=1-23202479633c2b380f79507a776743d5", ?ADMIN_REQ_HEADERS),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, Code, _, _} = test_request:delete(Url ++ "/db/a?rev=1-23202479633c2b380f79507a776743d5",
+        ?ADMIN_REQ_HEADERS),
     ?_assertEqual(201, Code).
 
 should_let_user_delete_doc_for_themselves(_PortType, Url) ->
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/a", ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
-    {ok, _, _, _} = test_request:get(Url ++ "/db/a", ?USERX_REQ_HEADERS),
-    {ok, Code, _, _} = test_request:delete(Url ++ "/db/a?rev=1-23202479633c2b380f79507a776743d5", ?USERX_REQ_HEADERS),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, _, _, _} = test_request:get(Url ++ "/db/a",
+        ?USERX_REQ_HEADERS),
+    {ok, Code, _, _} = test_request:delete(Url ++ "/db/a?rev=1-23202479633c2b380f79507a776743d5",
+        ?USERX_REQ_HEADERS),
     ?_assertEqual(201, Code).
 
 should_not_let_user_delete_doc_for_someone_else(_PortType, Url) ->
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/a", ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
-    {ok, Code, _, _} = test_request:delete(Url ++ "/db/a?rev=1-23202479633c2b380f79507a776743d5", ?USERY_REQ_HEADERS),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, Code, _, _} = test_request:delete(Url ++ "/db/a?rev=1-23202479633c2b380f79507a776743d5",
+        ?USERY_REQ_HEADERS),
     ?_assertEqual(403, Code).
 
 % _all_docs with include_docs
 should_let_admin_fetch_all_docs(_PortType, Url) ->
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/a", ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/b", ?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", ?ADMIN_REQ_HEADERS, "{\"d\":4,\"_access\":[\"y\"]}"),
-    {ok, 200, _, Body} = test_request:get(Url ++ "/db/_all_docs?include_docs=true", ?ADMIN_REQ_HEADERS),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/b",
+        ?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",
+        ?ADMIN_REQ_HEADERS, "{\"d\":4,\"_access\":[\"y\"]}"),
+    {ok, 200, _, Body} = test_request:get(Url ++ "/db/_all_docs?include_docs=true",
+        ?ADMIN_REQ_HEADERS),
     {Json} = jiffy:decode(Body),
     ?_assertEqual(4, proplists:get_value(<<"total_rows">>, Json)).
 
 should_let_user_fetch_their_own_all_docs(_PortType, Url) ->
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/a", ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/b", ?USERX_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),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/b",
+        ?USERX_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),
     ?_assertEqual(2, length(proplists:get_value(<<"rows">>, Json))).
     % TODO    ?_assertEqual(2, proplists:get_value(<<"total_rows">>, Json)).
 
 % _changes
 should_let_admin_fetch_changes(_PortType, Url) ->
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/a", ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/b", ?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", ?ADMIN_REQ_HEADERS, "{\"d\":4,\"_access\":[\"y\"]}"),
-    {ok, 200, _, Body} = test_request:get(Url ++ "/db/_changes", ?ADMIN_REQ_HEADERS),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/b",
+        ?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",
+        ?ADMIN_REQ_HEADERS, "{\"d\":4,\"_access\":[\"y\"]}"),
+    {ok, 200, _, Body} = test_request:get(Url ++ "/db/_changes",
+        ?ADMIN_REQ_HEADERS),
     {Json} = jiffy:decode(Body),
     AmountOfDocs = length(proplists:get_value(<<"results">>, Json)),
     ?_assertEqual(4, AmountOfDocs).
 
 should_let_user_fetch_their_own_changes(_PortType, Url) ->
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/a", ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
-    {ok, 201, _, _} = test_request:put(Url ++ "/db/b", ?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", ?ADMIN_REQ_HEADERS, "{\"d\":4,\"_access\":[\"y\"]}"),
-    {ok, 200, _, Body} = test_request:get(Url ++ "/db/_changes", ?USERX_REQ_HEADERS),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/b",
+        ?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",
+        ?ADMIN_REQ_HEADERS, "{\"d\":4,\"_access\":[\"y\"]}"),
+    {ok, 200, _, Body} = test_request:get(Url ++ "/db/_changes",
+        ?USERX_REQ_HEADERS),
     {Json} = jiffy:decode(Body),
     AmountOfDocs = length(proplists:get_value(<<"results">>, Json)),
     ?_assertEqual(2, AmountOfDocs).


[couchdb] 03/06: feat: forbid querying non _users ddocs

Posted by ja...@apache.org.
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 2a5f506ef31ba258a282e30b4cfb19c115ca6976
Author: Jan Lehnardt <ja...@apache.org>
AuthorDate: Sun Aug 25 16:22:59 2019 +0200

    feat: forbid querying non _users ddocs
---
 src/chttpd/src/chttpd_view.erl             |  9 +++++++++
 src/couch/src/couch_db.erl                 |  1 +
 src/couch/test/couchdb_access_tests.erl    | 23 ++++++++++++++++++++++-
 src/couch_mrview/src/couch_mrview_http.erl |  4 +---
 4 files changed, 33 insertions(+), 4 deletions(-)

diff --git a/src/chttpd/src/chttpd_view.erl b/src/chttpd/src/chttpd_view.erl
index 3c05c64..4d1b80a 100644
--- a/src/chttpd/src/chttpd_view.erl
+++ b/src/chttpd/src/chttpd_view.erl
@@ -16,6 +16,12 @@
 
 -export([handle_view_req/3, handle_temp_view_req/2]).
 
+validate_design_access(DDoc) ->
+    is_users_ddoc(DDoc).
+
+is_users_ddoc(#doc{access=[<<"_users">>]}) -> ok;
+is_users_ddoc(_) -> throw({forbidden, <<"per-user ddoc access">>}).
+
 multi_query_view(Req, Db, DDoc, ViewName, Queries) ->
     Args0 = couch_mrview_http:parse_params(Req, undefined),
     {ok, #mrst{views=Views}} = couch_mrview_util:ddoc_to_mrst(Db, DDoc),
@@ -51,6 +57,7 @@ design_doc_view(Req, Db, DDoc, ViewName, Keys) ->
 
 handle_view_req(#httpd{method='POST',
     path_parts=[_, _, _, _, ViewName, <<"queries">>]}=Req, Db, DDoc) ->
+    ok = validate_design_access(DDoc),
     chttpd:validate_ctype(Req, "application/json"),
     Props = couch_httpd:json_body_obj(Req),
     case couch_mrview_util:get_view_queries(Props) of
@@ -67,12 +74,14 @@ handle_view_req(#httpd{path_parts=[_, _, _, _, _, <<"queries">>]}=Req,
 
 handle_view_req(#httpd{method='GET',
         path_parts=[_, _, _, _, ViewName]}=Req, Db, DDoc) ->
+    ok = validate_design_access(DDoc),
     couch_stats:increment_counter([couchdb, httpd, view_reads]),
     Keys = chttpd:qs_json_value(Req, "keys", undefined),
     design_doc_view(Req, Db, DDoc, ViewName, Keys);
 
 handle_view_req(#httpd{method='POST',
         path_parts=[_, _, _, _, ViewName]}=Req, Db, DDoc) ->
+    ok = validate_design_access(DDoc),
     chttpd:validate_ctype(Req, "application/json"),
     Props = couch_httpd:json_body_obj(Req),
     Keys = couch_mrview_util:get_view_keys(Props),
diff --git a/src/couch/src/couch_db.erl b/src/couch/src/couch_db.erl
index 7aeb3d1..a4d0e67 100644
--- a/src/couch/src/couch_db.erl
+++ b/src/couch/src/couch_db.erl
@@ -32,6 +32,7 @@
     check_is_member/1,
     validate_access/2,
     check_access/2,
+    has_access_enabled/1,
 
     name/1,
     get_after_doc_read_fun/1,
diff --git a/src/couch/test/couchdb_access_tests.erl b/src/couch/test/couchdb_access_tests.erl
index 8058bd5..52d870c 100644
--- a/src/couch/test/couchdb_access_tests.erl
+++ b/src/couch/test/couchdb_access_tests.erl
@@ -85,7 +85,10 @@ access_test_() ->
         fun should_let_user_fetch_their_own_all_docs/2,
 
         fun should_let_admin_fetch_changes/2,
-        fun should_let_user_fetch_their_own_changes/2
+        fun should_let_user_fetch_their_own_changes/2,
+
+        fun should_not_allow_admin_access_ddoc_view_request/2,
+        fun should_not_allow_user_access_ddoc_view_request/2
                 
         % TODO: create test db with role and not _users in _security.members
         % and make sure a user in that group can access while a user not
@@ -266,6 +269,24 @@ should_let_user_fetch_their_own_changes(_PortType, Url) ->
     AmountOfDocs = length(proplists:get_value(<<"results">>, Json)),
     ?_assertEqual(2, AmountOfDocs).
 
+should_not_allow_admin_access_ddoc_view_request(_PortType, Url) ->
+    DDoc = "{\"a\":1,\"_access\":[\"x\"],\"views\":{\"foo\":{\"map\":\"function() {}\"}}}",
+    {ok, Code, _, _} = test_request:put(Url ++ "/db/_design/a",
+        ?ADMIN_REQ_HEADERS, DDoc),
+    ?_assertEqual(201, Code),
+    {ok, Code1, _, _} = test_request:get(Url ++ "/db/_design/a/_view/foo",
+        ?ADMIN_REQ_HEADERS),
+    ?_assertEqual(403, Code1).
+
+should_not_allow_user_access_ddoc_view_request(_PortType, Url) ->
+    DDoc = "{\"a\":1,\"_access\":[\"x\"],\"views\":{\"foo\":{\"map\":\"function() {}\"}}}",
+    {ok, Code, _, _} = test_request:put(Url ++ "/db/_design/a",
+        ?ADMIN_REQ_HEADERS, DDoc),
+    ?_assertEqual(201, Code),
+    {ok, Code1, _, _} = test_request:get(Url ++ "/db/_design/a/_view/foo",
+        ?USERX_REQ_HEADERS),
+    ?_assertEqual(403, Code1).
+
 %% ------------------------------------------------------------------
 %% Internal Function Definitions
 %% ------------------------------------------------------------------
diff --git a/src/couch_mrview/src/couch_mrview_http.erl b/src/couch_mrview/src/couch_mrview_http.erl
index 004caef..7c50beb 100644
--- a/src/couch_mrview/src/couch_mrview_http.erl
+++ b/src/couch_mrview/src/couch_mrview_http.erl
@@ -99,10 +99,9 @@ handle_view_changes_req(#httpd{path_parts=[_,<<"_design">>,DDocName,<<"_view_cha
     ChangesFun = couch_mrview_changes:handle_view_changes(ChangesArgs, Req, Db, <<"_design/", DDocName/binary>>, ViewName),
     couch_httpd_db:handle_changes_req(Req, Db, ChangesArgs, ChangesFun).
 
-
 handle_view_req(#httpd{method='GET',
                       path_parts=[_, _, DDocName, _, VName, <<"_info">>]}=Req,
-                Db, _DDoc) ->
+                Db, DDoc) ->
     DbName = couch_db:name(Db),
     DDocId = <<"_design/", DDocName/binary >>,
     {ok, Info} = couch_mrview:get_view_info(DbName, DDocId, VName),
@@ -273,7 +272,6 @@ get_view_callback(_DbName, _DbName, false) ->
 get_view_callback(_, _, _) ->
     fun view_cb/2.
 
-
 design_doc_view(Req, Db, DDoc, ViewName, Keys) ->
     Args0 = parse_params(Req, Keys),
     ETagFun = fun(Sig, Acc0) ->


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

Posted by ja...@apache.org.
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)


[couchdb] 05/06: test: doc updates

Posted by ja...@apache.org.
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 bde3a721b59ebf940c23bd7bcb170e00b98fa51d
Author: Jan Lehnardt <ja...@apache.org>
AuthorDate: Sun Aug 25 16:40:25 2019 +0200

    test: doc updates
---
 src/couch/test/couchdb_access_tests.erl | 52 ++++++++++++++++++++++++++++++---
 1 file changed, 48 insertions(+), 4 deletions(-)

diff --git a/src/couch/test/couchdb_access_tests.erl b/src/couch/test/couchdb_access_tests.erl
index 249f005..f985528 100644
--- a/src/couch/test/couchdb_access_tests.erl
+++ b/src/couch/test/couchdb_access_tests.erl
@@ -64,12 +64,19 @@ after_all(_) ->
 
 access_test_() ->
     Tests = [
+        % Doc creation
         fun should_not_let_anonymous_user_create_doc/2,
         fun should_let_admin_create_doc_with_access/2,
         fun should_let_admin_create_doc_without_access/2,
         fun should_let_user_create_doc_for_themselves/2,
         fun should_not_let_user_create_doc_for_someone_else/2,
 
+        % Doc updates
+        fun users_with_access_can_update_doc/2,
+        fun users_with_access_can_not_change_access/2,
+        fun users_with_access_can_not_remove_access/2,
+
+        % Doc reads
         fun should_let_admin_read_doc_with_access/2,
         fun user_with_access_can_read_doc/2,
         fun user_without_access_can_not_read_doc/2,
@@ -77,16 +84,20 @@ access_test_() ->
         fun admin_with_access_can_read_conflicted_doc/2,
         fun user_with_access_can_not_read_conflicted_doc/2,
 
+        % Doc deletes
         fun should_let_admin_delete_doc_with_access/2,
         fun should_let_user_delete_doc_for_themselves/2,
         fun should_not_let_user_delete_doc_for_someone_else/2,
 
+        % _all_docs with include_docs
         fun should_let_admin_fetch_all_docs/2,
         fun should_let_user_fetch_their_own_all_docs/2,
 
+        % _changes
         fun should_let_admin_fetch_changes/2,
         fun should_let_user_fetch_their_own_changes/2,
 
+        % views
         fun should_not_allow_admin_access_ddoc_view_request/2,
         fun should_not_allow_user_access_ddoc_view_request/2,
         fun should_allow_admin_users_access_ddoc_view_request/2,
@@ -139,6 +150,38 @@ should_not_let_user_create_doc_for_someone_else(_PortType, Url) ->
         ?USERY_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
     ?_assertEqual(403, Code).
 
+% Doc updates
+
+users_with_access_can_update_doc(_PortType, Url) ->
+    {ok, _, _, Body} = test_request:put(Url ++ "/db/b",
+        ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {Json} = jiffy:decode(Body),
+    Rev = couch_util:get_value(<<"rev">>, Json),
+    {ok, Code, _, _} = test_request:put(Url ++ "/db/b",
+        ?USERX_REQ_HEADERS,
+        "{\"a\":2,\"_access\":[\"x\"],\"_rev\":\"" ++ binary_to_list(Rev) ++ "\"}"),
+    ?_assertEqual(201, Code).
+
+users_with_access_can_not_change_access(_PortType, Url) ->
+    {ok, _, _, Body} = test_request:put(Url ++ "/db/b",
+        ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {Json} = jiffy:decode(Body),
+    Rev = couch_util:get_value(<<"rev">>, Json),
+    {ok, Code, _, _} = test_request:put(Url ++ "/db/b",
+        ?USERX_REQ_HEADERS,
+        "{\"a\":2,\"_access\":[\"y\"],\"_rev\":\"" ++ binary_to_list(Rev) ++ "\"}"),
+    ?_assertEqual(403, Code).
+
+users_with_access_can_not_remove_access(_PortType, Url) ->
+    {ok, _, _, Body} = test_request:put(Url ++ "/db/b",
+        ?USERX_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),
+    {Json} = jiffy:decode(Body),
+    Rev = couch_util:get_value(<<"rev">>, Json),
+    {ok, Code, _, _} = test_request:put(Url ++ "/db/b",
+        ?USERX_REQ_HEADERS,
+        "{\"a\":2,\"_rev\":\"" ++ binary_to_list(Rev) ++ "\"}"),
+    ?_assertEqual(403, Code).
+
 % Doc reads
 should_let_admin_read_doc_with_access(_PortType, Url) ->
     {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
@@ -271,11 +314,12 @@ should_let_user_fetch_their_own_changes(_PortType, Url) ->
     AmountOfDocs = length(proplists:get_value(<<"results">>, Json)),
     ?_assertEqual(2, AmountOfDocs).
 
+% views
 should_not_allow_admin_access_ddoc_view_request(_PortType, Url) ->
     DDoc = "{\"a\":1,\"_access\":[\"x\"],\"views\":{\"foo\":{\"map\":\"function() {}\"}}}",
     {ok, Code, _, _} = test_request:put(Url ++ "/db/_design/a",
         ?ADMIN_REQ_HEADERS, DDoc),
-    ?_assertEqual(201, Code),
+    ?assertEqual(201, Code),
     {ok, Code1, _, _} = test_request:get(Url ++ "/db/_design/a/_view/foo",
         ?ADMIN_REQ_HEADERS),
     ?_assertEqual(403, Code1).
@@ -284,7 +328,7 @@ should_not_allow_user_access_ddoc_view_request(_PortType, Url) ->
     DDoc = "{\"a\":1,\"_access\":[\"x\"],\"views\":{\"foo\":{\"map\":\"function() {}\"}}}",
     {ok, Code, _, _} = test_request:put(Url ++ "/db/_design/a",
         ?ADMIN_REQ_HEADERS, DDoc),
-    ?_assertEqual(201, Code),
+    ?assertEqual(201, Code),
     {ok, Code1, _, _} = test_request:get(Url ++ "/db/_design/a/_view/foo",
         ?USERX_REQ_HEADERS),
     ?_assertEqual(403, Code1).
@@ -293,7 +337,7 @@ should_allow_admin_users_access_ddoc_view_request(_PortType, Url) ->
     DDoc = "{\"a\":1,\"_access\":[\"_users\"],\"views\":{\"foo\":{\"map\":\"function() {}\"}}}",
     {ok, Code, _, _} = test_request:put(Url ++ "/db/_design/a",
         ?ADMIN_REQ_HEADERS, DDoc),
-    ?_assertEqual(201, Code),
+    ?assertEqual(201, Code),
     {ok, Code1, _, _} = test_request:get(Url ++ "/db/_design/a/_view/foo",
         ?ADMIN_REQ_HEADERS),
     ?_assertEqual(200, Code1).
@@ -302,7 +346,7 @@ should_allow_user_users_access_ddoc_view_request(_PortType, Url) ->
     DDoc = "{\"a\":1,\"_access\":[\"_users\"],\"views\":{\"foo\":{\"map\":\"function() {}\"}}}",
     {ok, Code, _, _} = test_request:put(Url ++ "/db/_design/a",
         ?ADMIN_REQ_HEADERS, DDoc),
-    ?_assertEqual(201, Code),
+    ?assertEqual(201, Code),
     {ok, Code1, _, _} = test_request:get(Url ++ "/db/_design/a/_view/foo",
         ?USERX_REQ_HEADERS),
     ?_assertEqual(200, Code1).


[couchdb] 02/06: feat: conflicted docs are admin-only

Posted by ja...@apache.org.
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 7f26e0440fa456120686b8fe99ad258547b5d4dd
Author: Jan Lehnardt <ja...@apache.org>
AuthorDate: Sun Aug 25 15:07:02 2019 +0200

    feat: conflicted docs are admin-only
---
 rel/overlay/etc/default.ini             |  2 +-
 src/couch/src/couch_db.erl              | 28 ++++++++++++++++++++++------
 src/couch/test/couchdb_access_tests.erl | 25 +++++++++++++++++++++++++
 3 files changed, 48 insertions(+), 7 deletions(-)

diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index 3e2e877..e8df095 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -505,7 +505,7 @@ _default = [{db_fragmentation, "70%"}, {view_fragmentation, "60%"}]
 ;  emergency, emerg
 ;  none
 ;
-level = info
+level = debug
 ;
 ; Set the maximum log message length in bytes that will be
 ; passed through the writer
diff --git a/src/couch/src/couch_db.erl b/src/couch/src/couch_db.erl
index 42a7176..7aeb3d1 100644
--- a/src/couch/src/couch_db.erl
+++ b/src/couch/src/couch_db.erl
@@ -271,6 +271,9 @@ wait_for_compaction(#db{main_pid=Pid}=Db, Timeout) ->
             ok
     end.
 
+has_access_enabled(#db{access=false}) -> false;
+has_access_enabled(_) -> true.
+    
 delete_doc(Db, Id, Revisions) ->
     DeletedDocs = [#doc{id=Id, revs=[Rev], deleted=true} || Rev <- Revisions],
     {ok, [Result]} = update_docs(Db, DeletedDocs, []),
@@ -279,8 +282,12 @@ delete_doc(Db, Id, Revisions) ->
 open_doc(Db, IdOrDocInfo) ->
     open_doc(Db, IdOrDocInfo, []).
 
-open_doc(Db, Id, Options) ->
+open_doc(Db, Id, Options0) ->
     increment_stat(Db, [couchdb, database_reads]),
+    Options = case has_access_enabled(Db) of
+        true -> Options0 ++ [conflicts];
+        _Else -> Options0
+    end,
     case open_doc_int(Db, Id, Options) of
     {ok, #doc{deleted=true}=Doc} ->
         case lists:member(deleted, Options) of
@@ -724,12 +731,21 @@ security_error_type(#user_ctx{name=null}) ->
 security_error_type(#user_ctx{name=_}) ->
     forbidden.
 
-validate_access(Db, Doc) ->
-    validate_access1(check_access(Db, Doc)).
-
-validate_access1(true) -> ok;
-validate_access1(_) -> throw({forbidden, <<"can't touch this">>}).
+validate_access(Db, #doc{meta=Meta}=Doc) ->
+    case proplists:get_value(conflicts, Meta) of
+        undefined -> % no conflicts
+            validate_access1(Db, Doc);
+        _Else -> % only admins can read conflicted docs in _access dbs
+            case is_admin(Db) of
+                true -> ok;
+                _Else2 -> throw({forbidden, <<"document is in conflict">>})
+            end
+    end.
+validate_access1(Db, Doc) ->
+    validate_access2(check_access(Db, Doc)).
 
+validate_access2(true) -> ok;
+validate_access2(_) -> throw({forbidden, <<"can't touch this">>}).
 
 check_access(Db, #doc{access=Access}=Doc) ->
     % couch_log:info("~ncheck da access, Doc: ~p, Db: ~p~n", [Doc, Db]),
diff --git a/src/couch/test/couchdb_access_tests.erl b/src/couch/test/couchdb_access_tests.erl
index e386f60..8058bd5 100644
--- a/src/couch/test/couchdb_access_tests.erl
+++ b/src/couch/test/couchdb_access_tests.erl
@@ -69,17 +69,24 @@ access_test_() ->
         fun should_let_admin_create_doc_without_access/2,
         fun should_let_user_create_doc_for_themselves/2,
         fun should_not_let_user_create_doc_for_someone_else/2,
+
         fun should_let_admin_read_doc_with_access/2,
         fun user_with_access_can_read_doc/2,
         fun user_without_access_can_not_read_doc/2,
         fun user_can_not_read_doc_without_access/2,
+        fun admin_with_access_can_read_conflicted_doc/2,
+        fun user_with_access_can_not_read_conflicted_doc/2,
+
         fun should_let_admin_delete_doc_with_access/2,
         fun should_let_user_delete_doc_for_themselves/2,
         fun should_not_let_user_delete_doc_for_someone_else/2,
+
         fun should_let_admin_fetch_all_docs/2,
         fun should_let_user_fetch_their_own_all_docs/2,
+
         fun should_let_admin_fetch_changes/2,
         fun should_let_user_fetch_their_own_changes/2
+                
         % TODO: create test db with role and not _users in _security.members
         % and make sure a user in that group can access while a user not
         % in that group cant
@@ -142,6 +149,24 @@ user_with_access_can_read_doc(_PortType, Url) ->
         ?USERX_REQ_HEADERS),
     ?_assertEqual(200, Code).
 
+user_with_access_can_not_read_conflicted_doc(_PortType, Url) ->
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS, "{\"_id\":\"f1\",\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a?new_edits=false",
+        ?ADMIN_REQ_HEADERS, "{\"_id\":\"f1\",\"_rev\":\"7-XYZ\",\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, Code, _, _} = test_request:get(Url ++ "/db/a",
+        ?USERX_REQ_HEADERS),
+    ?_assertEqual(403, Code).
+
+admin_with_access_can_read_conflicted_doc(_PortType, Url) ->
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS, "{\"_id\":\"a\",\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, 201, _, _} = test_request:put(Url ++ "/db/a?new_edits=false",
+        ?ADMIN_REQ_HEADERS, "{\"_id\":\"a\",\"_rev\":\"7-XYZ\",\"a\":1,\"_access\":[\"x\"]}"),
+    {ok, Code, _, _} = test_request:get(Url ++ "/db/a",
+        ?ADMIN_REQ_HEADERS),
+    ?_assertEqual(200, Code).
+
 user_without_access_can_not_read_doc(_PortType, Url) ->
     {ok, 201, _, _} = test_request:put(Url ++ "/db/a",
         ?ADMIN_REQ_HEADERS, "{\"a\":1,\"_access\":[\"x\"]}"),


[couchdb] 04/06: feat: allow querying _users ddocs

Posted by ja...@apache.org.
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 73acc3cc713c44b63d0a77220519effb8c868172
Author: Jan Lehnardt <ja...@apache.org>
AuthorDate: Sun Aug 25 16:26:36 2019 +0200

    feat: allow querying _users ddocs
---
 src/couch/test/couchdb_access_tests.erl | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/src/couch/test/couchdb_access_tests.erl b/src/couch/test/couchdb_access_tests.erl
index 52d870c..249f005 100644
--- a/src/couch/test/couchdb_access_tests.erl
+++ b/src/couch/test/couchdb_access_tests.erl
@@ -88,7 +88,9 @@ access_test_() ->
         fun should_let_user_fetch_their_own_changes/2,
 
         fun should_not_allow_admin_access_ddoc_view_request/2,
-        fun should_not_allow_user_access_ddoc_view_request/2
+        fun should_not_allow_user_access_ddoc_view_request/2,
+        fun should_allow_admin_users_access_ddoc_view_request/2,
+        fun should_allow_user_users_access_ddoc_view_request/2
                 
         % TODO: create test db with role and not _users in _security.members
         % and make sure a user in that group can access while a user not
@@ -287,6 +289,24 @@ should_not_allow_user_access_ddoc_view_request(_PortType, Url) ->
         ?USERX_REQ_HEADERS),
     ?_assertEqual(403, Code1).
 
+should_allow_admin_users_access_ddoc_view_request(_PortType, Url) ->
+    DDoc = "{\"a\":1,\"_access\":[\"_users\"],\"views\":{\"foo\":{\"map\":\"function() {}\"}}}",
+    {ok, Code, _, _} = test_request:put(Url ++ "/db/_design/a",
+        ?ADMIN_REQ_HEADERS, DDoc),
+    ?_assertEqual(201, Code),
+    {ok, Code1, _, _} = test_request:get(Url ++ "/db/_design/a/_view/foo",
+        ?ADMIN_REQ_HEADERS),
+    ?_assertEqual(200, Code1).
+
+should_allow_user_users_access_ddoc_view_request(_PortType, Url) ->
+    DDoc = "{\"a\":1,\"_access\":[\"_users\"],\"views\":{\"foo\":{\"map\":\"function() {}\"}}}",
+    {ok, Code, _, _} = test_request:put(Url ++ "/db/_design/a",
+        ?ADMIN_REQ_HEADERS, DDoc),
+    ?_assertEqual(201, Code),
+    {ok, Code1, _, _} = test_request:get(Url ++ "/db/_design/a/_view/foo",
+        ?USERX_REQ_HEADERS),
+    ?_assertEqual(200, Code1).
+
 %% ------------------------------------------------------------------
 %% Internal Function Definitions
 %% ------------------------------------------------------------------