From b88796518eb35fbd145dd538b48d7383f0a2f00f Mon Sep 17 00:00:00 2001 From: JimMoen Date: Thu, 30 Jun 2022 14:04:27 +0800 Subject: [PATCH] fix(authn): authn http `is_superuser` field only support true|false|[num] and for [num]: zero => false | non-zero => true other type and string will fallback to default vaule => false i.e null|undefined|[empty string] --- apps/emqx_authn/src/emqx_authn_utils.erl | 29 +++++- .../src/simple_authn/emqx_authn_http.erl | 98 +++++++++++-------- 2 files changed, 85 insertions(+), 42 deletions(-) diff --git a/apps/emqx_authn/src/emqx_authn_utils.erl b/apps/emqx_authn/src/emqx_authn_utils.erl index 20d2b57c9..8d3d45b1b 100644 --- a/apps/emqx_authn/src/emqx_authn_utils.erl +++ b/apps/emqx_authn/src/emqx_authn_utils.erl @@ -135,6 +135,18 @@ render_sql_params(ParamList, Credential) -> #{return => rawlist, var_trans => fun handle_sql_var/2} ). +%% true +is_superuser(#{<<"is_superuser">> := <<"true">>}) -> + #{is_superuser => true}; +is_superuser(#{<<"is_superuser">> := true}) -> + #{is_superuser => true}; +is_superuser(#{<<"is_superuser">> := <<"1">>}) -> + #{is_superuser => true}; +is_superuser(#{<<"is_superuser">> := I}) when + is_integer(I) andalso I >= 1 +-> + #{is_superuser => true}; +%% false is_superuser(#{<<"is_superuser">> := <<"">>}) -> #{is_superuser => false}; is_superuser(#{<<"is_superuser">> := <<"0">>}) -> @@ -145,10 +157,25 @@ is_superuser(#{<<"is_superuser">> := null}) -> #{is_superuser => false}; is_superuser(#{<<"is_superuser">> := undefined}) -> #{is_superuser => false}; +is_superuser(#{<<"is_superuser">> := <<"false">>}) -> + #{is_superuser => false}; is_superuser(#{<<"is_superuser">> := false}) -> #{is_superuser => false}; +is_superuser(#{<<"is_superuser">> := MaybeBinInt}) when + is_binary(MaybeBinInt) +-> + try binary_to_integer(MaybeBinInt) of + Int when Int >= 1 -> + #{is_superuser => true}; + Int when Int =< 0 -> + #{is_superuser => false} + catch + error:badarg -> + #{is_superuser => false} + end; +%% fallback to default is_superuser(#{<<"is_superuser">> := _}) -> - #{is_superuser => true}; + #{is_superuser => false}; is_superuser(#{}) -> #{is_superuser => false}. 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 c4ce9f2b1..8dccb9ee3 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl @@ -191,48 +191,21 @@ authenticate( case emqx_resource:query(ResourceId, {Method, Request, RequestTimeout}) of {ok, 204, _Headers} -> {ok, #{is_superuser => false}}; - {ok, 200, _Headers} -> - {ok, #{is_superuser => false}}; {ok, 200, Headers, Body} -> - ContentType = proplists:get_value(<<"content-type">>, Headers, <<"application/json">>), - case safely_parse_body(ContentType, Body) of - {ok, NBody} -> - %% TODO: Return by user property - UserProperty = maps:remove(<<"is_superuser">>, NBody), - IsSuperuser = emqx_authn_utils:is_superuser(NBody), - {ok, IsSuperuser#{user_property => UserProperty}}; - {error, _Reason} -> - {ok, #{is_superuser => false}} - end; + handle_response(Headers, Body); + {ok, _StatusCode, _Headers} = Response -> + log_response(ResourceId, Response), + ignore; + {ok, _StatusCode, _Headers, _Body} = Response -> + log_response(ResourceId, Response), + ignore; {error, Reason} -> ?SLOG(error, #{ msg => "http_server_query_failed", resource => ResourceId, reason => Reason }), - ignore; - Other -> - Output = may_append_body(#{resource => ResourceId}, Other), - case erlang:element(2, Other) of - Code5xx when Code5xx >= 500 andalso Code5xx < 600 -> - ?SLOG(error, Output#{ - msg => "http_server_error", - code => Code5xx - }), - ignore; - Code4xx when Code4xx >= 400 andalso Code4xx < 500 -> - ?SLOG(warning, Output#{ - msg => "refused_by_http_server", - code => Code4xx - }), - {error, not_authorized}; - OtherCode -> - ?SLOG(error, Output#{ - msg => "undesired_response_code", - code => OtherCode - }), - ignore - end + ignore end. destroy(#{resource_id := ResourceId}) -> @@ -366,20 +339,43 @@ qs([{K, V} | More], Acc) -> serialize_body(<<"application/json">>, Body) -> emqx_json:encode(Body); serialize_body(<<"application/x-www-form-urlencoded">>, Body) -> - qs(Body). + qs(maps:to_list(Body)). + +handle_response(Headers, Body) -> + ContentType = proplists:get_value(<<"content-type">>, Headers), + case safely_parse_body(ContentType, Body) of + {ok, NBody} -> + case maps:get(<<"result">>, NBody, <<"ignore">>) of + <<"allow">> -> + Res = emqx_authn_utils:is_superuser(NBody), + %% TODO: Return by user property + {ok, Res#{user_property => maps:get(<<"user_property">>, NBody, #{})}}; + <<"deny">> -> + {error, not_authorized}; + <<"ignore">> -> + ignore; + _ -> + ignore + end; + {error, _Reason} -> + ignore + end. safely_parse_body(ContentType, Body) -> - try parse_body(ContentType, Body) of - Result -> Result + try + parse_body(ContentType, Body) catch _Class:_Reason -> {error, invalid_body} end. -parse_body(<<"application/json">>, Body) -> +parse_body(<<"application/json", _/binary>>, Body) -> {ok, emqx_json:decode(Body, [return_maps])}; -parse_body(<<"application/x-www-form-urlencoded">>, Body) -> - {ok, maps:from_list(cow_qs:parse_qs(Body))}; +parse_body(<<"application/x-www-form-urlencoded", _/binary>>, Body) -> + Flags = [<<"result">>, <<"is_superuser">>], + RawMap = maps:from_list(cow_qs:parse_qs(Body)), + NBody = maps:with(Flags, RawMap), + {ok, NBody}; parse_body(ContentType, _) -> {error, {unsupported_content_type, ContentType}}. @@ -395,6 +391,26 @@ encode_path(Path) -> Parts = string:split(Path, "/", all), lists:flatten(["/" ++ Part || Part <- lists:map(fun uri_encode/1, Parts)]). +log_response(ResourceId, Other) -> + Output = may_append_body(#{resource => ResourceId}, Other), + case erlang:element(2, Other) of + Code5xx when Code5xx >= 500 andalso Code5xx < 600 -> + ?SLOG(error, Output#{ + msg => "http_server_error", + code => Code5xx + }); + Code4xx when Code4xx >= 400 andalso Code4xx < 500 -> + ?SLOG(warning, Output#{ + msg => "refused_by_http_server", + code => Code4xx + }); + OtherCode -> + ?SLOG(error, Output#{ + msg => "undesired_response_code", + code => OtherCode + }) + end. + to_list(A) when is_atom(A) -> atom_to_list(A); to_list(B) when is_binary(B) ->