Merge pull request #29 from rabbitmq/rabbitmq-auth-backend-ldap-13
Support variable vhost substitution in tag_queries
This commit is contained in:
		
						commit
						876bcd932d
					
				| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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() ->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue