chore: support strip double quote in authn/authz

more compatibility for https://github.com/emqx/emqx/pull/8827
This commit is contained in:
JianBo He 2022-09-14 12:05:15 +08:00
parent 53bc6f47e7
commit a461375b30
8 changed files with 152 additions and 25 deletions

View File

@ -112,7 +112,8 @@ parse_sql(Template, ReplaceWith) ->
Template, Template,
#{ #{
replace_with => ReplaceWith, replace_with => ReplaceWith,
placeholders => ?AUTHN_PLACEHOLDERS placeholders => ?AUTHN_PLACEHOLDERS,
strip_double_quote => true
} }
). ).

View File

@ -332,6 +332,32 @@ user_seeds() ->
result => {ok, #{is_superuser => true}} result => {ok, #{is_superuser => true}}
}, },
%% strip double quote support
#{
data => #{
username => "sha256",
password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf",
salt => "salt",
is_superuser_int => 1
},
credentials => #{
username => <<"sha256">>,
password => <<"sha256">>
},
config_params => #{
<<"query">> =>
<<
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
" FROM users where username = \"${username}\" LIMIT 1"
>>,
<<"password_hash_algorithm">> => #{
<<"name">> => <<"sha256">>,
<<"salt_position">> => <<"prefix">>
}
},
result => {ok, #{is_superuser => true}}
},
#{ #{
data => #{ data => #{
username => "sha256", username => "sha256",

View File

@ -394,6 +394,32 @@ user_seeds() ->
result => {ok, #{is_superuser => true}} result => {ok, #{is_superuser => true}}
}, },
%% strip double quote support
#{
data => #{
username => "sha256",
password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf",
salt => "salt",
is_superuser_int => 1
},
credentials => #{
username => <<"sha256">>,
password => <<"sha256">>
},
config_params => #{
<<"query">> =>
<<
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
" FROM users where username = \"${username}\" LIMIT 1"
>>,
<<"password_hash_algorithm">> => #{
<<"name">> => <<"sha256">>,
<<"salt_position">> => <<"prefix">>
}
},
result => {ok, #{is_superuser => true}}
},
#{ #{
data => #{ data => #{
username => "sha256", username => "sha256",

View File

@ -110,7 +110,8 @@ parse_sql(Template, ReplaceWith, PlaceHolders) ->
Template, Template,
#{ #{
replace_with => ReplaceWith, replace_with => ReplaceWith,
placeholders => PlaceHolders placeholders => PlaceHolders,
strip_double_quote => true
} }
). ).

View File

