diff options
Diffstat (limited to 'src/couch_peruser/test/eunit/couch_peruser_test.erl')
-rw-r--r-- | src/couch_peruser/test/eunit/couch_peruser_test.erl | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/src/couch_peruser/test/eunit/couch_peruser_test.erl b/src/couch_peruser/test/eunit/couch_peruser_test.erl new file mode 100644 index 000000000..8501cc36f --- /dev/null +++ b/src/couch_peruser/test/eunit/couch_peruser_test.erl @@ -0,0 +1,519 @@ +% 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. + +-module(couch_peruser_test). + +-include_lib("couch/include/couch_eunit.hrl"). +-include_lib("couch/include/couch_db.hrl"). + +-define(ADMIN_USERNAME, "admin"). +-define(ADMIN_PASSWORD, "secret"). + +-define(WAIT_FOR_USER_DELETE_TIMEOUT, 3000). + +setup_all() -> + TestCtx = test_util:start_couch([chttpd]), + ok = application:start(couch_peruser), + Hashed = couch_passwords:hash_admin_password(?ADMIN_PASSWORD), + ok = config:set("admins", ?ADMIN_USERNAME, ?b2l(Hashed), _Persist=false), + TestCtx. + +teardown_all(TestCtx) -> + config:delete("admins", ?ADMIN_USERNAME), + ok = application:stop(couch_peruser), + test_util:stop_couch(TestCtx). + +setup() -> + TestAuthDb = ?tempdb(), + do_request(put, get_base_url() ++ "/" ++ ?b2l(TestAuthDb)), + do_request(put, get_cluster_base_url() ++ "/" ++ ?b2l(TestAuthDb)), + set_config("couch_httpd_auth", "authentication_db", ?b2l(TestAuthDb)), + set_config("couch_peruser", "cluster_quiet_period", "1"), + set_config("couch_peruser", "cluster_start_period", "1"), + set_config("couch_peruser", "enable", "true"), + set_config("cluster", "n", "1"), + TestAuthDb. + +teardown(TestAuthDb) -> + set_config("couch_peruser", "enable", "false"), + set_config("couch_peruser", "delete_dbs", "false"), + set_config("couch_httpd_auth", "authentication_db", "_users"), + set_config("couch_peruser", "cluster_quiet_period", "60"), + set_config("couch_peruser", "cluster_start_period", "5"), + set_config("cluster", "n", "3"), + do_request(delete, get_cluster_base_url() ++ "/" ++ ?b2l(TestAuthDb)), + do_request(delete, get_base_url() ++ "/" ++ ?b2l(TestAuthDb)), + lists:foreach(fun(DbName) -> + case binary:part(DbName, 0, 7) of + <<"userdb-">> -> delete_db(DbName); + _ -> ok + end + end, all_dbs()). + +set_config(Section, Key, Value) -> + ok = config:set(Section, Key, Value, _Persist=false). + +delete_config(Section, Key) -> + ok = config:delete(Section, Key, _Persist=false). + +do_request(Method, Url) -> + Headers = [{basic_auth, {?ADMIN_USERNAME, ?ADMIN_PASSWORD}}], + {ok, _, _, _} = test_request:request(Method, Url, Headers). + +do_request(Method, Url, Body) -> + Headers = [ + {basic_auth, {?ADMIN_USERNAME, ?ADMIN_PASSWORD}}, + {"Content-Type", "application/json"}], + {ok, _, _, _} = test_request:request(Method, Url, Headers, Body). + +do_anon_request(Method, Url, Body) -> + Headers = [ + {"Content-Type", "application/json"}], + {ok, _, _, _} = test_request:request(Method, Url, Headers, Body). + +create_db(DbName) -> + {ok, _, _, _} = do_request(put, get_cluster_base_url() ++ "/" ++ ?b2l(DbName)). + +delete_db(DbName) -> + {ok, _, _, _} = do_request(delete, get_cluster_base_url() ++ "/" ++ ?b2l(DbName)). + +create_user(AuthDb, Name) -> + Body = "{\"name\":\"" ++ Name ++ + "\",\"type\":\"user\",\"roles\":[],\"password\":\"secret\"}", + Url = lists:concat([ + get_cluster_base_url(), "/", ?b2l(AuthDb), "/org.couchdb.user:", Name]), + {ok, 201, _, _} = do_request(put, Url, Body). + +create_anon_user(AuthDb, Name) -> + Body = "{\"name\":\"" ++ Name ++ + "\",\"type\":\"user\",\"roles\":[],\"password\":\"secret\"}", + Url = lists:concat([ + get_cluster_base_url(), "/", ?b2l(AuthDb), "/org.couchdb.user:", Name]), + {ok, 201, _, _} = do_anon_request(put, Url, Body). + +delete_user(AuthDb, Name) -> + Url = lists:concat([get_cluster_base_url(), "/", ?b2l(AuthDb), + "/org.couchdb.user:", Name]), + {ok, 200, _, Body} = do_request(get, Url), + {DocProps} = jiffy:decode(Body), + Rev = proplists:get_value(<<"_rev">>, DocProps), + {ok, 200, _, _} = do_request(delete, Url ++ "?rev=" ++ ?b2l(Rev)). + +get_security(DbName) -> + Url = lists:concat([ + get_cluster_base_url(), "/", ?b2l(DbName), "/_security"]), + test_util:wait(fun() -> + {ok, 200, _, Body} = do_request(get, Url), + case jiffy:decode(Body) of + {[]} -> wait; + {SecurityProperties} -> SecurityProperties + end + end). + +set_security(DbName, SecurityProperties) -> + Url = lists:concat([ + get_cluster_base_url(), "/", ?b2l(DbName), "/_security"]), + Body = jiffy:encode({SecurityProperties}), + {ok, 200, _, _} = do_request(put, Url, Body). + +all_dbs() -> + {ok, 200, _, Body} = do_request(get, get_cluster_base_url() ++ "/_all_dbs"), + jiffy:decode(Body). + +all_dbs_with_errors() -> + {Result, StatusCode, _Headers, Body} = do_request(get, get_cluster_base_url() ++ "/_all_dbs"), + {Result, StatusCode, _Headers, jiffy:decode(Body)}. + +get_base_url() -> + Addr = config:get("httpd", "bind_address", "127.0.0.1"), + Port = integer_to_list(mochiweb_socket_server:get(couch_httpd, port)), + "http://" ++ Addr ++ ":" ++ Port. + +get_cluster_base_url() -> + Addr = config:get("httpd", "bind_address", "127.0.0.1"), + Port = integer_to_list(mochiweb_socket_server:get(chttpd, port)), + "http://" ++ Addr ++ ":" ++ Port. + + +should_create_user_db_with_default(TestAuthDb) -> + create_user(TestAuthDb, "foo"), + wait_for_db_create(<<"userdb-666f6f">>), + {ok, DbInfo} = fabric:get_db_info(<<"userdb-666f6f">>), + {ClusterInfo} = couch_util:get_value(cluster, DbInfo), + [ + ?_assert(lists:member(<<"userdb-666f6f">>, all_dbs())), + ?_assertEqual(1, couch_util:get_value(q, ClusterInfo)) + ]. + +should_create_user_db_with_custom_prefix(TestAuthDb) -> + set_config("couch_peruser", "database_prefix", "newuserdb-"), + create_user(TestAuthDb, "fooo"), + wait_for_db_create(<<"newuserdb-666f6f6f">>), + delete_config("couch_peruser", "database_prefix"), + ?_assert(lists:member(<<"newuserdb-666f6f6f">>, all_dbs())). + +should_create_user_db_with_custom_special_prefix(TestAuthDb) -> + set_config("couch_peruser", "database_prefix", "userdb_$()+--/"), + create_user(TestAuthDb, "fooo"), + wait_for_db_create(<<"userdb_$()+--/666f6f6f">>), + delete_config("couch_peruser", "database_prefix"), + ?_assert(lists:member(<<"userdb_$()+--/666f6f6f">>, all_dbs())). + +should_create_anon_user_db_with_default(TestAuthDb) -> + create_anon_user(TestAuthDb, "fooo"), + wait_for_db_create(<<"userdb-666f6f6f">>), + {ok, DbInfo} = fabric:get_db_info(<<"userdb-666f6f6f">>), + {ClusterInfo} = couch_util:get_value(cluster, DbInfo), + [ + ?_assert(lists:member(<<"userdb-666f6f6f">>, all_dbs())), + ?_assertEqual(1, couch_util:get_value(q, ClusterInfo)) + ]. + +should_create_anon_user_db_with_custom_prefix(TestAuthDb) -> + set_config("couch_peruser", "database_prefix", "newuserdb-"), + create_anon_user(TestAuthDb, "fooo"), + wait_for_db_create(<<"newuserdb-666f6f6f">>), + delete_config("couch_peruser", "database_prefix"), + ?_assert(lists:member(<<"newuserdb-666f6f6f">>, all_dbs())). + +should_create_anon_user_db_with_custom_special_prefix(TestAuthDb) -> + set_config("couch_peruser", "database_prefix", "userdb_$()+--/"), + create_anon_user(TestAuthDb, "fooo"), + wait_for_db_create(<<"userdb_$()+--/666f6f6f">>), + delete_config("couch_peruser", "database_prefix"), + ?_assert(lists:member(<<"userdb_$()+--/666f6f6f">>, all_dbs())). + +should_create_user_db_with_q4(TestAuthDb) -> + set_config("couch_peruser", "q", "4"), + create_user(TestAuthDb, "foo"), + wait_for_db_create(<<"userdb-666f6f">>), + {ok, DbInfo} = fabric:get_db_info(<<"userdb-666f6f">>), + {ClusterInfo} = couch_util:get_value(cluster, DbInfo), + delete_config("couch_peruser", "q"), + [ + ?_assert(lists:member(<<"userdb-666f6f">>, all_dbs())), + ?_assertEqual(4, couch_util:get_value(q, ClusterInfo)) + ]. + +should_create_anon_user_db_with_q4(TestAuthDb) -> + set_config("couch_peruser", "q", "4"), + create_anon_user(TestAuthDb, "fooo"), + wait_for_db_create(<<"userdb-666f6f6f">>), + {ok, TargetInfo} = fabric:get_db_info(<<"userdb-666f6f6f">>), + {ClusterInfo} = couch_util:get_value(cluster, TargetInfo), + delete_config("couch_peruser", "q"), + [ + ?_assert(lists:member(<<"userdb-666f6f6f">>, all_dbs())), + ?_assertEqual(4, couch_util:get_value(q, ClusterInfo)) + ]. + +should_not_delete_user_db(TestAuthDb) -> + User = "foo", + UserDbName = <<"userdb-666f6f">>, + create_user(TestAuthDb, User), + wait_for_db_create(<<"userdb-666f6f">>), + AfterCreate = lists:member(UserDbName, all_dbs()), + delete_user(TestAuthDb, User), + timer:sleep(?WAIT_FOR_USER_DELETE_TIMEOUT), + AfterDelete = lists:member(UserDbName, all_dbs()), + [?_assert(AfterCreate), ?_assert(AfterDelete)]. + +should_delete_user_db(TestAuthDb) -> + User = "bar", + UserDbName = <<"userdb-626172">>, + set_config("couch_peruser", "delete_dbs", "true"), + create_user(TestAuthDb, User), + wait_for_db_create(UserDbName), + AfterCreate = lists:member(UserDbName, all_dbs()), + delete_user(TestAuthDb, User), + wait_for_db_delete(UserDbName), + AfterDelete = lists:member(UserDbName, all_dbs()), + [?_assert(AfterCreate), ?_assertNot(AfterDelete)]. + +should_delete_user_db_with_custom_prefix(TestAuthDb) -> + User = "bar", + UserDbName = <<"newuserdb-626172">>, + set_config("couch_peruser", "delete_dbs", "true"), + set_config("couch_peruser", "database_prefix", "newuserdb-"), + create_user(TestAuthDb, User), + wait_for_db_create(UserDbName), + AfterCreate = lists:member(UserDbName, all_dbs()), + delete_user(TestAuthDb, User), + wait_for_db_delete(UserDbName), + delete_config("couch_peruser", "database_prefix"), + AfterDelete = lists:member(UserDbName, all_dbs()), + [ + ?_assert(AfterCreate), + ?_assertNot(AfterDelete) + ]. + +should_delete_user_db_with_custom_special_prefix(TestAuthDb) -> + User = "bar", + UserDbName = <<"userdb_$()+--/626172">>, + set_config("couch_peruser", "delete_dbs", "true"), + set_config("couch_peruser", "database_prefix", "userdb_$()+--/"), + create_user(TestAuthDb, User), + wait_for_db_create(UserDbName), + AfterCreate = lists:member(UserDbName, all_dbs()), + delete_user(TestAuthDb, User), + wait_for_db_delete(UserDbName), + delete_config("couch_peruser", "database_prefix"), + AfterDelete = lists:member(UserDbName, all_dbs()), + [ + ?_assert(AfterCreate), + ?_assertNot(AfterDelete) + ]. + +should_reflect_config_changes(TestAuthDb) -> + User = "baz", + UserDbName = <<"userdb-62617a">>, + set_config("couch_peruser", "delete_dbs", "true"), + create_user(TestAuthDb, User), + wait_for_db_create(UserDbName), + AfterCreate1 = lists:member(UserDbName, all_dbs()), + delete_user(TestAuthDb, User), + timer:sleep(?WAIT_FOR_USER_DELETE_TIMEOUT), + wait_for_db_delete(UserDbName), + AfterDelete1 = lists:member(UserDbName, all_dbs()), + create_user(TestAuthDb, User), + wait_for_db_create(UserDbName), + AfterCreate2 = lists:member(UserDbName, all_dbs()), + set_config("couch_peruser", "delete_dbs", "false"), + delete_user(TestAuthDb, User), + timer:sleep(?WAIT_FOR_USER_DELETE_TIMEOUT), + AfterDelete2 = lists:member(UserDbName, all_dbs()), + create_user(TestAuthDb, User), + wait_for_db_create(UserDbName), + set_config("couch_peruser", "delete_dbs", "true"), + delete_user(TestAuthDb, User), + wait_for_db_delete(UserDbName), + AfterDelete3 = lists:member(UserDbName, all_dbs()), + set_config("couch_peruser", "enable", "false"), + create_user(TestAuthDb, User), + timer:sleep(?WAIT_FOR_USER_DELETE_TIMEOUT), + AfterCreate3 = lists:member(UserDbName, all_dbs()), + [ + ?_assert(AfterCreate1), + ?_assertNot(AfterDelete1), + ?_assert(AfterCreate2), + ?_assert(AfterDelete2), + ?_assertNot(AfterDelete3), + ?_assertNot(AfterCreate3) + ]. + + +should_add_user_to_db_admins(TestAuthDb) -> + User = "qux", + UserDbName = <<"userdb-717578">>, + create_user(TestAuthDb, User), + wait_for_db_create(UserDbName), + ?_assertEqual( + {[{<<"names">>,[<<"qux">>]}]}, + proplists:get_value(<<"admins">>, get_security(UserDbName))). + +should_add_user_to_db_members(TestAuthDb) -> + User = "qux", + UserDbName = <<"userdb-717578">>, + create_user(TestAuthDb, User), + wait_for_db_create(UserDbName), + ?_assertEqual( + {[{<<"names">>,[<<"qux">>]}]}, + proplists:get_value(<<"members">>, get_security(UserDbName))). + +should_not_remove_existing_db_admins(TestAuthDb) -> + User = "qux", + UserDbName = <<"userdb-717578">>, + SecurityProperties = [ + {<<"admins">>,{[{<<"names">>,[<<"foo">>,<<"bar">>]}]}}, + {<<"members">>,{[{<<"names">>,[<<"baz">>,<<"pow">>]}]}} + ], + create_db(UserDbName), + set_security(UserDbName, SecurityProperties), + create_user(TestAuthDb, User), + wait_for_security_create(<<"admins">>, User, UserDbName), + {AdminProperties} = proplists:get_value(<<"admins">>, + get_security(UserDbName)), + AdminNames = proplists:get_value(<<"names">>, AdminProperties), + [ + ?_assert(lists:member(<<"foo">>, AdminNames)), + ?_assert(lists:member(<<"bar">>, AdminNames)), + ?_assert(lists:member(<<"qux">>, AdminNames)) + ]. + +should_not_remove_existing_db_members(TestAuthDb) -> + User = "qux", + UserDbName = <<"userdb-717578">>, + SecurityProperties = [ + {<<"admins">>,{[{<<"names">>,[<<"pow">>,<<"wow">>]}]}}, + {<<"members">>,{[{<<"names">>,[<<"pow">>,<<"wow">>]}]}} + ], + create_db(UserDbName), + set_security(UserDbName, SecurityProperties), + create_user(TestAuthDb, User), + wait_for_security_create(<<"members">>, User, UserDbName), + {MemberProperties} = proplists:get_value(<<"members">>, + get_security(UserDbName)), + MemberNames = proplists:get_value(<<"names">>, MemberProperties), + [ + ?_assert(lists:member(<<"pow">>, MemberNames)), + ?_assert(lists:member(<<"wow">>, MemberNames)), + ?_assert(lists:member(<<"qux">>, MemberNames)) + ]. + +should_remove_user_from_db_admins(TestAuthDb) -> + User = "qux", + UserDbName = <<"userdb-717578">>, + SecurityProperties = [ + {<<"admins">>,{[{<<"names">>,[<<"foo">>,<<"bar">>]}]}}, + {<<"members">>,{[{<<"names">>,[<<"baz">>,<<"pow">>]}]}} + ], + create_db(UserDbName), + set_security(UserDbName, SecurityProperties), + create_user(TestAuthDb, User), + wait_for_security_create(<<"admins">>, User, UserDbName), + {AdminProperties} = proplists:get_value(<<"admins">>, + get_security(UserDbName)), + AdminNames = proplists:get_value(<<"names">>, AdminProperties), + FooBefore = lists:member(<<"foo">>, AdminNames), + BarBefore = lists:member(<<"bar">>, AdminNames), + QuxBefore = lists:member(<<"qux">>, AdminNames), + delete_user(TestAuthDb, User), + wait_for_security_delete(<<"admins">>, User, UserDbName), + {NewAdminProperties} = proplists:get_value(<<"admins">>, + get_security(UserDbName)), + NewAdminNames = proplists:get_value(<<"names">>, NewAdminProperties), + FooAfter = lists:member(<<"foo">>, NewAdminNames), + BarAfter = lists:member(<<"bar">>, NewAdminNames), + QuxAfter = lists:member(<<"qux">>, NewAdminNames), + [ + ?_assert(FooBefore), + ?_assert(BarBefore), + ?_assert(QuxBefore), + ?_assert(FooAfter), + ?_assert(BarAfter), + ?_assertNot(QuxAfter) + ]. + +should_remove_user_from_db_members(TestAuthDb) -> + User = "qux", + UserDbName = <<"userdb-717578">>, + SecurityProperties = [ + {<<"admins">>,{[{<<"names">>,[<<"pow">>,<<"wow">>]}]}}, + {<<"members">>,{[{<<"names">>,[<<"pow">>,<<"wow">>]}]}} + ], + create_db(UserDbName), + set_security(UserDbName, SecurityProperties), + create_user(TestAuthDb, User), + wait_for_security_create(<<"members">>, User, UserDbName), + {MemberProperties} = proplists:get_value(<<"members">>, + get_security(UserDbName)), + MemberNames = proplists:get_value(<<"names">>, MemberProperties), + PowBefore = lists:member(<<"pow">>, MemberNames), + WowBefore = lists:member(<<"wow">>, MemberNames), + QuxBefore = lists:member(<<"qux">>, MemberNames), + delete_user(TestAuthDb, User), + wait_for_security_delete(<<"members">>, User, UserDbName), + {NewMemberProperties} = proplists:get_value(<<"members">>, + get_security(UserDbName)), + NewMemberNames = proplists:get_value(<<"names">>, NewMemberProperties), + PowAfter = lists:member(<<"pow">>, NewMemberNames), + WowAfter = lists:member(<<"wow">>, NewMemberNames), + QuxAfter = lists:member(<<"qux">>, NewMemberNames), + [ + ?_assert(PowBefore), + ?_assert(WowBefore), + ?_assert(QuxBefore), + ?_assert(PowAfter), + ?_assert(WowAfter), + ?_assertNot(QuxAfter) + ]. + + +wait_for_db_create(UserDbName) -> + test_util:wait(fun() -> + case all_dbs_with_errors() of + {error, _, _ , _} -> wait; + {ok, _, _, AllDbs} -> + case lists:member(UserDbName, AllDbs) of + true -> true; + false -> wait + end + end + end). + +wait_for_db_delete(UserDbName) -> + test_util:wait(fun() -> + case all_dbs_with_errors() of + {ok, 500, _ , _} -> wait; + {ok, _, _, AllDbs} -> + case not lists:member(UserDbName, AllDbs) of + true -> true; + false -> wait + end + end + end). + +wait_for_security_create(Type, User0, UserDbName) -> + User = ?l2b(User0), + test_util:wait(fun() -> + {Props} = proplists:get_value(Type, get_security(UserDbName)), + Names = proplists:get_value(<<"names">>, Props), + case lists:member(User, Names) of + true -> true; + false -> wait + end + end). + +wait_for_security_delete(Type, User0, UserDbName) -> + User = ?l2b(User0), + test_util:wait(fun() -> + {Props} = proplists:get_value(Type, get_security(UserDbName)), + Names = proplists:get_value(<<"names">>, Props), + case not lists:member(User, Names) of + true -> true; + false -> wait + end + end). + +couch_peruser_test_() -> + { + "couch_peruser test", + { + setup, + fun setup_all/0, fun teardown_all/1, + { + foreach, + fun setup/0, fun teardown/1, + [ + fun should_create_anon_user_db_with_default/1, + fun should_create_anon_user_db_with_custom_prefix/1, + fun should_create_anon_user_db_with_custom_special_prefix/1, + fun should_create_user_db_with_default/1, + fun should_create_user_db_with_custom_prefix/1, + fun should_create_user_db_with_custom_special_prefix/1, + fun should_create_user_db_with_q4/1, + fun should_create_anon_user_db_with_q4/1, + fun should_not_delete_user_db/1, + fun should_delete_user_db/1, + fun should_delete_user_db_with_custom_prefix/1, + fun should_delete_user_db_with_custom_special_prefix/1, + fun should_reflect_config_changes/1, + fun should_add_user_to_db_admins/1, + fun should_add_user_to_db_members/1, + fun should_not_remove_existing_db_admins/1, + fun should_not_remove_existing_db_members/1, + fun should_remove_user_from_db_admins/1, + fun should_remove_user_from_db_members/1 + ] + } + } + }. |