summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Lehnardt <jan@apache.org>2020-05-24 17:14:05 +0200
committerJan Lehnardt <jan@apache.org>2020-05-24 17:17:49 +0200
commit4927832550bdb77d8883dd870086ae9cca51e075 (patch)
treea8b50cb670d35f424f685f49da93421c84bdaaa4
parent7ec5baa40a92bf1048e082ca54f1fa0344c6bb1e (diff)
downloadcouchdb-access.tar.gz
feat: add _access to local docs, teach replicator to add _accessaccess
-rw-r--r--src/couch/src/couch_db.erl11
-rw-r--r--src/couch/src/couch_db_updater.erl10
-rw-r--r--src/couch/test/couchdb_access_tests.erl115
-rw-r--r--src/couch_replicator/src/couch_replicator.erl5
-rw-r--r--src/couch_replicator/src/couch_replicator_api_wrap.erl2
-rw-r--r--src/couch_replicator/src/couch_replicator_ids.erl1
-rw-r--r--src/couch_replicator/src/couch_replicator_scheduler_job.erl39
-rw-r--r--src/fabric/src/fabric_db_info.erl2
8 files changed, 120 insertions, 65 deletions
diff --git a/src/couch/src/couch_db.erl b/src/couch/src/couch_db.erl
index 29e869727..a57dd4559 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 9853fb52a..b73e93082 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 e771b6c0b..8aedd34ef 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 39141c301..1566ffd83 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 44c290d33..90ec45bcb 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 e10b98082..28cce0984 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 f669d464d..63ee96bcb 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 97a31c237..a02185cba 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).