You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2020/02/06 20:44:13 UTC

[couchdb] branch master updated: Simplify couch_auth_cache

This is an automated email from the ASF dual-hosted git repository.

vatamane 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 27bb450  Simplify couch_auth_cache
27bb450 is described below

commit 27bb45043435828915bdcbdc130b685e5533bbd8
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Thu Feb 6 12:03:28 2020 -0500

    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
---
 src/couch/src/couch_auth_cache.erl              | 383 +++---------------------
 src/couch/src/couch_secondary_sup.erl           |   3 +-
 src/couch/test/eunit/couch_auth_cache_tests.erl |  28 +-
 test/javascript/tests/reader_acl.js             |   8 +-
 test/javascript/tests/security_validation.js    |   7 +-
 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 157b090..c564cee 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 9c7d414..bb78215 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 5439dd7..71faf77 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 3966b64..8dc28aa 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 128b90b..6f0bd0f 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
 };