Przeglądaj źródła

Merge pull request #9586 from lafirest/fix/disable_basic_auth_api

fix: disable basic auth for HTTP API
lafirest 3 lat temu
rodzic
commit
a26c05f4f6
29 zmienionych plików z 185 dodań i 208 usunięć
  1. 15 2
      .github/workflows/run_fvt_tests.yaml
  2. 5 5
      .github/workflows/run_jmeter_tests.yaml
  3. 4 6
      apps/emqx/include/http_api.hrl
  4. 14 5
      apps/emqx/test/emqx_common_test_http.erl
  5. 5 10
      apps/emqx_authn/test/emqx_authn_api_SUITE.erl
  6. 6 6
      apps/emqx_authz/test/emqx_authz_api_cache_SUITE.erl
  7. 4 4
      apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl
  8. 3 3
      apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl
  9. 4 4
      apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl
  10. 3 10
      apps/emqx_auto_subscribe/test/emqx_auto_subscribe_SUITE.erl
  11. 4 13
      apps/emqx_bridge/test/emqx_bridge_api_SUITE.erl
  12. 24 24
      apps/emqx_dashboard/src/emqx_dashboard.erl
  13. 3 3
      apps/emqx_dashboard/src/emqx_dashboard_api.erl
  14. 2 2
      apps/emqx_dashboard/test/emqx_dashboard_SUITE.erl
  15. 5 24
      apps/emqx_dashboard/test/emqx_dashboard_bad_api_SUITE.erl
  16. 2 4
      apps/emqx_dashboard/test/emqx_dashboard_monitor_SUITE.erl
  17. 1 7
      apps/emqx_exhook/test/emqx_exhook_api_SUITE.erl
  18. 1 4
      apps/emqx_gateway/test/emqx_gateway_test_utils.erl
  19. 14 6
      apps/emqx_management/src/emqx_mgmt_auth.erl
  20. 9 6
      apps/emqx_management/test/emqx_mgmt_api_api_keys_SUITE.erl
  21. 34 7
      apps/emqx_management/test/emqx_mgmt_api_test_util.erl
  22. 2 7
      apps/emqx_management/test/emqx_mgmt_api_trace_SUITE.erl
  23. 4 10
      apps/emqx_modules/test/emqx_delayed_api_SUITE.erl
  24. 5 11
      apps/emqx_modules/test/emqx_rewrite_api_SUITE.erl
  25. 5 5
      apps/emqx_modules/test/emqx_telemetry_api_SUITE.erl
  26. 4 13
      apps/emqx_modules/test/emqx_topic_metrics_api_SUITE.erl
  27. 1 7
      apps/emqx_slow_subs/test/emqx_slow_subs_api_SUITE.erl
  28. 1 0
      changes/v5.0.15/feat-9586.en.md
  29. 1 0
      changes/v5.0.15/feat-9586.zh.md

+ 15 - 2
.github/workflows/run_fvt_tests.yaml

@@ -201,12 +201,25 @@ jobs:
           echo "waiting emqx started";
           sleep 10;
         done
+    - name: Get Token
+      timeout-minutes: 1
+      run: |
+        kubectl port-forward service/${{ matrix.profile }} 18083:18083 > /dev/null &
+
+        while
+          [ "$(curl --silent -X 'GET' 'http://127.0.0.1:18083/api/v5/status' | tail -n1)" != "emqx is running" ]
+        do
+          echo "waiting emqx"
+          sleep 1
+        done
+
+        echo "TOKEN=$(curl --silent -X 'POST' 'http://127.0.0.1:18083/api/v5/login' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"username": "admin","password": "public"}' | jq -r ".token")" >> $GITHUB_ENV
+
     - name: Check cluster
       timeout-minutes: 10
       run: |
-        kubectl port-forward service/${{ matrix.profile }} 18083:18083 > /dev/null &
         while
-          [ "$(curl --silent --basic -u admin:public -X GET http://127.0.0.1:18083/api/v5/cluster| jq '.nodes|length')" != "3" ];
+          [ "$(curl --silent -H "Authorization: Bearer $TOKEN" -X GET http://127.0.0.1:18083/api/v5/cluster| jq '.nodes|length')" != "3" ];
         do
           echo "waiting ${{ matrix.profile }} cluster scale"
           sleep 1

+ 5 - 5
.github/workflows/run_jmeter_tests.yaml

@@ -92,7 +92,7 @@ jobs:
     - uses: actions/checkout@v3
       with:
         repository: emqx/emqx-fvt
-        ref: broker-autotest-v2
+        ref: broker-autotest-v4
         path: scripts
     - uses: actions/setup-java@v3
       with:
@@ -191,7 +191,7 @@ jobs:
     - uses: actions/checkout@v3
       with:
         repository: emqx/emqx-fvt
-        ref: broker-autotest-v2
+        ref: broker-autotest-v4
         path: scripts
     - uses: actions/setup-java@v3
       with:
@@ -297,7 +297,7 @@ jobs:
     - uses: actions/checkout@v3
       with:
         repository: emqx/emqx-fvt
-        ref: broker-autotest-v2
+        ref: broker-autotest-v4
         path: scripts
     - uses: actions/setup-java@v3
       with:
@@ -396,7 +396,7 @@ jobs:
     - uses: actions/checkout@v3
       with:
         repository: emqx/emqx-fvt
-        ref: broker-autotest-v2
+        ref: broker-autotest-v4
         path: scripts
     - name: run jwks_server
       timeout-minutes: 10
@@ -496,7 +496,7 @@ jobs:
     - uses: actions/checkout@v3
       with:
         repository: emqx/emqx-fvt
-        ref: broker-autotest-v2
+        ref: broker-autotest-v4
         path: scripts
     - uses: actions/setup-java@v3
       with:

