Updated Branches: refs/heads/master 573a7bb90 -> 84510eb0d
Clear credentials cache if _users db crashes COUCHDB-1357 Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/84510eb0 Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/84510eb0 Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/84510eb0 Branch: refs/heads/master Commit: 84510eb0d182b99891cce80579ba16cc67e71889 Parents: 573a7bb Author: Filipe David Borba Manana <fdman...@apache.org> Authored: Tue Dec 6 22:21:42 2011 +0000 Committer: Filipe David Borba Manana <fdman...@apache.org> Committed: Fri Dec 16 15:13:39 2011 +0000 ---------------------------------------------------------------------- share/www/script/test/cookie_auth.js | 1 - share/www/script/test/oauth.js | 1 - share/www/script/test/proxyauth.js | 1 - share/www/script/test/reader_acl.js | 1 - share/www/script/test/replicator_db.js | 8 +- src/couchdb/couch_auth_cache.erl | 47 ++--- test/etap/075-auth-cache.t | 272 +++++++++++++++++++++++++++ test/etap/Makefile.am | 1 + 8 files changed, 300 insertions(+), 32 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb/blob/84510eb0/share/www/script/test/cookie_auth.js ---------------------------------------------------------------------- diff --git a/share/www/script/test/cookie_auth.js b/share/www/script/test/cookie_auth.js index 9d98fff..a3fbb7a 100644 --- a/share/www/script/test/cookie_auth.js +++ b/share/www/script/test/cookie_auth.js @@ -34,7 +34,6 @@ couchTests.cookie_auth = function(debug) { // try using an invalid cookie var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); usersDb.deleteDb(); - usersDb.createDb(); // test that the users db is born with the auth ddoc var ddoc = usersDb.open("_design/_auth"); http://git-wip-us.apache.org/repos/asf/couchdb/blob/84510eb0/share/www/script/test/oauth.js ---------------------------------------------------------------------- diff --git a/share/www/script/test/oauth.js b/share/www/script/test/oauth.js index 9915bff..6398f94 100644 --- a/share/www/script/test/oauth.js +++ b/share/www/script/test/oauth.js @@ -125,7 +125,6 @@ couchTests.oauth = function(debug) { "Authorization": adminBasicAuthHeaderValue() }); usersDb.deleteDb(); - usersDb.createDb(); // Create a user var jasonUserDoc = CouchDB.prepareUserDoc({ http://git-wip-us.apache.org/repos/asf/couchdb/blob/84510eb0/share/www/script/test/proxyauth.js ---------------------------------------------------------------------- diff --git a/share/www/script/test/proxyauth.js b/share/www/script/test/proxyauth.js index 40af008..016cec5 100644 --- a/share/www/script/test/proxyauth.js +++ b/share/www/script/test/proxyauth.js @@ -34,7 +34,6 @@ couchTests.proxyauth = function(debug) { function TestFun() { usersDb.deleteDb(); - usersDb.createDb(); db.deleteDb(); db.createDb(); http://git-wip-us.apache.org/repos/asf/couchdb/blob/84510eb0/share/www/script/test/reader_acl.js ---------------------------------------------------------------------- diff --git a/share/www/script/test/reader_acl.js b/share/www/script/test/reader_acl.js index e662851..e0dde2c 100644 --- a/share/www/script/test/reader_acl.js +++ b/share/www/script/test/reader_acl.js @@ -18,7 +18,6 @@ couchTests.reader_acl = function(debug) { function testFun() { try { usersDb.deleteDb(); - usersDb.createDb(); secretDb.deleteDb(); secretDb.createDb(); http://git-wip-us.apache.org/repos/asf/couchdb/blob/84510eb0/share/www/script/test/replicator_db.js ---------------------------------------------------------------------- diff --git a/share/www/script/test/replicator_db.js b/share/www/script/test/replicator_db.js index e35635e..5eedc10 100644 --- a/share/www/script/test/replicator_db.js +++ b/share/www/script/test/replicator_db.js @@ -88,8 +88,10 @@ couchTests.replicator_db = function(debug) { function populate_db(db, docs) { - db.deleteDb(); - db.createDb(); + if (db.name !== usersDb.name) { + db.deleteDb(); + db.createDb(); + } for (var i = 0; i < docs.length; i++) { var d = docs[i]; delete d._rev; @@ -1486,10 +1488,12 @@ couchTests.replicator_db = function(debug) { ]); repDb.deleteDb(); + usersDb.deleteDb(); restartServer(); run_on_modified_server(server_config_2, test_user_ctx_validation); repDb.deleteDb(); + usersDb.deleteDb(); restartServer(); run_on_modified_server(server_config_2, test_replication_credentials_delegation); http://git-wip-us.apache.org/repos/asf/couchdb/blob/84510eb0/src/couchdb/couch_auth_cache.erl ---------------------------------------------------------------------- diff --git a/src/couchdb/couch_auth_cache.erl b/src/couchdb/couch_auth_cache.erl index 97dc7da..94adac8 100644 --- a/src/couchdb/couch_auth_cache.erl +++ b/src/couchdb/couch_auth_cache.erl @@ -30,7 +30,8 @@ -record(state, { max_cache_size = 0, cache_size = 0, - db_notifier = nil + db_notifier = nil, + db_mon_ref = nil }). @@ -103,16 +104,13 @@ 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 = couch_config:get("couch_httpd_auth", "authentication_db"), - true = ets:insert(?STATE, {auth_db_name, ?l2b(AuthDbName)}), - true = ets:insert(?STATE, {auth_db, open_auth_db()}), process_flag(trap_exit, true), ok = couch_config:register( fun("couch_httpd_auth", "auth_cache_size", SizeList) -> Size = list_to_integer(SizeList), ok = gen_server:call(?MODULE, {new_max_cache_size, Size}, infinity); - ("couch_httpd_auth", "authentication_db", DbName) -> - ok = gen_server:call(?MODULE, {new_auth_db, ?l2b(DbName)}, infinity) + ("couch_httpd_auth", "authentication_db", _DbName) -> + ok = gen_server:call(?MODULE, reinit_cache, infinity) end ), {ok, Notifier} = couch_db_update_notifier:start_link(fun handle_db_event/1), @@ -122,7 +120,7 @@ init(_) -> couch_config:get("couch_httpd_auth", "auth_cache_size", "50") ) }, - {ok, State}. + {ok, reinit_cache(State)}. handle_db_event({Event, DbName}) -> @@ -130,8 +128,7 @@ handle_db_event({Event, DbName}) -> case DbName =:= AuthDbName of true -> case Event of - deleted -> gen_server:call(?MODULE, auth_db_deleted, infinity); - created -> gen_server:call(?MODULE, auth_db_created, infinity); + created -> gen_server:call(?MODULE, reinit_cache, infinity); compacted -> gen_server:call(?MODULE, auth_db_compacted, infinity); _Else -> ok end; @@ -140,21 +137,10 @@ handle_db_event({Event, DbName}) -> end. -handle_call({new_auth_db, AuthDbName}, _From, State) -> - NewState = clear_cache(State), - true = ets:insert(?STATE, {auth_db_name, AuthDbName}), - true = ets:insert(?STATE, {auth_db, open_auth_db()}), - {reply, ok, NewState}; - -handle_call(auth_db_deleted, _From, State) -> - NewState = clear_cache(State), - true = ets:insert(?STATE, {auth_db, nil}), - {reply, ok, NewState}; - -handle_call(auth_db_created, _From, State) -> - NewState = clear_cache(State), - true = ets:insert(?STATE, {auth_db, open_auth_db()}), - {reply, ok, NewState}; +handle_call(reinit_cache, _From, State) -> + catch erlang:demonitor(State#state.db_mon_ref, [flush]), + 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( @@ -201,8 +187,8 @@ handle_cast({cache_hit, UserName}, State) -> {noreply, State}. -handle_info(_Msg, State) -> - {noreply, State}. +handle_info({'DOWN', Ref, _, _, _Reason}, #state{db_mon_ref = Ref} = State) -> + {noreply, reinit_cache(State)}. terminate(_Reason, #state{db_notifier = Notifier}) -> @@ -224,6 +210,15 @@ clear_cache(State) -> State#state{cache_size = 0}. +reinit_cache(State) -> + NewState = clear_cache(State), + AuthDbName = ?l2b(couch_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}), + NewState#state{db_mon_ref = couch_db:monitor(AuthDb)}. + + add_cache_entry(_, _, _, #state{max_cache_size = 0} = State) -> State; add_cache_entry(UserName, Credentials, ATime, State) -> http://git-wip-us.apache.org/repos/asf/couchdb/blob/84510eb0/test/etap/075-auth-cache.t ---------------------------------------------------------------------- diff --git a/test/etap/075-auth-cache.t b/test/etap/075-auth-cache.t new file mode 100755 index 0000000..5166c5a --- /dev/null +++ b/test/etap/075-auth-cache.t @@ -0,0 +1,272 @@ +#!/usr/bin/env escript +%% -*- erlang -*- +% Licensed under the Apache License, Version 2.0 (the "License"); you may not +% use this file except in compliance with the License. You may obtain a copy of +% the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +% License for the specific language governing permissions and limitations under +% the License. + +-record(user_ctx, { + name = null, + roles = [], + handler +}). + +-record(db, { + main_pid = nil, + update_pid = nil, + compactor_pid = nil, + instance_start_time, % number of microsecs since jan 1 1970 as a binary string + fd, + updater_fd, + fd_ref_counter, + header, + committed_update_seq, + fulldocinfo_by_id_btree, + docinfo_by_seq_btree, + local_docs_btree, + update_seq, + name, + filepath, + validate_doc_funs = [], + security = [], + security_ptr = nil, + user_ctx = #user_ctx{}, + waiting_delayed_commit = nil, + revs_limit = 1000, + fsync_options = [], + options = [], + compression +}). + +auth_db_name() -> <<"couch_test_auth_db">>. +auth_db_2_name() -> <<"couch_test_auth_db_2">>. +salt() -> <<"SALT">>. + + +main(_) -> + test_util:init_code_path(), + + etap:plan(19), + case (catch test()) of + ok -> + etap:end_tests(); + Other -> + etap:diag(io_lib:format("Test died abnormally: ~p", [Other])), + etap:bail(Other) + end, + ok. + + +test() -> + couch_server_sup:start_link(test_util:config_files()), + OrigName = couch_config:get("couch_httpd_auth", "authentication_db"), + couch_config:set( + "couch_httpd_auth", "authentication_db", + binary_to_list(auth_db_name()), false), + + test_auth_db_crash(), + + couch_config:set("couch_httpd_auth", "authentication_db", OrigName, false), + delete_db(auth_db_name()), + delete_db(auth_db_2_name()), + couch_server_sup:stop(), + ok. + + +test_auth_db_crash() -> + Creds0 = couch_auth_cache:get_user_creds("joe"), + etap:is(Creds0, nil, "Got nil when getting joe's credentials"), + + etap:diag("Adding first version of Joe's user doc"), + PasswordHash1 = hash_password("pass1"), + {ok, Rev1} = update_user_doc(auth_db_name(), "joe", "pass1"), + + Creds1 = couch_auth_cache:get_user_creds("joe"), + etap:is(is_list(Creds1), true, "Got joe's credentials from cache"), + etap:is(couch_util:get_value(<<"password_sha">>, Creds1), PasswordHash1, + "Cached credentials have the right password"), + + etap:diag("Updating Joe's user doc password"), + PasswordHash2 = hash_password("pass2"), + {ok, _Rev2} = update_user_doc(auth_db_name(), "joe", "pass2", Rev1), + + Creds2 = couch_auth_cache:get_user_creds("joe"), + etap:is(is_list(Creds2), true, "Got joe's credentials from cache"), + etap:is(couch_util:get_value(<<"password_sha">>, Creds2), PasswordHash2, + "Cached credentials have the new password"), + + etap:diag("Shutting down the auth database process"), + shutdown_db(auth_db_name()), + + {ok, UpdateRev} = get_doc_rev(auth_db_name(), "joe"), + PasswordHash3 = hash_password("pass3"), + {ok, _Rev3} = update_user_doc(auth_db_name(), "joe", "pass3", UpdateRev), + + etap:is(get_user_doc_password_sha(auth_db_name(), "joe"), + PasswordHash3, + "Latest Joe's doc revision has the new password hash"), + + Creds3 = couch_auth_cache:get_user_creds("joe"), + etap:is(is_list(Creds3), true, "Got joe's credentials from cache"), + etap:is(couch_util:get_value(<<"password_sha">>, Creds3), PasswordHash3, + "Cached credentials have the new password"), + + etap:diag("Deleting Joe's user doc"), + delete_user_doc(auth_db_name(), "joe"), + Creds4 = couch_auth_cache:get_user_creds("joe"), + etap:is(nil, Creds4, + "Joe's credentials not found in cache after user doc was deleted"), + + etap:diag("Adding new user doc for Joe"), + PasswordHash5 = hash_password("pass5"), + {ok, _NewRev1} = update_user_doc(auth_db_name(), "joe", "pass5"), + + Creds5 = couch_auth_cache:get_user_creds("joe"), + etap:is(is_list(Creds5), true, "Got joe's credentials from cache"), + etap:is(couch_util:get_value(<<"password_sha">>, Creds5), PasswordHash5, + "Cached credentials have the right password"), + + full_commit(auth_db_name()), + + etap:diag("Changing the auth database"), + couch_config:set( + "couch_httpd_auth", "authentication_db", + binary_to_list(auth_db_2_name()), false), + ok = timer:sleep(500), + + Creds6 = couch_auth_cache:get_user_creds("joe"), + etap:is(nil, Creds6, + "Joe's credentials not found in cache after auth database changed"), + + etap:diag("Adding first version of Joe's user doc to new auth database"), + PasswordHash7 = hash_password("pass7"), + {ok, _} = update_user_doc(auth_db_2_name(), "joe", "pass7"), + + Creds7 = couch_auth_cache:get_user_creds("joe"), + etap:is(is_list(Creds7), true, "Got joe's credentials from cache"), + etap:is(couch_util:get_value(<<"password_sha">>, Creds7), PasswordHash7, + "Cached credentials have the right password"), + + etap:diag("Shutting down the auth database process"), + shutdown_db(auth_db_2_name()), + + {ok, UpdateRev2} = get_doc_rev(auth_db_2_name(), "joe"), + PasswordHash8 = hash_password("pass8"), + {ok, _Rev8} = update_user_doc(auth_db_2_name(), "joe", "pass8", UpdateRev2), + + etap:is(get_user_doc_password_sha(auth_db_2_name(), "joe"), + PasswordHash8, + "Latest Joe's doc revision has the new password hash"), + + Creds8 = couch_auth_cache:get_user_creds("joe"), + etap:is(is_list(Creds8), true, "Got joe's credentials from cache"), + etap:is(couch_util:get_value(<<"password_sha">>, Creds8), PasswordHash8, + "Cached credentials have the new password"), + + etap:diag("Changing the auth database again"), + couch_config:set( + "couch_httpd_auth", "authentication_db", + binary_to_list(auth_db_name()), false), + ok = timer:sleep(500), + + Creds9 = couch_auth_cache:get_user_creds("joe"), + etap:is(Creds9, Creds5, + "Got same credentials as before the firt auth database change"), + etap:is(couch_util:get_value(<<"password_sha">>, Creds9), PasswordHash5, + "Cached credentials have the right password"), + ok. + + +update_user_doc(DbName, UserName, Password) -> + update_user_doc(DbName, UserName, Password, nil). + +update_user_doc(DbName, UserName, Password, Rev) -> + User = iolist_to_binary(UserName), + Doc = couch_doc:from_json_obj({[ + {<<"_id">>, <<"org.couchdb.user:", User/binary>>}, + {<<"name">>, User}, + {<<"type">>, <<"user">>}, + {<<"salt">>, salt()}, + {<<"password_sha">>, hash_password(Password)}, + {<<"roles">>, []} + ] ++ case Rev of + nil -> []; + _ -> [{<<"_rev">>, Rev}] + end}), + {ok, AuthDb} = open_auth_db(DbName), + {ok, NewRev} = couch_db:update_doc(AuthDb, Doc, []), + ok = couch_db:close(AuthDb), + {ok, couch_doc:rev_to_str(NewRev)}. + + +hash_password(Password) -> + list_to_binary( + couch_util:to_hex(crypto:sha(iolist_to_binary([Password, salt()])))). + + +shutdown_db(DbName) -> + {ok, AuthDb} = open_auth_db(DbName), + ok = couch_db:close(AuthDb), + couch_util:shutdown_sync(AuthDb#db.main_pid), + ok = timer:sleep(1000). + + +get_doc_rev(DbName, UserName) -> + DocId = iolist_to_binary([<<"org.couchdb.user:">>, UserName]), + {ok, AuthDb} = open_auth_db(DbName), + UpdateRev = + case couch_db:open_doc(AuthDb, DocId, []) of + {ok, Doc} -> + {Props} = couch_doc:to_json_obj(Doc, []), + couch_util:get_value(<<"_rev">>, Props); + {not_found, missing} -> + nil + end, + ok = couch_db:close(AuthDb), + {ok, UpdateRev}. + + +get_user_doc_password_sha(DbName, UserName) -> + DocId = iolist_to_binary([<<"org.couchdb.user:">>, UserName]), + {ok, AuthDb} = open_auth_db(DbName), + {ok, Doc} = couch_db:open_doc(AuthDb, DocId, []), + ok = couch_db:close(AuthDb), + {Props} = couch_doc:to_json_obj(Doc, []), + couch_util:get_value(<<"password_sha">>, Props). + + +delete_user_doc(DbName, UserName) -> + DocId = iolist_to_binary([<<"org.couchdb.user:">>, UserName]), + {ok, AuthDb} = open_auth_db(DbName), + {ok, Doc} = couch_db:open_doc(AuthDb, DocId, []), + {Props} = couch_doc:to_json_obj(Doc, []), + DeletedDoc = couch_doc:from_json_obj({[ + {<<"_id">>, DocId}, + {<<"_rev">>, couch_util:get_value(<<"_rev">>, Props)}, + {<<"_deleted">>, true} + ]}), + {ok, _} = couch_db:update_doc(AuthDb, DeletedDoc, []), + ok = couch_db:close(AuthDb). + + +full_commit(DbName) -> + {ok, AuthDb} = open_auth_db(DbName), + {ok, _} = couch_db:ensure_full_commit(AuthDb), + ok = couch_db:close(AuthDb). + + +open_auth_db(DbName) -> + couch_db:open_int( + DbName, [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}]). + + +delete_db(Name) -> + ok = couch_server:delete( + Name, [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}]). http://git-wip-us.apache.org/repos/asf/couchdb/blob/84510eb0/test/etap/Makefile.am ---------------------------------------------------------------------- diff --git a/test/etap/Makefile.am b/test/etap/Makefile.am index fe5d838..2eb4e6a 100644 --- a/test/etap/Makefile.am +++ b/test/etap/Makefile.am @@ -57,6 +57,7 @@ EXTRA_DIST = \ 072-cleanup.t \ 073-changes.t \ 074-doc-update-conflicts.t \ + 075-auth-cache.t \ 080-config-get-set.t \ 081-config-override.1.ini \ 081-config-override.2.ini \