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

Merge pull request #8374 from JimMoen/fix-authn-http

fix(authn): authn http `is_superuser` field
zhouzb 3 лет назад
Родитель
Сommit
8d4907eedd

+ 8 - 4
apps/emqx/src/emqx.appup.src

@@ -2,20 +2,24 @@
 %% Unless you know what you are doing, DO NOT edit manually!!
 {VSN,
   [{"5.0.0",
-    [{load_module,emqx_channel,brutal_purge,soft_purge,[]},
+    [{load_module,emqx_quic_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_config,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
      {load_module,emqx_schema,brutal_purge,soft_purge,[]},
      {load_module,emqx_release,brutal_purge,soft_purge,[]},
      {load_module,emqx_authentication,brutal_purge,soft_purge,[]},
      {load_module,emqx_metrics,brutal_purge,soft_purge,[]},
      {add_module,emqx_exclusive_subscription},
-     {apply, {emqx_exclusive_subscription, on_add_module, []}},
+     {apply,{emqx_exclusive_subscription,on_add_module,[]}},
      {load_module,emqx_broker,brutal_purge,soft_purge,[]},
      {load_module,emqx_mqtt_caps,brutal_purge,soft_purge,[]},
      {load_module,emqx_topic,brutal_purge,soft_purge,[]},
      {load_module,emqx_relup}]},
    {<<".*">>,[]}],
   [{"5.0.0",
-    [{load_module,emqx_channel,brutal_purge,soft_purge,[]},
+    [{load_module,emqx_quic_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_config,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
      {load_module,emqx_schema,brutal_purge,soft_purge,[]},
      {load_module,emqx_release,brutal_purge,soft_purge,[]},
      {load_module,emqx_authentication,brutal_purge,soft_purge,[]},
@@ -23,7 +27,7 @@
      {load_module,emqx_broker,brutal_purge,soft_purge,[]},
      {load_module,emqx_mqtt_caps,brutal_purge,soft_purge,[]},
      {load_module,emqx_topic,brutal_purge,soft_purge,[]},
-     {apply, {emqx_exclusive_subscription, on_delete_module, []}},
+     {apply,{emqx_exclusive_subscription,on_delete_module,[]}},
      {delete_module,emqx_exclusive_subscription},
      {load_module,emqx_relup}]},
    {<<".*">>,[]}]}.

+ 8 - 6
apps/emqx_authn/src/emqx_authn.appup.src

@@ -1,11 +1,13 @@
 %% -*- mode: erlang -*-
 %% Unless you know what you are doing, DO NOT edit manually!!
 {VSN,
-  [{"0.1.0",[
+  [{"0.1.0",
+    [{load_module,emqx_authn_http,brutal_purge,soft_purge,[]},
      {load_module,emqx_authn_utils,brutal_purge,soft_purge,[]},
-     {load_module,emqx_authn_redis,brutal_purge,soft_purge,[]}]
-   }],
-  [{"0.1.0",[
+     {load_module,emqx_authn_redis,brutal_purge,soft_purge,[]}]},
+   {<<".*">>,[]}],
+  [{"0.1.0",
+    [{load_module,emqx_authn_http,brutal_purge,soft_purge,[]},
      {load_module,emqx_authn_utils,brutal_purge,soft_purge,[]},
-     {load_module,emqx_authn_redis,brutal_purge,soft_purge,[]}]
-   }]}.
+     {load_module,emqx_authn_redis,brutal_purge,soft_purge,[]}]},
+   {<<".*">>,[]}]}.

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

+ 195 - 47
apps/emqx_authn/test/emqx_authn_http_SUITE.erl

@@ -37,6 +37,29 @@
     protocol => mqtt
 }).
 
+-define(SERVER_RESPONSE_JSON(Result), ?SERVER_RESPONSE_JSON(Result, false)).
+-define(SERVER_RESPONSE_JSON(Result, IsSuperuser),
+    jiffy:encode(#{
+        result => Result,
+        is_superuser => IsSuperuser
+    })
+).
+
+-define(SERVER_RESPONSE_URLENCODE(Result), ?SERVER_RESPONSE_URLENCODE(Result, false)).
+-define(SERVER_RESPONSE_URLENCODE(Result, IsSuperuser),
+    list_to_binary(
+        "result=" ++
+            uri_encode(Result) ++ "&" ++
+            "is_superuser=" ++
+            uri_encode(IsSuperuser)
+    )
+).
+
+-define(EXCEPTION_ALLOW, ?EXCEPTION_ALLOW(false)).
+-define(EXCEPTION_ALLOW(IsSuperuser), {ok, #{is_superuser := IsSuperuser}}).
+-define(EXCEPTION_DENY, {error, not_authorized}).
+-define(EXCEPTION_IGNORE, ignore).
+
 all() ->
     emqx_common_test_helpers:all(?MODULE).
 
@@ -149,9 +172,12 @@ t_destroy(_Config) ->
         {create_authenticator, ?GLOBAL, AuthConfig}
     ),
 
+    Headers = #{<<"content-type">> => <<"application/json">>},
+    Response = ?SERVER_RESPONSE_JSON(allow),
+
     ok = emqx_authn_http_test_server:set_handler(
         fun(Req0, State) ->
-            Req = cowboy_req:reply(200, Req0),
+            Req = cowboy_req:reply(200, Headers, Response, Req0),
             {ok, Req, State}
         end
     ),
@@ -161,9 +187,12 @@ t_destroy(_Config) ->
 
     Credentials = maps:with([username, password], ?CREDENTIALS),
 
-    {ok, _} = emqx_authn_http:authenticate(
-        Credentials,
-        State
+    ?assertMatch(
+        ?EXCEPTION_ALLOW,
+        emqx_authn_http:authenticate(
+            Credentials,
+            State
+        )
     ),
 
     emqx_authn_test_lib:delete_authenticators(
@@ -173,7 +202,7 @@ t_destroy(_Config) ->
 
     % Authenticator should not be usable anymore
     ?assertMatch(
-        ignore,
+        ?EXCEPTION_IGNORE,
         emqx_authn_http:authenticate(
             Credentials,
             State
@@ -190,14 +219,20 @@ t_update(_Config) ->
         {create_authenticator, ?GLOBAL, IncorrectConfig}
     ),
 
+    Headers = #{<<"content-type">> => <<"application/json">>},
+    Response = ?SERVER_RESPONSE_JSON(allow),
+
     ok = emqx_authn_http_test_server:set_handler(
         fun(Req0, State) ->
-            Req = cowboy_req:reply(200, Req0),
+            Req = cowboy_req:reply(200, Headers, Response, Req0),
             {ok, Req, State}
         end
     ),
 
-    {error, not_authorized} = emqx_access_control:authenticate(?CREDENTIALS),
+    ?assertMatch(
+        ?EXCEPTION_DENY,
+        emqx_access_control:authenticate(?CREDENTIALS)
+    ),
 
     % We update with config with correct query, provider should update and work properly
     {ok, _} = emqx:update_config(
@@ -205,7 +240,10 @@ t_update(_Config) ->
         {update_authenticator, ?GLOBAL, <<"password_based:http">>, CorrectConfig}
     ),
 
-    {ok, _} = emqx_access_control:authenticate(?CREDENTIALS).
+    ?assertMatch(
+        ?EXCEPTION_ALLOW,
+        emqx_access_control:authenticate(?CREDENTIALS)
+    ).
 
 t_is_superuser(_Config) ->
     Config = raw_http_auth_config(),
@@ -215,33 +253,56 @@ t_is_superuser(_Config) ->
     ),
 
     Checks = [
-        {json, <<"0">>, false},
-        {json, <<"">>, false},
-        {json, null, false},
-        {json, 0, false},
-
-        {json, <<"1">>, true},
-        {json, <<"val">>, true},
-        {json, 1, true},
-        {json, 123, true},
-
-        {form, <<"0">>, false},
-        {form, <<"">>, false},
-
-        {form, <<"1">>, true},
-        {form, <<"val">>, true}
+        %% {ContentType, ExpectedIsSuperuser, ResponseIsSuperuser}
+        %% Is Superuser
+        {json, true, <<"1">>},
+        {json, true, 1},
+        {json, true, 123},
+        {json, true, <<"true">>},
+        {json, true, true},
+
+        %% Not Superuser
+        {json, false, <<"">>},
+        {json, false, <<"0">>},
+        {json, false, 0},
+        {json, false, null},
+        {json, false, undefined},
+        {json, false, <<"false">>},
+        {json, false, false},
+
+        {json, false, <<"val">>},
+
+        %% Is Superuser
+        {form, true, <<"1">>},
+        {form, true, 1},
+        {form, true, 123},
+        {form, true, <<"true">>},
+        {form, true, true},
+
+        %% Not Superuser
+        {form, false, <<"">>},
+        {form, false, <<"0">>},
+        {form, false, 0},
+
+        {form, false, null},
+        {form, false, undefined},
+        {form, false, <<"false">>},
+        {form, false, false},
+
+        {form, false, <<"val">>}
     ],
 
     lists:foreach(fun test_is_superuser/1, Checks).
 
-test_is_superuser({Kind, Value, ExpectedValue}) ->
+test_is_superuser({Kind, ExpectedValue, ServerResponse}) ->
     {ContentType, Res} =
         case Kind of
             json ->
-                {<<"application/json">>, jiffy:encode(#{is_superuser => Value})};
+                {<<"application/json; charset=utf-8">>,
+                    ?SERVER_RESPONSE_JSON(allow, ServerResponse)};
             form ->
-                {<<"application/x-www-form-urlencoded">>,
-                    iolist_to_binary([<<"is_superuser=">>, Value])}
+                {<<"application/x-www-form-urlencoded; charset=utf-8">>,
+                    ?SERVER_RESPONSE_URLENCODE(allow, ServerResponse)}
         end,
 
     ok = emqx_authn_http_test_server:set_handler(
@@ -257,10 +318,58 @@ test_is_superuser({Kind, Value, ExpectedValue}) ->
     ),
 
     ?assertMatch(
-        {ok, #{is_superuser := ExpectedValue}},
+        ?EXCEPTION_ALLOW(ExpectedValue),
         emqx_access_control:authenticate(?CREDENTIALS)
     ).
 
+t_ignore_allow_deny(_Config) ->
+    Config = raw_http_auth_config(),
+    {ok, _} = emqx:update_config(
+        ?PATH,
+        {create_authenticator, ?GLOBAL, Config}
+    ),
+
+    Checks = [
+        %% only one chain, ignore by authn http and deny by default
+        {deny, ?SERVER_RESPONSE_JSON(ignore)},
+
+        {{allow, true}, ?SERVER_RESPONSE_JSON(allow, true)},
+        {{allow, false}, ?SERVER_RESPONSE_JSON(allow)},
+        {{allow, false}, ?SERVER_RESPONSE_JSON(allow, false)},
+
+        {deny, ?SERVER_RESPONSE_JSON(deny)},
+        {deny, ?SERVER_RESPONSE_JSON(deny, true)},
+        {deny, ?SERVER_RESPONSE_JSON(deny, false)}
+    ],
+
+    lists:foreach(fun test_ignore_allow_deny/1, Checks).
+
+test_ignore_allow_deny({ExpectedValue, ServerResponse}) ->
+    ok = emqx_authn_http_test_server:set_handler(
+        fun(Req0, State) ->
+            Req = cowboy_req:reply(
+                200,
+                #{<<"content-type">> => <<"application/json">>},
+                ServerResponse,
+                Req0
+            ),
+            {ok, Req, State}
+        end
+    ),
+
+    case ExpectedValue of
+        {allow, IsSuperuser} ->
+            ?assertMatch(
+                ?EXCEPTION_ALLOW(IsSuperuser),
+                emqx_access_control:authenticate(?CREDENTIALS)
+            );
+        deny ->
+            ?assertMatch(
+                ?EXCEPTION_DENY,
+                emqx_access_control:authenticate(?CREDENTIALS)
+            )
+    end.
+
 %%------------------------------------------------------------------------------
 %% Helpers
 %%------------------------------------------------------------------------------
@@ -287,11 +396,16 @@ samples() ->
                     password := <<"plain">>
                 } = cowboy_req:match_qs([username, password], Req0),
 
-                Req = cowboy_req:reply(200, Req0),
+                Req = cowboy_req:reply(
+                    200,
+                    #{<<"content-type">> => <<"application/json">>},
+                    jiffy:encode(#{result => allow, is_superuser => false}),
+                    Req0
+                ),
                 {ok, Req, State}
             end,
             config_params => #{},
-            result => {ok, #{is_superuser => false}}
+            result => {ok, #{is_superuser => false, user_property => #{}}}
         },
 
         %% get request with json body response
@@ -300,7 +414,7 @@ samples() ->
                 Req = cowboy_req:reply(
                     200,
                     #{<<"content-type">> => <<"application/json">>},
-                    jiffy:encode(#{is_superuser => true}),
+                    jiffy:encode(#{result => allow, is_superuser => true}),
                     Req0
                 ),
                 {ok, Req, State}
@@ -318,7 +432,7 @@ samples() ->
                         <<"content-type">> =>
                             <<"application/x-www-form-urlencoded">>
                     },
-                    <<"is_superuser=true">>,
+                    <<"is_superuser=true&result=allow">>,
                     Req0
                 ),
                 {ok, Req, State}
@@ -342,7 +456,8 @@ samples() ->
                 {ok, Req, State}
             end,
             config_params => #{},
-            result => {ok, #{is_superuser => false}}
+            %% only one chain, ignore by authn http and deny by default
+            result => {error, not_authorized}
         },
 
         %% simple post request, application/json
@@ -353,14 +468,19 @@ samples() ->
                     <<"username">> := <<"plain">>,
                     <<"password">> := <<"plain">>
                 } = jiffy:decode(RawBody, [return_maps]),
-                Req = cowboy_req:reply(200, Req1),
+                Req = cowboy_req:reply(
+                    200,
+                    #{<<"content-type">> => <<"application/json">>},
+                    jiffy:encode(#{result => allow, is_superuser => false}),
+                    Req1
+                ),
                 {ok, Req, State}
             end,
             config_params => #{
                 <<"method">> => <<"post">>,
                 <<"headers">> => #{<<"content-type">> => <<"application/json">>}
             },
-            result => {ok, #{is_superuser => false}}
+            result => {ok, #{is_superuser => false, user_property => #{}}}
         },
 
         %% simple post request, application/x-www-form-urlencoded
@@ -371,7 +491,12 @@ samples() ->
                     <<"username">> := <<"plain">>,
                     <<"password">> := <<"plain">>
                 } = maps:from_list(PostVars),
-                Req = cowboy_req:reply(200, Req1),
+                Req = cowboy_req:reply(
+                    200,
+                    #{<<"content-type">> => <<"application/json">>},
+                    jiffy:encode(#{result => allow, is_superuser => false}),
+                    Req1
+                ),
                 {ok, Req, State}
             end,
             config_params => #{
@@ -381,15 +506,7 @@ samples() ->
                         <<"application/x-www-form-urlencoded">>
                 }
             },
-            result => {ok, #{is_superuser => false}}
-        }#{
-            %% 204 code
-            handler => fun(Req0, State) ->
-                Req = cowboy_req:reply(204, Req0),
-                {ok, Req, State}
-            end,
-            config_params => #{},
-            result => {ok, #{is_superuser => false}}
+            result => {ok, #{is_superuser => false, user_property => #{}}}
         },
 
         %% simple post request for placeholders, application/json
@@ -402,7 +519,12 @@ samples() ->
                     <<"clientid">> := <<"clienta">>,
                     <<"peerhost">> := <<"127.0.0.1">>
                 } = jiffy:decode(RawBody, [return_maps]),
-                Req = cowboy_req:reply(200, Req1),
+                Req = cowboy_req:reply(
+                    200,
+                    #{<<"content-type">> => <<"application/json">>},
+                    jiffy:encode(#{result => allow, is_superuser => false}),
+                    Req1
+                ),
                 {ok, Req, State}
             end,
             config_params => #{
@@ -415,7 +537,7 @@ samples() ->
                     <<"peerhost">> => ?PH_PEERHOST
                 }
             },
-            result => {ok, #{is_superuser => false}}
+            result => {ok, #{is_superuser => false, user_property => #{}}}
         },
 
         %% custom headers
@@ -426,6 +548,17 @@ samples() ->
                 {ok, Req, State}
             end,
             config_params => #{},
+            %% only one chain, ignore by authn http and deny by default
+            result => {error, not_authorized}
+        },
+
+        %% 204 code
+        #{
+            handler => fun(Req0, State) ->
+                Req = cowboy_req:reply(204, Req0),
+                {ok, Req, State}
+            end,
+            config_params => #{},
             result => {ok, #{is_superuser => false}}
         },
 
@@ -436,6 +569,7 @@ samples() ->
                 {ok, Req, State}
             end,
             config_params => #{},
+            %% only one chain, ignore by authn http and deny by default
             result => {error, not_authorized}
         },
 
@@ -446,6 +580,7 @@ samples() ->
                 {ok, Req, State}
             end,
             config_params => #{},
+            %% only one chain, ignore by authn http and deny by default
             result => {error, not_authorized}
         },
 
@@ -456,6 +591,7 @@ samples() ->
                 {ok, Req0, State}
             end,
             config_params => #{},
+            %% only one chain, ignore by authn http and deny by default
             result => {error, not_authorized}
         }
     ].