+ 4 - 6
apps/emqx/include/http_api.hrl

@@ -15,10 +15,8 @@
 %%--------------------------------------------------------------------
 
 %% HTTP API Auth
--define(WRONG_USERNAME_OR_PWD, 'WRONG_USERNAME_OR_PWD').
--define(WRONG_USERNAME_OR_PWD_OR_API_KEY_OR_API_SECRET,
-    'WRONG_USERNAME_OR_PWD_OR_API_KEY_OR_API_SECRET'
-).
+-define(BAD_USERNAME_OR_PWD, 'BAD_USERNAME_OR_PWD').
+-define(BAD_API_KEY_OR_SECRET, 'BAD_API_KEY_OR_SECRET').
 
 %% Bad Request
 -define(BAD_REQUEST, 'BAD_REQUEST').
@@ -57,8 +55,8 @@
 
 %% All codes
 -define(ERROR_CODES, [
-    {'WRONG_USERNAME_OR_PWD', <<"Wrong username or pwd">>},
-    {'WRONG_USERNAME_OR_PWD_OR_API_KEY_OR_API_SECRET', <<"Wrong username & pwd or key & secret">>},
+    {?BAD_USERNAME_OR_PWD, <<"Bad username or password">>},
+    {?BAD_API_KEY_OR_SECRET, <<"Bad API key or secret">>},
     {'BAD_REQUEST', <<"Request parameters are not legal">>},
     {'NOT_MATCH', <<"Conditions are not matched">>},
     {'ALREADY_EXISTS', <<"Resource already existed">>},

+ 14 - 5
apps/emqx/test/emqx_common_test_http.erl

@@ -29,6 +29,9 @@
     auth_header/2
 ]).
 
+-define(DEFAULT_APP_ID, <<"default_appid">>).
+-define(DEFAULT_APP_SECRET, <<"default_app_secret">>).
+
 request_api(Method, Url, Auth) ->
     request_api(Method, Url, [], Auth, []).
 
@@ -74,12 +77,18 @@ auth_header(User, Pass) ->
     {"Authorization", "Basic " ++ Encoded}.
 
 default_auth_header() ->
-    AppId = <<"myappid">>,
-    AppSecret = emqx_mgmt_auth:get_appsecret(AppId),
-    auth_header(erlang:binary_to_list(AppId), erlang:binary_to_list(AppSecret)).
+    {ok, #{api_key := APIKey}} = emqx_mgmt_auth:read(?DEFAULT_APP_ID),
+    auth_header(
+        erlang:binary_to_list(APIKey), erlang:binary_to_list(?DEFAULT_APP_SECRET)
+    ).
 
 create_default_app() ->
-    emqx_mgmt_auth:add_app(<<"myappid">>, <<"test">>).
+    Now = erlang:system_time(second),
+    ExpiredAt = Now + timer:minutes(10),
+    emqx_mgmt_auth:create(
+        ?DEFAULT_APP_ID, ?DEFAULT_APP_SECRET, true, ExpiredAt, <<"default app key for test">>
+    ),
+    ok.
 
 delete_default_app() ->
-    emqx_mgmt_auth:del_app(<<"myappid">>).
+    emqx_mgmt_auth:delete(?DEFAULT_APP_ID).

+ 5 - 10
apps/emqx_authn/test/emqx_authn_api_SUITE.erl

@@ -18,7 +18,8 @@
 -compile(nowarn_export_all).
 -compile(export_all).
 
--import(emqx_dashboard_api_test_helpers, [request/3, uri/1, multipart_formdata_request/3]).
+-import(emqx_dashboard_api_test_helpers, [multipart_formdata_request/3]).
+-import(emqx_mgmt_api_test_util, [request/3, uri/1]).
 
 -include("emqx_authn.hrl").
 -include_lib("eunit/include/eunit.hrl").
@@ -65,9 +66,8 @@ end_per_testcase(_, Config) ->
 init_per_suite(Config) ->
     emqx_config:erase(?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY),
     _ = application:load(emqx_conf),
-    ok = emqx_common_test_helpers:start_apps(
-        [emqx_authn, emqx_dashboard],
-        fun set_special_configs/1
+    ok = emqx_mgmt_api_test_util:init_suite(
+        [emqx_authn]
     ),
 
     ?AUTHN:delete_chain(?GLOBAL),
@@ -76,12 +76,7 @@ init_per_suite(Config) ->
     Config.
 
 end_per_suite(_Config) ->
-    emqx_common_test_helpers:stop_apps([emqx_dashboard, emqx_authn]),
-    ok.
-
-set_special_configs(emqx_dashboard) ->
-    emqx_dashboard_api_test_helpers:set_default_config();
-set_special_configs(_App) ->
+    emqx_mgmt_api_test_util:end_suite([emqx_authn]),
     ok.
 
 %%------------------------------------------------------------------------------

+ 6 - 6
apps/emqx_authz/test/emqx_authz_api_cache_SUITE.erl

@@ -18,7 +18,7 @@
 -compile(nowarn_export_all).
 -compile(export_all).
 
--import(emqx_dashboard_api_test_helpers, [request/2, uri/1]).
+-import(emqx_mgmt_api_test_util, [request/2, uri/1]).
 
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
@@ -32,8 +32,8 @@ groups() ->
     [].
 
 init_per_suite(Config) ->
-    ok = emqx_common_test_helpers:start_apps(
-        [emqx_conf, emqx_authz, emqx_dashboard, emqx_management],
+    ok = emqx_mgmt_api_test_util:init_suite(
+        [emqx_conf, emqx_authz],
         fun set_special_configs/1
     ),
     Config.
@@ -47,7 +47,7 @@ end_per_suite(_Config) ->
             <<"sources">> => []
         }
     ),
-    emqx_common_test_helpers:stop_apps([emqx_dashboard, emqx_authz, emqx_conf, emqx_management]),
+    emqx_mgmt_api_test_util:end_suite([emqx_authz, emqx_conf]),
     ok.
 
 set_special_configs(emqx_dashboard) ->
@@ -67,12 +67,12 @@ t_clean_cahce(_) ->
     ok = emqtt:publish(C, <<"a/b/c">>, <<"{\"x\":1,\"y\":1}">>, 0),
 
     {ok, 200, Result3} = request(get, uri(["clients", "emqx0", "authorization", "cache"])),
-    ?assertEqual(2, length(jsx:decode(Result3))),
+    ?assertEqual(2, length(emqx_json:decode(Result3))),
 
     request(delete, uri(["authorization", "cache"])),
 
     {ok, 200, Result4} = request(get, uri(["clients", "emqx0", "authorization", "cache"])),
-    ?assertEqual(0, length(jsx:decode(Result4))),
+    ?assertEqual(0, length(emqx_json:decode(Result4))),
 
     ok.
 

+ 4 - 4
apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl

@@ -22,7 +22,7 @@
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
 
--import(emqx_dashboard_api_test_helpers, [request/3, uri/1]).
+-import(emqx_mgmt_api_test_util, [request/3, uri/1]).
 
 all() ->
     emqx_common_test_helpers:all(?MODULE).
@@ -31,8 +31,8 @@ groups() ->
     [].
 
 init_per_suite(Config) ->
-    ok = emqx_common_test_helpers:start_apps(
-        [emqx_conf, emqx_authz, emqx_dashboard],
+    ok = emqx_mgmt_api_test_util:init_suite(
+        [emqx_conf, emqx_authz],
         fun set_special_configs/1
     ),
     Config.
@@ -46,7 +46,7 @@ end_per_suite(_Config) ->
             <<"sources">> => []
         }
     ),
