fix(ldap): improve the filter lex && parse
1. auto escape special chars in the filter 2. fix a bug that the value can't be `dn`
This commit is contained in:
parent
2fe6e8e431
commit
b2a6724dc2
|
@ -1,6 +1,6 @@
|
||||||
{application, emqx_ldap, [
|
{application, emqx_ldap, [
|
||||||
{description, "EMQX LDAP Connector"},
|
{description, "EMQX LDAP Connector"},
|
||||||
{vsn, "0.1.3"},
|
{vsn, "0.1.4"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [
|
{applications, [
|
||||||
kernel,
|
kernel,
|
||||||
|
|
|
@ -226,7 +226,9 @@ on_query(
|
||||||
#{base_tokens := BaseTks, filter_tokens := FilterTks} = State
|
#{base_tokens := BaseTks, filter_tokens := FilterTks} = State
|
||||||
) ->
|
) ->
|
||||||
Base = emqx_placeholder:proc_tmpl(BaseTks, Data),
|
Base = emqx_placeholder:proc_tmpl(BaseTks, Data),
|
||||||
FilterBin = emqx_placeholder:proc_tmpl(FilterTks, Data),
|
FilterBin = emqx_placeholder:proc_tmpl(FilterTks, Data, #{
|
||||||
|
return => full_binary, var_trans => fun filter_escape/1
|
||||||
|
}),
|
||||||
case emqx_ldap_filter_parser:scan_and_parse(FilterBin) of
|
case emqx_ldap_filter_parser:scan_and_parse(FilterBin) of
|
||||||
{ok, Filter} ->
|
{ok, Filter} ->
|
||||||
do_ldap_query(
|
do_ldap_query(
|
||||||
|
@ -278,7 +280,7 @@ do_ldap_query(
|
||||||
error,
|
error,
|
||||||
LogMeta#{
|
LogMeta#{
|
||||||
msg => "ldap_query_found_more_than_one_match",
|
msg => "ldap_query_found_more_than_one_match",
|
||||||
count => length(Entries)
|
count => Count
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
{error, {unrecoverable_error, Msg}}
|
{error, {unrecoverable_error, Msg}}
|
||||||
|
@ -314,3 +316,20 @@ do_prepare_template([{filter, V} | T], State) ->
|
||||||
do_prepare_template(T, State#{filter_tokens => emqx_placeholder:preproc_tmpl(V)});
|
do_prepare_template(T, State#{filter_tokens => emqx_placeholder:preproc_tmpl(V)});
|
||||||
do_prepare_template([], State) ->
|
do_prepare_template([], State) ->
|
||||||
State.
|
State.
|
||||||
|
|
||||||
|
filter_escape(Binary) when is_binary(Binary) ->
|
||||||
|
filter_escape(erlang:binary_to_list(Binary));
|
||||||
|
filter_escape([$\\ | T]) ->
|
||||||
|
[$\\, $\\ | filter_escape(T)];
|
||||||
|
filter_escape([Char | T]) ->
|
||||||
|
case lists:member(Char, filter_control_chars()) of
|
||||||
|
true ->
|
||||||
|
[$\\, Char | filter_escape(T)];
|
||||||
|
_ ->
|
||||||
|
[Char | filter_escape(T)]
|
||||||
|
end;
|
||||||
|
filter_escape([]) ->
|
||||||
|
[].
|
||||||
|
|
||||||
|
filter_control_chars() ->
|
||||||
|
[$(, $), $&, $|, $=, $!, $~, $>, $<, $:, $*, $\t, $\n, $\r].
|
||||||
|
|
|
@ -2,8 +2,9 @@ Definitions.
|
||||||
|
|
||||||
Control = [()&|!=~><:*]
|
Control = [()&|!=~><:*]
|
||||||
White = [\s\t\n\r]+
|
White = [\s\t\n\r]+
|
||||||
NonString = [^()&|!=~><:*\s\t\n\r]
|
StringChars = [^()&|!=~><:*\t\n\r]
|
||||||
String = {NonString}+
|
Escape = \\{Control}|\\{White}
|
||||||
|
String = ({Escape}|{StringChars})+
|
||||||
|
|
||||||
Rules.
|
Rules.
|
||||||
|
|
||||||
|
@ -17,10 +18,10 @@ Rules.
|
||||||
>= : {token, {greaterOrEqual, TokenLine}}.
|
>= : {token, {greaterOrEqual, TokenLine}}.
|
||||||
<= : {token, {lessOrEqual, TokenLine}}.
|
<= : {token, {lessOrEqual, TokenLine}}.
|
||||||
\* : {token, {asterisk, TokenLine}}.
|
\* : {token, {asterisk, TokenLine}}.
|
||||||
|
\:dn : {token, {dn, TokenLine}}.
|
||||||
\: : {token, {colon, TokenLine}}.
|
\: : {token, {colon, TokenLine}}.
|
||||||
dn : {token, {dn, TokenLine}}.
|
|
||||||
{White} : skip_token.
|
{White} : skip_token.
|
||||||
{String} : {token, {string, TokenLine, TokenChars}}.
|
{String} : {token, {string, TokenLine, to_string(TokenChars)}}.
|
||||||
%% Leex will hang if a composite operation is missing a character
|
%% Leex will hang if a composite operation is missing a character
|
||||||
{Control} : {error, lists:flatten(io_lib:format("Unexpected Tokens:~ts", [TokenChars]))}.
|
{Control} : {error, lists:flatten(io_lib:format("Unexpected Tokens:~ts", [TokenChars]))}.
|
||||||
|
|
||||||
|
@ -29,3 +30,8 @@ Erlang code.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
%% eldap does not support neither the '\28value\29' nor '\(value\)'
|
||||||
|
%% so after the tokenization we should remove all escape character
|
||||||
|
to_string(TokenChars) ->
|
||||||
|
String = string:trim(TokenChars),
|
||||||
|
lists:flatten(string:replace(String, "\\", "", all)).
|
||||||
|
|
|
@ -92,7 +92,7 @@ type ->
|
||||||
value: {type, '$1'}.
|
value: {type, '$1'}.
|
||||||
|
|
||||||
dnattrs ->
|
dnattrs ->
|
||||||
colon dn: {dnAttributes, true}.
|
dn: {dnAttributes, true}.
|
||||||
|
|
||||||
matchingrule ->
|
matchingrule ->
|
||||||
colon value: {matchingRule, '$2'}.
|
colon value: {matchingRule, '$2'}.
|
||||||
|
|
|
@ -157,6 +157,15 @@ uid: mqttuser0007
|
||||||
isSuperuser: TRUE
|
isSuperuser: TRUE
|
||||||
userPassword: {SHA}axpQGbl00j3jvOG058y313ocnBk=
|
userPassword: {SHA}axpQGbl00j3jvOG058y313ocnBk=
|
||||||
|
|
||||||
|
objectClass: top
|
||||||
|
dn:uid=mqttuser0008 (test),ou=testdevice,dc=emqx,dc=io
|
||||||
|
objectClass: mqttUser
|
||||||
|
objectClass: mqttDevice
|
||||||
|
objectClass: mqttSecurity
|
||||||
|
uid: mqttuser0008 (test)
|
||||||
|
isSuperuser: TRUE
|
||||||
|
userPassword: {SHA}FCzJLOp66OwsZ9DQzXSxdTd9c0U=
|
||||||
|
|
||||||
## Try to test with base DN 'ou=dashboard,dc=emqx,dc=io'
|
## Try to test with base DN 'ou=dashboard,dc=emqx,dc=io'
|
||||||
## with a filter ugroup=group1
|
## with a filter ugroup=group1
|
||||||
## this should return 2 users in the query and fail the test
|
## this should return 2 users in the query and fail the test
|
||||||
|
|
|
@ -241,7 +241,8 @@ user_seeds() ->
|
||||||
%% Disabled
|
%% Disabled
|
||||||
New(<<"mqttuser0006">>, <<"mqttuser0006">>, {error, user_disabled}),
|
New(<<"mqttuser0006">>, <<"mqttuser0006">>, {error, user_disabled}),
|
||||||
%% IsSuperuser
|
%% IsSuperuser
|
||||||
New(<<"mqttuser0007">>, <<"mqttuser0007">>, {ok, #{is_superuser => true}})
|
New(<<"mqttuser0007">>, <<"mqttuser0007">>, {ok, #{is_superuser => true}}),
|
||||||
|
New(<<"mqttuser0008 (test)">>, <<"mqttuser0008 (test)">>, {ok, #{is_superuser => true}})
|
||||||
| Valid
|
| Valid
|
||||||
].
|
].
|
||||||
|
|
||||||
|
|
|
@ -167,7 +167,8 @@ t_update(_Config) ->
|
||||||
CorrectConfig = raw_ldap_auth_config(),
|
CorrectConfig = raw_ldap_auth_config(),
|
||||||
IncorrectConfig =
|
IncorrectConfig =
|
||||||
CorrectConfig#{
|
CorrectConfig#{
|
||||||
<<"base_dn">> => <<"ou=testdevice,dc=emqx,dc=io">>
|
<<"base_dn">> => <<"ou=testdevice,dc=emqx,dc=io">>,
|
||||||
|
<<"filter">> => <<"(objectClass=mqttUser)">>
|
||||||
},
|
},
|
||||||
|
|
||||||
{ok, _} = emqx:update_config(
|
{ok, _} = emqx:update_config(
|
||||||
|
@ -208,7 +209,8 @@ raw_ldap_auth_config() ->
|
||||||
<<"mechanism">> => <<"password_based">>,
|
<<"mechanism">> => <<"password_based">>,
|
||||||
<<"backend">> => <<"ldap_bind">>,
|
<<"backend">> => <<"ldap_bind">>,
|
||||||
<<"server">> => ldap_server(),
|
<<"server">> => ldap_server(),
|
||||||
<<"base_dn">> => <<"uid=${username},ou=testdevice,dc=emqx,dc=io">>,
|
<<"base_dn">> => <<"ou=testdevice,dc=emqx,dc=io">>,
|
||||||
|
<<"filter">> => <<"(uid=${username})">>,
|
||||||
<<"username">> => <<"cn=root,dc=emqx,dc=io">>,
|
<<"username">> => <<"cn=root,dc=emqx,dc=io">>,
|
||||||
<<"password">> => <<"public">>,
|
<<"password">> => <<"public">>,
|
||||||
<<"pool_size">> => 8,
|
<<"pool_size">> => 8,
|
||||||
|
@ -226,13 +228,22 @@ user_seeds() ->
|
||||||
result => Result
|
result => Result
|
||||||
}
|
}
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
Normal = lists:map(
|
||||||
|
fun(Idx) ->
|
||||||
|
erlang:iolist_to_binary(io_lib:format("mqttuser000~b", [Idx]))
|
||||||
|
end,
|
||||||
|
lists:seq(1, 5)
|
||||||
|
),
|
||||||
|
|
||||||
|
Specials = [<<"mqttuser0008 (test)">>],
|
||||||
|
|
||||||
Valid =
|
Valid =
|
||||||
lists:map(
|
lists:map(
|
||||||
fun(Idx) ->
|
fun(Username) ->
|
||||||
Username = erlang:iolist_to_binary(io_lib:format("mqttuser000~b", [Idx])),
|
|
||||||
New(Username, Username, {ok, #{is_superuser => false}})
|
New(Username, Username, {ok, #{is_superuser => false}})
|
||||||
end,
|
end,
|
||||||
lists:seq(1, 5)
|
Normal ++ Specials
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
%% Not exists
|
%% Not exists
|
||||||
|
|
|
@ -223,6 +223,23 @@ t_error(_Config) ->
|
||||||
?assertMatch({error, _}, scan_and_parse("attr=value")),
|
?assertMatch({error, _}, scan_and_parse("attr=value")),
|
||||||
?assertMatch({error, _}, scan_and_parse("(a=b)(c=d)")).
|
?assertMatch({error, _}, scan_and_parse("(a=b)(c=d)")).
|
||||||
|
|
||||||
|
t_escape(_Config) ->
|
||||||
|
?assertEqual(
|
||||||
|
'and'([equalityMatch("a", "(value)")]),
|
||||||
|
parse("(&(a=\\(value\\)))")
|
||||||
|
),
|
||||||
|
?assertEqual(
|
||||||
|
'or'([equalityMatch("a", "name (1)")]),
|
||||||
|
parse("(|(a=name \\(1\\)))")
|
||||||
|
),
|
||||||
|
?assertEqual(
|
||||||
|
'or'([equalityMatch("a", "name (1) *")]),
|
||||||
|
parse("(|(a=name\\ \\(1\\) \\*))")
|
||||||
|
).
|
||||||
|
|
||||||
|
t_value_eql_dn(_Config) ->
|
||||||
|
?assertEqual('and'([equalityMatch("a", "dn")]), parse("(&(a=dn))")).
|
||||||
|
|
||||||
% %%------------------------------------------------------------------------------
|
% %%------------------------------------------------------------------------------
|
||||||
% %% Helpers
|
% %% Helpers
|
||||||
% %%------------------------------------------------------------------------------
|
% %%------------------------------------------------------------------------------
|
||||||
|
|
Loading…
Reference in New Issue