dn_lookup_bind option, and rather more tests for the login phase.

This commit is contained in:
Simon MacMullen 2014-01-28 17:56:25 +00:00
parent 815e7cf787
commit f586ddd335
3 changed files with 103 additions and 32 deletions

View File

@ -46,16 +46,21 @@ check_user_login(Username, []) ->
%% Without password, e.g. EXTERNAL
?L("CHECK: passwordless login for ~s", [Username]),
R = with_ldap(creds(none),
fun(LDAP) -> do_login(Username, none, LDAP) end),
fun(LDAP) -> do_login(Username, unknown, none, LDAP) end),
?L("DECISION: passwordless login for ~s: ~p",
[Username, log_result(R)]),
R;
check_user_login(Username, [{password, Password}]) ->
?L("CHECK: login for ~s", [Username]),
R = with_ldap({ok, {fill_user_dn_pattern(Username), Password}},
fun(LDAP) -> do_login(Username, Password, LDAP) end),
?L("DECISION: login for ~s: ~p", [Username, log_result(R)]),
check_user_login(User, [{password, PW}]) ->
?L("CHECK: login for ~s", [User]),
R = case dn_lookup_when() of
prebind -> UserDN = username_to_dn_prebind(User),
with_ldap({ok, {UserDN, PW}},
fun(L) -> do_login(User, UserDN, PW, L) end);
_ -> with_ldap({ok, {fill_user_dn_pattern(User), PW}},
fun(L) -> do_login(User, unknown, PW, L) end)
end,
?L("DECISION: login for ~s: ~p", [User, log_result(R)]),
R;
check_user_login(Username, AuthProps) ->
@ -287,13 +292,15 @@ env(F) ->
{ok, V} = application:get_env(rabbitmq_auth_backend_ldap, F),
V.
do_login(Username, Password, LDAP) ->
UserDN = username_to_dn(Username, LDAP),
do_login(Username, PrebindUserDN, Password, LDAP) ->
UserDN = case PrebindUserDN of
unknown -> username_to_dn(Username, LDAP, dn_lookup_when());
_ -> PrebindUserDN
end,
User = #user{username = Username,
auth_backend = ?MODULE,
impl = #impl{user_dn = UserDN,
password = Password}},
TagRes = [begin
?L1("CHECK: does ~s have tag ~s?", [Username, Tag]),
R = evaluate(Q, [{username, Username},
@ -307,17 +314,25 @@ do_login(Username, Password, LDAP) ->
[E | _] -> E
end.
username_to_dn(Username, LDAP) ->
username_to_dn(Username, LDAP, env(dn_lookup_attribute)).
dn_lookup_when() -> case {env(dn_lookup_attribute), env(dn_lookup_bind)} of
{none, _} -> never;
{_, as_user} -> postbind;
{_, _} -> prebind
end.
username_to_dn(Username, _LDAP, none) ->
fill_user_dn_pattern(Username);
username_to_dn_prebind(Username) ->
with_ldap({ok, env(dn_lookup_bind)},
fun (LDAP) -> dn_lookup(Username, LDAP) end).
username_to_dn(Username, LDAP, Attr) ->
username_to_dn(Username, LDAP, postbind) -> dn_lookup(Username, LDAP);
username_to_dn(Username, _LDAP, _When) -> fill_user_dn_pattern(Username).
dn_lookup(Username, LDAP) ->
Filled = fill_user_dn_pattern(Username),
case eldap:search(LDAP,
[{base, env(dn_lookup_base)},
{filter, eldap:equalityMatch(Attr, Filled)},
{filter, eldap:equalityMatch(
env(dn_lookup_attribute), Filled)},
{attributes, ["distinguishedName"]}]) of
{ok, #eldap_search_result{entries = [#eldap_entry{object_name = DN}]}}->
DN;

View File

@ -9,6 +9,7 @@
{user_dn_pattern, "${username}"},
{dn_lookup_attribute, none},
{dn_lookup_base, none},
{dn_lookup_bind, as_user},
{other_bind, as_user},
{vhost_access_query, {constant, true}},
{resource_access_query, {constant, true}},

View File

@ -27,26 +27,78 @@
password = <<"password">>,
virtual_host = <<"test">>}).
login_test_() ->
[?_test(fail(#amqp_params_network{})),
?_test(fail(#amqp_params_network{username = <<"Simon MacMullen">>})),
?_test(fail(#amqp_params_network{username = <<"Simon MacMullen">>,
password = <<"password">>})),
?_test(succ(?SIMON)),
?_test(succ(?MIKEB))].
%%--------------------------------------------------------------------
succ(Params) -> ?assertMatch({ok, _}, amqp_connection:start(Params)).
fail(Params) -> ?assertMatch({error, _}, amqp_connection:start(Params)).
login_test_() ->
[test_login(Env, L, case {LGood, EnvGood} of
{good, good} -> fun succ/1;
_ -> fun fail/1
end) || {LGood, L} <- logins(),
{EnvGood, Env} <- login_envs()].
logins() ->
[{bad, #amqp_params_network{}},
{bad, #amqp_params_network{username = <<"Simon MacMullen">>}},
{bad, #amqp_params_network{username = <<"Simon MacMullen">>,
password = <<"password">>}},
{good, ?SIMON},
{good, ?MIKEB}].
login_envs() ->
[{good, base_login_env()},
{good, dn_lookup_pre_bind_env()},
{good, other_bind_admin_env()},
{good, other_bind_anon_env()},
{bad, other_bind_broken_env()}].
base_login_env() ->
[{user_dn_pattern, "cn=${username},ou=People,dc=example,dc=com"},
{dn_lookup_attribute, none},
{dn_lookup_base, none},
{dn_lookup_bind, as_user},
{other_bind, as_user}].
%% TODO configure OpenLDAP to allow a dn_lookup_post_bind_env()
dn_lookup_pre_bind_env() ->
[{user_dn_pattern, "${username}"},
{dn_lookup_attribute, "cn"},
{dn_lookup_base, "OU=People,DC=example,DC=com"},
{dn_lookup_bind, {"cn=admin,dc=example,dc=com", "admin"}}].
other_bind_admin_env() ->
[{other_bind, {"cn=admin,dc=example,dc=com", "admin"}}].
other_bind_anon_env() ->
[{other_bind, anon}].
other_bind_broken_env() ->
[{other_bind, {"cn=admin,dc=example,dc=com", "admi"}}].
test_login(Env, Login, ResultFun) ->
?_test(try
set_env(Env),
ResultFun(Login)
after
set_env(base_login_env())
end).
set_env(Env) ->
[application:set_env(rabbitmq_auth_backend_ldap, K, V) || {K, V} <- Env].
succ(Login) -> ?assertMatch({ok, _}, amqp_connection:start(Login)).
fail(Login) -> ?assertMatch({error, _}, amqp_connection:start(Login)).
%%--------------------------------------------------------------------
in_group_test_() ->
X = [#'exchange.declare'{exchange = <<"test">>}],
[test_resource_fun(PTR) || PTR <- [{?SIMON, X, ok},
{?MIKEB, X, fail}]].
test_resource_funs([{?SIMON, X, ok},
{?MIKEB, X, fail}]).
const_test_() ->
Q = [#'queue.declare'{queue = <<"test">>}],
[test_resource_fun(PTR) || PTR <- [{?SIMON, Q, ok},
{?MIKEB, Q, fail}]].
test_resource_funs([{?SIMON, Q, ok},
{?MIKEB, Q, fail}]).
string_match_test_() ->
B = fun(N) ->
@ -54,10 +106,9 @@ string_match_test_() ->
#'queue.declare'{queue = <<"test">>},
#'queue.bind'{exchange = N, queue = <<"test">>}]
end,
[test_resource_fun(PTR) ||
PTR <- [{?SIMON, B(<<"xch-Simon MacMullen-abc123">>), ok},
{?SIMON, B(<<"abc123">>), fail},
{?SIMON, B(<<"xch-Someone Else-abc123">>), fail}]].
test_resource_funs([{?SIMON, B(<<"xch-Simon MacMullen-abc123">>), ok},
{?SIMON, B(<<"abc123">>), fail},
{?SIMON, B(<<"xch-Someone Else-abc123">>), fail}]).
boolean_logic_test_() ->
Q1 = [#'queue.declare'{queue = <<"test1">>},
@ -69,6 +120,8 @@ boolean_logic_test_() ->
{?MIKEB, Q1, fail},
{?MIKEB, Q2, fail}]].
test_resource_funs(PTRs) -> [test_resource_fun(PTR) || PTR <- PTRs].
test_resource_fun({Person, Things, Result}) ->
fun() ->
{ok, Conn} = amqp_connection:start(Person),
@ -81,3 +134,5 @@ test_resource_fun({Person, Things, Result}) ->
catch exit:_ -> fail
end)
end.
%%--------------------------------------------------------------------