-    emqx_common_test_helpers:stop_apps([emqx_dashboard, emqx_authz, emqx_conf]),
+    emqx_mgmt_api_test_util:end_suite([emqx_authz, emqx_conf]),
     ok.
 
 set_special_configs(emqx_dashboard) ->

+ 3 - 3
apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl

@@ -18,7 +18,7 @@
 -compile(nowarn_export_all).
 -compile(export_all).
 
--import(emqx_dashboard_api_test_helpers, [request/3, uri/1]).
+-import(emqx_mgmt_api_test_util, [request/3, uri/1]).
 
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
@@ -30,7 +30,7 @@ groups() ->
     [].
 
 init_per_suite(Config) ->
-    ok = emqx_common_test_helpers:start_apps(
+    ok = emqx_mgmt_api_test_util:init_suite(
         [emqx_conf, emqx_authz, emqx_dashboard],
         fun set_special_configs/1
     ),
@@ -46,7 +46,7 @@ end_per_suite(_Config) ->
         }
     ),
     ok = stop_apps([emqx_resource]),
-    emqx_common_test_helpers:stop_apps([emqx_dashboard, emqx_authz, emqx_conf]),
+    emqx_mgmt_api_test_util:end_suite([emqx_authz, emqx_conf]),
     ok.
 
 set_special_configs(emqx_dashboard) ->

+ 4 - 4
apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl

@@ -18,7 +18,7 @@
 -compile(nowarn_export_all).
 -compile(export_all).
 
--import(emqx_dashboard_api_test_helpers, [request/3, uri/1]).
+-import(emqx_mgmt_api_test_util, [request/3, uri/1]).
 
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
@@ -115,8 +115,8 @@ init_per_suite(Config) ->
         end
     ),
 
-    ok = emqx_common_test_helpers:start_apps(
-        [emqx_conf, emqx_authz, emqx_dashboard],
+    ok = emqx_mgmt_api_test_util:init_suite(
+        [emqx_conf, emqx_authz],
         fun set_special_configs/1
     ),
     ok = start_apps([emqx_resource]),
@@ -134,7 +134,7 @@ end_per_suite(_Config) ->
     %% resource and connector should be stop first,
     %% or authz_[mysql|pgsql|redis..]_SUITE would be failed
     ok = stop_apps([emqx_resource]),
-    emqx_common_test_helpers:stop_apps([emqx_dashboard, emqx_authz, emqx_conf]),
+    emqx_mgmt_api_test_util:end_suite([emqx_authz, emqx_conf]),
     meck:unload(emqx_resource),
     ok.
 

+ 3 - 10
apps/emqx_auto_subscribe/test/emqx_auto_subscribe_SUITE.erl

@@ -93,9 +93,8 @@ init_per_suite(Config) ->
             "        }"
         >>
     ),