@ -202,6 +202,34 @@ t_lookups(_Config) ->
} }
), ),
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[
{allow, subscribe, <<"a">>},
{deny, subscribe, <<"b">>}
]
),
%% strip double quote support
ok = init_table(),
ok = q(
<<
"INSERT INTO acl(clientid, topic, permission, action)"
"VALUES(?, ?, ?, ?)"
>>,
[<<"clientid">>, <<"a">>, <<"allow">>, <<"subscribe">>]
),
ok = setup_config(
#{
<<"query">> => <<
"SELECT permission, action, topic "
"FROM acl WHERE clientid = \"${clientid}\""
>>
}
),
ok = emqx_authz_test_lib:test_samples( ok = emqx_authz_test_lib:test_samples(
ClientInfo, ClientInfo,
[ [

View File

@ -202,6 +202,34 @@ t_lookups(_Config) ->
} }
), ),
ok = emqx_authz_test_lib:test_samples(
ClientInfo,
[
{allow, subscribe, <<"a">>},
{deny, subscribe, <<"b">>}
]
),
%% strip double quote support
ok = init_table(),
ok = insert(
<<
"INSERT INTO acl(clientid, topic, permission, action)"
"VALUES($1, $2, $3, $4)"
>>,
[<<"clientid">>, <<"a">>, <<"allow">>, <<"subscribe">>]
),
ok = setup_config(
#{
<<"query">> => <<
"SELECT permission, action, topic "
"FROM acl WHERE clientid = \"${clientid}\""
>>
}
),
ok = emqx_authz_test_lib:test_samples( ok = emqx_authz_test_lib:test_samples(
ClientInfo, ClientInfo,
[ [

View File

@ -39,7 +39,10 @@
sql_data/1 sql_data/1
]). ]).
-define(EX_PLACE_HOLDER, "(\\$\\{[a-zA-Z0-9\\._]+\\}|\"\\$\\{[a-zA-Z0-9\\._]+\\}\")"). -define(EX_PLACE_HOLDER, "(\\$\\{[a-zA-Z0-9\\._]+\\})").
-define(EX_PLACE_HOLDER_DOUBLE_QUOTE, "(\\$\\{[a-zA-Z0-9\\._]+\\}|\"\\$\\{[a-zA-Z0-9\\._]+\\}\")").
%% Space and CRLF %% Space and CRLF
-define(EX_WITHE_CHARS, "\\s"). -define(EX_WITHE_CHARS, "\\s").
@ -57,7 +60,8 @@
-type preproc_sql_opts() :: #{ -type preproc_sql_opts() :: #{
placeholders => list(binary()), placeholders => list(binary()),
replace_with => '?' | '$n' replace_with => '?' | '$n',
strip_double_quote => boolean()
}. }.
-type preproc_deep_opts() :: #{ -type preproc_deep_opts() :: #{
@ -89,7 +93,7 @@ preproc_tmpl(Str) ->
preproc_tmpl(Str, Opts) -> preproc_tmpl(Str, Opts) ->
RE = preproc_var_re(Opts), RE = preproc_var_re(Opts),
Tokens = re:split(Str, RE, [{return, binary}, group, trim]), Tokens = re:split(Str, RE, [{return, binary}, group, trim]),
do_preproc_tmpl(Tokens, []). do_preproc_tmpl(Opts, Tokens, []).
-spec proc_tmpl(tmpl_token(), map()) -> binary(). -spec proc_tmpl(tmpl_token(), map()) -> binary().
proc_tmpl(Tokens, Data) -> proc_tmpl(Tokens, Data) ->
@ -140,10 +144,11 @@ preproc_sql(Sql, ReplaceWith) when is_atom(ReplaceWith) ->
preproc_sql(Sql, #{replace_with => ReplaceWith}); preproc_sql(Sql, #{replace_with => ReplaceWith});
preproc_sql(Sql, Opts) -> preproc_sql(Sql, Opts) ->
RE = preproc_var_re(Opts), RE = preproc_var_re(Opts),
Strip = maps:get(strip_double_quote, Opts, false),
ReplaceWith = maps:get(replace_with, Opts, '?'), ReplaceWith = maps:get(replace_with, Opts, '?'),
case re:run(Sql, RE, [{capture, all_but_first, binary}, global]) of case re:run(Sql, RE, [{capture, all_but_first, binary}, global]) of
{match, PlaceHolders} -> {match, PlaceHolders} ->
PhKs = [parse_nested(unwrap(Phld)) || [Phld | _] <- PlaceHolders], PhKs = [parse_nested(unwrap(Phld, Strip)) || [Phld | _] <- PlaceHolders],
{replace_with(Sql, RE, ReplaceWith), [{var, Phld} || Phld <- PhKs]}; {replace_with(Sql, RE, ReplaceWith), [{var, Phld} || Phld <- PhKs]};
nomatch -> nomatch ->
{Sql, []} {Sql, []}
@ -234,29 +239,36 @@ get_phld_var(Fun, Data) when is_function(Fun) ->
get_phld_var(Phld, Data) -> get_phld_var(Phld, Data) ->
emqx_rule_maps:nested_get(Phld, Data). emqx_rule_maps:nested_get(Phld, Data).
preproc_var_re(#{placeholders := PHs}) -> preproc_var_re(#{placeholders := PHs, strip_double_quote := true}) ->
Res = [ph_to_re(PH) || PH <- PHs], Res = [ph_to_re(PH) || PH <- PHs],
QuoteRes = ["\"" ++ Re ++ "\"" || Re <- Res], QuoteRes = ["\"" ++ Re ++ "\"" || Re <- Res],
"(" ++ string:join(Res ++ QuoteRes, "|") ++ ")"; "(" ++ string:join(Res ++ QuoteRes, "|") ++ ")";
preproc_var_re(#{placeholders := PHs}) ->
"(" ++ string:join([ph_to_re(PH) || PH <- PHs], "|") ++ ")";
preproc_var_re(#{strip_double_quote := true}) ->
?EX_PLACE_HOLDER_DOUBLE_QUOTE;
preproc_var_re(#{}) -> preproc_var_re(#{}) ->
?EX_PLACE_HOLDER. ?EX_PLACE_HOLDER.
ph_to_re(VarPH) -> ph_to_re(VarPH) ->
re:replace(VarPH, "[\\$\\{\\}]", "\\\\&", [global, {return, list}]). re:replace(VarPH, "[\\$\\{\\}]", "\\\\&", [global, {return, list}]).
do_preproc_tmpl([], Acc) -> do_preproc_tmpl(_Opts, [], Acc) ->
lists:reverse(Acc); lists:reverse(Acc);
do_preproc_tmpl([[Str, Phld] | Tokens], Acc) -> do_preproc_tmpl(Opts, [[Str, Phld] | Tokens], Acc) ->
Strip = maps:get(strip_double_quote, Opts, false),
do_preproc_tmpl( do_preproc_tmpl(
Opts,
Tokens, Tokens,
put_head( put_head(
var, var,
parse_nested(unwrap(Phld)), parse_nested(unwrap(Phld, Strip)),
put_head(str, Str, Acc) put_head(str, Str, Acc)
) )
); );
do_preproc_tmpl([[Str] | Tokens], Acc) -> do_preproc_tmpl(Opts, [[Str] | Tokens], Acc) ->
do_preproc_tmpl( do_preproc_tmpl(
Opts,
Tokens, Tokens,
put_head(str, Str, Acc) put_head(str, Str, Acc)
). ).
@ -293,10 +305,10 @@ parse_nested(Attr) ->
Nested -> {path, [{key, P} || P <- Nested]} Nested -> {path, [{key, P} || P <- Nested]}
end. end.
unwrap(<<"${", Val/binary>>) -> unwrap(<<"\"${", Val/binary>>, _StripDoubleQuote = true) ->
binary:part(Val, {0, byte_size(Val) - 1}); binary:part(Val, {0, byte_size(Val) - 2});
unwrap(<<"\"${", Val/binary>>) -> unwrap(<<"${", Val/binary>>, _StripDoubleQuote) ->
binary:part(Val, {0, byte_size(Val) - 2}). binary:part(Val, {0, byte_size(Val) - 1}).
quote_sql(Str) -> quote_sql(Str) ->
quote(Str, <<"\\\\'">>). quote(Str, <<"\\\\'">>).

View File

@ -150,20 +150,25 @@ t_preproc_sql6(_) ->
emqx_placeholder:proc_sql(ParamsTokens, Selected) emqx_placeholder:proc_sql(ParamsTokens, Selected)
). ).
t_preproc_sql7(_) -> t_preproc_sql_strip_double_quote(_) ->
Selected = #{a => <<"a">>, b => <<"b">>}, Selected = #{a => <<"a">>, b => <<"b">>},
Opts = #{replace_with => '$n', placeholders => [<<"${a}">>]},
%% no strip_double_quote option: "${key}" -> "value"
{PrepareStatement, ParamsTokens} = emqx_placeholder:preproc_sql( {PrepareStatement, ParamsTokens} = emqx_placeholder:preproc_sql(
<<"a:\"${a}\",b:\"${b}\"">>, <<"a:\"${a}\",b:\"${b}\"">>,
#{ Opts
replace_with => '$n',
placeholders => [<<"${a}">>]
}
), ),
?assertEqual(<<"a:$1,b:\"${b}\"">>, PrepareStatement), ?assertEqual(<<"a:\"$1\",b:\"${b}\"">>, PrepareStatement),
?assertEqual( ?assertEqual([<<"a">>], emqx_placeholder:proc_sql(ParamsTokens, Selected)),
[<<"a">>],
emqx_placeholder:proc_sql(ParamsTokens, Selected) %% strip_double_quote = true: "${key}" -> value
). {PrepareStatement1, ParamsTokens1} = emqx_placeholder:preproc_sql(
<<"a:\"${a}\",b:\"${b}\"">>,
Opts#{strip_double_quote => true}
),
?assertEqual(<<"a:$1,b:\"${b}\"">>, PrepareStatement1),
?assertEqual([<<"a">>], emqx_placeholder:proc_sql(ParamsTokens1, Selected)).
t_preproc_tmpl_deep(_) -> t_preproc_tmpl_deep(_) ->
Selected = #{a => <<"1">>, b => 1, c => 1.0, d => #{d1 => <<"hi">>}}, Selected = #{a => <<"1">>, b => 1, c => 1.0, d => #{d1 => <<"hi">>}},