Merge pull request #7817 from JimMoen/fix-auth-http

This commit is contained in:
JianBo He 2022-04-29 16:36:10 +08:00 committed by GitHub
commit 344a754674
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 241 additions and 147 deletions

View File

@ -18,7 +18,7 @@ emqx_authn_http {
en: """HTTP request method.""" en: """HTTP request method."""
zh: """HTTP 请求方法。""" zh: """HTTP 请求方法。"""
} }
label: { label {
en: """Request Method""" en: """Request Method"""
zh: """请求方法""" zh: """请求方法"""
} }
@ -27,9 +27,9 @@ emqx_authn_http {
url { url {
desc { desc {
en: """URL of the HTTP server.""" en: """URL of the HTTP server."""
zh: """HTTP 服务器地址。""" zh: """认证 HTTP 服务器地址。"""
} }
label: { label {
en: """URL""" en: """URL"""
zh: """URL""" zh: """URL"""
} }
@ -37,23 +37,23 @@ emqx_authn_http {
headers { headers {
desc { desc {
en: """HTTP request headers.""" en: """List of HTTP Headers."""
zh: """HTTP request headers。""" zh: """HTTP Headers 列表"""
} }
label: { label {
en: """Request Headers""" en: """Headers"""
zh: """Request Headers""" zh: """请求头"""
} }
} }
headers_no_content_type { headers_no_content_type {
desc { desc {
en: """HTTP request headers (without <code>content-type</code>).""" en: """List of HTTP headers (without <code>content-type</code>)."""
zh: """HTTP request headers无 <code>content-type</code>。""" zh: """HTTP Headers 列表 (无 <code>content-type</code>) 。"""
} }
label: { label {
en: """Request Headers""" en: """headers_no_content_type"""
zh: """Request Headers""" zh: """请求头(无 content-type)"""
} }
} }
@ -62,7 +62,7 @@ emqx_authn_http {
en: """HTTP request body.""" en: """HTTP request body."""
zh: """HTTP request body。""" zh: """HTTP request body。"""
} }
label: { label {
en: """Request Body""" en: """Request Body"""
zh: """Request Body""" zh: """Request Body"""
} }
@ -73,7 +73,7 @@ emqx_authn_http {
en: """HTTP request timeout.""" en: """HTTP request timeout."""
zh: """HTTP 请求超时时长。""" zh: """HTTP 请求超时时长。"""
} }
label: { label {
en: """Request Timeout""" en: """Request Timeout"""
zh: """请求超时时间""" zh: """请求超时时间"""
} }

View File

@ -83,7 +83,10 @@ common_fields() ->
{mechanism, emqx_authn_schema:mechanism(password_based)}, {mechanism, emqx_authn_schema:mechanism(password_based)},
{backend, emqx_authn_schema:backend(http)}, {backend, emqx_authn_schema:backend(http)},
{url, fun url/1}, {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} {request_timeout, fun request_timeout/1}
] ++ emqx_authn_schema:common_fields() ++ ] ++ emqx_authn_schema:common_fields() ++
maps:to_list( maps:to_list(
@ -127,7 +130,10 @@ headers_no_content_type(desc) ->
?DESC(?FUNCTION_NAME); ?DESC(?FUNCTION_NAME);
headers_no_content_type(converter) -> headers_no_content_type(converter) ->
fun(Headers) -> 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; end;
headers_no_content_type(default) -> headers_no_content_type(default) ->
default_headers_no_content_type(); default_headers_no_content_type();
@ -155,26 +161,23 @@ create(_AuthenticatorID, Config) ->
create( create(
#{ #{
method := Method, method := Method,
url := RawURL, url := RawUrl,
headers := HeadersT, headers := Headers,
body := Body,
request_timeout := RequestTimeout request_timeout := RequestTimeout
} = Config } = Config
) -> ) ->
Headers = ensure_header_name_type(HeadersT), {BaseUrl0, Path, Query} = parse_url(RawUrl),
{BsaeUrlWithPath, Query} = parse_fullpath(RawURL), {ok, BaseUrl} = emqx_http_lib:uri_parse(BaseUrl0),
URIMap = parse_url(BsaeUrlWithPath),
ResourceId = emqx_authn_utils:make_resource_id(?MODULE), ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
State = #{ State = #{
method => Method, 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( base_query_template => emqx_authn_utils:parse_deep(
cow_qs:parse_qs(to_bin(Query)) cow_qs:parse_qs(to_bin(Query))
), ),
headers => maps:to_list(Headers), body_template => emqx_authn_utils:parse_deep(maps:get(body, Config, #{})),
body_template => emqx_authn_utils:parse_deep(
maps:to_list(Body)
),
request_timeout => RequestTimeout, request_timeout => RequestTimeout,
resource_id => ResourceId resource_id => ResourceId
}, },
@ -184,7 +187,7 @@ create(
?RESOURCE_GROUP, ?RESOURCE_GROUP,
emqx_connector_http, emqx_connector_http,
Config#{ Config#{
base_url => maps:remove(query, URIMap), base_url => BaseUrl,
pool_type => random pool_type => random
}, },
#{} #{}
@ -273,9 +276,6 @@ destroy(#{resource_id := ResourceId}) ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
parse_fullpath(RawURL) ->
cow_http:parse_fullpath(to_bin(RawURL)).
default_headers() -> default_headers() ->
maps:put( maps:put(
<<"content-type">>, <<"content-type">>,
@ -302,14 +302,14 @@ transform_header_name(Headers) ->
). ).
check_ssl_opts(Conf) -> check_ssl_opts(Conf) ->
{BaseUrlWithPath, _Query} = parse_fullpath(get_conf_val("url", Conf)), {BaseUrl, _Path, _Query} = parse_url(get_conf_val("url", Conf)),
case parse_url(BaseUrlWithPath) of case BaseUrl of
#{scheme := https} -> <<"https://", _/binary>> ->
case get_conf_val("ssl.enable", Conf) of case get_conf_val("ssl.enable", Conf) of
true -> ok; true -> ok;
false -> false false -> false
end; end;
#{scheme := http} -> <<"http://", _/binary>> ->
ok ok
end. end.
@ -318,39 +318,51 @@ check_headers(Conf) ->
Headers = get_conf_val("headers", Conf), Headers = get_conf_val("headers", Conf),
Method =:= <<"post">> orelse (not maps:is_key(<<"content-type">>, Headers)). Method =:= <<"post">> orelse (not maps:is_key(<<"content-type">>, Headers)).
parse_url(URL) -> parse_url(Url) ->
{ok, URIMap} = emqx_http_lib:uri_parse(URL), case string:split(Url, "//", leading) of
case maps:get(query, URIMap, undefined) of [Scheme, UrlRem] ->
undefined -> case string:split(UrlRem, "/", leading) of
URIMap#{query => ""}; [HostPort, Remaining] ->
_ -> BaseUrl = iolist_to_binary([Scheme, "//", HostPort]),
URIMap 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. end.
generate_request(Credential, #{ generate_request(Credential, #{
method := Method, method := Method,
path := Path, headers := Headers0,
base_path_templete := BasePathTemplate,
base_query_template := BaseQueryTemplate, base_query_template := BaseQueryTemplate,
headers := Headers,
body_template := BodyTemplate 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), Body = emqx_authn_utils:render_deep(BodyTemplate, Credential),
NBaseQuery = emqx_authn_utils:render_deep(BaseQueryTemplate, Credential),
case Method of case Method of
get -> get ->
NPath = append_query(Path, NBaseQuery ++ Body), NPathQuery = append_query(to_list(Path), to_list(Query) ++ maps:to_list(Body)),
{NPath, Headers}; {NPathQuery, Headers};
post -> post ->
NPath = append_query(Path, NBaseQuery), NPathQuery = append_query(to_list(Path), to_list(Query)),
ContentType = proplists:get_value(<<"content-type">>, Headers), ContentType = proplists:get_value(<<"content-type">>, Headers),
NBody = serialize_body(ContentType, Body), NBody = serialize_body(ContentType, Body),
{NPath, Headers, NBody} {NPathQuery, Headers, NBody}
end. end.
append_query(Path, []) -> append_query(Path, []) ->
Path; encode_path(Path);
append_query(Path, Query) -> append_query(Path, Query) ->
Path ++ "?" ++ binary_to_list(qs(Query)). encode_path(Path) ++ "?" ++ binary_to_list(qs(Query)).
qs(KVs) -> qs(KVs) ->
qs(KVs, []). qs(KVs, []).
@ -387,12 +399,18 @@ may_append_body(Output, {ok, _, _}) ->
Output. Output.
uri_encode(T) -> 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) -> to_list(A) when is_atom(A) ->
atom_to_list(A); atom_to_list(A);
to_list(B) when is_binary(B) -> 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) -> to_bin(A) when is_atom(A) ->
atom_to_binary(A); atom_to_binary(A);
@ -403,14 +421,3 @@ to_bin(L) when is_list(L) ->
get_conf_val(Name, Conf) -> get_conf_val(Name, Conf) ->
hocon_maps:get(?CONF_NS ++ "." ++ 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).

