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 2020/05/24 15:18:34 UTC

[couchdb] 04/04: feat: add _access to local docs, teach replicator to add _access

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 4927832550bdb77d8883dd870086ae9cca51e075
Author: Jan Lehnardt <ja...@apache.org>
AuthorDate: Sun May 24 17:14:05 2020 +0200

    feat: add _access to local docs, teach replicator to add _access
---
 src/couch/src/couch_db.erl                         |  11 +-
 src/couch/src/couch_db_updater.erl                 |  10 +-
 src/couch/test/couchdb_access_tests.erl            | 115 ++++++++++++---------
 src/couch_replicator/src/couch_replicator.erl      |   5 +-
 .../src/couch_replicator_api_wrap.erl              |   2 +-
 src/couch_replicator/src/couch_replicator_ids.erl  |   1 +
 .../src/couch_replicator_scheduler_job.erl         |  39 +++++--
 src/fabric/src/fabric_db_info.erl                  |   2 +
 8 files changed, 120 insertions(+), 65 deletions(-)

diff --git a/src/couch/src/couch_db.erl b/src/couch/src/couch_db.erl
index 29e8697..a57dd45 100644
--- a/src/couch/src/couch_db.erl
+++ b/src/couch/src/couch_db.erl
@@ -585,7 +585,8 @@ get_db_info(Db) ->
         name = Name,
         compactor_pid = Compactor,
         instance_start_time = StartTime,
-        committed_update_seq = CommittedUpdateSeq
+        committed_update_seq = CommittedUpdateSeq,
+        access = Access
     } = Db,
     {ok, DocCount} = get_doc_count(Db),
     {ok, DelDocCount} = get_del_doc_count(Db),
@@ -624,7 +625,8 @@ get_db_info(Db) ->
         {disk_format_version, DiskVersion},
         {committed_update_seq, CommittedUpdateSeq},
         {compacted_seq, CompactedSeq},
-        {uuid, Uuid}
+        {uuid, Uuid},
+        {access, Access}
     ],
     {ok, InfoList}.
 
