|
|
@@ -29,6 +29,7 @@
|
|
|
, destroy/1
|
|
|
, dry_run/1
|
|
|
, authorize/4
|
|
|
+ , parse_url/1
|
|
|
]).
|
|
|
|
|
|
-ifdef(TEST).
|
|
|
@@ -39,62 +40,29 @@
|
|
|
description() ->
|
|
|
"AuthZ with http".
|
|
|
|
|
|
-init(Source) ->
|
|
|
- case emqx_authz_utils:create_resource(emqx_connector_http, Source) of
|
|
|
+init(Config) ->
|
|
|
+ NConfig = parse_config(Config),
|
|
|
+ case emqx_authz_utils:create_resource(emqx_connector_http, NConfig) of
|
|
|
{error, Reason} -> error({load_config_error, Reason});
|
|
|
- {ok, Id} -> Source#{annotations => #{id => Id}}
|
|
|
+ {ok, Id} -> NConfig#{annotations => #{id => Id}}
|
|
|
end.
|
|
|
|
|
|
destroy(#{annotations := #{id := Id}}) ->
|
|
|
ok = emqx_resource:remove_local(Id).
|
|
|
|
|
|
-dry_run(Source) ->
|
|
|
- emqx_resource:create_dry_run_local(emqx_connector_http, Source).
|
|
|
-
|
|
|
-authorize(Client, PubSub, Topic,
|
|
|
- #{type := http,
|
|
|
- query := Query,
|
|
|
- path := Path,
|
|
|
- headers := Headers,
|
|
|
- method := Method,
|
|
|
- request_timeout := RequestTimeout,
|
|
|
- annotations := #{id := ResourceID}
|
|
|
- } = Source) ->
|
|
|
- Request = case Method of
|
|
|
- get ->
|
|
|
- Path1 = replvar(
|
|
|
- Path ++ "?" ++ Query,
|
|
|
- PubSub,
|
|
|
- Topic,
|
|
|
- maps:to_list(Client),
|
|
|
- fun var_uri_encode/1),
|
|
|
-
|
|
|
- {Path1, maps:to_list(Headers)};
|
|
|
-
|
|
|
- _ ->
|
|
|
- Body0 = maps:get(body, Source, #{}),
|
|
|
- Body1 = replvar_deep(
|
|
|
- Body0,
|
|
|
- PubSub,
|
|
|
- Topic,
|
|
|
- maps:to_list(Client),
|
|
|
- fun var_bin_encode/1),
|
|
|
-
|
|
|
- Body2 = serialize_body(
|
|
|
- maps:get(<<"content-type">>, Headers, <<"application/json">>),
|
|
|
- Body1),
|
|
|
-
|
|
|
- Path1 = replvar(
|
|
|
- Path,
|
|
|
- PubSub,
|
|
|
- Topic,
|
|
|
- maps:to_list(Client),
|
|
|
- fun var_uri_encode/1),
|
|
|
-
|
|
|
- {Path1, maps:to_list(Headers), Body2}
|
|
|
- end,
|
|
|
- HttpResult = emqx_resource:query(ResourceID, {Method, Request, RequestTimeout}),
|
|
|
- case HttpResult of
|
|
|
+dry_run(Config) ->
|
|
|
+ emqx_resource:create_dry_run_local(emqx_connector_http, parse_config(Config)).
|
|
|
+
|
|
|
+authorize( Client
|
|
|
+ , PubSub
|
|
|
+ , Topic
|
|
|
+ , #{ type := http
|
|
|
+ , annotations := #{id := ResourceID}
|
|
|
+ , method := Method
|
|
|
+ , request_timeout := RequestTimeout
|
|
|
+ } = Config) ->
|
|
|
+ Request = generate_request(PubSub, Topic, Client, Config),
|
|
|
+ case emqx_resource:query(ResourceID, {Method, Request, RequestTimeout}) of
|
|
|
{ok, 200, _Headers} ->
|
|
|
{matched, allow};
|
|
|
{ok, 204, _Headers} ->
|
|
|
@@ -112,12 +80,75 @@ authorize(Client, PubSub, Topic,
|
|
|
ignore
|
|
|
end.
|
|
|
|
|
|
+parse_config(#{ url := URL
|
|
|
+ , method := Method
|
|
|
+ , headers := Headers
|
|
|
+ , request_timeout := ReqTimeout
|
|
|
+ } = Conf) ->
|
|
|
+ {BaseURLWithPath, Query} = parse_fullpath(URL),
|
|
|
+ BaseURLMap = parse_url(BaseURLWithPath),
|
|
|
+ Conf#{ method => Method
|
|
|
+ , base_url => maps:remove(query, BaseURLMap)
|
|
|
+ , base_query => cow_qs:parse_qs(bin(Query))
|
|
|
+ , body => maps:get(body, Conf, #{})
|
|
|
+ , headers => Headers
|
|
|
+ , request_timeout => ReqTimeout
|
|
|
+ }.
|
|
|
+
|
|
|
+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
|
|
|
+ end.
|
|
|
+
|
|
|
+generate_request( PubSub
|
|
|
+ , Topic
|
|
|
+ , Client
|
|
|
+ , #{ method := Method
|
|
|
+ , base_url := #{path := Path}
|
|
|
+ , base_query := BaseQuery
|
|
|
+ , headers := Headers
|
|
|
+ , body := Body0
|
|
|
+ }) ->
|
|
|
+ Body = replace_placeholders(maps:to_list(Body0), PubSub, Topic, Client),
|
|
|
+ NBaseQuery = replace_placeholders(BaseQuery, PubSub, Topic, Client),
|
|
|
+ case Method of
|
|
|
+ get ->
|
|
|
+ NPath = append_query(Path, NBaseQuery ++ Body),
|
|
|
+ {NPath, Headers};
|
|
|
+ _ ->
|
|
|
+ NPath = append_query(Path, NBaseQuery),
|
|
|
+ NBody = serialize_body(
|
|
|
+ proplists:get_value(<<"Accept">>, Headers, <<"application/json">>),
|
|
|
+ Body
|
|
|
+ ),
|
|
|
+ {NPath, Headers, NBody}
|
|
|
+ end.
|
|
|
+
|
|
|
+append_query(Path, []) ->
|
|
|
+ Path;
|
|
|
+append_query(Path, Query) ->
|
|
|
+ Path ++ "?" ++ binary_to_list(query_string(Query)).
|
|
|
+
|
|
|
query_string(Body) ->
|
|
|
- query_string(maps:to_list(Body), []).
|
|
|
+ query_string(Body, []).
|
|
|
|
|
|
query_string([], Acc) ->
|
|
|
- <<$&, Str/binary>> = iolist_to_binary(lists:reverse(Acc)),
|
|
|
- Str;
|
|
|
+ case iolist_to_binary(lists:reverse(Acc)) of
|
|
|
+ <<$&, Str/binary>> ->
|
|
|
+ Str;
|
|
|
+ <<>> ->
|
|
|
+ <<>>
|
|
|
+ end;
|
|
|
query_string([{K, V} | More], Acc) ->
|
|
|
query_string( More
|
|
|
, [ ["&", emqx_http_lib:uri_encode(K), "=", emqx_http_lib:uri_encode(V)]
|
|
|
@@ -128,67 +159,36 @@ serialize_body(<<"application/json">>, Body) ->
|
|
|
serialize_body(<<"application/x-www-form-urlencoded">>, Body) ->
|
|
|
query_string(Body).
|
|
|
|
|
|
+replace_placeholders(KVs, PubSub, Topic, Client) ->
|
|
|
+ replace_placeholders(KVs, PubSub, Topic, Client, []).
|
|
|
+
|
|
|
+replace_placeholders([], _PubSub, _Topic, _Client, Acc) ->
|
|
|
+ lists:reverse(Acc);
|
|
|
+replace_placeholders([{K, V0} | More], PubSub, Topic, Client, Acc) ->
|
|
|
+ case replace_placeholder(V0, PubSub, Topic, Client) of
|
|
|
+ undefined ->
|
|
|
+ error({cannot_get_variable, V0});
|
|
|
+ V ->
|
|
|
+ replace_placeholders(More, PubSub, Topic, Client, [{bin(K), bin(V)} | Acc])
|
|
|
+ end.
|
|
|
|
|
|
-replvar_deep(Map, PubSub, Topic, Vars, VarEncode) when is_map(Map) ->
|
|
|
- maps:from_list(
|
|
|
- lists:map(
|
|
|
- fun({Key, Value}) ->
|
|
|
- {replvar(Key, PubSub, Topic, Vars, VarEncode),
|
|
|
- replvar_deep(Value, PubSub, Topic, Vars, VarEncode)}
|
|
|
- end,
|
|
|
- maps:to_list(Map)));
|
|
|
-replvar_deep(List, PubSub, Topic, Vars, VarEncode) when is_list(List) ->
|
|
|
- lists:map(
|
|
|
- fun(Value) ->
|
|
|
- replvar_deep(Value, PubSub, Topic, Vars, VarEncode)
|
|
|
- end,
|
|
|
- List);
|
|
|
-replvar_deep(Number, _PubSub, _Topic, _Vars, _VarEncode) when is_number(Number) ->
|
|
|
- Number;
|
|
|
-replvar_deep(Binary, PubSub, Topic, Vars, VarEncode) when is_binary(Binary) ->
|
|
|
- replvar(Binary, PubSub, Topic, Vars, VarEncode).
|
|
|
-
|
|
|
-replvar(Str0, PubSub, Topic, [], VarEncode) ->
|
|
|
- NTopic = emqx_http_lib:uri_encode(Topic),
|
|
|
- Str1 = re:replace(Str0, emqx_authz:ph_to_re(?PH_S_TOPIC),
|
|
|
- VarEncode(NTopic), [global, {return, binary}]),
|
|
|
- re:replace(Str1, emqx_authz:ph_to_re(?PH_S_ACTION),
|
|
|
- VarEncode(PubSub), [global, {return, binary}]);
|
|
|
-
|
|
|
-
|
|
|
-replvar(Str, PubSub, Topic, [{username, Username} | Rest], VarEncode) ->
|
|
|
- Str1 = re:replace(Str, emqx_authz:ph_to_re(?PH_S_USERNAME),
|
|
|
- VarEncode(Username), [global, {return, binary}]),
|
|
|
- replvar(Str1, PubSub, Topic, Rest, VarEncode);
|
|
|
-
|
|
|
-replvar(Str, PubSub, Topic, [{clientid, Clientid} | Rest], VarEncode) ->
|
|
|
- Str1 = re:replace(Str, emqx_authz:ph_to_re(?PH_S_CLIENTID),
|
|
|
- VarEncode(Clientid), [global, {return, binary}]),
|
|
|
- replvar(Str1, PubSub, Topic, Rest, VarEncode);
|
|
|
-
|
|
|
-replvar(Str, PubSub, Topic, [{peerhost, IpAddress} | Rest], VarEncode) ->
|
|
|
- Str1 = re:replace(Str, emqx_authz:ph_to_re(?PH_S_PEERHOST),
|
|
|
- VarEncode(inet_parse:ntoa(IpAddress)), [global, {return, binary}]),
|
|
|
- replvar(Str1, PubSub, Topic, Rest, VarEncode);
|
|
|
-
|
|
|
-replvar(Str, PubSub, Topic, [{protocol, Protocol} | Rest], VarEncode) ->
|
|
|
- Str1 = re:replace(Str, emqx_authz:ph_to_re(?PH_S_PROTONAME),
|
|
|
- VarEncode(Protocol), [global, {return, binary}]),
|
|
|
- replvar(Str1, PubSub, Topic, Rest, VarEncode);
|
|
|
-
|
|
|
-replvar(Str, PubSub, Topic, [{mountpoint, Mountpoint} | Rest], VarEncode) ->
|
|
|
- Str1 = re:replace(Str, emqx_authz:ph_to_re(?PH_S_MOUNTPOINT),
|
|
|
- VarEncode(Mountpoint), [global, {return, binary}]),
|
|
|
- replvar(Str1, PubSub, Topic, Rest, VarEncode);
|
|
|
-
|
|
|
-replvar(Str, PubSub, Topic, [_Unknown | Rest], VarEncode) ->
|
|
|
- replvar(Str, PubSub, Topic, Rest, VarEncode).
|
|
|
-
|
|
|
-var_uri_encode(S) ->
|
|
|
- emqx_http_lib:uri_encode(bin(S)).
|
|
|
-
|
|
|
-var_bin_encode(S) ->
|
|
|
- bin(S).
|
|
|
+replace_placeholder(?PH_USERNAME, _PubSub, _Topic, Client) ->
|
|
|
+ bin(maps:get(username, Client, undefined));
|
|
|
+replace_placeholder(?PH_CLIENTID, _PubSub, _Topic, Client) ->
|
|
|
+ bin(maps:get(clientid, Client, undefined));
|
|
|
+replace_placeholder(?PH_HOST, _PubSub, _Topic, Client) ->
|
|
|
+ inet_parse:ntoa(maps:get(peerhost, Client, undefined));
|
|
|
+replace_placeholder(?PH_PROTONAME, _PubSub, _Topic, Client) ->
|
|
|
+ bin(maps:get(protocol, Client, undefined));
|
|
|
+replace_placeholder(?PH_MOUNTPOINT, _PubSub, _Topic, Client) ->
|
|
|
+ bin(maps:get(mountpoint, Client, undefined));
|
|
|
+replace_placeholder(?PH_TOPIC, _PubSub, Topic, _Client) ->
|
|
|
+ bin(emqx_http_lib:uri_encode(Topic));
|
|
|
+replace_placeholder(?PH_ACTION, PubSub, _Topic, _Client) ->
|
|
|
+ bin(PubSub);
|
|
|
+
|
|
|
+replace_placeholder(Constant, _, _, _) ->
|
|
|
+ Constant.
|
|
|
|
|
|
bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
|
|
|
bin(B) when is_binary(B) -> B;
|