Merge pull request #29 from rabbitmq/rabbitmq-auth-backend-ldap-13

Support variable vhost substitution in tag_queries
This commit is contained in:
Daniil Fedotov 2016-04-22 10:49:54 +01:00
commit 876bcd932d
2 changed files with 114 additions and 50 deletions

View File

@ -46,24 +46,27 @@ user_login_authentication(Username, []) ->
[Username, log_result(R)]),
R;
user_login_authentication(Username, [{password, <<>>}]) ->
user_login_authentication(Username, AuthProps) when is_list(AuthProps) ->
case pget(password, AuthProps) of
undefined -> user_login_authentication(Username, []);
<<>> ->
%% Password "" is special in LDAP, see
%% https://tools.ietf.org/html/rfc4513#section-5.1.2
?L("CHECK: unauthenticated login for ~s", [Username]),
?L("DECISION: unauthenticated login for ~s: denied", [Username]),
{refused, "user '~s' - unauthenticated bind not allowed", [Username]};
user_login_authentication(User, [{password, PW}]) ->
?L("CHECK: login for ~s", [User]),
PW ->
?L("CHECK: login for ~s", [Username]),
R = case dn_lookup_when() of
prebind -> UserDN = username_to_dn_prebind(User),
prebind -> UserDN = username_to_dn_prebind(Username),
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)
login_fun(Username, UserDN, PW, AuthProps));
_ -> with_ldap({ok, {fill_user_dn_pattern(Username), PW}},
login_fun(Username, unknown, PW, AuthProps))
end,
?L("DECISION: login for ~s: ~p", [User, log_result(R)]),
R;
?L("DECISION: login for ~s: ~p", [Username, log_result(R)]),
R
end;
user_login_authentication(Username, AuthProps) ->
exit({unknown_auth_props, Username, AuthProps}).
@ -415,7 +418,17 @@ env(F) ->
{ok, V} = application:get_env(rabbitmq_auth_backend_ldap, F),
V.
login_fun(User, UserDN, Password, AuthProps) ->
fun(L) -> case pget(vhost, AuthProps) of
undefined -> do_login(User, UserDN, Password, L);
VHost -> do_login(User, UserDN, Password, VHost, L)
end
end.
do_login(Username, PrebindUserDN, Password, LDAP) ->
do_login(Username, PrebindUserDN, Password, <<>>, LDAP).
do_login(Username, PrebindUserDN, Password, VHost, LDAP) ->
UserDN = case PrebindUserDN of
unknown -> username_to_dn(Username, LDAP, dn_lookup_when());
_ -> PrebindUserDN
@ -423,7 +436,7 @@ do_login(Username, PrebindUserDN, Password, LDAP) ->
User = #auth_user{username = Username,
impl = #impl{user_dn = UserDN,
password = Password}},
DTQ = fun (LDAPn) -> do_tag_queries(Username, UserDN, User, LDAPn) end,
DTQ = fun (LDAPn) -> do_tag_queries(Username, UserDN, User, VHost, LDAPn) end,
TagRes = case env(other_bind) of
as_user -> DTQ(LDAP);
_ -> with_ldap(creds(User), DTQ)
@ -433,16 +446,21 @@ do_login(Username, PrebindUserDN, Password, LDAP) ->
E -> E
end.
do_tag_queries(Username, UserDN, User, LDAP) ->
do_tag_queries(Username, UserDN, User, VHost, LDAP) ->
{ok, [begin
?L1("CHECK: does ~s have tag ~s?", [Username, Tag]),
R = evaluate(Q, [{username, Username},
{user_dn, UserDN}], User, LDAP),
{user_dn, UserDN} | vhost_if_defined(VHost)],
User, LDAP),
?L1("DECISION: does ~s have tag ~s? ~p",
[Username, Tag, R]),
{Tag, R}
end || {Tag, Q} <- env(tag_queries)]}.
vhost_if_defined([]) -> [];
vhost_if_defined(<<>>) -> [];
vhost_if_defined(VHost) -> [{vhost, VHost}].
dn_lookup_when() -> case {env(dn_lookup_attribute), env(dn_lookup_bind)} of
{none, _} -> never;
{_, as_user} -> postbind;

View File