View File

@ -184,14 +184,14 @@ test_authenticators(PathPrefix) ->
InvalidConfig0 InvalidConfig0
), ),
InvalidConfig1 = ValidConfig#{ ValidConfig1 = ValidConfig#{
method => <<"get">>, method => <<"get">>,
headers => #{<<"content-type">> => <<"application/json">>} headers => #{<<"content-type">> => <<"application/json">>}
}, },
{ok, 400, _} = request( {ok, 200, _} = request(
post, put,
uri(PathPrefix ++ [?CONF_NS]), uri(PathPrefix ++ [?CONF_NS, "password_based:http"]),
InvalidConfig1 ValidConfig1
), ),
?assertAuthenticatorsMatch( ?assertAuthenticatorsMatch(
@ -264,23 +264,23 @@ test_authenticator(PathPrefix) ->
InvalidConfig0 InvalidConfig0
), ),
InvalidConfig1 = ValidConfig0#{ ValidConfig1 = ValidConfig0#{
method => <<"get">>, method => <<"get">>,
headers => #{<<"content-type">> => <<"application/json">>} 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( {ok, 200, _} = request(
put, put,
uri(PathPrefix ++ [?CONF_NS, "password_based:http"]), uri(PathPrefix ++ [?CONF_NS, "password_based:http"]),
ValidConfig1 ValidConfig1
), ),
ValidConfig2 = ValidConfig0#{pool_size => 9},
{ok, 200, _} = request(
put,
uri(PathPrefix ++ [?CONF_NS, "password_based:http"]),
ValidConfig2
),
{ok, 404, _} = request( {ok, 404, _} = request(
delete, delete,
uri(PathPrefix ++ [?CONF_NS, "password_based:redis"]) uri(PathPrefix ++ [?CONF_NS, "password_based:redis"])

View File

@ -60,19 +60,19 @@ emqx_authz_api_schema {
headers { headers {
desc { desc {
en: """List of HTTP headers.""" en: """List of HTTP Headers."""
zh: """""" zh: """HTTP Headers 列表"""
} }
label { label {
en: """headers""" en: """Headers"""
zh: """请求头""" zh: """请求头"""
} }
} }
headers_no_content_type { headers_no_content_type {
desc { desc {
en: """List of HTTP headers (without `content_type`).""" en: """List of HTTP headers (without <code>content-type</code>)."""
zh: """""" zh: """HTTP Headers 列表(无 <code>content-type</code>"""
} }
label { label {
en: """headers_no_content_type""" en: """headers_no_content_type"""

View File

@ -122,7 +122,7 @@ and the new rules will override all rules from the old config file.
http_get { http_get {
desc { desc {
en: """Authorization using an external HTTP server (via GET requests).""" en: """Authorization using an external HTTP server (via GET requests)."""
zh: """使用外部 HTTP 服务器鉴权(GET 请求).""" zh: """使用外部 HTTP 服务器鉴权(GET 请求)"""
} }
label { label {
en: """http_get""" en: """http_get"""
@ -133,7 +133,7 @@ and the new rules will override all rules from the old config file.
http_post { http_post {
desc { desc {
en: """Authorization using an external HTTP server (via POST requests).""" en: """Authorization using an external HTTP server (via POST requests)."""
zh: """使用外部 HTTP 服务器鉴权(POST 请求).""" zh: """使用外部 HTTP 服务器鉴权(POST 请求)"""
} }
label { label {
en: """http_post""" en: """http_post"""
@ -155,29 +155,29 @@ and the new rules will override all rules from the old config file.
url { url {
desc { desc {
en: """URL of the auth server.""" en: """URL of the auth server."""
zh: """认证服务器 URL""" zh: """鉴权 HTTP 服务器地址。"""
} }
label { label {
en: """url""" en: """URL"""
zh: """url""" zh: """URL"""
} }
} }
headers { headers {
desc { desc {
en: """List of HTTP headers.""" en: """List of HTTP Headers."""
zh: """""" zh: """HTTP Headers 列表"""
} }
label { label {
en: """headers""" en: """Headers"""
zh: """请求头""" zh: """请求头"""
} }
} }
headers_no_content_type { headers_no_content_type {
desc { desc {
en: """List of HTTP headers (without `content_type`).""" en: """List of HTTP headers (without <code>content-type</code>)."""
zh: """""" zh: """HTTP Headers 列表 (无 <code>content-type</code>) 。"""
} }
label { label {
en: """headers_no_content_type""" en: """headers_no_content_type"""
@ -188,22 +188,22 @@ and the new rules will override all rules from the old config file.
body { body {
desc { desc {
en: """HTTP request body.""" en: """HTTP request body."""
zh: """HTTP 请求体""" zh: """HTTP request body。"""
} }
label { label {
en: """body""" en: """Request Body"""
zh: """请求体""" zh: """Request Body"""
} }
} }
request_timeout { request_timeout {
desc { desc {
en: """Request timeout.""" en: """HTTP request timeout."""
zh: """请求超时时间""" zh: """HTTP 请求超时时长。"""
} }
label { label {
en: """request_timeout""" en: """Request Timeout"""
zh: """请求超时""" zh: """请求超时时间"""
} }
} }

View File

@ -102,9 +102,14 @@ authz_http_common_fields() ->
authz_common_fields(http) ++ authz_common_fields(http) ++
[ [
{url, fun url/1}, {url, fun url/1},
{body, map([{fuzzy, term(), binary()}])}, {body,
hoconsc:mk(map([{fuzzy, term(), binary()}]), #{
required => false, desc => ?DESC(body)
})},
{request_timeout, {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:to_list(
maps:without( maps:without(
@ -141,7 +146,10 @@ headers_no_content_type(desc) ->
?DESC(?FUNCTION_NAME); ?DESC(?FUNCTION_NAME);
headers_no_content_type(converter) -> headers_no_content_type(converter) ->
fun(Headers) -> 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; end;
headers_no_content_type(default) -> headers_no_content_type(default) ->
default_headers_no_content_type(); default_headers_no_content_type();

View File

@ -94,45 +94,49 @@ authorize(
parse_config( parse_config(
#{ #{
url := URL, url := RawUrl,
method := Method, method := Method,
headers := Headers, headers := Headers,
request_timeout := ReqTimeout request_timeout := ReqTimeout
} = Conf } = Conf
) -> ) ->
{BaseURLWithPath, Query} = parse_fullpath(URL), {BaseUrl0, Path, Query} = parse_url(RawUrl),
BaseURLMap = parse_url(BaseURLWithPath), {ok, BaseUrl} = emqx_http_lib:uri_parse(BaseUrl0),
Conf#{ Conf#{
method => Method, 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( base_query_template => emqx_authz_utils:parse_deep(
cow_qs:parse_qs(bin(Query)), cow_qs:parse_qs(to_bin(Query)),
?PLACEHOLDERS ?PLACEHOLDERS
), ),
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 ?PLACEHOLDERS
), ),
headers => Headers,
request_timeout => ReqTimeout, request_timeout => ReqTimeout,
%% pool_type default value `random` %% pool_type default value `random`
pool_type => random pool_type => random
}. }.
parse_fullpath(RawURL) -> parse_url(Url) ->
cow_http:parse_fullpath(bin(RawURL)). case string:split(Url, "//", leading) of
[Scheme, UrlRem] ->
parse_url(URL) when case string:split(UrlRem, "/", leading) of
URL =:= undefined [HostPort, Remaining] ->
-> BaseUrl = iolist_to_binary([Scheme, "//", HostPort]),
#{}; case string:split(Remaining, "?", leading) of
parse_url(URL) -> [Path, QueryString] ->
{ok, URIMap} = emqx_http_lib:uri_parse(URL), {BaseUrl, Path, QueryString};
case maps:get(query, URIMap, undefined) of [Path] ->
undefined -> {BaseUrl, Path, <<>>}
URIMap#{query => ""}; end;
_ -> [HostPort] ->
URIMap {iolist_to_binary([Scheme, "//", HostPort]), <<>>, <<>>}
end;
[Url] ->
throw({invalid_url, Url})
end. end.
generate_request( generate_request(
@ -141,32 +145,33 @@ generate_request(
Client, Client,
#{ #{
method := Method, method := Method,
base_url := #{path := Path},
base_query_template := BaseQueryTemplate,
headers := Headers, headers := Headers,
base_path_templete := BasePathTemplate,
base_query_template := BaseQueryTemplate,
body_template := BodyTemplate body_template := BodyTemplate
} }
) -> ) ->
Values = client_vars(Client, PubSub, Topic), 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), Body = emqx_authz_utils:render_deep(BodyTemplate, Values),
NBaseQuery = emqx_authz_utils:render_deep(BaseQueryTemplate, Values),
case Method of case Method of
get -> get ->
NPath = append_query(Path, NBaseQuery ++ Body), NPath = append_query(Path, Query ++ Body),
{NPath, Headers}; {NPath, Headers};
_ -> _ ->
NPath = append_query(Path, NBaseQuery), NPath = append_query(Path, Query),
NBody = serialize_body( NBody = serialize_body(
proplists:get_value(<<"Accept">>, Headers, <<"application/json">>), proplists:get_value(<<"accept">>, Headers, <<"application/json">>),
Body Body
), ),
{NPath, Headers, NBody} {NPath, Headers, NBody}
end. end.
append_query(Path, []) -> append_query(Path, []) ->
Path; encode_path(Path);
append_query(Path, Query) -> 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) ->
query_string(Body, []). query_string(Body, []).
@ -179,13 +184,14 @@ query_string([], Acc) ->
<<>> <<>>
end; end;
query_string([{K, V} | More], Acc) -> query_string([{K, V} | More], Acc) ->
query_string( query_string(More, [["&", uri_encode(K), "=", uri_encode(V)] | Acc]).
More,
[ uri_encode(T) ->
["&", emqx_http_lib:uri_encode(K), "=", emqx_http_lib:uri_encode(V)] emqx_http_lib:uri_encode(to_list(T)).
| Acc
] encode_path(Path) ->
). Parts = string:split(Path, "/", all),
lists:flatten(["/" ++ Part || Part <- lists:map(fun uri_encode/1, Parts)]).
serialize_body(<<"application/json">>, Body) -> serialize_body(<<"application/json">>, Body) ->
jsx:encode(Body); jsx:encode(Body);
@ -198,7 +204,13 @@ client_vars(Client, PubSub, Topic) ->
topic => Topic topic => Topic
}. }.
bin(A) when is_atom(A) -> atom_to_binary(A, utf8); to_list(A) when is_atom(A) ->
bin(B) when is_binary(B) -> B; atom_to_list(A);
bin(L) when is_list(L) -> list_to_binary(L); to_list(B) when is_binary(B) ->
bin(X) -> X. 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.

View File

@ -29,6 +29,8 @@
-type action() :: publish | subscribe | all. -type action() :: publish | subscribe | all.
-type permission() :: allow | deny. -type permission() :: allow | deny.
-import(emqx_schema, [mk_duration/2]).
-export([ -export([
namespace/0, namespace/0,
roots/0, roots/0,
@ -249,8 +251,8 @@ http_common_fields() ->
[ [
{url, fun url/1}, {url, fun url/1},
{request_timeout, {request_timeout,
emqx_schema:mk_duration("Request timeout", #{ mk_duration("Request timeout", #{
default => "30s", desc => ?DESC(request_timeout) required => false, default => "30s", desc => ?DESC(request_timeout)
})}, })},
{body, #{type => map(), required => false, desc => ?DESC(body)}} {body, #{type => map(), required => false, desc => ?DESC(body)}}
] ++ ] ++
@ -303,7 +305,12 @@ headers_no_content_type(desc) ->
?DESC(?FUNCTION_NAME); ?DESC(?FUNCTION_NAME);
headers_no_content_type(converter) -> headers_no_content_type(converter) ->
fun(Headers) -> 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; end;
headers_no_content_type(default) -> headers_no_content_type(default) ->
default_headers_no_content_type(); default_headers_no_content_type();
@ -359,12 +366,12 @@ check_ssl_opts(Conf) ->
true; true;
Url -> Url ->
case emqx_authz_http:parse_url(Url) of case emqx_authz_http:parse_url(Url) of
#{scheme := https} -> {<<"https", _>>, _, _} ->
case hocon_maps:get("config.ssl.enable", Conf) of case hocon_maps:get("config.ssl.enable", Conf) of
true -> true; true -> true;
_ -> {error, ssl_not_enable} _ -> {error, ssl_not_enable}
end; end;
#{scheme := http} -> {<<"http", _>>, _, _} ->
true; true;
Bad -> Bad ->
{bad_scheme, Url, Bad} {bad_scheme, Url, Bad}

View File

@ -25,8 +25,10 @@
create_resource/2, create_resource/2,
update_config/2, update_config/2,
parse_deep/2, parse_deep/2,
parse_str/2,
parse_sql/3, parse_sql/3,
render_deep/2, render_deep/2,
render_str/2,
render_sql_params/2 render_sql_params/2
]). ]).
@ -69,6 +71,9 @@ update_config(Path, ConfigRequest) ->
parse_deep(Template, PlaceHolders) -> parse_deep(Template, PlaceHolders) ->
emqx_placeholder:preproc_tmpl_deep(Template, #{placeholders => 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) -> parse_sql(Template, ReplaceWith, PlaceHolders) ->
emqx_placeholder:preproc_sql( emqx_placeholder:preproc_sql(
Template, Template,
@ -85,6 +90,13 @@ render_deep(Template, Values) ->
#{return => full_binary, var_trans => fun handle_var/2} #{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) -> render_sql_params(ParamList, Values) ->
emqx_placeholder:proc_tmpl( emqx_placeholder:proc_tmpl(
ParamList, ParamList,

View File

@ -201,6 +201,54 @@ t_query_params(_Config) ->
emqx_access_control:authorize(ClientInfo, publish, <<"t">>) 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) -> t_json_body(_Config) ->
ok = setup_handler_and_config( ok = setup_handler_and_config(
fun(Req0, State) -> fun(Req0, State) ->