diff --git a/apps/emqx_authn/i18n/emqx_authn_http_i18n.conf b/apps/emqx_authn/i18n/emqx_authn_http_i18n.conf
index 35a5b4973..129db5054 100644
--- a/apps/emqx_authn/i18n/emqx_authn_http_i18n.conf
+++ b/apps/emqx_authn/i18n/emqx_authn_http_i18n.conf
@@ -18,7 +18,7 @@ emqx_authn_http {
en: """HTTP request method."""
zh: """HTTP 请求方法。"""
}
- label: {
+ label {
en: """Request Method"""
zh: """请求方法"""
}
@@ -27,9 +27,9 @@ emqx_authn_http {
url {
desc {
en: """URL of the HTTP server."""
- zh: """HTTP 服务器地址。"""
+ zh: """认证 HTTP 服务器地址。"""
}
- label: {
+ label {
en: """URL"""
zh: """URL"""
}
@@ -37,23 +37,23 @@ emqx_authn_http {
headers {
desc {
- en: """HTTP request headers."""
- zh: """HTTP request headers。"""
+ en: """List of HTTP Headers."""
+ zh: """HTTP Headers 列表"""
}
- label: {
- en: """Request Headers"""
- zh: """Request Headers"""
+ label {
+ en: """Headers"""
+ zh: """请求头"""
}
}
headers_no_content_type {
desc {
- en: """HTTP request headers (without content-type
)."""
- zh: """HTTP request headers(无 content-type
)。"""
+ en: """List of HTTP headers (without content-type
)."""
+ zh: """HTTP Headers 列表 (无 content-type
) 。"""
}
- label: {
- en: """Request Headers"""
- zh: """Request Headers"""
+ label {
+ en: """headers_no_content_type"""
+ zh: """请求头(无 content-type)"""
}
}
@@ -62,7 +62,7 @@ emqx_authn_http {
en: """HTTP request body."""
zh: """HTTP request body。"""
}
- label: {
+ label {
en: """Request Body"""
zh: """Request Body"""
}
@@ -73,7 +73,7 @@ emqx_authn_http {
en: """HTTP request timeout."""
zh: """HTTP 请求超时时长。"""
}
- label: {
+ label {
en: """Request Timeout"""
zh: """请求超时时间"""
}
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl
index b03a7f1b1..9e25f5a7a 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl
@@ -83,7 +83,10 @@ common_fields() ->
{mechanism, emqx_authn_schema:mechanism(password_based)},
{backend, emqx_authn_schema:backend(http)},
{url, fun url/1},
- {body, hoconsc:mk(map([{fuzzy, term(), binary()}]), #{desc => ?DESC(body)})},
+ {body,
+ hoconsc:mk(map([{fuzzy, term(), binary()}]), #{
+ required => false, desc => ?DESC(body)
+ })},
{request_timeout, fun request_timeout/1}
] ++ emqx_authn_schema:common_fields() ++
maps:to_list(
@@ -127,7 +130,10 @@ headers_no_content_type(desc) ->
?DESC(?FUNCTION_NAME);
headers_no_content_type(converter) ->
fun(Headers) ->
- maps:merge(default_headers_no_content_type(), transform_header_name(Headers))
+ maps:without(
+ [<<"content-type">>],
+ maps:merge(default_headers_no_content_type(), transform_header_name(Headers))
+ )
end;
headers_no_content_type(default) ->
default_headers_no_content_type();
@@ -155,26 +161,23 @@ create(_AuthenticatorID, Config) ->
create(
#{
method := Method,
- url := RawURL,
- headers := HeadersT,
- body := Body,
+ url := RawUrl,
+ headers := Headers,
request_timeout := RequestTimeout
} = Config
) ->
- Headers = ensure_header_name_type(HeadersT),
- {BsaeUrlWithPath, Query} = parse_fullpath(RawURL),
- URIMap = parse_url(BsaeUrlWithPath),
+ {BaseUrl0, Path, Query} = parse_url(RawUrl),
+ {ok, BaseUrl} = emqx_http_lib:uri_parse(BaseUrl0),
ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
State = #{
method => Method,
- path => maps:get(path, URIMap),
+ path => Path,
+ headers => Headers,
+ base_path_templete => emqx_authn_utils:parse_str(Path),
base_query_template => emqx_authn_utils:parse_deep(
cow_qs:parse_qs(to_bin(Query))
),
- headers => maps:to_list(Headers),
- body_template => emqx_authn_utils:parse_deep(
- maps:to_list(Body)
- ),
+ body_template => emqx_authn_utils:parse_deep(maps:get(body, Config, #{})),
request_timeout => RequestTimeout,
resource_id => ResourceId
},
@@ -184,7 +187,7 @@ create(
?RESOURCE_GROUP,
emqx_connector_http,
Config#{
- base_url => maps:remove(query, URIMap),
+ base_url => BaseUrl,
pool_type => random
},
#{}
@@ -273,9 +276,6 @@ destroy(#{resource_id := ResourceId}) ->
%% Internal functions
%%--------------------------------------------------------------------
-parse_fullpath(RawURL) ->
- cow_http:parse_fullpath(to_bin(RawURL)).
-
default_headers() ->
maps:put(
<<"content-type">>,
@@ -302,14 +302,14 @@ transform_header_name(Headers) ->
).
check_ssl_opts(Conf) ->
- {BaseUrlWithPath, _Query} = parse_fullpath(get_conf_val("url", Conf)),
- case parse_url(BaseUrlWithPath) of
- #{scheme := https} ->
+ {BaseUrl, _Path, _Query} = parse_url(get_conf_val("url", Conf)),
+ case BaseUrl of
+ <<"https://", _/binary>> ->
case get_conf_val("ssl.enable", Conf) of
true -> ok;
false -> false
end;
- #{scheme := http} ->
+ <<"http://", _/binary>> ->
ok
end.
@@ -318,39 +318,51 @@ check_headers(Conf) ->
Headers = get_conf_val("headers", Conf),
Method =:= <<"post">> orelse (not maps:is_key(<<"content-type">>, Headers)).
-parse_url(URL) ->
- {ok, URIMap} = emqx_http_lib:uri_parse(URL),
- case maps:get(query, URIMap, undefined) of
- undefined ->
- URIMap#{query => ""};
- _ ->
- URIMap
+parse_url(Url) ->
+ case string:split(Url, "//", leading) of
+ [Scheme, UrlRem] ->
+ case string:split(UrlRem, "/", leading) of
+ [HostPort, Remaining] ->
+ BaseUrl = iolist_to_binary([Scheme, "//", HostPort]),
+ case string:split(Remaining, "?", leading) of
+ [Path, QueryString] ->
+ {BaseUrl, Path, QueryString};
+ [Path] ->
+ {BaseUrl, Path, <<>>}
+ end;
+ [HostPort] ->
+ {iolist_to_binary([Scheme, "//", HostPort]), <<>>, <<>>}
+ end;
+ [Url] ->
+ throw({invalid_url, Url})
end.
generate_request(Credential, #{
method := Method,
- path := Path,
+ headers := Headers0,
+ base_path_templete := BasePathTemplate,
base_query_template := BaseQueryTemplate,
- headers := Headers,
body_template := BodyTemplate
}) ->
+ Headers = maps:to_list(Headers0),
+ Path = emqx_authn_utils:render_str(BasePathTemplate, Credential),
+ Query = emqx_authn_utils:render_deep(BaseQueryTemplate, Credential),
Body = emqx_authn_utils:render_deep(BodyTemplate, Credential),
- NBaseQuery = emqx_authn_utils:render_deep(BaseQueryTemplate, Credential),
case Method of
get ->
- NPath = append_query(Path, NBaseQuery ++ Body),
- {NPath, Headers};
+ NPathQuery = append_query(to_list(Path), to_list(Query) ++ maps:to_list(Body)),
+ {NPathQuery, Headers};
post ->
- NPath = append_query(Path, NBaseQuery),
+ NPathQuery = append_query(to_list(Path), to_list(Query)),
ContentType = proplists:get_value(<<"content-type">>, Headers),
NBody = serialize_body(ContentType, Body),
- {NPath, Headers, NBody}
+ {NPathQuery, Headers, NBody}
end.
append_query(Path, []) ->
- Path;
+ encode_path(Path);
append_query(Path, Query) ->
- Path ++ "?" ++ binary_to_list(qs(Query)).
+ encode_path(Path) ++ "?" ++ binary_to_list(qs(Query)).
qs(KVs) ->
qs(KVs, []).
@@ -387,12 +399,18 @@ may_append_body(Output, {ok, _, _}) ->
Output.
uri_encode(T) ->
- emqx_http_lib:uri_encode(to_bin(T)).
+ emqx_http_lib:uri_encode(to_list(T)).
+
+encode_path(Path) ->
+ Parts = string:split(Path, "/", all),
+ lists:flatten(["/" ++ Part || Part <- lists:map(fun uri_encode/1, Parts)]).
to_list(A) when is_atom(A) ->
atom_to_list(A);
to_list(B) when is_binary(B) ->
- binary_to_list(B).
+ binary_to_list(B);
+to_list(L) when is_list(L) ->
+ L.
to_bin(A) when is_atom(A) ->
atom_to_binary(A);
@@ -403,14 +421,3 @@ to_bin(L) when is_list(L) ->
get_conf_val(Name, Conf) ->
hocon_maps:get(?CONF_NS ++ "." ++ Name, Conf).
-
-ensure_header_name_type(Headers) ->
- Fun = fun
- (Key, _Val, Acc) when is_binary(Key) ->
- Acc;
- (Key, Val, Acc) when is_atom(Key) ->
- Acc2 = maps:remove(Key, Acc),
- BinKey = erlang:atom_to_binary(Key),
- Acc2#{BinKey => Val}
- end,
- maps:fold(Fun, Headers, Headers).
diff --git a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl b/apps/emqx_authn/test/emqx_authn_api_SUITE.erl
index 5fe1f9b17..fe4a8af7e 100644
--- a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl
+++ b/apps/emqx_authn/test/emqx_authn_api_SUITE.erl
@@ -184,14 +184,14 @@ test_authenticators(PathPrefix) ->
InvalidConfig0
),
- InvalidConfig1 = ValidConfig#{
+ ValidConfig1 = ValidConfig#{
method => <<"get">>,
headers => #{<<"content-type">> => <<"application/json">>}
},
- {ok, 400, _} = request(
- post,
- uri(PathPrefix ++ [?CONF_NS]),
- InvalidConfig1
+ {ok, 200, _} = request(
+ put,
+ uri(PathPrefix ++ [?CONF_NS, "password_based:http"]),
+ ValidConfig1
),
?assertAuthenticatorsMatch(
@@ -264,23 +264,23 @@ test_authenticator(PathPrefix) ->
InvalidConfig0
),
- InvalidConfig1 = ValidConfig0#{
+ ValidConfig1 = ValidConfig0#{
method => <<"get">>,
headers => #{<<"content-type">> => <<"application/json">>}
},
- {ok, 400, _} = request(
- put,
- uri(PathPrefix ++ [?CONF_NS, "password_based:http"]),
- InvalidConfig1
- ),
-
- ValidConfig1 = ValidConfig0#{pool_size => 9},
{ok, 200, _} = request(
put,
uri(PathPrefix ++ [?CONF_NS, "password_based:http"]),
ValidConfig1
),
+ ValidConfig2 = ValidConfig0#{pool_size => 9},
+ {ok, 200, _} = request(
+ put,
+ uri(PathPrefix ++ [?CONF_NS, "password_based:http"]),
+ ValidConfig2
+ ),
+
{ok, 404, _} = request(
delete,
uri(PathPrefix ++ [?CONF_NS, "password_based:redis"])
diff --git a/apps/emqx_authz/i18n/emqx_authz_api_schema_i18n.conf b/apps/emqx_authz/i18n/emqx_authz_api_schema_i18n.conf
index d5c86ba59..6d2c66b79 100644
--- a/apps/emqx_authz/i18n/emqx_authz_api_schema_i18n.conf
+++ b/apps/emqx_authz/i18n/emqx_authz_api_schema_i18n.conf
@@ -60,19 +60,19 @@ emqx_authz_api_schema {
headers {
desc {
- en: """List of HTTP headers."""
- zh: """"""
+ en: """List of HTTP Headers."""
+ zh: """HTTP Headers 列表"""
}
label {
- en: """headers"""
+ en: """Headers"""
zh: """请求头"""
}
}
headers_no_content_type {
desc {
- en: """List of HTTP headers (without `content_type`)."""
- zh: """"""
+ en: """List of HTTP headers (without content-type
)."""
+ zh: """HTTP Headers 列表(无 content-type
)"""
}
label {
en: """headers_no_content_type"""
diff --git a/apps/emqx_authz/i18n/emqx_authz_schema_i18n.conf b/apps/emqx_authz/i18n/emqx_authz_schema_i18n.conf
index a05cb3fa1..4f1c35ee8 100644
--- a/apps/emqx_authz/i18n/emqx_authz_schema_i18n.conf
+++ b/apps/emqx_authz/i18n/emqx_authz_schema_i18n.conf
@@ -122,7 +122,7 @@ and the new rules will override all rules from the old config file.
http_get {
desc {
en: """Authorization using an external HTTP server (via GET requests)."""
- zh: """使用外部 HTTP 服务器鉴权(GET 请求)."""
+ zh: """使用外部 HTTP 服务器鉴权(GET 请求)。"""
}
label {
en: """http_get"""
@@ -133,7 +133,7 @@ and the new rules will override all rules from the old config file.
http_post {
desc {
en: """Authorization using an external HTTP server (via POST requests)."""
- zh: """使用外部 HTTP 服务器鉴权(POST 请求)."""
+ zh: """使用外部 HTTP 服务器鉴权(POST 请求)。"""
}
label {
en: """http_post"""
@@ -155,29 +155,29 @@ and the new rules will override all rules from the old config file.
url {
desc {
en: """URL of the auth server."""
- zh: """认证服务器 URL"""
+ zh: """鉴权 HTTP 服务器地址。"""
}
label {
- en: """url"""
- zh: """url"""
+ en: """URL"""
+ zh: """URL"""
}
}
headers {
desc {
- en: """List of HTTP headers."""
- zh: """"""
+ en: """List of HTTP Headers."""
+ zh: """HTTP Headers 列表"""
}
label {
- en: """headers"""
+ en: """Headers"""
zh: """请求头"""
}
}
headers_no_content_type {
desc {
- en: """List of HTTP headers (without `content_type`)."""
- zh: """"""
+ en: """List of HTTP headers (without content-type
)."""
+ zh: """HTTP Headers 列表 (无 content-type
) 。"""
}
label {
en: """headers_no_content_type"""
@@ -188,22 +188,22 @@ and the new rules will override all rules from the old config file.
body {
desc {
en: """HTTP request body."""
- zh: """HTTP 请求体"""
+ zh: """HTTP request body。"""
}
label {
- en: """body"""
- zh: """请求体"""
+ en: """Request Body"""
+ zh: """Request Body"""
}
}
request_timeout {
desc {
- en: """Request timeout."""
- zh: """请求超时时间"""
+ en: """HTTP request timeout."""
+ zh: """HTTP 请求超时时长。"""
}
label {
- en: """request_timeout"""
- zh: """请求超时"""
+ en: """Request Timeout"""
+ zh: """请求超时时间"""
}
}
diff --git a/apps/emqx_authz/src/emqx_authz_api_schema.erl b/apps/emqx_authz/src/emqx_authz_api_schema.erl
index ff015cee2..b971faa77 100644
--- a/apps/emqx_authz/src/emqx_authz_api_schema.erl
+++ b/apps/emqx_authz/src/emqx_authz_api_schema.erl
@@ -102,9 +102,14 @@ authz_http_common_fields() ->
authz_common_fields(http) ++
[
{url, fun url/1},
- {body, map([{fuzzy, term(), binary()}])},
+ {body,
+ hoconsc:mk(map([{fuzzy, term(), binary()}]), #{
+ required => false, desc => ?DESC(body)
+ })},
{request_timeout,
- mk_duration("Request timeout", #{default => "30s", desc => ?DESC(request_timeout)})}
+ mk_duration("Request timeout", #{
+ required => false, default => "30s", desc => ?DESC(request_timeout)
+ })}
] ++
maps:to_list(
maps:without(
@@ -141,7 +146,10 @@ headers_no_content_type(desc) ->
?DESC(?FUNCTION_NAME);
headers_no_content_type(converter) ->
fun(Headers) ->
- maps:merge(default_headers_no_content_type(), transform_header_name(Headers))
+ maps:without(
+ [<<"content-type">>],
+ maps:merge(default_headers_no_content_type(), transform_header_name(Headers))
+ )
end;
headers_no_content_type(default) ->
default_headers_no_content_type();
diff --git a/apps/emqx_authz/src/emqx_authz_http.erl b/apps/emqx_authz/src/emqx_authz_http.erl
index 94dfcecf3..319928670 100644
--- a/apps/emqx_authz/src/emqx_authz_http.erl
+++ b/apps/emqx_authz/src/emqx_authz_http.erl
@@ -94,45 +94,49 @@ authorize(
parse_config(
#{
- url := URL,
+ url := RawUrl,
method := Method,
headers := Headers,
request_timeout := ReqTimeout
} = Conf
) ->
- {BaseURLWithPath, Query} = parse_fullpath(URL),
- BaseURLMap = parse_url(BaseURLWithPath),
+ {BaseUrl0, Path, Query} = parse_url(RawUrl),
+ {ok, BaseUrl} = emqx_http_lib:uri_parse(BaseUrl0),
Conf#{
method => Method,
- base_url => maps:remove(query, BaseURLMap),
+ base_url => BaseUrl,
+ headers => Headers,
+ base_path_templete => emqx_authz_utils:parse_str(Path, ?PLACEHOLDERS),
base_query_template => emqx_authz_utils:parse_deep(
- cow_qs:parse_qs(bin(Query)),
+ cow_qs:parse_qs(to_bin(Query)),
?PLACEHOLDERS
),
body_template => emqx_authz_utils:parse_deep(
maps:to_list(maps:get(body, Conf, #{})),
?PLACEHOLDERS
),
- headers => Headers,
request_timeout => ReqTimeout,
%% pool_type default value `random`
pool_type => random
}.
-parse_fullpath(RawURL) ->
- cow_http:parse_fullpath(bin(RawURL)).
-
-parse_url(URL) when
- URL =:= undefined
-->
- #{};
-parse_url(URL) ->
- {ok, URIMap} = emqx_http_lib:uri_parse(URL),
- case maps:get(query, URIMap, undefined) of
- undefined ->
- URIMap#{query => ""};
- _ ->
- URIMap
+parse_url(Url) ->
+ case string:split(Url, "//", leading) of
+ [Scheme, UrlRem] ->
+ case string:split(UrlRem, "/", leading) of
+ [HostPort, Remaining] ->
+ BaseUrl = iolist_to_binary([Scheme, "//", HostPort]),
+ case string:split(Remaining, "?", leading) of
+ [Path, QueryString] ->
+ {BaseUrl, Path, QueryString};
+ [Path] ->
+ {BaseUrl, Path, <<>>}
+ end;
+ [HostPort] ->
+ {iolist_to_binary([Scheme, "//", HostPort]), <<>>, <<>>}
+ end;
+ [Url] ->
+ throw({invalid_url, Url})
end.
generate_request(
@@ -141,32 +145,33 @@ generate_request(
Client,
#{
method := Method,
- base_url := #{path := Path},
- base_query_template := BaseQueryTemplate,
headers := Headers,
+ base_path_templete := BasePathTemplate,
+ base_query_template := BaseQueryTemplate,
body_template := BodyTemplate
}
) ->
Values = client_vars(Client, PubSub, Topic),
+ Path = emqx_authz_utils:render_str(BasePathTemplate, Values),
+ Query = emqx_authz_utils:render_deep(BaseQueryTemplate, Values),
Body = emqx_authz_utils:render_deep(BodyTemplate, Values),
- NBaseQuery = emqx_authz_utils:render_deep(BaseQueryTemplate, Values),
case Method of
get ->
- NPath = append_query(Path, NBaseQuery ++ Body),
+ NPath = append_query(Path, Query ++ Body),
{NPath, Headers};
_ ->
- NPath = append_query(Path, NBaseQuery),
+ NPath = append_query(Path, Query),
NBody = serialize_body(
- proplists:get_value(<<"Accept">>, Headers, <<"application/json">>),
+ proplists:get_value(<<"accept">>, Headers, <<"application/json">>),
Body
),
{NPath, Headers, NBody}
end.
append_query(Path, []) ->
- Path;
+ encode_path(Path);
append_query(Path, Query) ->
- Path ++ "?" ++ binary_to_list(query_string(Query)).
+ encode_path(Path) ++ "?" ++ to_list(query_string(Query)).
query_string(Body) ->
query_string(Body, []).
@@ -179,13 +184,14 @@ query_string([], Acc) ->
<<>>
end;
query_string([{K, V} | More], Acc) ->
- query_string(
- More,
- [
- ["&", emqx_http_lib:uri_encode(K), "=", emqx_http_lib:uri_encode(V)]
- | Acc
- ]
- ).
+ query_string(More, [["&", uri_encode(K), "=", uri_encode(V)] | Acc]).
+
+uri_encode(T) ->
+ emqx_http_lib:uri_encode(to_list(T)).
+
+encode_path(Path) ->
+ Parts = string:split(Path, "/", all),
+ lists:flatten(["/" ++ Part || Part <- lists:map(fun uri_encode/1, Parts)]).
serialize_body(<<"application/json">>, Body) ->
jsx:encode(Body);
@@ -198,7 +204,13 @@ client_vars(Client, PubSub, Topic) ->
topic => Topic
}.
-bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
-bin(B) when is_binary(B) -> B;
-bin(L) when is_list(L) -> list_to_binary(L);
-bin(X) -> X.
+to_list(A) when is_atom(A) ->
+ atom_to_list(A);
+to_list(B) when is_binary(B) ->
+ binary_to_list(B);
+to_list(L) when is_list(L) ->
+ L.
+
+to_bin(B) when is_binary(B) -> B;
+to_bin(L) when is_list(L) -> list_to_binary(L);
+to_bin(X) -> X.
diff --git a/apps/emqx_authz/src/emqx_authz_schema.erl b/apps/emqx_authz/src/emqx_authz_schema.erl
index 4946eb2a2..e8e0a7f3e 100644
--- a/apps/emqx_authz/src/emqx_authz_schema.erl
+++ b/apps/emqx_authz/src/emqx_authz_schema.erl
@@ -29,6 +29,8 @@
-type action() :: publish | subscribe | all.
-type permission() :: allow | deny.
+-import(emqx_schema, [mk_duration/2]).
+
-export([
namespace/0,
roots/0,
@@ -249,8 +251,8 @@ http_common_fields() ->
[
{url, fun url/1},
{request_timeout,
- emqx_schema:mk_duration("Request timeout", #{
- default => "30s", desc => ?DESC(request_timeout)
+ mk_duration("Request timeout", #{
+ required => false, default => "30s", desc => ?DESC(request_timeout)
})},
{body, #{type => map(), required => false, desc => ?DESC(body)}}
] ++
@@ -303,7 +305,12 @@ headers_no_content_type(desc) ->
?DESC(?FUNCTION_NAME);
headers_no_content_type(converter) ->
fun(Headers) ->
- maps:to_list(maps:merge(default_headers_no_content_type(), transform_header_name(Headers)))
+ maps:to_list(
+ maps:without(
+ [<<"content-type">>],
+ maps:merge(default_headers_no_content_type(), transform_header_name(Headers))
+ )
+ )
end;
headers_no_content_type(default) ->
default_headers_no_content_type();
@@ -359,12 +366,12 @@ check_ssl_opts(Conf) ->
true;
Url ->
case emqx_authz_http:parse_url(Url) of
- #{scheme := https} ->
+ {<<"https", _>>, _, _} ->
case hocon_maps:get("config.ssl.enable", Conf) of
true -> true;
_ -> {error, ssl_not_enable}
end;
- #{scheme := http} ->
+ {<<"http", _>>, _, _} ->
true;
Bad ->
{bad_scheme, Url, Bad}
diff --git a/apps/emqx_authz/src/emqx_authz_utils.erl b/apps/emqx_authz/src/emqx_authz_utils.erl
index c153dbc1f..4babb373b 100644
--- a/apps/emqx_authz/src/emqx_authz_utils.erl
+++ b/apps/emqx_authz/src/emqx_authz_utils.erl
@@ -25,8 +25,10 @@
create_resource/2,
update_config/2,
parse_deep/2,
+ parse_str/2,
parse_sql/3,
render_deep/2,
+ render_str/2,
render_sql_params/2
]).
@@ -69,6 +71,9 @@ update_config(Path, ConfigRequest) ->
parse_deep(Template, PlaceHolders) ->
emqx_placeholder:preproc_tmpl_deep(Template, #{placeholders => PlaceHolders}).
+parse_str(Template, PlaceHolders) ->
+ emqx_placeholder:preproc_tmpl(Template, #{placeholders => PlaceHolders}).
+
parse_sql(Template, ReplaceWith, PlaceHolders) ->
emqx_placeholder:preproc_sql(
Template,
@@ -85,6 +90,13 @@ render_deep(Template, Values) ->
#{return => full_binary, var_trans => fun handle_var/2}
).
+render_str(Template, Values) ->
+ emqx_placeholder:proc_tmpl(
+ Template,
+ client_vars(Values),
+ #{return => full_binary, var_trans => fun handle_var/2}
+ ).
+
render_sql_params(ParamList, Values) ->
emqx_placeholder:proc_tmpl(
ParamList,
diff --git a/apps/emqx_authz/test/emqx_authz_http_SUITE.erl b/apps/emqx_authz/test/emqx_authz_http_SUITE.erl
index 03016f7e2..9c00c5966 100644
--- a/apps/emqx_authz/test/emqx_authz_http_SUITE.erl
+++ b/apps/emqx_authz/test/emqx_authz_http_SUITE.erl
@@ -201,6 +201,54 @@ t_query_params(_Config) ->
emqx_access_control:authorize(ClientInfo, publish, <<"t">>)
).
+t_path(_Config) ->
+ ok = setup_handler_and_config(
+ fun(Req0, State) ->
+ ?assertEqual(
+ <<
+ "/authz/users/"
+ "user%20name/"
+ "client%20id/"
+ "127.0.0.1/"
+ "MQTT/"
+ "MOUNTPOINT/"
+ "t/1/"
+ "publish"
+ >>,
+ cowboy_req:path(Req0)
+ ),
+ Req = cowboy_req:reply(200, Req0),
+ {ok, Req, State}
+ end,
+ #{
+ <<"url">> => <<
+ "http://127.0.0.1:33333/authz/users/"
+ "${username}/"
+ "${clientid}/"
+ "${peerhost}/"
+ "${proto_name}/"
+ "${mountpoint}/"
+ "${topic}/"
+ "${action}"
+ >>
+ }
+ ),
+
+ ClientInfo = #{
+ clientid => <<"client id">>,
+ username => <<"user name">>,
+ peerhost => {127, 0, 0, 1},
+ protocol => <<"MQTT">>,
+ mountpoint => <<"MOUNTPOINT">>,
+ zone => default,
+ listener => {tcp, default}
+ },
+
+ ?assertEqual(
+ allow,
+ emqx_access_control:authorize(ClientInfo, publish, <<"t/1">>)
+ ).
+
t_json_body(_Config) ->
ok = setup_handler_and_config(
fun(Req0, State) ->