@ -54,7 +54,8 @@ ldap_only_test_() ->
{"LDAP Constant", const()},
{"LDAP String match", string_match()},
{"LDAP Boolean check", boolean_logic()},
{"LDAP Tags", tag_check([monitor])}
{"LDAP Tags", tag_check([monitor])},
{"LDAP Tag Substitution", tag_check_subst()}
]}.
ldap_and_internal_test_() ->
@ -169,23 +170,34 @@ login() ->
{good, good} -> fun succ/1;
_ -> fun fail/1
end) ||
{LGood, FilterList, L} <- logins(),
{LGood, FilterList, L, _Tags} <- logins(),
{N, {EnvGood, Env}} <- login_envs()]).
%% Format for login tests, {Outcome, FilterList, Login}.
logins() -> logins_network() ++ logins_direct().
%% Format for login tests, {Outcome, FilterList, Login, Tags}.
%% Tests skipped for each login_env reference in FilterList.
logins() ->
[{bad, [5], #amqp_params_network{}},
{bad, [5], #amqp_params_network{username = << ?ALICE_NAME >>}},
{bad, [5], #amqp_params_network{username = << ?ALICE_NAME >>,
password = <<"password">>}},
{bad, [5], #amqp_params_network{username = <<"Alice">>,
logins_network() ->
[{bad, [5, 6], #amqp_params_network{}, []},
{bad, [5, 6], #amqp_params_network{username = <<?ALICE_NAME>>}, []},
{bad, [5, 6], #amqp_params_network{username = <<?ALICE_NAME>>,
password = <<"password">>}, []},
{bad, [5, 6], #amqp_params_network{username = <<"Alice">>,
password = <<"Alicja">>,
virtual_host = << ?VHOST >>}},
{bad, [1, 2, 3, 4, 6], ?CAROL},
{good, [5], ?ALICE},
{good, [5], ?BOB},
{good, [1, 2, 3, 4, 6], ?PETER}].
virtual_host = <<?VHOST>>}, []},
{bad, [1, 2, 3, 4, 6, 7], ?CAROL, []},
{good, [5, 6], ?ALICE, []},
{good, [5, 6], ?BOB, []},
{good, [1, 2, 3, 4, 6, 7], ?PETER, []}].
logins_direct() ->
[{bad, [5], #amqp_params_direct{}, []},
{bad, [5], #amqp_params_direct{username = <<?ALICE_NAME>>}, []},
{bad, [5], #amqp_params_direct{username = <<?ALICE_NAME>>,
password = <<"password">>}, [management]},
{good, [5], #amqp_params_direct{username = <<?ALICE_NAME>>,
password = <<"password">>,
virtual_host = <<?VHOST>>}, [management]}].
%% Format for login envs, {Reference, {Outcome, Env}}
login_envs() ->
@ -194,7 +206,8 @@ login_envs() ->
{3, {good, other_bind_admin_env()}},
{4, {good, other_bind_anon_env()}},
{5, {good, posix_vhost_access_multiattr_env()}},
{6, {bad, other_bind_broken_env()}}].
{6, {good, tag_queries_subst_env()}},
{7, {bad, other_bind_broken_env()}}].
base_login_env() ->
[{user_dn_pattern, "cn=${username},ou=People,dc=example,dc=com"},
@ -202,6 +215,9 @@ base_login_env() ->
{dn_lookup_base, none},
{dn_lookup_bind, as_user},
{other_bind, as_user},
{tag_queries, [{monitor, {constant, true}},
{administrator, {constant, false}},
{management, {constant, false}}]},
{vhost_access_query, {exists, "ou=${vhost},ou=vhosts,dc=example,dc=com"}}].
%% TODO configure OpenLDAP to allow a dn_lookup_post_bind_env()
@ -220,6 +236,11 @@ other_bind_anon_env() ->
other_bind_broken_env() ->
[{other_bind, {"cn=admin,dc=example,dc=com", "admi"}}].
tag_queries_subst_env() ->
[{tag_queries, [{administrator, {constant, false}},
{management,
{exists, "ou=${vhost},ou=vhosts,dc=example,dc=com"}}]}].
posix_vhost_access_multiattr_env() ->
[{user_dn_pattern, "uid=${username},ou=People,dc=example,dc=com"},
{vhost_access_query,
@ -310,16 +331,41 @@ permission_match() ->
{?ALICE, B(<<"abc123">>), fail},
{?ALICE, B(<<"xch-Alice-abc123">>), fail}]).
%% Tag check tests, with substitution
tag_check_subst() ->
lists:flatten(
[test_tag_check(tag_check(Username, Password, VHost, Outcome, Tags)) ||
{Outcome, _FilterList, #amqp_params_direct{username = Username,
password = Password,
virtual_host = VHost},
Tags} <- logins_direct()]).
%% Tag check
tag_check(Tags) ->
tag_check(<<?ALICE_NAME>>, <<"password">>, Tags).
tag_check(Username, Password, Tags)
when is_binary(Username), is_binary(Password), is_list(Tags) ->
tag_check(Username, Password, Tags) ->
tag_check(Username, Password, <<>>, good, Tags).
tag_check(Username, Password, VHost, Outcome, Tags)
when is_binary(Username), is_binary(Password), is_binary(VHost), is_list(Tags) ->
fun() ->
{ok, User} = rabbit_access_control:check_user_pass_login(
Username, Password),
?assertEqual(Tags, User#user.tags)
end.
{ok, User} = rabbit_access_control:check_user_login(
Username, [{password, Password}, {vhost, VHost}]),
tag_check_outcome(Outcome, Tags, User)
end;
tag_check(_, _, _, _, _) -> fun() -> [] end.
tag_check_outcome(good, Tags, User) -> ?assertEqual(Tags, User#user.tags);
tag_check_outcome(bad, Tags, User) -> ?assertNotEqual(Tags, User#user.tags).
test_tag_check(TagCheckFun) ->
?_test(try
set_env(tag_queries_subst_env()),
TagCheckFun()
after
set_env(base_login_env())
end).
tag_query_configuration() ->