fix(tpl): ensure backward compat with authz / authn templates
This commit leans heavy into discouraging the former approach where only part of placeholders were interpolated, depending on `placeholders` option.
This commit is contained in:
parent
49f5325c67
commit
49fba40ee7
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx_placeholder.hrl").
|
-include_lib("emqx/include/emqx_placeholder.hrl").
|
||||||
-include_lib("emqx_authn.hrl").
|
-include_lib("emqx_authn.hrl").
|
||||||
|
-include_lib("snabbkaffe/include/trace.hrl").
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
create_resource/3,
|
create_resource/3,
|
||||||
|
@ -44,13 +45,13 @@
|
||||||
default_headers_no_content_type/0
|
default_headers_no_content_type/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(AUTHN_PLACEHOLDERS, [
|
-define(ALLOWED_VARS, [
|
||||||
<<?VAR_USERNAME>>,
|
?VAR_USERNAME,
|
||||||
<<?VAR_CLIENTID>>,
|
?VAR_CLIENTID,
|
||||||
<<?VAR_PASSWORD>>,
|
?VAR_PASSWORD,
|
||||||
<<?VAR_PEERHOST>>,
|
?VAR_PEERHOST,
|
||||||
<<?VAR_CERT_SUBJECT>>,
|
?VAR_CERT_SUBJECT,
|
||||||
<<?VAR_CERT_CN_NAME>>
|
?VAR_CERT_CN_NAME
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(DEFAULT_RESOURCE_OPTS, #{
|
-define(DEFAULT_RESOURCE_OPTS, #{
|
||||||
|
@ -108,21 +109,55 @@ check_password_from_selected_map(Algorithm, Selected, Password) ->
|
||||||
|
|
||||||
parse_deep(Template) ->
|
parse_deep(Template) ->
|
||||||
Result = emqx_connector_template:parse_deep(Template),
|
Result = emqx_connector_template:parse_deep(Template),
|
||||||
ok = emqx_connector_template:validate(?AUTHN_PLACEHOLDERS, Result),
|
handle_disallowed_placeholders(Result, {deep, Template}).
|
||||||
Result.
|
|
||||||
|
|
||||||
parse_str(Template) ->
|
parse_str(Template) ->
|
||||||
Result = emqx_connector_template:parse(Template),
|
Result = emqx_connector_template:parse(Template),
|
||||||
ok = emqx_connector_template:validate(?AUTHN_PLACEHOLDERS, Result),
|
handle_disallowed_placeholders(Result, {string, Template}).
|
||||||
Result.
|
|
||||||
|
|
||||||
parse_sql(Template, ReplaceWith) ->
|
parse_sql(Template, ReplaceWith) ->
|
||||||
{Statement, Result} = emqx_connector_template_sql:parse_prepstmt(
|
{Statement, Result} = emqx_connector_template_sql:parse_prepstmt(
|
||||||
Template,
|
Template,
|
||||||
#{parameters => ReplaceWith, strip_double_quote => true}
|
#{parameters => ReplaceWith, strip_double_quote => true}
|
||||||
),
|
),
|
||||||
ok = emqx_connector_template:validate(?AUTHN_PLACEHOLDERS, Result),
|
{Statement, handle_disallowed_placeholders(Result, {string, Template})}.
|
||||||
{Statement, Result}.
|
|
||||||
|
handle_disallowed_placeholders(Template, Source) ->
|
||||||
|
case emqx_connector_template:validate(?ALLOWED_VARS, Template) of
|
||||||
|
ok ->
|
||||||
|
Template;
|
||||||
|
{error, Disallowed} ->
|
||||||
|
?tp(warning, "authn_template_invalid", #{
|
||||||
|
template => Source,
|
||||||
|
reason => Disallowed,
|
||||||
|
allowed => #{placeholders => ?ALLOWED_VARS},
|
||||||
|
notice =>
|
||||||
|
"Disallowed placeholders will be rendered as is."
|
||||||
|
" However, consider using `$${...}` escaping for literal `${...}` where"
|
||||||
|
" needed to avoid unexpected results."
|
||||||
|
}),
|
||||||
|
Result = prerender_disallowed_placeholders(Template),
|
||||||
|
case Source of
|
||||||
|
{string, _} ->
|
||||||
|
emqx_connector_template:parse(Result);
|
||||||
|
{deep, _} ->
|
||||||
|
emqx_connector_template:parse_deep(Result)
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
prerender_disallowed_placeholders(Template) ->
|
||||||
|
{Result, _} = emqx_connector_template:render(Template, #{}, #{
|
||||||
|
var_trans => fun(Name, _) ->
|
||||||
|
% NOTE
|
||||||
|
% Rendering disallowed placeholders in escaped form, which will then
|
||||||
|
% parse as a literal string.
|
||||||
|
case lists:member(Name, ?ALLOWED_VARS) of
|
||||||
|
true -> "${" ++ Name ++ "}";
|
||||||
|
false -> "$${" ++ Name ++ "}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}),
|
||||||
|
Result.
|
||||||
|
|
||||||
render_deep(Template, Credential) ->
|
render_deep(Template, Credential) ->
|
||||||
% NOTE
|
% NOTE
|
||||||
|
@ -130,7 +165,7 @@ render_deep(Template, Credential) ->
|
||||||
{Term, _Errors} = emqx_connector_template:render(
|
{Term, _Errors} = emqx_connector_template:render(
|
||||||
Template,
|
Template,
|
||||||
mapping_credential(Credential),
|
mapping_credential(Credential),
|
||||||
#{var_trans => fun handle_var/2}
|
#{var_trans => fun to_string/2}
|
||||||
),
|
),
|
||||||
Term.
|
Term.
|
||||||
|
|
||||||
|
@ -140,7 +175,7 @@ render_str(Template, Credential) ->
|
||||||
{String, _Errors} = emqx_connector_template:render(
|
{String, _Errors} = emqx_connector_template:render(
|
||||||
Template,
|
Template,
|
||||||
mapping_credential(Credential),
|
mapping_credential(Credential),
|
||||||
#{var_trans => fun handle_var/2}
|
#{var_trans => fun to_string/2}
|
||||||
),
|
),
|
||||||
unicode:characters_to_binary(String).
|
unicode:characters_to_binary(String).
|
||||||
|
|
||||||
|
@ -150,7 +185,7 @@ render_urlencoded_str(Template, Credential) ->
|
||||||
{String, _Errors} = emqx_connector_template:render(
|
{String, _Errors} = emqx_connector_template:render(
|
||||||
Template,
|
Template,
|
||||||
mapping_credential(Credential),
|
mapping_credential(Credential),
|
||||||
#{var_trans => fun urlencode_var/2}
|
#{var_trans => fun to_urlencoded_string/2}
|
||||||
),
|
),
|
||||||
unicode:characters_to_binary(String).
|
unicode:characters_to_binary(String).
|
||||||
|
|
||||||
|
@ -160,7 +195,7 @@ render_sql_params(ParamList, Credential) ->
|
||||||
{Row, _Errors} = emqx_connector_template:render(
|
{Row, _Errors} = emqx_connector_template:render(
|
||||||
ParamList,
|
ParamList,
|
||||||
mapping_credential(Credential),
|
mapping_credential(Credential),
|
||||||
#{var_trans => fun handle_sql_var/2}
|
#{var_trans => fun to_sql_valaue/2}
|
||||||
),
|
),
|
||||||
Row.
|
Row.
|
||||||
|
|
||||||
|
@ -283,22 +318,24 @@ without_password(Credential, [Name | Rest]) ->
|
||||||
without_password(Credential, Rest)
|
without_password(Credential, Rest)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
urlencode_var(Var, Value) ->
|
to_urlencoded_string(Name, Value) ->
|
||||||
emqx_http_lib:uri_encode(handle_var(Var, Value)).
|
emqx_http_lib:uri_encode(to_string(Name, Value)).
|
||||||
|
|
||||||
handle_var(_, undefined) ->
|
to_string(Name, Value) ->
|
||||||
<<>>;
|
emqx_connector_template:to_string(render_var(Name, Value)).
|
||||||
handle_var([<<"peerhost">>], PeerHost) ->
|
|
||||||
emqx_connector_template:to_string(inet:ntoa(PeerHost));
|
|
||||||
handle_var(_, Value) ->
|
|
||||||
emqx_connector_template:to_string(Value).
|
|
||||||
|
|
||||||
handle_sql_var(_, undefined) ->
|
to_sql_valaue(Name, Value) ->
|
||||||
|
emqx_connector_sql:to_sql_value(render_var(Name, Value)).
|
||||||
|
|
||||||
|
render_var(_, undefined) ->
|
||||||
|
% NOTE
|
||||||
|
% Any allowed but undefined binding will be replaced with empty string, even when
|
||||||
|
% rendering SQL values.
|
||||||
<<>>;
|
<<>>;
|
||||||
handle_sql_var([<<"peerhost">>], PeerHost) ->
|
render_var(?VAR_PEERHOST, Value) ->
|
||||||
emqx_connector_sql:to_sql_value(inet:ntoa(PeerHost));
|
inet:ntoa(Value);
|
||||||
handle_sql_var(_, Value) ->
|
render_var(_Name, Value) ->
|
||||||
emqx_connector_sql:to_sql_value(Value).
|
Value.
|
||||||
|
|
||||||
mapping_credential(C = #{cn := CN, dn := DN}) ->
|
mapping_credential(C = #{cn := CN, dn := DN}) ->
|
||||||
C#{cert_common_name => CN, cert_subject => DN};
|
C#{cert_common_name => CN, cert_subject => DN};
|
||||||
|
|
|
@ -183,8 +183,7 @@ compile_topic(<<"eq ", Topic/binary>>) ->
|
||||||
compile_topic({eq, Topic}) ->
|
compile_topic({eq, Topic}) ->
|
||||||
{eq, emqx_topic:words(bin(Topic))};
|
{eq, emqx_topic:words(bin(Topic))};
|
||||||
compile_topic(Topic) ->
|
compile_topic(Topic) ->
|
||||||
Template = emqx_connector_template:parse(Topic),
|
Template = emqx_authz_utils:parse_str(Topic, [?VAR_USERNAME, ?VAR_CLIENTID]),
|
||||||
ok = emqx_connector_template:validate([?VAR_USERNAME, ?VAR_CLIENTID], Template),
|
|
||||||
case emqx_connector_template:trivial(Template) of
|
case emqx_connector_template:trivial(Template) of
|
||||||
true -> emqx_topic:words(bin(Topic));
|
true -> emqx_topic:words(bin(Topic));
|
||||||
false -> {pattern, Template}
|
false -> {pattern, Template}
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
|
|
||||||
-module(emqx_authz_utils).
|
-module(emqx_authz_utils).
|
||||||
|
|
||||||
|
-include_lib("emqx/include/emqx_placeholder.hrl").
|
||||||
-include_lib("emqx_authz.hrl").
|
-include_lib("emqx_authz.hrl").
|
||||||
|
-include_lib("snabbkaffe/include/trace.hrl").
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
cleanup_resources/0,
|
cleanup_resources/0,
|
||||||
|
@ -109,21 +111,56 @@ update_config(Path, ConfigRequest) ->
|
||||||
|
|
||||||
parse_deep(Template, PlaceHolders) ->
|
parse_deep(Template, PlaceHolders) ->
|
||||||
Result = emqx_connector_template:parse_deep(Template),
|
Result = emqx_connector_template:parse_deep(Template),
|
||||||
ok = emqx_connector_template:validate(PlaceHolders, Result),
|
handle_disallowed_placeholders(Result, {deep, Template}, PlaceHolders).
|
||||||
Result.
|
|
||||||
|
|
||||||
parse_str(Template, PlaceHolders) ->
|
parse_str(Template, PlaceHolders) ->
|
||||||
Result = emqx_connector_template:parse(Template),
|
Result = emqx_connector_template:parse(Template),
|
||||||
ok = emqx_connector_template:validate(PlaceHolders, Result),
|
handle_disallowed_placeholders(Result, {string, Template}, PlaceHolders).
|
||||||
Result.
|
|
||||||
|
|
||||||
parse_sql(Template, ReplaceWith, PlaceHolders) ->
|
parse_sql(Template, ReplaceWith, PlaceHolders) ->
|
||||||
{Statement, Result} = emqx_connector_template_sql:parse_prepstmt(
|
{Statement, Result} = emqx_connector_template_sql:parse_prepstmt(
|
||||||
Template,
|
Template,
|
||||||
#{parameters => ReplaceWith, strip_double_quote => true}
|
#{parameters => ReplaceWith, strip_double_quote => true}
|
||||||
),
|
),
|
||||||
ok = emqx_connector_template:validate(PlaceHolders, Result),
|
FResult = handle_disallowed_placeholders(Result, {string, Template}, PlaceHolders),
|
||||||
{Statement, Result}.
|
{Statement, FResult}.
|
||||||
|
|
||||||
|
handle_disallowed_placeholders(Template, Source, Allowed) ->
|
||||||
|
case emqx_connector_template:validate(Allowed, Template) of
|
||||||
|
ok ->
|
||||||
|
Template;
|
||||||
|
{error, Disallowed} ->
|
||||||
|
?tp(warning, "authz_template_invalid", #{
|
||||||
|
template => Source,
|
||||||
|
reason => Disallowed,
|
||||||
|
allowed => #{placeholders => Allowed},
|
||||||
|
notice =>
|
||||||
|
"Disallowed placeholders will be rendered as is."
|
||||||
|
" However, consider using `$${...}` escaping for literal `${...}` where"
|
||||||
|
" needed to avoid unexpected results."
|
||||||
|
}),
|
||||||
|
Result = prerender_disallowed_placeholders(Template, Allowed),
|
||||||
|
case Source of
|
||||||
|
{string, _} ->
|
||||||
|
emqx_connector_template:parse(Result);
|
||||||
|
{deep, _} ->
|
||||||
|
emqx_connector_template:parse_deep(Result)
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
prerender_disallowed_placeholders(Template, Allowed) ->
|
||||||
|
{Result, _} = emqx_connector_template:render(Template, #{}, #{
|
||||||
|
var_trans => fun(Name, _) ->
|
||||||
|
% NOTE
|
||||||
|
% Rendering disallowed placeholders in escaped form, which will then
|
||||||
|
% parse as a literal string.
|
||||||
|
case lists:member(Name, Allowed) of
|
||||||
|
true -> "${" ++ Name ++ "}";
|
||||||
|
false -> "$${" ++ Name ++ "}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}),
|
||||||
|
Result.
|
||||||
|
|
||||||
render_deep(Template, Values) ->
|
render_deep(Template, Values) ->
|
||||||
% NOTE
|
% NOTE
|
||||||
|
@ -131,7 +168,7 @@ render_deep(Template, Values) ->
|
||||||
{Term, _Errors} = emqx_connector_template:render(
|
{Term, _Errors} = emqx_connector_template:render(
|
||||||
Template,
|
Template,
|
||||||
client_vars(Values),
|
client_vars(Values),
|
||||||
#{var_trans => fun handle_var/2}
|
#{var_trans => fun to_string/2}
|
||||||
),
|
),
|
||||||
Term.
|
Term.
|
||||||
|
|
||||||
|
@ -141,7 +178,7 @@ render_str(Template, Values) ->
|
||||||
{String, _Errors} = emqx_connector_template:render(
|
{String, _Errors} = emqx_connector_template:render(
|
||||||
Template,
|
Template,
|
||||||
client_vars(Values),
|
client_vars(Values),
|
||||||
#{var_trans => fun handle_var/2}
|
#{var_trans => fun to_string/2}
|
||||||
),
|
),
|
||||||
unicode:characters_to_binary(String).
|
unicode:characters_to_binary(String).
|
||||||
|
|
||||||
|
@ -151,7 +188,7 @@ render_urlencoded_str(Template, Values) ->
|
||||||
{String, _Errors} = emqx_connector_template:render(
|
{String, _Errors} = emqx_connector_template:render(
|
||||||
Template,
|
Template,
|
||||||
client_vars(Values),
|
client_vars(Values),
|
||||||
#{var_trans => fun urlencode_var/2}
|
#{var_trans => fun to_urlencoded_string/2}
|
||||||
),
|
),
|
||||||
unicode:characters_to_binary(String).
|
unicode:characters_to_binary(String).
|
||||||
|
|
||||||
|
@ -161,7 +198,7 @@ render_sql_params(ParamList, Values) ->
|
||||||
{Row, _Errors} = emqx_connector_template:render(
|
{Row, _Errors} = emqx_connector_template:render(
|
||||||
ParamList,
|
ParamList,
|
||||||
client_vars(Values),
|
client_vars(Values),
|
||||||
#{var_trans => fun handle_sql_var/2}
|
#{var_trans => fun to_sql_value/2}
|
||||||
),
|
),
|
||||||
Row.
|
Row.
|
||||||
|
|
||||||
|
@ -229,22 +266,24 @@ convert_client_var({dn, DN}) -> {cert_subject, DN};
|
||||||
convert_client_var({protocol, Proto}) -> {proto_name, Proto};
|
convert_client_var({protocol, Proto}) -> {proto_name, Proto};
|
||||||
convert_client_var(Other) -> Other.
|
convert_client_var(Other) -> Other.
|
||||||
|
|
||||||
urlencode_var(Var, Value) ->
|
to_urlencoded_string(Name, Value) ->
|
||||||
emqx_http_lib:uri_encode(handle_var(Var, Value)).
|
emqx_http_lib:uri_encode(to_string(Name, Value)).
|
||||||
|
|
||||||
handle_var(_, undefined) ->
|
to_string(Name, Value) ->
|
||||||
<<>>;
|
emqx_connector_template:to_string(render_var(Name, Value)).
|
||||||
handle_var([<<"peerhost">>], IpAddr) ->
|
|
||||||
inet_parse:ntoa(IpAddr);
|
|
||||||
handle_var(_Name, Value) ->
|
|
||||||
emqx_connector_template:to_string(Value).
|
|
||||||
|
|
||||||
handle_sql_var(_, undefined) ->
|
to_sql_value(Name, Value) ->
|
||||||
|
emqx_connector_sql:to_sql_value(render_var(Name, Value)).
|
||||||
|
|
||||||
|
render_var(_, undefined) ->
|
||||||
|
% NOTE
|
||||||
|
% Any allowed but undefined binding will be replaced with empty string, even when
|
||||||
|
% rendering SQL values.
|
||||||
<<>>;
|
<<>>;
|
||||||
handle_sql_var([<<"peerhost">>], IpAddr) ->
|
render_var(?VAR_PEERHOST, Value) ->
|
||||||
inet_parse:ntoa(IpAddr);
|
inet:ntoa(Value);
|
||||||
handle_sql_var(_Name, Value) ->
|
render_var(_Name, Value) ->
|
||||||
emqx_connector_sql:to_sql_value(Value).
|
Value.
|
||||||
|
|
||||||
bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
|
bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
|
||||||
bin(L) when is_list(L) -> list_to_binary(L);
|
bin(L) when is_list(L) -> list_to_binary(L);
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-define(PLACEHOLDERS, [
|
-define(ALLOWED_VARS, [
|
||||||
?VAR_USERNAME,
|
?VAR_USERNAME,
|
||||||
?VAR_CLIENTID,
|
?VAR_CLIENTID,
|
||||||
?VAR_PEERHOST,
|
?VAR_PEERHOST,
|
||||||
|
@ -50,9 +50,9 @@
|
||||||
?VAR_CERT_CN_NAME
|
?VAR_CERT_CN_NAME
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(PLACEHOLDERS_FOR_RICH_ACTIONS, [
|
-define(ALLOWED_VARS_RICH_ACTIONS, [
|
||||||
<<?VAR_QOS>>,
|
?VAR_QOS,
|
||||||
<<?VAR_RETAIN>>
|
?VAR_RETAIN
|
||||||
]).
|
]).
|
||||||
|
|
||||||
description() ->
|
description() ->
|
||||||
|
@ -157,14 +157,14 @@ parse_config(
|
||||||
method => Method,
|
method => Method,
|
||||||
base_url => BaseUrl,
|
base_url => BaseUrl,
|
||||||
headers => Headers,
|
headers => Headers,
|
||||||
base_path_templete => emqx_authz_utils:parse_str(Path, placeholders()),
|
base_path_templete => emqx_authz_utils:parse_str(Path, allowed_vars()),
|
||||||
base_query_template => emqx_authz_utils:parse_deep(
|
base_query_template => emqx_authz_utils:parse_deep(
|
||||||
cow_qs:parse_qs(to_bin(Query)),
|
cow_qs:parse_qs(to_bin(Query)),
|
||||||
placeholders()
|
allowed_vars()
|
||||||
),
|
),
|
||||||
body_template => emqx_authz_utils:parse_deep(
|
body_template => emqx_authz_utils:parse_deep(
|
||||||
maps:to_list(maps:get(body, Conf, #{})),
|
maps:to_list(maps:get(body, Conf, #{})),
|
||||||
placeholders()
|
allowed_vars()
|
||||||
),
|
),
|
||||||
request_timeout => ReqTimeout,
|
request_timeout => ReqTimeout,
|
||||||
%% pool_type default value `random`
|
%% pool_type default value `random`
|
||||||
|
@ -260,10 +260,10 @@ to_bin(B) when is_binary(B) -> B;
|
||||||
to_bin(L) when is_list(L) -> list_to_binary(L);
|
to_bin(L) when is_list(L) -> list_to_binary(L);
|
||||||
to_bin(X) -> X.
|
to_bin(X) -> X.
|
||||||
|
|
||||||
placeholders() ->
|
allowed_vars() ->
|
||||||
placeholders(emqx_authz:feature_available(rich_actions)).
|
allowed_vars(emqx_authz:feature_available(rich_actions)).
|
||||||
|
|
||||||
placeholders(true) ->
|
allowed_vars(true) ->
|
||||||
?PLACEHOLDERS ++ ?PLACEHOLDERS_FOR_RICH_ACTIONS;
|
?ALLOWED_VARS ++ ?ALLOWED_VARS_RICH_ACTIONS;
|
||||||
placeholders(false) ->
|
allowed_vars(false) ->
|
||||||
?PLACEHOLDERS.
|
?ALLOWED_VARS.
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
-define(PATH, [?CONF_NS_ATOM]).
|
-define(PATH, [?CONF_NS_ATOM]).
|
||||||
|
|
||||||
-define(HTTP_PORT, 32333).
|
-define(HTTP_PORT, 32333).
|
||||||
-define(HTTP_PATH, "/auth").
|
-define(HTTP_PATH, "/auth/[...]").
|
||||||
-define(CREDENTIALS, #{
|
-define(CREDENTIALS, #{
|
||||||
clientid => <<"clienta">>,
|
clientid => <<"clienta">>,
|
||||||
username => <<"plain">>,
|
username => <<"plain">>,
|
||||||
|
@ -146,8 +146,12 @@ t_authenticate(_Config) ->
|
||||||
test_user_auth(#{
|
test_user_auth(#{
|
||||||
handler := Handler,
|
handler := Handler,
|
||||||
config_params := SpecificConfgParams,
|
config_params := SpecificConfgParams,
|
||||||
result := Result
|
result := Expect
|
||||||
}) ->
|
}) ->
|
||||||
|
Result = perform_user_auth(SpecificConfgParams, Handler, ?CREDENTIALS),
|
||||||
|
?assertEqual(Expect, Result).
|
||||||
|
|
||||||
|
perform_user_auth(SpecificConfgParams, Handler, Credentials) ->
|
||||||
AuthConfig = maps:merge(raw_http_auth_config(), SpecificConfgParams),
|
AuthConfig = maps:merge(raw_http_auth_config(), SpecificConfgParams),
|
||||||
|
|
||||||
{ok, _} = emqx:update_config(
|
{ok, _} = emqx:update_config(
|
||||||
|
@ -157,21 +161,21 @@ test_user_auth(#{
|
||||||
|
|
||||||
ok = emqx_authn_http_test_server:set_handler(Handler),
|
ok = emqx_authn_http_test_server:set_handler(Handler),
|
||||||
|
|
||||||
?assertEqual(Result, emqx_access_control:authenticate(?CREDENTIALS)),
|
Result = emqx_access_control:authenticate(Credentials),
|
||||||
|
|
||||||
emqx_authn_test_lib:delete_authenticators(
|
emqx_authn_test_lib:delete_authenticators(
|
||||||
[authentication],
|
[authentication],
|
||||||
?GLOBAL
|
?GLOBAL
|
||||||
).
|
),
|
||||||
|
|
||||||
|
Result.
|
||||||
|
|
||||||
t_authenticate_path_placeholders(_Config) ->
|
t_authenticate_path_placeholders(_Config) ->
|
||||||
ok = emqx_authn_http_test_server:stop(),
|
|
||||||
{ok, _} = emqx_authn_http_test_server:start_link(?HTTP_PORT, <<"/[...]">>),
|
|
||||||
ok = emqx_authn_http_test_server:set_handler(
|
ok = emqx_authn_http_test_server:set_handler(
|
||||||
fun(Req0, State) ->
|
fun(Req0, State) ->
|
||||||
Req =
|
Req =
|
||||||
case cowboy_req:path(Req0) of
|
case cowboy_req:path(Req0) of
|
||||||
<<"/my/p%20ath//us%20er/auth//">> ->
|
<<"/auth/p%20ath//us%20er/auth//">> ->
|
||||||
cowboy_req:reply(
|
cowboy_req:reply(
|
||||||
200,
|
200,
|
||||||
#{<<"content-type">> => <<"application/json">>},
|
#{<<"content-type">> => <<"application/json">>},
|
||||||
|
@ -193,7 +197,7 @@ t_authenticate_path_placeholders(_Config) ->
|
||||||
AuthConfig = maps:merge(
|
AuthConfig = maps:merge(
|
||||||
raw_http_auth_config(),
|
raw_http_auth_config(),
|
||||||
#{
|
#{
|
||||||
<<"url">> => <<"http://127.0.0.1:32333/my/p%20ath//${username}/auth//">>,
|
<<"url">> => <<"http://127.0.0.1:32333/auth/p%20ath//${username}/auth//">>,
|
||||||
<<"body">> => #{}
|
<<"body">> => #{}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -255,6 +259,39 @@ t_no_value_for_placeholder(_Config) ->
|
||||||
?GLOBAL
|
?GLOBAL
|
||||||
).
|
).
|
||||||
|
|
||||||
|
t_disallowed_placeholders_preserved(_Config) ->
|
||||||
|
Config = #{
|
||||||
|
<<"method">> => <<"post">>,
|
||||||
|
<<"headers">> => #{<<"content-type">> => <<"application/json">>},
|
||||||
|
<<"body">> => #{
|
||||||
|
<<"username">> => ?PH_USERNAME,
|
||||||
|
<<"password">> => ?PH_PASSWORD,
|
||||||
|
<<"this">> => <<"${whatisthis}">>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Handler = fun(Req0, State) ->
|
||||||
|
{ok, Body, Req1} = cowboy_req:read_body(Req0),
|
||||||
|
#{
|
||||||
|
<<"username">> := <<"plain">>,
|
||||||
|
<<"password">> := <<"plain">>,
|
||||||
|
<<"this">> := <<"${whatisthis}">>
|
||||||
|
} = emqx_utils_json:decode(Body),
|
||||||
|
Req = cowboy_req:reply(
|
||||||
|
200,
|
||||||
|
#{<<"content-type">> => <<"application/json">>},
|
||||||
|
emqx_utils_json:encode(#{result => allow, is_superuser => false}),
|
||||||
|
Req1
|
||||||
|
),
|
||||||
|
{ok, Req, State}
|
||||||
|
end,
|
||||||
|
?assertMatch({ok, _}, perform_user_auth(Config, Handler, ?CREDENTIALS)),
|
||||||
|
|
||||||
|
% NOTE: disallowed placeholder left intact, which makes the URL invalid
|
||||||
|
ConfigUrl = Config#{
|
||||||
|
<<"url">> => <<"http://127.0.0.1:32333/auth/${whatisthis}">>
|
||||||
|
},
|
||||||
|
?assertMatch({error, _}, perform_user_auth(ConfigUrl, Handler, ?CREDENTIALS)).
|
||||||
|
|
||||||
t_destroy(_Config) ->
|
t_destroy(_Config) ->
|
||||||
AuthConfig = raw_http_auth_config(),
|
AuthConfig = raw_http_auth_config(),
|
||||||
|
|
||||||
|
|
|
@ -494,6 +494,67 @@ t_no_value_for_placeholder(_Config) ->
|
||||||
emqx_access_control:authorize(ClientInfo, ?AUTHZ_PUBLISH, <<"t">>)
|
emqx_access_control:authorize(ClientInfo, ?AUTHZ_PUBLISH, <<"t">>)
|
||||||
).
|
).
|
||||||
|
|
||||||
|
t_disallowed_placeholders_preserved(_Config) ->
|
||||||
|
ok = setup_handler_and_config(
|
||||||
|
fun(Req0, State) ->
|
||||||
|
{ok, Body, Req1} = cowboy_req:read_body(Req0),
|
||||||
|
?assertMatch(
|
||||||
|
#{
|
||||||
|
<<"cname">> := <<>>,
|
||||||
|
<<"usertypo">> := <<"${usertypo}">>
|
||||||
|
},
|
||||||
|
emqx_utils_json:decode(Body)
|
||||||
|
),
|
||||||
|
{ok, ?AUTHZ_HTTP_RESP(allow, Req1), State}
|
||||||
|
end,
|
||||||
|
#{
|
||||||
|
<<"method">> => <<"post">>,
|
||||||
|
<<"body">> => #{
|
||||||
|
<<"cname">> => ?PH_CERT_CN_NAME,
|
||||||
|
<<"usertypo">> => <<"${usertypo}">>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
ClientInfo = #{
|
||||||
|
clientid => <<"client id">>,
|
||||||
|
username => <<"user name">>,
|
||||||
|
peerhost => {127, 0, 0, 1},
|
||||||
|
protocol => <<"MQTT">>,
|
||||||
|
zone => default,
|
||||||
|
listener => {tcp, default}
|
||||||
|
},
|
||||||
|
|
||||||
|
?assertEqual(
|
||||||
|
allow,
|
||||||
|
emqx_access_control:authorize(ClientInfo, ?AUTHZ_PUBLISH, <<"t">>)
|
||||||
|
).
|
||||||
|
|
||||||
|
t_disallowed_placeholders_path(_Config) ->
|
||||||
|
ok = setup_handler_and_config(
|
||||||
|
fun(Req, State) ->
|
||||||
|
{ok, ?AUTHZ_HTTP_RESP(allow, Req), State}
|
||||||
|
end,
|
||||||
|
#{
|
||||||
|
<<"url">> => <<"http://127.0.0.1:33333/authz/use%20rs/${typo}">>
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
ClientInfo = #{
|
||||||
|
clientid => <<"client id">>,
|
||||||
|
username => <<"user name">>,
|
||||||
|
peerhost => {127, 0, 0, 1},
|
||||||
|
protocol => <<"MQTT">>,
|
||||||
|
zone => default,
|
||||||
|
listener => {tcp, default}
|
||||||
|
},
|
||||||
|
|
||||||
|
% % NOTE: disallowed placeholder left intact, which makes the URL invalid
|
||||||
|
?assertEqual(
|
||||||
|
deny,
|
||||||
|
emqx_access_control:authorize(ClientInfo, ?AUTHZ_PUBLISH, <<"t">>)
|
||||||
|
).
|
||||||
|
|
||||||
t_create_replace(_Config) ->
|
t_create_replace(_Config) ->
|
||||||
ClientInfo = #{
|
ClientInfo = #{
|
||||||
clientid => <<"clientid">>,
|
clientid => <<"clientid">>,
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-define(PLACEHOLDERS, [
|
-define(ALLOWED_VARS, [
|
||||||
?VAR_USERNAME,
|
?VAR_USERNAME,
|
||||||
?VAR_CLIENTID,
|
?VAR_CLIENTID,
|
||||||
?VAR_PEERHOST,
|
?VAR_PEERHOST,
|
||||||
|
@ -49,11 +49,11 @@ description() ->
|
||||||
create(#{filter := Filter} = Source) ->
|
create(#{filter := Filter} = Source) ->
|
||||||
ResourceId = emqx_authz_utils:make_resource_id(?MODULE),
|
ResourceId = emqx_authz_utils:make_resource_id(?MODULE),
|
||||||
{ok, _Data} = emqx_authz_utils:create_resource(ResourceId, emqx_mongodb, Source),
|
{ok, _Data} = emqx_authz_utils:create_resource(ResourceId, emqx_mongodb, Source),
|
||||||
FilterTemp = emqx_authz_utils:parse_deep(Filter, ?PLACEHOLDERS),
|
FilterTemp = emqx_authz_utils:parse_deep(Filter, ?ALLOWED_VARS),
|
||||||
Source#{annotations => #{id => ResourceId}, filter_template => FilterTemp}.
|
Source#{annotations => #{id => ResourceId}, filter_template => FilterTemp}.
|
||||||
|
|
||||||
update(#{filter := Filter} = Source) ->
|
update(#{filter := Filter} = Source) ->
|
||||||
FilterTemp = emqx_authz_utils:parse_deep(Filter, ?PLACEHOLDERS),
|
FilterTemp = emqx_authz_utils:parse_deep(Filter, ?ALLOWED_VARS),
|
||||||
case emqx_authz_utils:update_resource(emqx_mongodb, Source) of
|
case emqx_authz_utils:update_resource(emqx_mongodb, Source) of
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
error({load_config_error, Reason});
|
error({load_config_error, Reason});
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-define(PLACEHOLDERS, [
|
-define(ALLOWED_VARS, [
|
||||||
?VAR_USERNAME,
|
?VAR_USERNAME,
|
||||||
?VAR_CLIENTID,
|
?VAR_CLIENTID,
|
||||||
?VAR_PEERHOST,
|
?VAR_PEERHOST,
|
||||||
|
@ -49,14 +49,14 @@ description() ->
|
||||||
"AuthZ with Mysql".
|
"AuthZ with Mysql".
|
||||||
|
|
||||||
create(#{query := SQL} = Source0) ->
|
create(#{query := SQL} = Source0) ->
|
||||||
{PrepareSQL, TmplToken} = emqx_authz_utils:parse_sql(SQL, '?', ?PLACEHOLDERS),
|
{PrepareSQL, TmplToken} = emqx_authz_utils:parse_sql(SQL, '?', ?ALLOWED_VARS),
|
||||||
ResourceId = emqx_authz_utils:make_resource_id(?MODULE),
|
ResourceId = emqx_authz_utils:make_resource_id(?MODULE),
|
||||||
Source = Source0#{prepare_statement => #{?PREPARE_KEY => PrepareSQL}},
|
Source = Source0#{prepare_statement => #{?PREPARE_KEY => PrepareSQL}},
|
||||||
{ok, _Data} = emqx_authz_utils:create_resource(ResourceId, emqx_mysql, Source),
|
{ok, _Data} = emqx_authz_utils:create_resource(ResourceId, emqx_mysql, Source),
|
||||||
Source#{annotations => #{id => ResourceId, tmpl_token => TmplToken}}.
|
Source#{annotations => #{id => ResourceId, tmpl_token => TmplToken}}.
|
||||||
|
|
||||||
update(#{query := SQL} = Source0) ->
|
update(#{query := SQL} = Source0) ->
|
||||||
{PrepareSQL, TmplToken} = emqx_authz_utils:parse_sql(SQL, '?', ?PLACEHOLDERS),
|
{PrepareSQL, TmplToken} = emqx_authz_utils:parse_sql(SQL, '?', ?ALLOWED_VARS),
|
||||||
Source = Source0#{prepare_statement => #{?PREPARE_KEY => PrepareSQL}},
|
Source = Source0#{prepare_statement => #{?PREPARE_KEY => PrepareSQL}},
|
||||||
case emqx_authz_utils:update_resource(emqx_mysql, Source) of
|
case emqx_authz_utils:update_resource(emqx_mysql, Source) of
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-define(PLACEHOLDERS, [
|
-define(ALLOWED_VARS, [
|
||||||
?VAR_USERNAME,
|
?VAR_USERNAME,
|
||||||
?VAR_CLIENTID,
|
?VAR_CLIENTID,
|
||||||
?VAR_PEERHOST,
|
?VAR_PEERHOST,
|
||||||
|
@ -49,7 +49,7 @@ description() ->
|
||||||
"AuthZ with PostgreSQL".
|
"AuthZ with PostgreSQL".
|
||||||
|
|
||||||
create(#{query := SQL0} = Source) ->
|
create(#{query := SQL0} = Source) ->
|
||||||
{SQL, PlaceHolders} = emqx_authz_utils:parse_sql(SQL0, '$n', ?PLACEHOLDERS),
|
{SQL, PlaceHolders} = emqx_authz_utils:parse_sql(SQL0, '$n', ?ALLOWED_VARS),
|
||||||
ResourceID = emqx_authz_utils:make_resource_id(emqx_postgresql),
|
ResourceID = emqx_authz_utils:make_resource_id(emqx_postgresql),
|
||||||
{ok, _Data} = emqx_authz_utils:create_resource(
|
{ok, _Data} = emqx_authz_utils:create_resource(
|
||||||
ResourceID,
|
ResourceID,
|
||||||
|
@ -59,7 +59,7 @@ create(#{query := SQL0} = Source) ->
|
||||||
Source#{annotations => #{id => ResourceID, placeholders => PlaceHolders}}.
|
Source#{annotations => #{id => ResourceID, placeholders => PlaceHolders}}.
|
||||||
|
|
||||||
update(#{query := SQL0, annotations := #{id := ResourceID}} = Source) ->
|
update(#{query := SQL0, annotations := #{id := ResourceID}} = Source) ->
|
||||||
{SQL, PlaceHolders} = emqx_authz_utils:parse_sql(SQL0, '$n', ?PLACEHOLDERS),
|
{SQL, PlaceHolders} = emqx_authz_utils:parse_sql(SQL0, '$n', ?ALLOWED_VARS),
|
||||||
case
|
case
|
||||||
emqx_authz_utils:update_resource(
|
emqx_authz_utils:update_resource(
|
||||||
emqx_postgresql,
|
emqx_postgresql,
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-define(PLACEHOLDERS, [
|
-define(ALLOWED_VARS, [
|
||||||
?VAR_CERT_CN_NAME,
|
?VAR_CERT_CN_NAME,
|
||||||
?VAR_CERT_SUBJECT,
|
?VAR_CERT_SUBJECT,
|
||||||
?VAR_PEERHOST,
|
?VAR_PEERHOST,
|
||||||
|
@ -133,7 +133,7 @@ parse_cmd(Query) ->
|
||||||
case emqx_redis_command:split(Query) of
|
case emqx_redis_command:split(Query) of
|
||||||
{ok, Cmd} ->
|
{ok, Cmd} ->
|
||||||
ok = validate_cmd(Cmd),
|
ok = validate_cmd(Cmd),
|
||||||
emqx_authz_utils:parse_deep(Cmd, ?PLACEHOLDERS);
|
emqx_authz_utils:parse_deep(Cmd, ?ALLOWED_VARS);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
error({invalid_redis_cmd, Reason, Query})
|
error({invalid_redis_cmd, Reason, Query})
|
||||||
end.
|
end.
|
||||||
|
|
Loading…
Reference in New Issue