-    emqx_common_test_helpers:start_apps(
-        [emqx_conf, emqx_dashboard, ?APP],
-        fun set_special_configs/1
+    emqx_mgmt_api_test_util:init_suite(
+        [emqx_conf, ?APP]
     ),
     Config.
 
@@ -111,12 +110,6 @@ end_per_testcase(t_get_basic_usage_info, _Config) ->
 end_per_testcase(_TestCase, _Config) ->
     ok.
 
-set_special_configs(emqx_dashboard) ->
-    emqx_dashboard_api_test_helpers:set_default_config(),
-    ok;
-set_special_configs(_) ->
-    ok.
-
 topic_config(T) ->
     #{
         topic => T,
@@ -132,7 +125,7 @@ end_per_suite(_) ->
     application:unload(?APP),
     meck:unload(emqx_resource),
     meck:unload(emqx_schema),
-    emqx_common_test_helpers:stop_apps([emqx_dashboard, emqx_conf, ?APP]).
+    emqx_mgmt_api_test_util:end_suite([emqx_conf, ?APP]).
 
 t_auto_subscribe(_) ->
     emqx_auto_subscribe:update([#{<<"topic">> => Topic} || Topic <- ?TOPICS]),

+ 4 - 13
apps/emqx_bridge/test/emqx_bridge_api_SUITE.erl

@@ -18,7 +18,7 @@
 -compile(nowarn_export_all).
 -compile(export_all).
 
--import(emqx_dashboard_api_test_helpers, [request/4, uri/1]).
+-import(emqx_mgmt_api_test_util, [request/3, uri/1]).
 
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
@@ -60,9 +60,8 @@ init_per_suite(Config) ->
     %% some testcases (may from other app) already get emqx_connector started
     _ = application:stop(emqx_resource),
     _ = application:stop(emqx_connector),
-    ok = emqx_common_test_helpers:start_apps(
-        [emqx_rule_engine, emqx_bridge, emqx_dashboard],
-        fun set_special_configs/1
+    ok = emqx_mgmt_api_test_util:init_suite(
+        [emqx_rule_engine, emqx_bridge]
     ),
     ok = emqx_common_test_helpers:load_config(
         emqx_rule_engine_schema,
@@ -72,12 +71,7 @@ init_per_suite(Config) ->
     Config.
 
 end_per_suite(_Config) ->
-    emqx_common_test_helpers:stop_apps([emqx_rule_engine, emqx_bridge, emqx_dashboard]),
-    ok.
-
-set_special_configs(emqx_dashboard) ->
-    emqx_dashboard_api_test_helpers:set_default_config(<<"bridge_admin">>);
-set_special_configs(_) ->
+    emqx_mgmt_api_test_util:end_suite([emqx_rule_engine, emqx_bridge]),
     ok.
 
 init_per_testcase(_, Config) ->
@@ -605,9 +599,6 @@ t_with_redact_update(_Config) ->
     ?assertEqual(Password, Value),
     ok.
 
-request(Method, Url, Body) ->
-    request(<<"bridge_admin">>, Method, Url, Body).
-
 operation_path(node, Oper, BridgeID) ->
     uri(["nodes", node(), "bridges", BridgeID, "operation", Oper]);
 operation_path(cluster, Oper, BridgeID) ->

+ 24 - 24
apps/emqx_dashboard/src/emqx_dashboard.erl

@@ -65,8 +65,12 @@ start_listeners(Listeners) ->
         components => #{
             schemas => #{},
             'securitySchemes' => #{
-                'basicAuth' => #{type => http, scheme => basic},
-                'bearerAuth' => #{type => http, scheme => bearer}
+                'basicAuth' => #{
+                    type => http,
+                    scheme => basic,
+                    description =>
+                        <<"Authorize with [API Keys](https://www.emqx.io/docs/en/v5.0/admin/api.html#api-keys)">>
+                }
             }
         }
     },
@@ -215,28 +219,7 @@ listener_name(Protocol) ->
 authorize(Req) ->
     case cowboy_req:parse_header(<<"authorization">>, Req) of
         {basic, Username, Password} ->
-            case emqx_dashboard_admin:check(Username, Password) of
-                ok ->
-                    ok;
-                {error, <<"username_not_found">>} ->
-                    Path = cowboy_req:path(Req),
-                    case emqx_mgmt_auth:authorize(Path, Username, Password) of
-                        ok ->
-                            ok;
-                        {error, <<"not_allowed">>} ->
-                            return_unauthorized(
-                                ?WRONG_USERNAME_OR_PWD,
-                                <<"Check username/password">>
-                            );
-                        {error, _} ->
-                            return_unauthorized(
-                                ?WRONG_USERNAME_OR_PWD_OR_API_KEY_OR_API_SECRET,
-                                <<"Check username/password or api_key/api_secret">>
-                            )
-                    end;
-                {error, _} ->
-                    return_unauthorized(?WRONG_USERNAME_OR_PWD, <<"Check username/password">>)
-            end;
+            api_key_authorize(Req, Username, Password);
         {bearer, Token} ->
             case emqx_dashboard_admin:verify_token(Token) of
                 ok ->
@@ -269,3 +252,20 @@ i18n_file() ->
 
 listeners() ->
     emqx_conf:get([dashboard, listeners], []).
+
+api_key_authorize(Req, Key, Secret) ->
+    Path = cowboy_req:path(Req),
+    case emqx_mgmt_auth:authorize(Path, Key, Secret) of
+        ok ->
+            ok;
+        {error, <<"not_allowed">>} ->
+            return_unauthorized(
+                ?BAD_API_KEY_OR_SECRET,
+                <<"Not allowed, Check api_key/api_secret">>
+            );
+        {error, _} ->
+            return_unauthorized(
+                ?BAD_API_KEY_OR_SECRET,
+                <<"Check api_key/api_secret">>
+            )
+    end.

+ 3 - 3
apps/emqx_dashboard/src/emqx_dashboard_api.erl

@@ -47,7 +47,7 @@
 
 -define(EMPTY(V), (V == undefined orelse V == <<>>)).
 
--define(WRONG_USERNAME_OR_PWD, 'WRONG_USERNAME_OR_PWD').
+-define(BAD_USERNAME_OR_PWD, 'BAD_USERNAME_OR_PWD').
 -define(WRONG_TOKEN_OR_USERNAME, 'WRONG_TOKEN_OR_USERNAME').
 -define(USER_NOT_FOUND, 'USER_NOT_FOUND').
 -define(ERROR_PWD_NOT_MATCH, 'ERROR_PWD_NOT_MATCH').
@@ -164,7 +164,7 @@ schema("/users/:username/change_pwd") ->
     }.
 
 response_schema(401) ->