@@ -465,3 +601,15 @@ start_apps(Apps) ->
 
 stop_apps(Apps) ->
     lists:foreach(fun application:stop/1, Apps).
+
+uri_encode(T) ->
+    emqx_http_lib:uri_encode(to_list(T)).
+
+to_list(A) when is_atom(A) ->
+    atom_to_list(A);
+to_list(N) when is_integer(N) ->
+    integer_to_list(N);
+to_list(B) when is_binary(B) ->
+    binary_to_list(B);
+to_list(L) when is_list(L) ->
+    L.

+ 2 - 0
apps/emqx_authn/test/emqx_authn_https_SUITE.erl

@@ -167,6 +167,8 @@ cert_path(FileName) ->
 cowboy_handler(Req0, State) ->
     Req = cowboy_req:reply(
         200,
+        #{<<"content-type">> => <<"application/json">>},
+        jiffy:encode(#{result => allow, is_superuser => false}),
         Req0
     ),
     {ok, Req, State}.

+ 2 - 1
apps/emqx_authn/test/emqx_authn_mongo_SUITE.erl

@@ -225,9 +225,10 @@ t_is_superuser(_Config) ->
         {null, false},
         {false, false},
         {0, false},
+        {<<"val">>, false},
 
         {<<"1">>, true},
-        {<<"val">>, true},
+        {<<"123">>, true},
         {1, true},
         {123, true},
         {true, true}

+ 1 - 1
apps/emqx_authn/test/emqx_authn_pgsql_SUITE.erl

@@ -243,7 +243,7 @@ t_is_superuser(_Config) ->
         {is_superuser_str, "", false},
         {is_superuser_str, null, false},
         {is_superuser_str, "1", true},
-        {is_superuser_str, "val", true},
+        {is_superuser_str, "val", false},
 
         {is_superuser_int, 0, false},
         {is_superuser_int, null, false},

+ 14 - 16
apps/emqx_conf/src/emqx_conf.appup.src

@@ -1,21 +1,19 @@
 %% -*- mode: erlang -*-
 {VSN,
- [ {"0.1.0",
-    [
-      {add_module, emqx_conf_proto_v2},
-      {load_module,emqx_conf_schema,brutal_purge,soft_purge,[]},
-      {load_module,emqx_conf,brutal_purge,soft_purge,[]},
-      {load_module,emqx_conf_app,brutal_purge,soft_purge,[]}
-    ]},
-   {<<".*">>, []}
+ [{"0.1.0",
+   [{add_module, emqx_conf_proto_v2},
+    {load_module,emqx_conf_schema,brutal_purge,soft_purge,[]},
+    {load_module,emqx_conf,brutal_purge,soft_purge,[]},
+    {load_module,emqx_conf_app,brutal_purge,soft_purge,[]}
+   ]},
+  {<<".*">>, []}
  ],
- [ {"0.1.0",
-    [
-      {delete_module, emqx_conf_proto_v2},
-      {load_module,emqx_conf_schema,brutal_purge,soft_purge,[]},
-      {load_module,emqx_conf,brutal_purge,soft_purge,[]},
-      {load_module,emqx_conf_app,brutal_purge,soft_purge,[]}
-    ]},
-   {<<".*">>, []}
+ [{"0.1.0",
+   [{delete_module, emqx_conf_proto_v2},
+    {load_module,emqx_conf_schema,brutal_purge,soft_purge,[]},
+    {load_module,emqx_conf,brutal_purge,soft_purge,[]},
+    {load_module,emqx_conf_app,brutal_purge,soft_purge,[]}
+   ]},
+  {<<".*">>, []}
  ]
 }.

+ 8 - 8
apps/emqx_dashboard/src/emqx_dashboard.appup.src

@@ -1,13 +1,13 @@
 %% -*- mode: erlang -*-
 %% Unless you know what you are doing, DO NOT edit manually!!
 {VSN,
-  [{"5.0.0", [
-    {load_module,emqx_dashboard_api,brutal_purge,soft_purge,[]},
-    {load_module,emqx_dashboard_token,brutal_purge,soft_purge,[]}
-   ]},
+  [{"5.0.0",
+    [{load_module,emqx_dashboard,brutal_purge,soft_purge,[]},
+     {load_module,emqx_dashboard_api,brutal_purge,soft_purge,[]},
+     {load_module,emqx_dashboard_token,brutal_purge,soft_purge,[]}]},
    {<<".*">>,[]}],
-  [{"5.0.0", [
-    {load_module,emqx_dashboard_api,brutal_purge,soft_purge,[]},
-    {load_module,emqx_dashboard_token,brutal_purge,soft_purge,[]}
-   ]},
+  [{"5.0.0",
+    [{load_module,emqx_dashboard,brutal_purge,soft_purge,[]},
+     {load_module,emqx_dashboard_api,brutal_purge,soft_purge,[]},
+     {load_module,emqx_dashboard_token,brutal_purge,soft_purge,[]}]},
    {<<".*">>,[]}]}.

+ 3 - 1
apps/emqx_gateway/test/emqx_gateway_auth_ct.erl

@@ -141,12 +141,14 @@ on_start_auth(authn_http) ->
     %% set handler for test server
     Handler = fun(Req0, State) ->
         ct:pal("Authn Req:~p~nState:~p~n", [Req0, State]),
+        Headers = #{<<"content-type">> => <<"application/json">>},
+        Response = jiffy:encode(#{result => allow, is_superuser => false}),
         case cowboy_req:match_qs([username, password], Req0) of
             #{
                 username := <<"admin">>,
                 password := <<"public">>
             } ->
-                Req = cowboy_req:reply(200, Req0);
+                Req = cowboy_req:reply(200, Headers, Response, Req0);
             _ ->
                 Req = cowboy_req:reply(400, Req0)
         end,

+ 20 - 20
apps/emqx_management/src/emqx_management.appup.src

@@ -1,25 +1,25 @@
 %% -*- mode: erlang -*-
 %% Unless you know what you are doing, DO NOT edit manually!!
 {VSN,
-  [{"5.0.0",[
-    {load_module,emqx_mgmt_cli,brutal_purge,soft_purge,[]},
-    {add_module,emqx_management_proto_v2},
-    {load_module,emqx_mgmt_api_clients,brutal_purge,soft_purge,[]},
-    {load_module,emqx_mgmt_api_publish,brutal_purge,soft_purge,[]},
-    {load_module,emqx_mgmt_api_listeners,brutal_purge,soft_purge,[]},
-    {load_module,emqx_mgmt_api_configs,brutal_purge,soft_purge,[]},
-    {load_module,emqx_mgmt_util,brutal_purge,soft_purge,[]},
-    {load_module,emqx_mgmt,brutal_purge,soft_purge,[]}
-   ]},
+  [{"5.0.0",
+    [{load_module,emqx_mgmt_api_nodes,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mgmt_cli,brutal_purge,soft_purge,[]},
+     {add_module,emqx_management_proto_v2},
+     {load_module,emqx_mgmt_api_clients,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mgmt_api_publish,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mgmt_api_listeners,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mgmt_api_configs,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mgmt_util,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mgmt,brutal_purge,soft_purge,[]}]},
    {<<".*">>,[]}],
-  [{"5.0.0",[
-    {load_module,emqx_mgmt_cli,brutal_purge,soft_purge,[]},
-    {delete_module,emqx_management_proto_v2},
-    {load_module,emqx_mgmt_api_clients,brutal_purge,soft_purge,[]},
-    {load_module,emqx_mgmt_api_publish,brutal_purge,soft_purge,[]},
-    {load_module,emqx_mgmt_api_listeners,brutal_purge,soft_purge,[]},
-    {load_module,emqx_mgmt_api_configs,brutal_purge,soft_purge,[]},
-    {load_module,emqx_mgmt_util,brutal_purge,soft_purge,[]},
-    {load_module,emqx_mgmt,brutal_purge,soft_purge,[]}
-   ]},
+  [{"5.0.0",
+    [{load_module,emqx_mgmt_api_nodes,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mgmt_cli,brutal_purge,soft_purge,[]},
+     {delete_module,emqx_management_proto_v2},
+     {load_module,emqx_mgmt_api_clients,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mgmt_api_publish,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mgmt_api_listeners,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mgmt_api_configs,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mgmt_util,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mgmt,brutal_purge,soft_purge,[]}]},
    {<<".*">>,[]}]}.