summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@apache.org>2020-02-06 12:03:28 -0500
committerNick Vatamaniuc <nickva@users.noreply.github.com>2020-02-06 15:43:55 -0500
commit27bb45043435828915bdcbdc130b685e5533bbd8 (patch)
tree2228faf0b61b372918fb1ff51d3f7aac66e33e2b
parent9729ab3c6b92e35b2c93ac4274d07801d90662cc (diff)
downloadcouchdb-27bb45043435828915bdcbdc130b685e5533bbd8.tar.gz
Simplify 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
-rw-r--r--test/javascript/tests/reader_acl.js8
-rw-r--r--test/javascript/tests/security_validation.js7
5 files changed, 45 insertions, 384 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)))};
diff --git a/test/javascript/tests/reader_acl.js b/test/javascript/tests/reader_acl.js
index 3966b64cb..8dc28aae9 100644
--- a/test/javascript/tests/reader_acl.js
+++ b/test/javascript/tests/reader_acl.js
@@ -215,11 +215,7 @@ couchTests.reader_acl = function(debug) {
);
usersDb.deleteDb();
- // have to delete the backside version now too :(
- var req = CouchDB.newXhr();
- req.open("DELETE", "/_node/node1@127.0.0.1/" + users_db_name, false);
- req.send("");
- CouchDB.maybeThrowError(req);
-
+ // don't have to delete the backside db since in this case couch_auth_cache only read
+ // admin from the config section and so it never auto-created the node local db
secretDb.deleteDb();
}
diff --git a/test/javascript/tests/security_validation.js b/test/javascript/tests/security_validation.js
index 128b90b8b..6f0bd0f42 100644
--- a/test/javascript/tests/security_validation.js
+++ b/test/javascript/tests/security_validation.js
@@ -325,9 +325,6 @@ couchTests.security_validation = function(debug) {
adminDbB.deleteDb();
}
authDb.deleteDb();
- // have to clean up authDb on the backside :(
- var req = CouchDB.newXhr();
- req.open("DELETE", "/_node/node1@127.0.0.1/" + authDb_name, false);
- req.send("");
- CouchDB.maybeThrowError(req);
+ // don't have to clean the backend authDb since this test only calls
+ // couch_auth_cache:get_admin/1 which doesn't auto-create the users db
};