-    emqx_dashboard_swagger:error_codes([?WRONG_USERNAME_OR_PWD], ?DESC(login_failed401));
+    emqx_dashboard_swagger:error_codes([?BAD_USERNAME_OR_PWD], ?DESC(login_failed401));
 response_schema(404) ->
     emqx_dashboard_swagger:error_codes([?USER_NOT_FOUND], ?DESC(users_api404)).
 
@@ -223,7 +223,7 @@ login(post, #{body := Params}) ->
             }};
         {error, R} ->
             ?SLOG(info, #{msg => "Dashboard login failed", username => Username, reason => R}),
-            {401, ?WRONG_USERNAME_OR_PWD, <<"Auth failed">>}
+            {401, ?BAD_USERNAME_OR_PWD, <<"Auth failed">>}
     end.
 
 logout(_, #{

+ 2 - 2
apps/emqx_dashboard/test/emqx_dashboard_SUITE.erl

@@ -114,9 +114,9 @@ t_admin_delete_self_failed(_) ->
     ?assertEqual(1, length(Admins)),
     Header = auth_header_(<<"username1">>, <<"password">>),
     {error, {_, 400, _}} = request_dashboard(delete, api_path(["users", "username1"]), Header),
-    Token = erlang:iolist_to_binary(["Basic ", base64:encode("username1:password")]),
+    Token = ["Basic ", base64:encode("username1:password")],
     Header2 = {"Authorization", Token},
-    {error, {_, 400, _}} = request_dashboard(delete, api_path(["users", "username1"]), Header2),
+    {error, {_, 401, _}} = request_dashboard(delete, api_path(["users", "username1"]), Header2),
     mnesia:clear_table(?ADMIN).
 
 t_rest_api(_Config) ->

+ 5 - 24
apps/emqx_dashboard/test/emqx_dashboard_bad_api_SUITE.erl

@@ -25,43 +25,24 @@
 
 -define(SERVER, "http://127.0.0.1:18083/api/v5").
 
+-import(emqx_mgmt_api_test_util, [request/2]).
+
 all() ->
     emqx_common_test_helpers:all(?MODULE).
 
 init_per_suite(Config) ->
     mria:start(),
-    application:load(emqx_dashboard),
-    emqx_common_test_helpers:start_apps([emqx_conf, emqx_dashboard], fun set_special_configs/1),
+    emqx_mgmt_api_test_util:init_suite([emqx_conf]),
     Config.
 
-set_special_configs(emqx_dashboard) ->
-    emqx_dashboard_api_test_helpers:set_default_config(),
-    ok;
-set_special_configs(_) ->
-    ok.
-
 end_per_suite(Config) ->
     end_suite(),
     Config.
 
 end_suite() ->
-    application:unload(emqx_management),
-    emqx_common_test_helpers:stop_apps([emqx_dashboard]).
+    emqx_mgmt_api_test_util:end_suite([emqx_conf]).
 
 t_bad_api_path(_) ->
     Url = ?SERVER ++ "/for/test/some/path/not/exist",
-    {error, {"HTTP/1.1", 404, "Not Found"}} = request(Url),
+    {ok, 404, _} = request(get, Url),
     ok.
-
-request(Url) ->
-    Request = {Url, []},
-    case httpc:request(get, Request, [], []) of
-        {error, Reason} ->
-            {error, Reason};
-        {ok, {{"HTTP/1.1", Code, _}, _, Return}} when
-            Code >= 200 andalso Code =< 299
-        ->
-            {ok, emqx_json:decode(Return, [return_maps])};
-        {ok, {Reason, _, _}} ->
-            {error, Reason}
-    end.

+ 2 - 4
apps/emqx_dashboard/test/emqx_dashboard_monitor_SUITE.erl

@@ -19,6 +19,8 @@
 -compile(nowarn_export_all).
 -compile(export_all).
 
+-import(emqx_dashboard_SUITE, [auth_header_/0]).
+
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
 -include_lib("emqx/include/emqx.hrl").
@@ -153,10 +155,6 @@ do_request_api(Method, Request) ->
             {error, Reason}
     end.
 
-auth_header_() ->
-    Basic = binary_to_list(base64:encode(<<"admin:public">>)),
-    {"Authorization", "Basic " ++ Basic}.
-
 restart_monitor() ->
     OldMonitor = erlang:whereis(emqx_dashboard_monitor),
     erlang:exit(OldMonitor, kill),

+ 1 - 7
apps/emqx_exhook/test/emqx_exhook_api_SUITE.erl

@@ -347,13 +347,7 @@ do_request_api(Method, Request) ->
     end.
 
 auth_header_() ->
-    AppId = <<"admin">>,
-    AppSecret = <<"public">>,
-    auth_header_(binary_to_list(AppId), binary_to_list(AppSecret)).
-
-auth_header_(User, Pass) ->
-    Encoded = base64:encode_to_string(lists:append([User, ":", Pass])),
-    {"Authorization", "Basic " ++ Encoded}.
+    emqx_mgmt_api_test_util:auth_header_().
 
 api_path(Parts) ->
     ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION] ++ Parts).

+ 1 - 4
apps/emqx_gateway/test/emqx_gateway_test_utils.erl

@@ -106,8 +106,6 @@ assert_fields_exist(Ks, Map) ->
 %% http
 
 -define(http_api_host, "http://127.0.0.1:18083/api/v5").
--define(default_user, "admin").
--define(default_pass, "public").
 
 request(delete = Mth, Path) ->
     do_request(Mth, req(Path, []));
@@ -176,5 +174,4 @@ url(Path, Qs) ->
     lists:concat([?http_api_host, Path, "?", binary_to_list(cow_qs:qs(Qs))]).
 
 auth(Headers) ->
