diff --git a/deps/rabbitmq_auth_backend_ldap/src/rabbit_auth_backend_ldap.erl b/deps/rabbitmq_auth_backend_ldap/src/rabbit_auth_backend_ldap.erl index e22da9823d..e2f943d0c0 100644 --- a/deps/rabbitmq_auth_backend_ldap/src/rabbit_auth_backend_ldap.erl +++ b/deps/rabbitmq_auth_backend_ldap/src/rabbit_auth_backend_ldap.erl @@ -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; diff --git a/deps/rabbitmq_auth_backend_ldap/src/rabbitmq_auth_backend_ldap.app.src b/deps/rabbitmq_auth_backend_ldap/src/rabbitmq_auth_backend_ldap.app.src index 619c4f820b..d3df83504b 100644 --- a/deps/rabbitmq_auth_backend_ldap/src/rabbitmq_auth_backend_ldap.app.src +++ b/deps/rabbitmq_auth_backend_ldap/src/rabbitmq_auth_backend_ldap.app.src @@ -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}}, diff --git a/deps/rabbitmq_auth_backend_ldap/test/src/rabbit_auth_backend_ldap_test.erl b/deps/rabbitmq_auth_backend_ldap/test/src/rabbit_auth_backend_ldap_test.erl index 9868904a1b..8530d4e0d3 100644 --- a/deps/rabbitmq_auth_backend_ldap/test/src/rabbit_auth_backend_ldap_test.erl +++ b/deps/rabbitmq_auth_backend_ldap/test/src/rabbit_auth_backend_ldap_test.erl @@ -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. + +%%--------------------------------------------------------------------