Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
Merge remote-tracking branch 'iilyak/2585-allowed_owner-hook'
This closes #3
  • Loading branch information
kxepal committed Feb 27, 2015
2 parents 0e9c3eb + cedf542 commit a7a45633e2c331f2b6984e079dc9cfc393e80ced
Showing 2 changed files with 157 additions and 2 deletions.
@@ -41,9 +41,9 @@ handle_global_changes_req(#httpd{method='GET'}=Req) ->
Limit = couch_util:get_value(limit, Options),
%Options1 = lists:keydelete(limit, 1, Options),
Options1 = Options,
chttpd:verify_is_server_admin(Req),
Owner = allowed_owner(Req),
Acc = #acc{
username=admin,
username=Owner,
feed=Feed,
resp=Req,
heartbeat_interval=Heartbeat,
@@ -248,3 +248,14 @@ to_non_neg_int(Value) ->
catch error:badarg ->
throw({bad_request, invalid_integer})
end.

allowed_owner(Req) ->
case config:get("global_changes", "allowed_owner", undefined) of
undefined ->
chttpd:verify_is_server_admin(Req),
admin;
SpecStr ->
{ok, {M, F, A}} = couch_util:parse_term(SpecStr),
couch_util:validate_callback_exists(M, F, 2),
M:F(Req, A)
end.
@@ -0,0 +1,144 @@
-module(global_changes_hooks_tests).

-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").

-export([allowed_owner/2]).

-define(t2l(V), lists:flatten(io_lib:format("~p", [V]))).

start() ->
Ctx = test_util:start_couch([chttpd]),
DbName = ?tempdb(),
ok = fabric:create_db(DbName, [?ADMIN_CTX]),
application:set_env(global_changes, dbname, DbName),
{Ctx, DbName}.

stop({Ctx, DbName}) ->
ok = fabric:delete_db(DbName, [?ADMIN_CTX]),
test_util:stop_couch(Ctx),
ok.

setup(default) ->
add_admin("admin", <<"pass">>),
config:delete("couch_httpd_auth", "authentication_redirect", false),
config:set("couch_httpd_auth", "require_valid_user", "false", false),
get_host();
setup(A) ->
Host = setup(default),
ok = config:set("global_changes", "allowed_owner",
?t2l({?MODULE, allowed_owner, A}), false),
Host.

teardown(_) ->
delete_admin("admin"),
config:delete("global_changes", "allowed_owner", false),
ok.

allowed_owner(Req, "throw") ->
throw({unauthorized, <<"Exception thrown.">>});
allowed_owner(Req, "pass") ->
"super".

allowed_owner_hook_test_() ->
{
"Check allowed_owner hook",
{
setup,
fun start/0, fun stop/1,
[
disabled_allowed_owner_integration_point(),
enabled_allowed_owner_integration_point()
]
}
}.

disabled_allowed_owner_integration_point() ->
{
"disabled allowed_owner integration point",
{
foreach,
fun() -> setup(default) end, fun teardown/1,
[
fun should_not_fail_for_admin/1,
fun should_fail_for_non_admin/1
]
}
}.

enabled_allowed_owner_integration_point() ->
{
"enabled allowed_owner integration point",
[
{
foreach,
fun() -> setup("throw") end, fun teardown/1,
[fun should_throw/1]
},
{
foreach,
fun() -> setup("pass") end, fun teardown/1,
[fun should_pass/1]
}
]
}.

should_not_fail_for_admin(Host) ->
?_test(begin
Headers = [{basic_auth, {"admin", "pass"}}],
{Status, [Error, Reason]} =
request(Host, Headers, [<<"error">>, <<"reason">>]),
?assertEqual(200, Status),
?assertEqual(undefined, Error),
?assertEqual(undefined, Reason)
end).

should_fail_for_non_admin(Host) ->
?_test(begin
Headers = [],
{Status, [Error, Reason]} =
request(Host, Headers, [<<"error">>, <<"reason">>]),
?assertEqual(401, Status),
?assertEqual(<<"unauthorized">>, Error),
?assertEqual(<<"You are not a server admin.">>, Reason)
end).

should_pass(Host) ->
?_test(begin
Headers = [{basic_auth, {"admin", "pass"}}],
{Status, [Error, Reason]} =
request(Host, Headers, [<<"error">>, <<"reason">>]),
?assertEqual(200, Status),
?assertEqual(undefined, Error),
?assertEqual(undefined, Reason)
end).

should_throw(Host) ->
?_test(begin
Headers = [{basic_auth, {"admin", "pass"}}],
{Status, [Error, Reason]} =
request(Host, Headers, [<<"error">>, <<"reason">>]),
?assertEqual(401, Status),
?assertEqual(<<"unauthorized">>, Error),
?assertEqual(<<"Exception thrown.">>, Reason)
end).

request(Host, Headers, ToDecode) ->
Url = Host ++ "/_db_updates",
{ok, Status, _Headers, BinBody} = test_request:get(Url, Headers),
{Body} = jiffy:decode(BinBody),
Values = [couch_util:get_value(Key, Body) || Key <- ToDecode],
{Status, Values}.

add_admin(User, Pass) ->
Hashed = couch_passwords:hash_admin_password(Pass),
config:set("admins", User, ?b2l(Hashed), false).

delete_admin(User) ->
config:delete("admins", User, false).

get_host() ->
Addr = config:get("httpd", "bind_address", "127.0.0.1"),
Port = config:get("chttpd", "port", "5984"),
Host = "http://" ++ Addr ++ ":" ++ Port,
Host.

0 comments on commit a7a4563

Please sign in to comment.