-    Token = base64:encode(?default_user ++ ":" ++ ?default_pass),
-    [{"Authorization", "Basic " ++ binary_to_list(Token)}] ++ Headers.
+    [emqx_mgmt_api_test_util:auth_header_() | Headers].

+ 14 - 6
apps/emqx_management/src/emqx_mgmt_auth.erl

@@ -40,6 +40,10 @@
     do_force_create_app/3
 ]).
 
+-ifdef(TEST).
+-export([create/5]).
+-endif.
+
 -define(APP, emqx_app).
 
 -record(?APP, {
@@ -68,8 +72,12 @@ init_bootstrap_file() ->
     init_bootstrap_file(File).
 
 create(Name, Enable, ExpiredAt, Desc) ->
+    ApiSecret = generate_api_secret(),
+    create(Name, ApiSecret, Enable, ExpiredAt, Desc).
+
+create(Name, ApiSecret, Enable, ExpiredAt, Desc) ->
     case mnesia:table_info(?APP, size) < 100 of
-        true -> create_app(Name, Enable, ExpiredAt, Desc);
+        true -> create_app(Name, ApiSecret, Enable, ExpiredAt, Desc);
         false -> {error, "Maximum ApiKey"}
     end.
 
@@ -157,8 +165,7 @@ to_map(#?APP{name = N, api_key = K, enable = E, expired_at = ET, created_at = CT
 is_expired(undefined) -> false;
 is_expired(ExpiredTime) -> ExpiredTime < erlang:system_time(second).
 
-create_app(Name, Enable, ExpiredAt, Desc) ->
-    ApiSecret = generate_api_secret(),
+create_app(Name, ApiSecret, Enable, ExpiredAt, Desc) ->
     App =
         #?APP{
             name = Name,
@@ -170,9 +177,10 @@ create_app(Name, Enable, ExpiredAt, Desc) ->
             api_key = list_to_binary(emqx_misc:gen_id(16))
         },
     case create_app(App) of
-        {error, api_key_already_existed} -> create_app(Name, Enable, ExpiredAt, Desc);
-        {ok, Res} -> {ok, Res#{api_secret => ApiSecret}};
-        Error -> Error
+        {ok, Res} ->
+            {ok, Res#{api_secret => ApiSecret}};
+        Error ->
+            Error
     end.
 
 create_app(App = #?APP{api_key = ApiKey, name = Name}) ->

+ 9 - 6
apps/emqx_management/test/emqx_mgmt_api_api_keys_SUITE.erl

@@ -225,21 +225,23 @@ t_create_unexpired_app(_Config) ->
     ok.
 
 list_app() ->
+    AuthHeader = emqx_dashboard_SUITE:auth_header_(),
     Path = emqx_mgmt_api_test_util:api_path(["api_key"]),
-    case emqx_mgmt_api_test_util:request_api(get, Path) of
+    case emqx_mgmt_api_test_util:request_api(get, Path, AuthHeader) of
         {ok, Apps} -> {ok, emqx_json:decode(Apps, [return_maps])};
         Error -> Error
     end.
 
 read_app(Name) ->
+    AuthHeader = emqx_dashboard_SUITE:auth_header_(),
     Path = emqx_mgmt_api_test_util:api_path(["api_key", Name]),
-    case emqx_mgmt_api_test_util:request_api(get, Path) of
+    case emqx_mgmt_api_test_util:request_api(get, Path, AuthHeader) of
         {ok, Res} -> {ok, emqx_json:decode(Res, [return_maps])};
         Error -> Error
     end.
 
 create_app(Name) ->
-    AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
+    AuthHeader = emqx_dashboard_SUITE:auth_header_(),
     Path = emqx_mgmt_api_test_util:api_path(["api_key"]),
     ExpiredAt = to_rfc3339(erlang:system_time(second) + 1000),
     App = #{
@@ -254,7 +256,7 @@ create_app(Name) ->
     end.
 
 create_unexpired_app(Name, Params) ->
-    AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
+    AuthHeader = emqx_dashboard_SUITE:auth_header_(),
     Path = emqx_mgmt_api_test_util:api_path(["api_key"]),
     App = maps:merge(#{name => Name, desc => <<"Note"/utf8>>, enable => true}, Params),
     case emqx_mgmt_api_test_util:request_api(post, Path, "", AuthHeader, App) of
@@ -263,11 +265,12 @@ create_unexpired_app(Name, Params) ->
     end.
 
 delete_app(Name) ->
+    AuthHeader = emqx_dashboard_SUITE:auth_header_(),
     DeletePath = emqx_mgmt_api_test_util:api_path(["api_key", Name]),
-    emqx_mgmt_api_test_util:request_api(delete, DeletePath).
+    emqx_mgmt_api_test_util:request_api(delete, DeletePath, AuthHeader).
 
 update_app(Name, Change) ->
-    AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
+    AuthHeader = emqx_dashboard_SUITE:auth_header_(),
     UpdatePath = emqx_mgmt_api_test_util:api_path(["api_key", Name]),
     case emqx_mgmt_api_test_util:request_api(put, UpdatePath, "", AuthHeader, Change) of
         {ok, Update} -> {ok, emqx_json:decode(Update, [return_maps])};

+ 34 - 7
apps/emqx_management/test/emqx_mgmt_api_test_util.erl

@@ -24,14 +24,19 @@ init_suite() ->
     init_suite([]).
 
 init_suite(Apps) ->
+    init_suite(Apps, fun set_special_configs/1).
+
+init_suite(Apps, SetConfigs) ->
     mria:start(),
     application:load(emqx_management),
-    emqx_common_test_helpers:start_apps(Apps ++ [emqx_dashboard], fun set_special_configs/1).
+    emqx_common_test_helpers:start_apps(Apps ++ [emqx_dashboard], SetConfigs),
+    emqx_common_test_http:create_default_app().
 
 end_suite() ->
     end_suite([]).
 
 end_suite(Apps) ->
+    emqx_common_test_http:delete_default_app(),
     application:unload(emqx_management),
     emqx_common_test_helpers:stop_apps(Apps ++ [emqx_dashboard]),
     emqx_config:delete_override_conf_files(),
@@ -43,8 +48,23 @@ set_special_configs(emqx_dashboard) ->
 set_special_configs(_App) ->
     ok.
 
+%% there is no difference between the 'request' and 'request_api'
+%% the 'request' is only to be compatible with the 'emqx_dashboard_api_test_helpers:request'
+request(Method, Url) ->
+    request(Method, Url, []).
+
+request(Method, Url, Body) ->
+    request_api_with_body(Method, Url, Body).
+
+uri(Parts) ->
+    emqx_dashboard_api_test_helpers:uri(Parts).
+
+%% compatible_mode will return as same as 'emqx_dashboard_api_test_helpers:request'
+request_api_with_body(Method, Url, Body) ->
+    request_api(Method, Url, [], auth_header_(), Body, #{compatible_mode => true}).
+
 request_api(Method, Url) ->
-    request_api(Method, Url, [], [], [], #{}).
+    request_api(Method, Url, auth_header_()).
 
 request_api(Method, Url, AuthOrHeaders) ->
     request_api(Method, Url, [], AuthOrHeaders, [], #{}).
@@ -90,10 +110,20 @@ request_api(Method, Url, QueryParams, AuthOrHeaders, Body, Opts) when
 
 do_request_api(Method, Request, Opts) ->
     ReturnAll = maps:get(return_all, Opts, false),
+    CompatibleMode = maps:get(compatible_mode, Opts, false),
+    ReqOpts =
+        case CompatibleMode of
+            true ->
+                [{body_format, binary}];
+            _ ->
+                []
+        end,
     ct:pal("Method: ~p, Request: ~p", [Method, Request]),
-    case httpc:request(Method, Request, [], []) of
+    case httpc:request(Method, Request, [], ReqOpts) of
         {error, socket_closed_remotely} ->
             {error, socket_closed_remotely};
+        {ok, {{_, Code, _}, _Headers, Body}} when CompatibleMode ->
+            {ok, Code, Body};
         {ok, {{"HTTP/1.1", Code, _} = Reason, Headers, Body}} when
             Code >= 200 andalso Code =< 299 andalso ReturnAll
         ->
@@ -109,10 +139,7 @@ do_request_api(Method, Request, Opts) ->
     end.
 
 auth_header_() ->
-    Username = <<"admin">>,
-    Password = <<"public">>,
-    {ok, Token} = emqx_dashboard_admin:sign_token(Username, Password),
-    {"Authorization", "Bearer " ++ binary_to_list(Token)}.
+    emqx_common_test_http:default_auth_header().
 
 build_http_header(X) when is_list(X) ->
     X;

+ 2 - 7
apps/emqx_management/test/emqx_mgmt_api_trace_SUITE.erl

@@ -30,6 +30,8 @@
 -define(API_VERSION, "v5").
 -define(BASE_PATH, "api").
 
+-import(emqx_dashboard_SUITE, [auth_header_/0]).
+
 %%--------------------------------------------------------------------
 %% Setups
 %%--------------------------------------------------------------------
@@ -330,13 +332,6 @@ t_stream_log(_Config) ->
 to_rfc3339(Second) ->
     list_to_binary(calendar:system_time_to_rfc3339(Second)).
 
-auth_header_() ->
-    auth_header_("admin", "public").
-
-auth_header_(User, Pass) ->
-    Encoded = base64:encode_to_string(lists:append([User, ":", Pass])),
-    {"Authorization", "Basic " ++ Encoded}.
-
 request_api(Method, Url, Auth) -> do_request_api(Method, {Url, [Auth]}).
 
 request_api(Method, Url, Auth, Body) ->

+ 4 - 10
apps/emqx_modules/test/emqx_delayed_api_SUITE.erl

@@ -26,7 +26,7 @@
     <<"max_delayed_messages">> => <<"0">>
 }).
 
--import(emqx_dashboard_api_test_helpers, [request/2, request/3, uri/1]).
+-import(emqx_mgmt_api_test_util, [request/2, request/3, uri/1]).
 
 all() ->
     emqx_common_test_helpers:all(?MODULE).
@@ -36,27 +36,21 @@ init_per_suite(Config) ->
         raw_with_default => true
     }),
 
-    ok = emqx_common_test_helpers:start_apps(
-        [emqx_conf, emqx_modules, emqx_dashboard],
-        fun set_special_configs/1
+    ok = emqx_mgmt_api_test_util:init_suite(
+        [emqx_conf, emqx_modules]
     ),
     emqx_delayed:load(),
     Config.
 
 end_per_suite(Config) ->
     ok = emqx_delayed:unload(),
-    emqx_common_test_helpers:stop_apps([emqx_conf, emqx_dashboard, emqx_modules]),
+    emqx_mgmt_api_test_util:end_suite([emqx_conf, emqx_modules]),
     Config.
 
 init_per_testcase(_, Config) ->
     {ok, _} = emqx_cluster_rpc:start_link(),
     Config.
 
-set_special_configs(emqx_dashboard) ->
-    emqx_dashboard_api_test_helpers:set_default_config();
-set_special_configs(_App) ->
-    ok.
-
 %%------------------------------------------------------------------------------
 %% Test Cases
 %%------------------------------------------------------------------------------

+ 5 - 11
apps/emqx_modules/test/emqx_rewrite_api_SUITE.erl

@@ -18,7 +18,7 @@
 -compile(nowarn_export_all).
 -compile(export_all).
 
--import(emqx_dashboard_api_test_helpers, [request/3, uri/1]).
+-import(emqx_mgmt_api_test_util, [request/3, uri/1]).
 
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
@@ -37,20 +37,14 @@ init_per_suite(Config) ->
         raw_with_default => true
     }),
 
-    ok = emqx_common_test_helpers:start_apps(
-        [emqx_conf, emqx_modules, emqx_dashboard],
-        fun set_special_configs/1
+    ok = emqx_mgmt_api_test_util:init_suite(
+        [emqx_conf, emqx_modules]
     ),
 
     Config.
 
 end_per_suite(_Config) ->
-    emqx_common_test_helpers:stop_apps([emqx_conf, emqx_dashboard, emqx_modules]),
-    ok.
-
-set_special_configs(emqx_dashboard) ->
-    emqx_dashboard_api_test_helpers:set_default_config();
-set_special_configs(_App) ->
+    emqx_mgmt_api_test_util:end_suite([emqx_conf, emqx_modules]),
     ok.
 
 %%------------------------------------------------------------------------------
@@ -81,7 +75,7 @@ t_mqtt_topic_rewrite(_) ->
 
     ?assertEqual(
         Rules,
-        jsx:decode(Result)
+        emqx_json:decode(Result, [return_maps])
     ).
 
 t_mqtt_topic_rewrite_limit(_) ->

+ 5 - 5
apps/emqx_modules/test/emqx_telemetry_api_SUITE.erl

@@ -18,7 +18,7 @@
 -compile(nowarn_export_all).
 -compile(export_all).
 
--import(emqx_dashboard_api_test_helpers, [request/2, request/3, uri/1]).
+-import(emqx_mgmt_api_test_util, [request/2, request/3, uri/1]).
 
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
@@ -33,8 +33,8 @@ init_per_suite(Config) ->
         raw_with_default => true
     }),
 
-    ok = emqx_common_test_helpers:start_apps(
-        [emqx_conf, emqx_authn, emqx_authz, emqx_modules, emqx_dashboard],
+    ok = emqx_mgmt_api_test_util:init_suite(
+        [emqx_conf, emqx_authn, emqx_authz, emqx_modules],
         fun set_special_configs/1
     ),
 
@@ -49,8 +49,8 @@ end_per_suite(_Config) ->
             <<"sources">> => []
         }
     ),
-    emqx_common_test_helpers:stop_apps([
-        emqx_dashboard, emqx_conf, emqx_authn, emqx_authz, emqx_modules
+    emqx_mgmt_api_test_util:end_suite([
+        emqx_conf, emqx_authn, emqx_authz, emqx_modules
     ]),
     ok.
 

+ 4 - 13
apps/emqx_modules/test/emqx_topic_metrics_api_SUITE.erl

@@ -18,7 +18,7 @@
 -compile(nowarn_export_all).
 -compile(export_all).
 
--import(emqx_dashboard_api_test_helpers, [request/3, uri/1]).
+-import(emqx_mgmt_api_test_util, [request/2, request/3, uri/1]).
 
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
@@ -44,9 +44,8 @@ init_per_suite(Config) ->
         raw_with_default => true
     }),
 
-    ok = emqx_common_test_helpers:start_apps(
-        [emqx_conf, emqx_modules, emqx_dashboard],
-        fun set_special_configs/1
+    ok = emqx_mgmt_api_test_util:init_suite(
+        [emqx_conf, emqx_modules]
     ),
 
     %% When many tests run in an obscure order, it may occur that
@@ -59,15 +58,10 @@ init_per_suite(Config) ->
     Config.
 
 end_per_suite(_Config) ->
-    emqx_common_test_helpers:stop_apps([emqx_conf, emqx_dashboard, emqx_modules]),
+    emqx_mgmt_api_test_util:end_suite([emqx_conf, emqx_modules]),
     application:stop(gen_rpc),
     ok.
 
-set_special_configs(emqx_dashboard) ->
-    emqx_dashboard_api_test_helpers:set_default_config();
-set_special_configs(_App) ->
-    ok.
-
 %%------------------------------------------------------------------------------
 %% Tests
 %%------------------------------------------------------------------------------
@@ -315,6 +309,3 @@ t_badrpc(_) ->
 %%------------------------------------------------------------------------------
 %% Helpers
 %%------------------------------------------------------------------------------
-
-request(Method, Url) ->
-    request(Method, Url, []).

+ 1 - 7
apps/emqx_slow_subs/test/emqx_slow_subs_api_SUITE.erl

@@ -203,13 +203,7 @@ do_request_api(Method, Request) ->
     end.
 
 auth_header_() ->
-    AppId = <<"admin">>,
-    AppSecret = <<"public">>,
-    auth_header_(binary_to_list(AppId), binary_to_list(AppSecret)).
-
-auth_header_(User, Pass) ->
-    Encoded = base64:encode_to_string(lists:append([User, ":", Pass])),
-    {"Authorization", "Basic " ++ Encoded}.
+    emqx_mgmt_api_test_util:auth_header_().
 
 api_path(Parts) ->
     ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION] ++ Parts).

+ 1 - 0
changes/v5.0.15/feat-9586.en.md

@@ -0,0 +1 @@
+Basic auth is no longer allowed for API calls, must use API key instead.

+ 1 - 0
changes/v5.0.15/feat-9586.zh.md

@@ -0,0 +1 @@
+API 调用不再支持基于 `username:password` 的 `baisc` 认证, 现在 API 必须通过 API Key 才能进行调用。