From 15ef9892c5314b089bcd3ac08e6acea6887bde46 Mon Sep 17 00:00:00 2001 From: JimMoen Date: Wed, 27 Apr 2022 18:15:49 +0800 Subject: [PATCH] fix(auth): authn & authz http support placeholder in HTTP path --- .../src/simple_authn/emqx_authn_http.erl | 77 +++++++++------- apps/emqx_authz/src/emqx_authz_http.erl | 88 +++++++++++-------- apps/emqx_authz/src/emqx_authz_schema.erl | 4 +- apps/emqx_authz/src/emqx_authz_utils.erl | 12 +++ 4 files changed, 111 insertions(+), 70 deletions(-) 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 4ea43edee..9e25f5a7a 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl @@ -161,17 +161,19 @@ create(_AuthenticatorID, Config) -> create( #{ method := Method, - url := RawURL, + url := RawUrl, headers := Headers, request_timeout := RequestTimeout } = Config ) -> - {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)) ), @@ -185,7 +187,7 @@ create( ?RESOURCE_GROUP, emqx_connector_http, Config#{ - base_url => maps:remove(query, URIMap), + base_url => BaseUrl, pool_type => random }, #{} @@ -274,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">>, @@ -303,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. @@ -319,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, []). @@ -388,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); diff --git a/apps/emqx_authz/src/emqx_authz_http.erl b/apps/emqx_authz/src/emqx_authz_http.erl index 2b766b063..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,21 +145,22 @@ 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">>), Body @@ -164,9 +169,9 @@ generate_request( 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 4d200dad2..8497b0a44 100644 --- a/apps/emqx_authz/src/emqx_authz_schema.erl +++ b/apps/emqx_authz/src/emqx_authz_schema.erl @@ -293,12 +293,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,