Просмотр исходного кода

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]
JimMoen 3 лет назад
Родитель
Сommit
b88796518e

+ 28 - 1
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}.
 

+ 57 - 41
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) ->