summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@apache.org>2020-02-06 12:03:28 -0500
committerNick Vatamaniuc <vatamane@apache.org>2020-02-06 12:03:28 -0500
commit2df0c327e3792774fd2771551853a50ec043b36f (patch)
tree1caf54e38043a6b42c606f7e4553be435b5adf99
parent1c02da2edb25789d07ee9f947d86449ee3239a4b (diff)
downloadcouchdb-simplify-couch-auth-cache.tar.gz
Simplify couch_auth_cachesimplify-couch-auth-cache
Since the backend port is closed by default, remove the cache service since it was shown to cause monitor leaks, and it just consumes resources running in the background when almost nobody uses it. The few utility scripts that might use the backend port can just get the auth docs directly from the db. Issue: https://github.com/apache/couchdb/issues/2493
-rw-r--r--src/couch/src/couch_auth_cache.erl383
-rw-r--r--src/couch/src/couch_secondary_sup.erl3
-rw-r--r--src/couch/test/eunit/couch_auth_cache_tests.erl28
3 files changed, 41 insertions, 373 deletions
diff --git a/src/couch/src/couch_auth_cache.erl b/src/couch/src/couch_auth_cache.erl
index 157b0902e..c564cee00 100644
--- a/src/couch/src/couch_auth_cache.erl
+++ b/src/couch/src/couch_auth_cache.erl
@@ -11,37 +11,22 @@
% the License.
-module(couch_auth_cache).
--behaviour(gen_server).
--vsn(3).
--behaviour(config_listener).
-% public API
--export([get_user_creds/1, get_user_creds/2, update_user_creds/3]).
--export([get_admin/1, add_roles/2, auth_design_doc/1]).
-% gen_server API
--export([start_link/0, init/1, handle_call/3, handle_info/2, handle_cast/2]).
--export([code_change/3, terminate/2]).
+-export([
+ get_user_creds/1,
+ get_user_creds/2,
+ update_user_creds/3,
+ get_admin/1,
+ add_roles/2,
+ auth_design_doc/1,
+ ensure_users_db_exists/0
+]).
--export([handle_config_change/5, handle_config_terminate/3]).
--export([handle_db_event/3]).
-include_lib("couch/include/couch_db.hrl").
-include_lib("couch/include/couch_js_functions.hrl").
--define(STATE, auth_state_ets).
--define(BY_USER, auth_by_user_ets).
--define(BY_ATIME, auth_by_atime_ets).
-
--define(RELISTEN_DELAY, 5000).
-
--record(state, {
- max_cache_size = 0,
- cache_size = 0,
- db_notifier = nil,
- event_listener = nil
-}).
-
-spec get_user_creds(UserName::string() | binary()) ->
{ok, Credentials::list(), term()} | nil.
@@ -58,9 +43,9 @@ get_user_creds(Req, UserName) when is_list(UserName) ->
get_user_creds(_Req, UserName) ->
UserCreds = case get_admin(UserName) of
nil ->
- get_from_cache(UserName);
+ get_from_db(UserName);
Props ->
- case get_from_cache(UserName) of
+ case get_from_db(UserName) of
nil ->
Props;
UserProps when is_list(UserProps) ->
@@ -70,8 +55,8 @@ get_user_creds(_Req, UserName) ->
validate_user_creds(UserCreds).
update_user_creds(_Req, UserDoc, _AuthCtx) ->
- DbNameList = config:get("couch_httpd_auth", "authentication_db", "_users"),
- couch_util:with_db(?l2b(DbNameList), fun(UserDb) ->
+ ok = ensure_users_db_exists(),
+ couch_util:with_db(users_db(), fun(UserDb) ->
{ok, _NewRev} = couch_db:update_doc(UserDb, UserDoc, []),
ok
end).
@@ -109,21 +94,20 @@ make_admin_doc(DerivedKey, Salt, Iterations) ->
{<<"password_scheme">>, <<"pbkdf2">>},
{<<"derived_key">>, ?l2b(DerivedKey)}].
-get_from_cache(UserName) ->
- exec_if_auth_db(
- fun(_AuthDb) ->
- maybe_refresh_cache(),
- case ets:lookup(?BY_USER, UserName) of
- [] ->
- gen_server:call(?MODULE, {fetch, UserName}, infinity);
- [{UserName, {Credentials, _ATime}}] ->
- couch_stats:increment_counter([couchdb, auth_cache_hits]),
- gen_server:cast(?MODULE, {cache_hit, UserName}),
- Credentials
- end
- end,
- nil
- ).
+
+get_from_db(UserName) ->
+ ok = ensure_users_db_exists(),
+ couch_util:with_db(users_db(), fun(Db) ->
+ DocId = <<"org.couchdb.user:", UserName/binary>>,
+ try
+ {ok, Doc} = couch_db:open_doc(Db, DocId, [conflicts]),
+ {DocProps} = couch_doc:to_json_obj(Doc, []),
+ DocProps
+ catch
+ _:_Error ->
+ nil
+ end
+ end).
validate_user_creds(nil) ->
@@ -141,313 +125,24 @@ validate_user_creds(UserCreds) ->
{ok, UserCreds, nil}.
-start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-
-
-init(_) ->
- ?STATE = ets:new(?STATE, [set, protected, named_table]),
- ?BY_USER = ets:new(?BY_USER, [set, protected, named_table]),
- ?BY_ATIME = ets:new(?BY_ATIME, [ordered_set, private, named_table]),
- AuthDbName = config:get("couch_httpd_auth", "authentication_db"),
- process_flag(trap_exit, true),
- ok = config:listen_for_changes(?MODULE, nil),
- {ok, Listener} = couch_event:link_listener(
- ?MODULE, handle_db_event, nil, [{dbname, AuthDbName}]
- ),
- State = #state{
- event_listener = Listener,
- max_cache_size = list_to_integer(
- config:get("couch_httpd_auth", "auth_cache_size", "50")
- )
- },
- {ok, reinit_cache(State)}.
-
-
-handle_db_event(_DbName, created, St) ->
- gen_server:call(?MODULE, reinit_cache, infinity),
- {ok, St};
-handle_db_event(_DbName, compacted, St) ->
- gen_server:call(?MODULE, auth_db_compacted, infinity),
- {ok, St};
-handle_db_event(_, _, St) ->
- {ok, St}.
-
-
-handle_call(reinit_cache, _From, State) ->
- exec_if_auth_db(fun(AuthDb) -> catch couch_db:close(AuthDb) end),
- {reply, ok, reinit_cache(State)};
-
-handle_call(auth_db_compacted, _From, State) ->
- exec_if_auth_db(
- fun(AuthDb) ->
- true = ets:insert(?STATE, {auth_db, reopen_auth_db(AuthDb)})
- end
- ),
- {reply, ok, State};
-
-handle_call({new_max_cache_size, NewSize},
- _From, #state{cache_size = Size} = State) when NewSize >= Size ->
- {reply, ok, State#state{max_cache_size = NewSize}};
-
-handle_call({new_max_cache_size, NewSize}, _From, State) ->
- free_mru_cache_entries(State#state.cache_size - NewSize),
- {reply, ok, State#state{max_cache_size = NewSize, cache_size = NewSize}};
-
-handle_call({fetch, UserName}, _From, State) ->
- {Credentials, NewState} = case ets:lookup(?BY_USER, UserName) of
- [{UserName, {Creds, ATime}}] ->
- couch_stats:increment_counter([couchdb, auth_cache_hits]),
- cache_hit(UserName, Creds, ATime),
- {Creds, State};
- [] ->
- couch_stats:increment_counter([couchdb, auth_cache_misses]),
- Creds = get_user_props_from_db(UserName),
- ATime = couch_util:unique_monotonic_integer(),
- State1 = add_cache_entry(UserName, Creds, ATime, State),
- {Creds, State1}
- end,
- {reply, Credentials, NewState};
-
-handle_call(refresh, _From, State) ->
- exec_if_auth_db(fun refresh_entries/1),
- {reply, ok, State}.
-
-
-handle_cast({cache_hit, UserName}, State) ->
- case ets:lookup(?BY_USER, UserName) of
- [{UserName, {Credentials, ATime}}] ->
- cache_hit(UserName, Credentials, ATime);
- _ ->
- ok
- end,
- {noreply, State}.
-
-
-handle_info({'EXIT', LPid, _Reason}, #state{event_listener=LPid}=State) ->
- erlang:send_after(5000, self(), restart_event_listener),
- {noreply, State#state{event_listener=undefined}};
-handle_info(restart_event_listener, State) ->
- [{auth_db_name, AuthDbName}] = ets:lookup(?STATE, auth_db_name),
- {ok, NewListener} = couch_event:link_listener(
- ?MODULE, handle_db_event, nil, [{dbname, AuthDbName}]
- ),
- {noreply, State#state{event_listener=NewListener}};
-handle_info({'DOWN', _Ref, _, _, shutdown}, State) ->
- {stop, shutdown, State};
-handle_info({'DOWN', _Ref, _, _, _}, State) ->
- {noreply, reinit_cache(State)};
-handle_info(restart_config_listener, State) ->
- ok = config:listen_for_changes(?MODULE, nil),
- {noreply, State}.
-
-
-
-terminate(_Reason, #state{event_listener = Listener}) ->
- couch_event:stop_listener(Listener),
- exec_if_auth_db(fun(AuthDb) -> catch couch_db:close(AuthDb) end),
- true = ets:delete(?BY_USER),
- true = ets:delete(?BY_ATIME),
- true = ets:delete(?STATE).
-
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-
-handle_config_change("couch_httpd_auth", "auth_cache_size", SizeList, _, _) ->
- Size = list_to_integer(SizeList),
- {ok, gen_server:call(?MODULE, {new_max_cache_size, Size}, infinity)};
-handle_config_change("couch_httpd_auth", "authentication_db", _DbName, _, _) ->
- {ok, gen_server:call(?MODULE, reinit_cache, infinity)};
-handle_config_change(_, _, _, _, _) ->
- {ok, nil}.
-
-handle_config_terminate(_, stop, _) ->
- ok;
-handle_config_terminate(_Server, _Reason, _State) ->
- erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
-
-clear_cache(State) ->
- exec_if_auth_db(fun(AuthDb) -> catch couch_db:close(AuthDb) end),
- true = ets:delete_all_objects(?BY_USER),
- true = ets:delete_all_objects(?BY_ATIME),
- State#state{cache_size = 0}.
-
-
-reinit_cache(#state{} = State) ->
- NewState = clear_cache(State),
- AuthDbName = ?l2b(config:get("couch_httpd_auth", "authentication_db")),
- true = ets:insert(?STATE, {auth_db_name, AuthDbName}),
- AuthDb = open_auth_db(),
- true = ets:insert(?STATE, {auth_db, AuthDb}),
- couch_db:monitor(AuthDb),
- NewState.
-
-
-add_cache_entry(_, _, _, #state{max_cache_size = 0} = State) ->
- State;
-add_cache_entry(UserName, Credentials, ATime, State) ->
- case State#state.cache_size >= State#state.max_cache_size of
- true ->
- free_mru_cache_entry();
- false ->
- ok
- end,
- true = ets:insert(?BY_ATIME, {ATime, UserName}),
- true = ets:insert(?BY_USER, {UserName, {Credentials, ATime}}),
- State#state{cache_size = couch_util:get_value(size, ets:info(?BY_USER))}.
-
-free_mru_cache_entries(0) ->
- ok;
-free_mru_cache_entries(N) when N > 0 ->
- free_mru_cache_entry(),
- free_mru_cache_entries(N - 1).
-
-free_mru_cache_entry() ->
- MruTime = ets:last(?BY_ATIME),
- [{MruTime, UserName}] = ets:lookup(?BY_ATIME, MruTime),
- true = ets:delete(?BY_ATIME, MruTime),
- true = ets:delete(?BY_USER, UserName).
-
-
-cache_hit(UserName, Credentials, ATime) ->
- NewATime = couch_util:unique_monotonic_integer(),
- true = ets:delete(?BY_ATIME, ATime),
- true = ets:insert(?BY_ATIME, {NewATime, UserName}),
- true = ets:insert(?BY_USER, {UserName, {Credentials, NewATime}}).
-
-
-refresh_entries(AuthDb) ->
- case reopen_auth_db(AuthDb) of
- nil ->
- ok;
- AuthDb2 ->
- AuthDbSeq = couch_db:get_update_seq(AuthDb),
- AuthDb2Seq = couch_db:get_update_seq(AuthDb2),
- case AuthDb2Seq > AuthDbSeq of
- true ->
- Fun = fun(DocInfo, _) -> refresh_entry(AuthDb2, DocInfo) end,
- {ok, _} = couch_db:fold_changes(AuthDb2, AuthDbSeq, Fun, nil),
- true = ets:insert(?STATE, {auth_db, AuthDb2});
- false ->
- ok
- end
- end.
-
-
-refresh_entry(Db, #full_doc_info{} = FDI) ->
- refresh_entry(Db, couch_doc:to_doc_info(FDI));
-refresh_entry(Db, #doc_info{high_seq = DocSeq} = DocInfo) ->
- case is_user_doc(DocInfo) of
- {true, UserName} ->
- case ets:lookup(?BY_USER, UserName) of
- [] ->
- ok;
- [{UserName, {_OldCreds, ATime}}] ->
- {ok, Doc} = couch_db:open_doc(Db, DocInfo, [conflicts, deleted]),
- NewCreds = user_creds(Doc),
- true = ets:insert(?BY_USER, {UserName, {NewCreds, ATime}})
- end;
- false ->
- ok
- end,
- {ok, DocSeq}.
-
-
-user_creds(#doc{deleted = true}) ->
- nil;
-user_creds(#doc{} = Doc) ->
- {Creds} = couch_doc:to_json_obj(Doc, []),
- Creds.
-
-
-is_user_doc(#doc_info{id = <<"org.couchdb.user:", UserName/binary>>}) ->
- {true, UserName};
-is_user_doc(_) ->
- false.
-
-
-maybe_refresh_cache() ->
- case cache_needs_refresh() of
- true ->
- ok = gen_server:call(?MODULE, refresh, infinity);
- false ->
- ok
- end.
-
-
-cache_needs_refresh() ->
- exec_if_auth_db(
- fun(AuthDb) ->
- case reopen_auth_db(AuthDb) of
- nil ->
- false;
- AuthDb2 ->
- AuthDbSeq = couch_db:get_update_seq(AuthDb),
- AuthDb2Seq = couch_db:get_update_seq(AuthDb2),
- AuthDb2Seq > AuthDbSeq
- end
- end,
- false
- ).
-
-
-reopen_auth_db(AuthDb) ->
- case (catch couch_db:reopen(AuthDb)) of
- {ok, AuthDb2} ->
- AuthDb2;
- _ ->
- nil
- end.
-
-
-exec_if_auth_db(Fun) ->
- exec_if_auth_db(Fun, ok).
-
-exec_if_auth_db(Fun, DefRes) ->
- case ets:lookup(?STATE, auth_db) of
- [{auth_db, AuthDb}] ->
- Fun(AuthDb);
- _ ->
- DefRes
- end.
-
-
-open_auth_db() ->
- [{auth_db_name, DbName}] = ets:lookup(?STATE, auth_db_name),
- {ok, AuthDb} = ensure_users_db_exists(DbName, [sys_db]),
- AuthDb.
-
+users_db() ->
+ DbNameList = config:get("couch_httpd_auth", "authentication_db", "_users"),
+ ?l2b(DbNameList).
-get_user_props_from_db(UserName) ->
- exec_if_auth_db(
- fun(AuthDb) ->
- Db = reopen_auth_db(AuthDb),
- DocId = <<"org.couchdb.user:", UserName/binary>>,
- try
- {ok, Doc} = couch_db:open_doc(Db, DocId, [conflicts]),
- {DocProps} = couch_doc:to_json_obj(Doc, []),
- DocProps
- catch
- _:_Error ->
- nil
- end
- end,
- nil
- ).
-ensure_users_db_exists(DbName, Options) ->
- Options1 = [?ADMIN_CTX, nologifmissing | Options],
- case couch_db:open(DbName, Options1) of
+ensure_users_db_exists() ->
+ Options = [?ADMIN_CTX, nologifmissing],
+ case couch_db:open(users_db(), Options) of
{ok, Db} ->
ensure_auth_ddoc_exists(Db, <<"_design/_auth">>),
- {ok, Db};
+ couch_db:close(Db);
_Error ->
- {ok, Db} = couch_db:create(DbName, Options1),
+ {ok, Db} = couch_db:create(users_db(), Options),
ok = ensure_auth_ddoc_exists(Db, <<"_design/_auth">>),
- {ok, Db}
- end.
+ couch_db:close(Db)
+ end,
+ ok.
+
ensure_auth_ddoc_exists(Db, DDocId) ->
case couch_db:open_doc(Db, DDocId) of
diff --git a/src/couch/src/couch_secondary_sup.erl b/src/couch/src/couch_secondary_sup.erl
index 9c7d414d0..bb7821555 100644
--- a/src/couch/src/couch_secondary_sup.erl
+++ b/src/couch/src/couch_secondary_sup.erl
@@ -30,8 +30,7 @@ init([]) ->
{index_server, {couch_index_server, start_link, []}},
{query_servers, {couch_proc_manager, start_link, []}},
{vhosts, {couch_httpd_vhost, start_link, []}},
- {uuids, {couch_uuids, start, []}},
- {auth_cache, {couch_auth_cache, start_link, []}}
+ {uuids, {couch_uuids, start, []}}
],
MaybeHttp = case http_enabled() of
diff --git a/src/couch/test/eunit/couch_auth_cache_tests.erl b/src/couch/test/eunit/couch_auth_cache_tests.erl
index 5439dd7b9..71faf77d6 100644
--- a/src/couch/test/eunit/couch_auth_cache_tests.erl
+++ b/src/couch/test/eunit/couch_auth_cache_tests.erl
@@ -52,7 +52,6 @@ couch_auth_cache_test_() ->
fun should_drop_cache_on_auth_db_change/1,
fun should_restore_cache_on_auth_db_change/1,
fun should_recover_cache_after_shutdown/1,
- fun should_close_old_db_on_auth_db_change/1,
fun should_get_admin_from_config/1
]
}
@@ -226,13 +225,6 @@ should_recover_cache_after_shutdown(DbName) ->
?assertEqual(PasswordHash, get_user_doc_password_sha(DbName, "joe"))
end).
-should_close_old_db_on_auth_db_change(DbName) ->
- {timeout, ?DB_TIMEOUT, ?_test(begin
- ?assertEqual(ok, wait_db(DbName, fun is_opened/1)),
- config:set("couch_httpd_auth", "authentication_db",
- ?b2l(?tempdb()), false),
- ?assertEqual(ok, wait_db(DbName, fun is_closed/1))
- end)}.
should_get_admin_from_config(_DbName) ->
?_test(begin
@@ -251,6 +243,7 @@ update_user_doc(DbName, UserName, Password) ->
update_user_doc(DbName, UserName, Password, nil).
update_user_doc(DbName, UserName, Password, Rev) ->
+ ok = couch_auth_cache:ensure_users_db_exists(),
User = iolist_to_binary(UserName),
Doc = couch_doc:from_json_obj({[
{<<"_id">>, <<"org.couchdb.user:", User/binary>>},
@@ -269,17 +262,6 @@ update_user_doc(DbName, UserName, Password, Rev) ->
ok = couch_db:close(AuthDb),
{ok, couch_doc:rev_to_str(NewRev)}.
-wait_db(Db, DbFun) ->
- test_util:wait(fun() ->
- case DbFun(Db) of
- true ->
- ok;
- false ->
- wait
- end
- end, ?DB_TIMEOUT, 500).
-
-
hash_password(Password) ->
?l2b(couch_util:to_hex(crypto:hash(sha, iolist_to_binary([Password, ?SALT])))).
@@ -324,14 +306,6 @@ delete_user_doc(DbName, UserName) ->
{ok, _} = couch_db:update_doc(AuthDb, DeletedDoc, []),
ok = couch_db:close(AuthDb).
-is_opened(DbName) ->
- {ok, AuthDb} = couch_db:open_int(DbName, [?ADMIN_CTX]),
- Monitors = couch_db:monitored_by(AuthDb) -- [self()],
- ok = couch_db:close(AuthDb),
- Monitors /= [].
-
-is_closed(DbName) ->
- not is_opened(DbName).
make_validate_test({Old, New, "ok"} = Case) ->
{test_id(Case), ?_assertEqual(ok, validate(doc(Old), doc(New)))};