diff options
Diffstat (limited to 'src/chttpd/src/chttpd_auth_cache.erl')
-rw-r--r-- | src/chttpd/src/chttpd_auth_cache.erl | 86 |
1 files changed, 71 insertions, 15 deletions
diff --git a/src/chttpd/src/chttpd_auth_cache.erl b/src/chttpd/src/chttpd_auth_cache.erl index fdae27b79..c5a56bddb 100644 --- a/src/chttpd/src/chttpd_auth_cache.erl +++ b/src/chttpd/src/chttpd_auth_cache.erl @@ -12,16 +12,19 @@ -module(chttpd_auth_cache). -behaviour(gen_server). +-behaviour(config_listener). -export([start_link/0, get_user_creds/2, update_user_creds/3]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -export([listen_for_changes/1, changes_callback/2]). +-export([handle_config_change/5, handle_config_terminate/3]). -include_lib("couch/include/couch_db.hrl"). -include_lib("couch/include/couch_js_functions.hrl"). -define(CACHE, chttpd_auth_cache_lru). +-define(RELISTEN_DELAY, 5000). -record(state, { changes_pid, @@ -52,7 +55,8 @@ get_user_creds(_Req, UserName) when is_binary(UserName) -> update_user_creds(_Req, UserDoc, _Ctx) -> {_, Ref} = spawn_monitor(fun() -> - case fabric:update_doc(dbname(), UserDoc, []) of + {ok, Db} = fabric2_db:open(dbname(), [?ADMIN_CTX]), + case fabric2_db:update_doc(Db, UserDoc) of {ok, _} -> exit(ok); Else -> @@ -100,9 +104,28 @@ maybe_increment_auth_cache_miss(UserName) -> %% gen_server callbacks init([]) -> + ensure_auth_db(), + ok = config:listen_for_changes(?MODULE, nil), self() ! {start_listener, 0}, {ok, #state{}}. +handle_call(reinit_cache, _From, State) -> + #state{ + changes_pid = Pid + } = State, + + % The database may currently be cached. This + % ensures that we've removed it so that the + % system db callbacks are installed. + fabric2_server:remove(dbname()), + + ensure_auth_db(), + ets_lru:clear(?CACHE), + exit(Pid, shutdown), + self() ! {start_listener, 0}, + + {reply, ok, State#state{changes_pid = undefined}}; + handle_call(_Call, _From, State) -> {noreply, State}. @@ -124,6 +147,9 @@ handle_info({'DOWN', _, _, Pid, Reason}, #state{changes_pid=Pid} = State) -> {noreply, State#state{last_seq=Seq}}; handle_info({start_listener, Seq}, State) -> {noreply, State#state{changes_pid = spawn_changes(Seq)}}; +handle_info(restart_config_listener, State) -> + ok = config:listen_for_changes(?MODULE, nil), + {noreply, State}; handle_info(_Msg, State) -> {noreply, State}. @@ -142,7 +168,8 @@ spawn_changes(Since) -> Pid. listen_for_changes(Since) -> - ensure_auth_ddoc_exists(dbname(), <<"_design/_auth">>), + {ok, Db} = fabric2_db:open(dbname(), [?ADMIN_CTX]), + ensure_auth_ddoc_exists(Db, <<"_design/_auth">>), CBFun = fun ?MODULE:changes_callback/2, Args = #changes_args{ feed = "continuous", @@ -150,7 +177,8 @@ listen_for_changes(Since) -> heartbeat = true, filter = {default, main_only} }, - fabric:changes(dbname(), CBFun, Since, Args). + ChangesFun = chttpd_changes:handle_db_changes(Args, nil, Db), + ChangesFun({CBFun, Since}). changes_callback(waiting_for_updates, Acc) -> {ok, Acc}; @@ -159,7 +187,7 @@ changes_callback(start, Since) -> changes_callback({stop, EndSeq, _Pending}, _) -> exit({seq, EndSeq}); changes_callback({change, {Change}}, _) -> - case couch_util:get_value(id, Change) of + case couch_util:get_value(<<"id">>, Change) of <<"_design/", _/binary>> -> ok; DocId -> @@ -168,13 +196,27 @@ changes_callback({change, {Change}}, _) -> ets_lru:remove(?CACHE, UserName) end, {ok, couch_util:get_value(seq, Change)}; -changes_callback(timeout, Acc) -> +changes_callback({timeout, _ResponseType}, Acc) -> {ok, Acc}; changes_callback({error, _}, EndSeq) -> exit({seq, EndSeq}). + +handle_config_change("chttpd_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) -> + Dst = whereis(?MODULE), + erlang:send_after(?RELISTEN_DELAY, Dst, restart_config_listener). + + load_user_from_db(UserName) -> - try fabric:open_doc(dbname(), docid(UserName), [?ADMIN_CTX, ejson_body, conflicts]) of + {ok, Db} = fabric2_db:open(dbname(), [?ADMIN_CTX]), + try fabric2_db:open_doc(Db, docid(UserName), [conflicts]) of {ok, Doc} -> {Props} = couch_doc:to_json_obj(Doc, []), Props; @@ -185,8 +227,21 @@ load_user_from_db(UserName) -> nil end. + +ensure_auth_db() -> + try + fabric2_db:open(dbname(), [?ADMIN_CTX]) + catch error:database_does_not_exist -> + case fabric2_db:create(dbname(), [?ADMIN_CTX]) of + {ok, _} -> ok; + {error, file_exists} -> ok + end + end. + + dbname() -> - config:get("chttpd_auth", "authentication_db", "_users"). + DbNameStr = config:get("chttpd_auth", "authentication_db", "_users"), + iolist_to_binary(DbNameStr). docid(UserName) -> <<"org.couchdb.user:", UserName/binary>>. @@ -194,11 +249,11 @@ docid(UserName) -> username(<<"org.couchdb.user:", UserName/binary>>) -> UserName. -ensure_auth_ddoc_exists(DbName, DDocId) -> - case fabric:open_doc(DbName, DDocId, [?ADMIN_CTX, ejson_body]) of +ensure_auth_ddoc_exists(Db, DDocId) -> + case fabric2_db:open_doc(Db, DDocId) of {not_found, _Reason} -> {ok, AuthDesign} = couch_auth_cache:auth_design_doc(DDocId), - update_doc_ignoring_conflict(DbName, AuthDesign, [?ADMIN_CTX]); + update_doc_ignoring_conflict(Db, AuthDesign); {ok, Doc} -> {Props} = couch_doc:to_json_obj(Doc, []), case couch_util:get_value(<<"validate_doc_update">>, Props, []) of @@ -208,19 +263,20 @@ ensure_auth_ddoc_exists(DbName, DDocId) -> Props1 = lists:keyreplace(<<"validate_doc_update">>, 1, Props, {<<"validate_doc_update">>, ?AUTH_DB_DOC_VALIDATE_FUNCTION}), - update_doc_ignoring_conflict(DbName, couch_doc:from_json_obj({Props1}), [?ADMIN_CTX]) + NewDoc = couch_doc:from_json_obj({Props1}), + update_doc_ignoring_conflict(Db, NewDoc) end; {error, Reason} -> - couch_log:notice("Failed to ensure auth ddoc ~s/~s exists for reason: ~p", [DbName, DDocId, Reason]), + couch_log:notice("Failed to ensure auth ddoc ~s/~s exists for reason: ~p", [dbname(), DDocId, Reason]), ok end, ok. -update_doc_ignoring_conflict(DbName, Doc, Options) -> +update_doc_ignoring_conflict(DbName, Doc) -> try - fabric:update_doc(DbName, Doc, Options) + fabric2_db:update_doc(DbName, Doc) catch - throw:conflict -> + error:conflict -> ok end. |