@@ -1765,7 +1767,10 @@ open_doc_revs_int(Db, IdRevs, Options) ->
 open_doc_int(Db, <<?LOCAL_DOC_PREFIX, _/binary>> = Id, Options) ->
     case couch_db_engine:open_local_docs(Db, [Id]) of
     [#doc{} = Doc] ->
-        apply_open_options(Db, {ok, Doc}, Options);
+        couch_log:debug("~n===========================Doc: ~p~n", [Doc]),
+        { Body } = Doc#doc.body,
+        Access = couch_util:get_value(<<"_access">>, Body),
+        apply_open_options(Db, {ok, Doc#doc{access = Access}}, Options);
     [not_found] ->
         {not_found, missing}
     end;
diff --git a/src/couch/src/couch_db_updater.erl b/src/couch/src/couch_db_updater.erl
index 9853fb5..b73e930 100644
--- a/src/couch/src/couch_db_updater.erl
+++ b/src/couch/src/couch_db_updater.erl
@@ -614,7 +614,8 @@ update_docs_int(Db, DocsList, LocalDocs, MergeConflicts, FullCommit) ->
     % the trees, the attachments are already written to disk)
     {ok, IndexFDIs} = flush_trees(Db, NewFullDocInfos, []),
     Pairs = pair_write_info(OldDocLookups, IndexFDIs),
-    LocalDocs2 = update_local_doc_revs(LocalDocs),
+    LocalDocs1 = apply_local_docs_access(LocalDocs),
+    LocalDocs2 = update_local_doc_revs(LocalDocs1),
     {ok, Db1} = couch_db_engine:write_doc_infos(Db, Pairs, LocalDocs2),
 
     WriteCount = length(IndexFDIs),
@@ -636,7 +637,12 @@ update_docs_int(Db, DocsList, LocalDocs, MergeConflicts, FullCommit) ->
 
     {ok, commit_data(Db1, not FullCommit), UpdatedDDocIds}.
 
-
+apply_local_docs_access(Docs) ->
+    lists:map(fun({Client, #doc{access = Access, body = {Body}} = Doc}) ->
+        Doc1 = Doc#doc{body = {[{<<"_access">>, Access} | Body]}},
+        {Client, Doc1}
+    end, Docs).
+    
 update_local_doc_revs(Docs) ->
     lists:foldl(fun({Client, Doc}, Acc) ->
         case increment_local_doc_revs(Doc) of
diff --git a/src/couch/test/couchdb_access_tests.erl b/src/couch/test/couchdb_access_tests.erl
index e771b6c..8aedd34 100644
--- a/src/couch/test/couchdb_access_tests.erl
+++ b/src/couch/test/couchdb_access_tests.erl
@@ -42,6 +42,7 @@ before_all() ->
     Couch = test_util:start_couch([chttpd, couch_replicator]),
     Hashed = couch_passwords:hash_admin_password("a"),
     ok = config:set("admins", "a", binary_to_list(Hashed), _Persist=false),
+    ok = config:set("couchdb", "uuid", "21ac467c1bc05e9d9e9d2d850bb1108f", _Persist=false),
     ok = config:set("log", "level", "debug", _Persist=false),
 
     % cleanup and setup
@@ -68,58 +69,58 @@ 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,
-        % fun should_let_user_create_access_ddoc/2,
-        % fun access_ddoc_should_have_no_effects/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,
-        % 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,
-        %
-        % % 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,
-        % % % potential future feature
-        % % % fun should_let_user_fetch_their_own_all_docs_plus_users_ddocs/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,
-        % fun should_allow_user_users_access_ddoc_view_request/2,
+        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_user_create_access_ddoc/2,
+        fun access_ddoc_should_have_no_effects/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,
+        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,
+
+        % 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,
+        % % potential future feature
+        % % fun should_let_user_fetch_their_own_all_docs_plus_users_ddocs/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,
+        fun should_allow_user_users_access_ddoc_view_request/2,
 
         % replication
-        % fun should_allow_admin_to_replicate_from_access_to_access/2,
-        % fun should_allow_admin_to_replicate_from_no_access_to_access/2,
-        % fun should_allow_admin_to_replicate_from_access_to_no_access/2,
-        % fun should_allow_admin_to_replicate_from_no_access_to_no_access/2
+        fun should_allow_admin_to_replicate_from_access_to_access/2,
+        fun should_allow_admin_to_replicate_from_no_access_to_access/2,
+        fun should_allow_admin_to_replicate_from_access_to_no_access/2,
+        fun should_allow_admin_to_replicate_from_no_access_to_no_access/2,
 
-        % fun should_allow_user_to_replicate_from_access_to_access/2,
-        % fun should_allow_user_to_replicate_from_access_to_no_access/2,
-        % fun should_allow_user_to_replicate_from_no_access_to_access/2,
-        % fun should_allow_user_to_replicate_from_no_access_to_no_access/2
+        fun should_allow_user_to_replicate_from_access_to_access/2,
+        fun should_allow_user_to_replicate_from_access_to_no_access/2,
+        fun should_allow_user_to_replicate_from_no_access_to_access/2,
+        fun should_allow_user_to_replicate_from_no_access_to_no_access/2,
 
         % TODO: try getting _revs_diff for docs you don’t have access to
         fun should_not_allow_user_to_revs_diff_other_docs/2
@@ -719,11 +720,13 @@ should_allow_user_to_replicate_from_access_to_access(_PortType, Url) ->
         ]},
         {ok, ResponseCode, _, ResponseBody} = test_request:post(Url ++ "/_replicate",
             ?USERX_REQ_HEADERS, jiffy:encode(EJRequestBody)),
+        % ?debugFmt("~nResponseBody: ~p~n", [ResponseBody]),
 
         % assert replication status
         {EJResponseBody} = jiffy:decode(ResponseBody),
         ?assertEqual(ResponseCode, 200),
         ?assertEqual(true, couch_util:get_value(<<"ok">>, EJResponseBody)),
+
         [{History}] = couch_util:get_value(<<"history">>, EJResponseBody),
 
         MissingChecked = couch_util:get_value(<<"missing_checked">>, History),
@@ -738,6 +741,18 @@ should_allow_user_to_replicate_from_access_to_access(_PortType, Url) ->
         ?assertEqual(2, DocsWritten),
         ?assertEqual(0, DocWriteFailures),
       
+        % assert access in local doc
+        ReplicationId = couch_util:get_value(<<"replication_id">>, EJResponseBody),
+        {ok, 200, _, CheckPoint} = test_request:get(Url ++ "/db/_local/" ++ ReplicationId,
+            ?USERX_REQ_HEADERS),
+        {EJCheckPoint} = jiffy:decode(CheckPoint),
+        Access = couch_util:get_value(<<"_access">>, EJCheckPoint),
+        ?assertEqual([<<"x">>], Access),
+
+        % make sure others can’t read our local docs
+        {ok, 403, _, _} = test_request:get(Url ++ "/db/_local/" ++ ReplicationId,
+            ?USERY_REQ_HEADERS),
+
         % assert docs in target db
         {ok, 200, _, ADBody} = test_request:get(Url ++ "/db2/_all_docs?include_docs=true",
             ?ADMIN_REQ_HEADERS),
@@ -902,8 +917,6 @@ should_allow_user_to_replicate_from_no_access_to_no_access(_PortType, Url) ->
         {Json} = jiffy:decode(ADBody),
         ?assertEqual(2, proplists:get_value(<<"total_rows">>, Json))
     end).
-    
-    
 
 % revs_diff
 should_not_allow_user_to_revs_diff_other_docs(_PortType, Url) ->
diff --git a/src/couch_replicator/src/couch_replicator.erl b/src/couch_replicator/src/couch_replicator.erl
index 39141c3..1566ffd 100644
--- a/src/couch_replicator/src/couch_replicator.erl
+++ b/src/couch_replicator/src/couch_replicator.erl
@@ -73,9 +73,10 @@ replicate(PostBody, Ctx) ->
     false ->
         check_authorization(RepId, UserCtx),
         {ok, Listener} = rep_result_listener(RepId),
-        Result = do_replication_loop(Rep),
+        {ok, {Result}} = do_replication_loop(Rep),
         couch_replicator_notifier:stop(Listener),
-        Result
+        {PublicRepId, _} = couch_replicator_ids:replication_id(Rep), % TODO: check with options
+        {ok, {[{<<"replication_id">>, ?l2b(PublicRepId)} | Result]}}
     end.
 
 
diff --git a/src/couch_replicator/src/couch_replicator_api_wrap.erl b/src/couch_replicator/src/couch_replicator_api_wrap.erl
index 44c290d..90ec45b 100644
--- a/src/couch_replicator/src/couch_replicator_api_wrap.erl
+++ b/src/couch_replicator/src/couch_replicator_api_wrap.erl
@@ -362,7 +362,6 @@ open_doc(Db, Id, Options) ->
         {error, <<"not_found">>}
     end.
 
-
 update_doc(Db, Doc, Options) ->
     update_doc(Db, Doc, Options, interactive_edit).
 
@@ -386,6 +385,7 @@ update_doc(#httpdb{} = HttpDb, #doc{id = DocId} = Doc, Options, Type) ->
         []
     end ++ [{"Content-Type", ?b2l(ContentType)}, {"Content-Length", Len}],
     Body = {fun stream_doc/1, {JsonBytes, Doc#doc.atts, Boundary, Len}},
+    couch_log:debug("~nBody: ~p~n", [Body]),
     send_req(
         % A crash here bubbles all the way back up to run_user_fun inside
         % open_doc_revs, which will retry the whole thing.  That's the
diff --git a/src/couch_replicator/src/couch_replicator_ids.erl b/src/couch_replicator/src/couch_replicator_ids.erl
index e10b980..28cce09 100644
--- a/src/couch_replicator/src/couch_replicator_ids.erl
+++ b/src/couch_replicator/src/couch_replicator_ids.erl
@@ -41,6 +41,7 @@ replication_id(#rep{options = Options} = Rep) ->
 
 replication_id(#rep{user_ctx = UserCtx} = Rep, 4) ->
     UUID = couch_server:get_uuid(),
+    couch_log:debug("~nUUID: ~p~n", [UUID]),
     SrcInfo = get_v4_endpoint(UserCtx, Rep#rep.source),
     TgtInfo = get_v4_endpoint(UserCtx, Rep#rep.target),
     maybe_append_filters([UUID, SrcInfo, TgtInfo], Rep);
diff --git a/src/couch_replicator/src/couch_replicator_scheduler_job.erl b/src/couch_replicator/src/couch_replicator_scheduler_job.erl
index f669d46..63ee96b 100644
--- a/src/couch_replicator/src/couch_replicator_scheduler_job.erl
+++ b/src/couch_replicator/src/couch_replicator_scheduler_job.erl
@@ -68,6 +68,8 @@
     rep_starttime,
     src_starttime,
     tgt_starttime,
+    src_access,
+    tgt_access,
     timer, % checkpoint timer
     changes_queue,
     changes_manager,
@@ -587,6 +589,9 @@ init_state(Rep) ->
     {ok, SourceInfo} = couch_replicator_api_wrap:get_db_info(Source),
     {ok, TargetInfo} = couch_replicator_api_wrap:get_db_info(Target),
 
+    couch_log:debug("~nSourceInfo: ~p~n", [SourceInfo]),
+    couch_log:debug("~nTargetInfo: ~p~n", [TargetInfo]),
+
     [SourceLog, TargetLog] = find_and_migrate_logs([Source, Target], Rep),
 
     {StartSeq0, History} = compare_replication_logs(SourceLog, TargetLog),
@@ -612,6 +617,8 @@ init_state(Rep) ->
         rep_starttime = StartTime,
         src_starttime = get_value(<<"instance_start_time">>, SourceInfo),
         tgt_starttime = get_value(<<"instance_start_time">>, TargetInfo),
+        src_access = get_value(<<"access">>, SourceInfo),
+        tgt_access = get_value(<<"access">>, TargetInfo),
         session_id = couch_uuids:random(),
         source_db_compaction_notifier =
             start_db_compaction_notifier(Source, self()),
@@ -721,8 +728,10 @@ do_checkpoint(State) ->
         rep_starttime = ReplicationStartTime,
         src_starttime = SrcInstanceStartTime,
         tgt_starttime = TgtInstanceStartTime,
+        src_access = SrcAccess,
+        tgt_access = TgtAccess,
         stats = Stats,
-        rep_details = #rep{options = Options},
+        rep_details = #rep{options = Options, user_ctx = UserCtx},
         session_id = SessionId
     } = State,
     case commit_to_both(Source, Target) of
@@ -778,9 +787,9 @@ do_checkpoint(State) ->
 
         try
             {SrcRevPos, SrcRevId} = update_checkpoint(
-                Source, SourceLog#doc{body = NewRepHistory}, source),
+                Source, SourceLog#doc{body = NewRepHistory}, SrcAccess, UserCtx, source),
             {TgtRevPos, TgtRevId} = update_checkpoint(
-                Target, TargetLog#doc{body = NewRepHistory}, target),
+                Target, TargetLog#doc{body = NewRepHistory}, TgtAccess, UserCtx, target),
             NewState = State#rep_state{
                 checkpoint_history = NewRepHistory,
                 committed_seq = NewTsSeq,
@@ -805,16 +814,34 @@ do_checkpoint(State) ->
 
 
 update_checkpoint(Db, Doc, DbType) ->
+    update_checkpoint(Db, Doc, false, #user_ctx{}, DbType).
+
+update_checkpoint(Db, Doc) ->
+    update_checkpoint(Db, Doc, false, #user_ctx{}).
+
+update_checkpoint(Db, Doc, Access, UserCtx, DbType) ->
     try
-        update_checkpoint(Db, Doc)
+        update_checkpoint(Db, Doc, Access, UserCtx)
     catch throw:{checkpoint_commit_failure, Reason} ->
         throw({checkpoint_commit_failure,
             <<"Error updating the ", (to_binary(DbType))/binary,
                 " checkpoint document: ", (to_binary(Reason))/binary>>})
     end.
 
+update_checkpoint(Db, #doc{id = LogId} = Doc0, Access, UserCtx) ->
+    % UserCtx = couch_db:get_user_ctx(Db),
+    % couch_log:debug("~n~n~n~nUserCtx: ~p~n", [UserCtx]),
+    % if db has _access, then:
+    %    get userCtx from replication and splice into doc _access
+    Doc = case Access of
+        true -> Doc0#doc{access = [UserCtx#user_ctx.name]};
+        _False -> Doc0
+    end,
+    couch_log:debug("~n++++++++++++++++:~n", []),
+    couch_log:debug("~nAccess: ~p~n", [Access]),
+    couch_log:debug("~nUserCtx: ~p~n", [UserCtx]),
 
-update_checkpoint(Db, #doc{id = LogId, body = LogBody} = Doc) ->
+    couch_log:debug("~nDoc: ~p~n", [Doc]),
     try
         case couch_replicator_api_wrap:update_doc(Db, Doc, [delay_commit]) of
         {ok, PosRevId} ->
@@ -822,7 +849,7 @@ update_checkpoint(Db, #doc{id = LogId, body = LogBody} = Doc) ->
         {error, Reason} ->
             throw({checkpoint_commit_failure, Reason})
         end
-    catch throw:conflict ->
+    catch throw:conflict -> %TODO: splice in access
         case (catch couch_replicator_api_wrap:open_doc(Db, LogId, [ejson_body])) of
         {ok, #doc{body = LogBody, revs = {Pos, [RevId | _]}}} ->
             % This means that we were able to update successfully the
diff --git a/src/fabric/src/fabric_db_info.erl b/src/fabric/src/fabric_db_info.erl
index 97a31c2..a02185c 100644
--- a/src/fabric/src/fabric_db_info.erl
+++ b/src/fabric/src/fabric_db_info.erl
@@ -112,6 +112,8 @@ merge_results(Info) ->
             [{disk_format_version, lists:max(X)} | Acc];
         (cluster, [X], Acc) ->
             [{cluster, {X}} | Acc];
+        (access, [X], Acc) ->
+            [{access, X} | Acc];
         (_, _, Acc) ->
             Acc
     end, [{instance_start_time, <<"0">>}], Dict).