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 2018/08/13 10:21:04 UTC
[couchdb] branch master updated: Fix session based replicator auth
when endpoints have require_valid_user set
This is an automated email from the ASF dual-hosted git repository.
jan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb.git
The following commit(s) were added to refs/heads/master by this push:
new ed1db09 Fix session based replicator auth when endpoints have require_valid_user set
ed1db09 is described below
commit ed1db09225c3c4757d8d869a4885e89ae3620983
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Thu Aug 9 16:05:04 2018 -0400
Fix session based replicator auth when endpoints have require_valid_user set
If _session response is 401 and WWW-Authentication header is set assume
endpoint has require_valid_user set. Remember that in the state and
retry to reinitialize again. If it succeeds, keep sending basic auth
creds with every subsequent _session request.
Since session uses the replicator worker pool, it needs to handle worker
cleanup properly just like couch_replicator_httpc module does. If response
headers indicate the connection will be closed, don't recycle it back to the
pool, otherwise during an immediate retry there will be a connection_closing
error, instead follow what the server indicated and stop the worker then
release it to the pool. The pool already knows how to handle dead worker
processes. This is needed with this commit, because we now have a pattern of an
immediate retry after an auth failure.
Fixes #1550
---
.../src/couch_replicator_auth_session.erl | 90 +++++++++++++++++++---
1 file changed, 78 insertions(+), 12 deletions(-)
diff --git a/src/couch_replicator/src/couch_replicator_auth_session.erl b/src/couch_replicator/src/couch_replicator_auth_session.erl
index fedc4c6..24f7c8e 100644
--- a/src/couch_replicator/src/couch_replicator_auth_session.erl
+++ b/src/couch_replicator/src/couch_replicator_auth_session.erl
@@ -94,7 +94,8 @@
httpdb_ibrowse_options = [] :: list(),
session_url :: string(),
next_refresh = infinity :: infinity | non_neg_integer(),
- refresh_tstamp = 0 :: non_neg_integer()
+ refresh_tstamp = 0 :: non_neg_integer(),
+ require_valid_user = false :: boolean()
}).
@@ -210,6 +211,19 @@ init_state(#httpdb{} = HttpDb) ->
{ok, HttpDb1, State1};
{error, {session_not_supported, _, _}} ->
ignore;
+ {error, {session_requires_valid_user, _, _}} ->
+ % If endpoint requires basic auth for _session then try
+ % to refresh again with basic auth creds, then remember
+ % this fact in the state for all subsequent requests to
+ % _session endpoint
+ case refresh(State#state{require_valid_user = true}) of
+ {ok, State1} ->
+ {ok, HttpDb1, State1};
+ {error, {session_not_supported, _, _}} ->
+ ignore;
+ {error, Error} ->
+ {error, Error}
+ end;
{error, Error} ->
{error, Error}
end;
@@ -359,7 +373,13 @@ maybe_refresh(#state{next_refresh = T} = State) ->
-spec refresh(#state{}) -> {ok, #state{}} | {error, term()}.
refresh(#state{session_url = Url, user = User, pass = Pass} = State) ->
Body = mochiweb_util:urlencode([{name, User}, {password, Pass}]),
- Headers = [{"Content-Type", "application/x-www-form-urlencoded"}],
+ Headers0 = [{"Content-Type", "application/x-www-form-urlencoded"}],
+ Headers = case State#state.require_valid_user of
+ true ->
+ Headers0 ++ [{"Authorization", "Basic " ++ b64creds(User, Pass)}];
+ false ->
+ Headers0
+ end,
Result = http_request(State, Url, Headers, post, Body),
http_response(Result, State).
@@ -375,9 +395,33 @@ http_request(#state{httpdb_pool = Pool} = State, Url, Headers, Method, Body) ->
],
{ok, Wrk} = couch_replicator_httpc_pool:get_worker(Pool),
try
- ibrowse:send_req_direct(Wrk, Url, Headers, Method, Body, Opts, Timeout)
+ Result = ibrowse:send_req_direct(Wrk, Url, Headers, Method, Body, Opts,
+ Timeout),
+ case Result of
+ {ok, _, ResultHeaders, _} ->
+ stop_worker_if_server_requested(ResultHeaders, Wrk);
+ _Other ->
+ ok
+ end,
+ Result
after
- ok = couch_replicator_httpc_pool:release_worker(Pool, Wrk)
+ ok = couch_replicator_httpc_pool:release_worker_sync(Pool, Wrk)
+ end.
+
+
+-spec stop_worker_if_server_requested(headers(), pid()) -> ok.
+stop_worker_if_server_requested(ResultHeaders0, Worker) ->
+ ResultHeaders = mochiweb_headers:make(ResultHeaders0),
+ case mochiweb_headers:get_value("Connection", ResultHeaders) of
+ "close" ->
+ Ref = erlang:monitor(process, Worker),
+ ibrowse_http_client:stop(Worker),
+ receive
+ {'DOWN', Ref, _, _, _} ->
+ ok
+ end;
+ _Other ->
+ ok
end.
@@ -385,8 +429,15 @@ http_request(#state{httpdb_pool = Pool} = State, Url, Headers, Method, Body) ->
#state{}) -> {ok, #state{}} | {error, term()}.
http_response({ok, "200", Headers, _}, State) ->
maybe_update_cookie(Headers, State);
-http_response({ok, "401", _, _}, #state{session_url = Url, user = User}) ->
- {error, {session_request_unauthorized, Url, User}};
+http_response({ok, "401", Headers0, _}, #state{session_url = Url,
+ user = User}) ->
+ Headers = mochiweb_headers:make(Headers0),
+ case mochiweb_headers:get_value("WWW-Authenticate", Headers) of
+ undefined ->
+ {error, {session_request_unauthorized, Url, User}};
+ _SomeValue ->
+ {error, {session_requires_valid_user, Url, User}}
+ end;
http_response({ok, "403", _, _}, #state{session_url = Url, user = User}) ->
{error, {session_request_forbidden, Url, User}};
http_response({ok, "404", _, _}, #state{session_url = Url, user = User}) ->
@@ -454,6 +505,11 @@ min_update_interval() ->
?MIN_UPDATE_INTERVAL).
+-spec b64creds(string(), string()) -> string().
+b64creds(User, Pass) ->
+ base64:encode_to_string(User ++ ":" ++ Pass).
+
+
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
@@ -545,6 +601,7 @@ cookie_update_test_() ->
t_process_ok_update_cookie(),
t_process_ok_no_cookie(),
t_init_state_fails_on_401(),
+ t_init_state_401_with_require_valid_user(),
t_init_state_404(),
t_init_state_no_creds(),
t_init_state_http_error()
@@ -627,6 +684,14 @@ t_init_state_fails_on_401() ->
end).
+t_init_state_401_with_require_valid_user() ->
+ ?_test(begin
+ mock_http_401_response_with_require_valid_user(),
+ ?assertMatch({ok, #httpdb{}, #state{cookie = "Cookie"}},
+ init_state(#httpdb{url = "http://u:p@h"}))
+ end).
+
+
t_init_state_404() ->
?_test(begin
mock_http_404_response(),
@@ -651,7 +716,7 @@ t_init_state_http_error() ->
setup() ->
meck:expect(couch_replicator_httpc_pool, get_worker, 1, {ok, worker}),
- meck:expect(couch_replicator_httpc_pool, release_worker, 2, ok),
+ meck:expect(couch_replicator_httpc_pool, release_worker_sync, 2, ok),
meck:expect(config, get, fun(_, _, Default) -> Default end),
mock_http_cookie_response("Abc"),
ok.
@@ -670,6 +735,12 @@ mock_http_401_response() ->
meck:expect(ibrowse, send_req_direct, 7, {ok, "401", [], []}).
+mock_http_401_response_with_require_valid_user() ->
+ Resp1 = {ok, "401", [{"WWW-Authenticate", "Basic realm=\"server\""}], []},
+ Resp2 = {ok, "200", [{"Set-Cookie", "AuthSession=Cookie"}], []},
+ meck:expect(ibrowse, send_req_direct, 7, meck:seq([Resp1, Resp2])).
+
+
mock_http_404_response() ->
meck:expect(ibrowse, send_req_direct, 7, {ok, "404", [], []}).
@@ -685,9 +756,4 @@ extract_creds_error_test_() ->
{#httpdb{url = "http://h/db"}, missing_credentials}
]].
-
-b64creds(User, Pass) ->
- base64:encode_to_string(User ++ ":" ++ Pass).
-
-
-endif.