From 4746204f6f0ff5ed0d0dd1350180b64b189c1580 Mon Sep 17 00:00:00 2001 From: JimMoen Date: Tue, 29 Mar 2022 18:09:50 +0800 Subject: [PATCH 1/9] fix(authz): rm authz source update dry_run --- apps/emqx_authz/src/emqx_authz.erl | 29 ------------------- .../emqx_authz/src/emqx_authz_api_sources.erl | 10 +------ apps/emqx_authz/src/emqx_authz_file.erl | 7 ----- apps/emqx_authz/src/emqx_authz_http.erl | 4 --- apps/emqx_authz/src/emqx_authz_mnesia.erl | 3 -- apps/emqx_authz/src/emqx_authz_mongodb.erl | 4 --- apps/emqx_authz/src/emqx_authz_mysql.erl | 4 --- apps/emqx_authz/src/emqx_authz_postgresql.erl | 4 --- apps/emqx_authz/src/emqx_authz_redis.erl | 4 --- apps/emqx_authz/test/emqx_authz_SUITE.erl | 1 - .../test/emqx_authz_api_sources_SUITE.erl | 9 ++---- .../emqx_authz/test/emqx_authz_http_SUITE.erl | 14 --------- 12 files changed, 3 insertions(+), 90 deletions(-) diff --git a/apps/emqx_authz/src/emqx_authz.erl b/apps/emqx_authz/src/emqx_authz.erl index 916b08632..3e5a53649 100644 --- a/apps/emqx_authz/src/emqx_authz.erl +++ b/apps/emqx_authz/src/emqx_authz.erl @@ -70,12 +70,6 @@ %% An authz backend will not be used after `destroy`. -callback(destroy(source()) -> ok). -%% Check if a configuration map is valid for further -%% authz backend initialization. -%% The callback must deallocate all resources allocated -%% during verification. --callback(dry_run(source()) -> ok | {error, term()}). - %% Authorize client action. -callback(authorize( emqx_types:clientinfo(), @@ -140,18 +134,6 @@ do_pre_config_update({?CMD_APPEND, Source}, Sources) -> NSources = Sources ++ [NSource], ok = check_dup_types(NSources), NSources; -do_pre_config_update({{?CMD_REPLACE, Type}, #{<<"enable">> := Enable} = Source}, Sources) - when ?IS_ENABLED(Enable) -> - NSource = maybe_write_files(Source), - {_Old, Front, Rear} = take(Type, Sources), - case create_dry_run(Type, NSource) of - ok -> - NSources = Front ++ [NSource | Rear], - ok = check_dup_types(NSources), - NSources; - {error, _} = Error -> - throw(Error) - end; do_pre_config_update({{?CMD_REPLACE, Type}, Source}, Sources) -> NSource = maybe_write_files(Source), {_Old, Front, Rear} = take(Type, Sources), @@ -250,11 +232,6 @@ check_dup_types([Source | Sources], Checked) -> check_dup_types(Sources, [Type | Checked]) end. -create_dry_run(Type, Source) -> - [CheckedSource] = check_sources([Source]), - Module = authz_module(Type), - Module:dry_run(CheckedSource). - init_sources(Sources) -> {_Enabled, Disabled} = lists:partition(fun(#{enable := Enable}) -> Enable end, Sources), case Disabled =/= [] of @@ -329,12 +306,6 @@ do_authorize(Client, PubSub, Topic, %% Internal function %%-------------------------------------------------------------------- -check_sources(RawSources) -> - Schema = #{roots => emqx_authz_schema:fields("authorization"), fields => #{}}, - Conf = #{<<"sources">> => RawSources}, - #{sources := Sources} = hocon_tconf:check_plain(Schema, Conf, #{atom_key => true}), - Sources. - take(Type) -> take(Type, lookup()). %% Take the source of give type, the sources list is split into two parts diff --git a/apps/emqx_authz/src/emqx_authz_api_sources.erl b/apps/emqx_authz/src/emqx_authz_api_sources.erl index 5a62e11e1..22a4f0f86 100644 --- a/apps/emqx_authz/src/emqx_authz_api_sources.erl +++ b/apps/emqx_authz/src/emqx_authz_api_sources.erl @@ -194,15 +194,7 @@ source(get, #{bindings := #{type := Type}}) -> source(put, #{bindings := #{type := <<"file">>}, body := #{<<"type">> := <<"file">>} = Body}) -> update_authz_file(Body); source(put, #{bindings := #{type := Type}, body := Body}) -> - case emqx_authz:update({?CMD_REPLACE, Type}, Body) of - {ok, _} -> {204}; - {error, {emqx_conf_schema, _}} -> - {400, #{code => <<"BAD_REQUEST">>, - message => <<"BAD_SCHEMA">>}}; - {error, Reason} -> - {400, #{code => <<"BAD_REQUEST">>, - message => bin(Reason)}} - end; + update_config({?CMD_REPLACE, Type}, Body); source(delete, #{bindings := #{type := Type}}) -> update_config({?CMD_DELETE, Type}, #{}). diff --git a/apps/emqx_authz/src/emqx_authz_file.erl b/apps/emqx_authz/src/emqx_authz_file.erl index b9a4284e5..729646714 100644 --- a/apps/emqx_authz/src/emqx_authz_file.erl +++ b/apps/emqx_authz/src/emqx_authz_file.erl @@ -30,7 +30,6 @@ -export([ description/0 , init/1 , destroy/1 - , dry_run/1 , authorize/4 ]). @@ -54,11 +53,5 @@ init(#{path := Path} = Source) -> destroy(_Source) -> ok. -dry_run(#{path := Path}) -> - case file:consult(Path) of - {ok, _} -> ok; - {error, _} = Error -> Error - end. - authorize(Client, PubSub, Topic, #{annotations := #{rules := Rules}}) -> emqx_authz_rule:matches(Client, PubSub, Topic, Rules). diff --git a/apps/emqx_authz/src/emqx_authz_http.erl b/apps/emqx_authz/src/emqx_authz_http.erl index 3c11660d7..592c59b03 100644 --- a/apps/emqx_authz/src/emqx_authz_http.erl +++ b/apps/emqx_authz/src/emqx_authz_http.erl @@ -27,7 +27,6 @@ -export([ description/0 , init/1 , destroy/1 - , dry_run/1 , authorize/4 , parse_url/1 ]). @@ -58,9 +57,6 @@ init(Config) -> destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -dry_run(Config) -> - emqx_resource:create_dry_run_local(emqx_connector_http, parse_config(Config)). - authorize( Client , PubSub , Topic diff --git a/apps/emqx_authz/src/emqx_authz_mnesia.erl b/apps/emqx_authz/src/emqx_authz_mnesia.erl index 9f4e0ffeb..f02419b0c 100644 --- a/apps/emqx_authz/src/emqx_authz_mnesia.erl +++ b/apps/emqx_authz/src/emqx_authz_mnesia.erl @@ -47,7 +47,6 @@ -export([ description/0 , init/1 , destroy/1 - , dry_run/1 , authorize/4 ]). @@ -90,8 +89,6 @@ init(Source) -> Source. destroy(_Source) -> ok. -dry_run(_Source) -> ok. - authorize(#{username := Username, clientid := Clientid } = Client, PubSub, Topic, #{type := 'built_in_database'}) -> diff --git a/apps/emqx_authz/src/emqx_authz_mongodb.erl b/apps/emqx_authz/src/emqx_authz_mongodb.erl index 61c23f3a9..158bff152 100644 --- a/apps/emqx_authz/src/emqx_authz_mongodb.erl +++ b/apps/emqx_authz/src/emqx_authz_mongodb.erl @@ -27,7 +27,6 @@ -export([ description/0 , init/1 , destroy/1 - , dry_run/1 , authorize/4 ]). @@ -52,9 +51,6 @@ init(#{selector := Selector} = Source) -> ?PLACEHOLDERS)} end. -dry_run(Source) -> - emqx_resource:create_dry_run_local(emqx_connector_mongo, Source). - destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). diff --git a/apps/emqx_authz/src/emqx_authz_mysql.erl b/apps/emqx_authz/src/emqx_authz_mysql.erl index c864d8ecc..31e8dcd98 100644 --- a/apps/emqx_authz/src/emqx_authz_mysql.erl +++ b/apps/emqx_authz/src/emqx_authz_mysql.erl @@ -27,7 +27,6 @@ -export([ description/0 , init/1 , destroy/1 - , dry_run/1 , authorize/4 ]). @@ -56,9 +55,6 @@ init(#{query := SQL} = Source) -> ?PLACEHOLDERS)}} end. -dry_run(Source) -> - emqx_resource:create_dry_run_local(emqx_connector_mysql, Source). - destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). diff --git a/apps/emqx_authz/src/emqx_authz_postgresql.erl b/apps/emqx_authz/src/emqx_authz_postgresql.erl index 9a783ddcf..275b39f2b 100644 --- a/apps/emqx_authz/src/emqx_authz_postgresql.erl +++ b/apps/emqx_authz/src/emqx_authz_postgresql.erl @@ -27,7 +27,6 @@ -export([ description/0 , init/1 , destroy/1 - , dry_run/1 , authorize/4 ]). @@ -68,9 +67,6 @@ init(#{query := SQL0} = Source) -> destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -dry_run(Source) -> - emqx_resource:create_dry_run_local(emqx_connector_pgsql, Source). - authorize(Client, PubSub, Topic, #{annotations := #{id := ResourceID, placeholders := Placeholders diff --git a/apps/emqx_authz/src/emqx_authz_redis.erl b/apps/emqx_authz/src/emqx_authz_redis.erl index 4e67746ec..7e9ea8325 100644 --- a/apps/emqx_authz/src/emqx_authz_redis.erl +++ b/apps/emqx_authz/src/emqx_authz_redis.erl @@ -27,7 +27,6 @@ -export([ description/0 , init/1 , destroy/1 - , dry_run/1 , authorize/4 ]). @@ -57,9 +56,6 @@ init(#{cmd := CmdStr} = Source) -> destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -dry_run(Source) -> - emqx_resource:create_dry_run_local(emqx_connector_redis, Source). - authorize(Client, PubSub, Topic, #{cmd_template := CmdTemplate, annotations := #{id := ResourceID} diff --git a/apps/emqx_authz/test/emqx_authz_SUITE.erl b/apps/emqx_authz/test/emqx_authz_SUITE.erl index 605a8782b..2d2648913 100644 --- a/apps/emqx_authz/test/emqx_authz_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_SUITE.erl @@ -33,7 +33,6 @@ init_per_suite(Config) -> meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_resource, create_local, fun(_, _, _, _) -> {ok, meck_data} end), meck:expect(emqx_resource, remove_local, fun(_) -> ok end), - meck:expect(emqx_resource, create_dry_run_local, fun(_, _) -> ok end), meck:expect(emqx_authz, acl_conf_file, fun() -> emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") diff --git a/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl b/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl index 72d43d23d..7e7d5db09 100644 --- a/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl @@ -101,11 +101,6 @@ init_per_suite(Config) -> ok = stop_apps([emqx_resource, emqx_connector]), meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_resource, create_local, fun(_, _, _, _) -> {ok, meck_data} end), - meck:expect(emqx_resource, create_dry_run_local, - fun(emqx_connector_mysql, _) -> ok; - (emqx_connector_mongo, _) -> ok; - (T, C) -> meck:passthrough([T, C]) - end), meck:expect(emqx_resource, health_check, fun(St) -> {ok, St} end), meck:expect(emqx_resource, remove_local, fun(_) -> ok end ), meck:expect(emqx_authz, acl_conf_file, @@ -294,11 +289,11 @@ t_api(_) -> uri(["authorization", "sources", "mysql"]), ?SOURCE3#{<<"server">> := <<"192.168.1.100:3306">>}), - {ok, 400, _} = request( + {ok, 204, _} = request( put, uri(["authorization", "sources", "postgresql"]), ?SOURCE4#{<<"server">> := <<"fake">>}), - {ok, 400, _} = request( + {ok, 204, _} = request( put, uri(["authorization", "sources", "redis"]), ?SOURCE5#{<<"servers">> := [<<"192.168.1.100:6379">>, diff --git a/apps/emqx_authz/test/emqx_authz_http_SUITE.erl b/apps/emqx_authz/test/emqx_authz_http_SUITE.erl index 42a265a9e..e6aeb703c 100644 --- a/apps/emqx_authz/test/emqx_authz_http_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_http_SUITE.erl @@ -294,20 +294,6 @@ t_create_replace(_Config) -> allow, emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), - %% Changing to other bad config does not work - BadConfig = maps:merge( - raw_http_authz_config(), - #{<<"url">> => - <<"http://127.0.0.1:33332/authz/users/?topic=${topic}&action=${action}">>}), - - ?assertMatch( - {error, _}, - emqx_authz:update({?CMD_REPLACE, http}, BadConfig)), - - ?assertEqual( - allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), - %% Changing to valid config OkConfig = maps:merge( raw_http_authz_config(), From 877db375edca65a65f9857a83a5600982079e005 Mon Sep 17 00:00:00 2001 From: firest Date: Thu, 31 Mar 2022 15:29:52 +0800 Subject: [PATCH 2/9] test(exhook): add test case for exhook metrics --- apps/emqx_exhook/src/emqx_exhook.erl | 4 +- apps/emqx_exhook/src/emqx_exhook_metrics.erl | 8 +- apps/emqx_exhook/src/emqx_exhook_server.erl | 18 +- apps/emqx_exhook/test/emqx_exhook_SUITE.erl | 5 +- .../test/emqx_exhook_metrics_SUITE.erl | 220 ++++++++++++++++++ 5 files changed, 245 insertions(+), 10 deletions(-) create mode 100644 apps/emqx_exhook/test/emqx_exhook_metrics_SUITE.erl diff --git a/apps/emqx_exhook/src/emqx_exhook.erl b/apps/emqx_exhook/src/emqx_exhook.erl index 10d71b5f2..1539dfdf1 100644 --- a/apps/emqx_exhook/src/emqx_exhook.erl +++ b/apps/emqx_exhook/src/emqx_exhook.erl @@ -33,7 +33,7 @@ cast(Hookpoint, Req) -> cast(_, _, []) -> ok; -cast(Hookpoint, Req, [ServerName|More]) -> +cast(Hookpoint, Req, [ServerName | More]) -> %% XXX: Need a real asynchronous running _ = emqx_exhook_server:call(Hookpoint, Req, emqx_exhook_mgr:server(ServerName)), @@ -51,7 +51,7 @@ call_fold(Hookpoint, Req, AccFun) -> call_fold(_, Req, _, []) -> {ok, Req}; -call_fold(Hookpoint, Req, AccFun, [ServerName|More]) -> +call_fold(Hookpoint, Req, AccFun, [ServerName | More]) -> Server = emqx_exhook_mgr:server(ServerName), case emqx_exhook_server:call(Hookpoint, Req, Server) of {ok, Resp} -> diff --git a/apps/emqx_exhook/src/emqx_exhook_metrics.erl b/apps/emqx_exhook/src/emqx_exhook_metrics.erl index ae161fc4b..f59ad4f65 100644 --- a/apps/emqx_exhook/src/emqx_exhook_metrics.erl +++ b/apps/emqx_exhook/src/emqx_exhook_metrics.erl @@ -72,16 +72,16 @@ new_metric_info() -> succeed(Server, Hook) -> inc(Server, Hook, #metrics.succeed, #metrics{ index = {Server, Hook} - , window_rate = 1 - , succeed = 1 + , window_rate = 0 + , succeed = 0 }). -spec failed(server_name(), hookpoint()) -> ok. failed(Server, Hook) -> inc(Server, Hook, #metrics.failed, #metrics{ index = {Server, Hook} - , window_rate = 1 - , failed = 1 + , window_rate = 0 + , failed = 0 }). -spec update(pos_integer()) -> true. diff --git a/apps/emqx_exhook/src/emqx_exhook_server.erl b/apps/emqx_exhook/src/emqx_exhook_server.erl index d9e75693b..60c6f7ca9 100644 --- a/apps/emqx_exhook/src/emqx_exhook_server.erl +++ b/apps/emqx_exhook/src/emqx_exhook_server.erl @@ -36,6 +36,9 @@ , failed_action/1 ]). +-ifdef(TEST). +-export([hk2func/1]). +-endif. -type server() :: #{%% Server name (equal to grpc client channel name) name := binary(), @@ -105,7 +108,8 @@ load(Name, #{request_timeout := Timeout, failed_action := FailedAction} = Opts) hookspec => HookSpecs, prefix => Prefix }}; {error, _} = E -> - emqx_exhook_sup:stop_grpc_client_channel(Name), E + emqx_exhook_sup:stop_grpc_client_channel(Name), + E end; {error, _} = E -> E end. @@ -287,12 +291,20 @@ match_topic_filter(_, []) -> match_topic_filter(TopicName, TopicFilter) -> lists:any(fun(F) -> emqx_topic:match(TopicName, F) end, TopicFilter). +-ifdef(TEST). +-define(CALL_PB_CLIENT(ChanneName, Fun, Req, Options), + apply(?PB_CLIENT_MOD, Fun, [Req, #{<<"channel">> => ChannName}, Options])). +-else. +-define(CALL_PB_CLIENT(ChanneName, Fun, Req, Options), + apply(?PB_CLIENT_MOD, Fun, [Req, Options])). +-endif. + -spec do_call(binary(), atom(), atom(), map(), map()) -> {ok, map()} | {error, term()}. do_call(ChannName, Hookpoint, Fun, Req, ReqOpts) -> Options = ReqOpts#{channel => ChannName}, ?SLOG(debug, #{msg => "do_call", module => ?PB_CLIENT_MOD, function => Fun, - req => Req, options => Options}), - case catch apply(?PB_CLIENT_MOD, Fun, [Req, Options]) of + req => Req, options => Options}), + case catch ?CALL_PB_CLIENT(ChanneName, Fun, Req, Options) of {ok, Resp, Metadata} -> ?SLOG(debug, #{msg => "do_call_ok", resp => Resp, metadata => Metadata}), update_metrics(Hookpoint, ChannName, fun emqx_exhook_metrics:succeed/2), diff --git a/apps/emqx_exhook/test/emqx_exhook_SUITE.erl b/apps/emqx_exhook/test/emqx_exhook_SUITE.erl index ed3e529f6..543f0b9fc 100644 --- a/apps/emqx_exhook/test/emqx_exhook_SUITE.erl +++ b/apps/emqx_exhook/test/emqx_exhook_SUITE.erl @@ -59,7 +59,7 @@ init_per_suite(Cfg) -> meck:expect(emqx_alarm, deactivate, 3, ok), _ = emqx_exhook_demo_svr:start(), - ok = emqx_common_test_helpers:load_config(emqx_exhook_schema, ?CONF_DEFAULT), + load_cfg(?CONF_DEFAULT), emqx_common_test_helpers:start_apps([emqx_exhook]), Cfg. @@ -86,6 +86,9 @@ end_per_testcase(_, Config) -> end, Config. +load_cfg(Cfg) -> + ok = emqx_common_test_helpers:load_config(emqx_exhook_schema, Cfg). + %%-------------------------------------------------------------------- %% Test cases %%-------------------------------------------------------------------- diff --git a/apps/emqx_exhook/test/emqx_exhook_metrics_SUITE.erl b/apps/emqx_exhook/test/emqx_exhook_metrics_SUITE.erl new file mode 100644 index 000000000..d26f37b4b --- /dev/null +++ b/apps/emqx_exhook/test/emqx_exhook_metrics_SUITE.erl @@ -0,0 +1,220 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_exhook_metrics_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). +-include_lib("emqx_exhook/include/emqx_exhook.hrl"). +-define(SvrFun(SvrName, FuncName), {SvrName, FuncName}). + +-define(TARGET_HOOK, 'message.publish'). + +-define(CONF, <<" +exhook { + servers = [ + { name = succed, + url = \"http://127.0.0.1:9000\" + }, + { name = failed, + failed_action = ignore, + url = \"http://127.0.0.1:9001\" + }, + ] +} +">>). + +%%-------------------------------------------------------------------- +%% Setups +%%-------------------------------------------------------------------- + +all() -> emqx_common_test_helpers:all(?MODULE). + +init_per_suite(Cfg) -> + application:load(emqx_conf), + meck:new(emqx_exhook_mgr, [non_strict, passthrough, no_link]), + meck:new(emqx_exhook_demo_svr, [non_strict, passthrough, no_link]), + meck:expect(emqx_exhook_mgr, refresh_tick, fun() -> ok end), + init_injections(hook_injects()), + + emqx_exhook_SUITE:load_cfg(?CONF), + _ = emqx_exhook_demo_svr:start(), + _ = emqx_exhook_demo_svr:start(failed, 9001), + emqx_common_test_helpers:start_apps([emqx_exhook]), + Cfg. + +end_per_suite(Cfg) -> + meck:unload(emqx_exhook_demo_svr), + meck:unload(emqx_exhook_mgr), + emqx_exhook_demo_svr:stop(), + emqx_exhook_demo_svr:stop(failed), + emqx_common_test_helpers:stop_apps([emqx_exhook]). + +init_per_testcase(_, Config) -> + clear_metrics(), + Config. + +end_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test cases +%%-------------------------------------------------------------------- +t_servers_metrics(_Cfg) -> + Test = fun(C) -> + Repeat = fun() -> + emqtt:publish(C, <<"/exhook/metrics">>, <<>>, qos0) + end, + repeat(Repeat, 10) + end, + with_connection(Test), + + timer:sleep(200), + SM = emqx_exhook_metrics:server_metrics(<<"succed">>), + ?assertMatch(#{failed := 0, succeed := 10}, SM), + + FM = emqx_exhook_metrics:server_metrics(<<"failed">>), + ?assertMatch(#{failed := 10, succeed := 0}, FM), + + SvrsM = emqx_exhook_metrics:servers_metrics(), + ?assertEqual(SM, maps:get(<<"succed">>, SvrsM)), + ?assertEqual(FM, maps:get(<<"failed">>, SvrsM)), + ok. + +t_rate(_) -> + Test = fun(C) -> + Repeat = fun() -> + emqtt:publish(C, <<"/exhook/metrics">>, <<>>, qos0) + end, + + repeat(Repeat, 5), + timer:sleep(200), + emqx_exhook_metrics:update(timer:seconds(1)), + SM = emqx_exhook_metrics:server_metrics(<<"succed">>), + ?assertMatch(#{rate := 5, max_rate := 5}, SM), + + repeat(Repeat, 6), + timer:sleep(200), + emqx_exhook_metrics:update(timer:seconds(1)), + SM2 = emqx_exhook_metrics:server_metrics(<<"succed">>), + ?assertMatch(#{rate := 6, max_rate := 6}, SM2), + + repeat(Repeat, 3), + timer:sleep(200), + emqx_exhook_metrics:update(timer:seconds(1)), + SM3 = emqx_exhook_metrics:server_metrics(<<"succed">>), + ?assertMatch(#{rate := 3, max_rate := 6}, SM3) + end, + with_connection(Test), + ok. + +t_hooks_metrics(_) -> + Test = fun(C) -> + Repeat = fun() -> + emqtt:publish(C, <<"/exhook/metrics">>, <<>>, qos0) + end, + + repeat(Repeat, 5), + timer:sleep(200), + HM = emqx_exhook_metrics:hooks_metrics(<<"succed">>), + ?assertMatch(#{'message.publish' := + #{failed := 0, succeed := 5}}, HM) + end, + with_connection(Test), + ok. + +t_on_server_deleted(_) -> + + Test = fun(C) -> + Repeat = fun() -> + emqtt:publish(C, <<"/exhook/metrics">>, <<>>, qos0) + end, + repeat(Repeat, 10) + end, + with_connection(Test), + + timer:sleep(200), + SM = emqx_exhook_metrics:server_metrics(<<"succed">>), + ?assertMatch(#{failed := 0, succeed := 10}, SM), + + emqx_exhook_metrics:on_server_deleted(<<"succed">>), + SM2 = emqx_exhook_metrics:server_metrics(<<"succed">>), + ?assertMatch(#{failed := 0, succeed := 0}, SM2), + ok. + +%%-------------------------------------------------------------------- +%% Utils +%%-------------------------------------------------------------------- +clear_metrics() -> + ets:delete_all_objects(?HOOKS_METRICS). + +init_injections(Injects) -> + lists:map(fun({Name, _}) -> + Str = erlang:atom_to_list(Name), + case lists:prefix("on_", Str) of + true -> + Action = fun(Req, #{<<"channel">> := SvrName} = Md) -> + case maps:get(?SvrFun(SvrName, Name), Injects, undefined) of + undefined -> + meck:passthrough([Req, Md]); + Injection -> + Injection(Req, Md) + end + end, + + meck:expect(emqx_exhook_demo_svr, Name, Action); + _ -> + false + end + end, + emqx_exhook_demo_svr:module_info(exports)). + +hook_injects() -> + #{?SvrFun(<<"failed">>, emqx_exhook_server:hk2func(?TARGET_HOOK)) => + fun(_Req, _Md) -> + {error, "Error due to test"} + end, + ?SvrFun(<<"failed">>, on_provider_loaded) => + fun(_Req, Md) -> + {ok, #{hooks => [#{name => <<"message.publish">>}]}, Md} + end, + ?SvrFun(<<"succed">>, on_provider_loaded) => + fun(_Req, Md) -> + {ok, #{hooks => [#{name => <<"message.publish">>}]}, Md} + end + }. + +with_connection(Fun) -> + {ok, C} = emqtt:start_link([{host, "localhost"}, + {port, 1883}, + {username, <<"admin">>}, + {clientid, <<"exhook_tester">>}]), + {ok, _} = emqtt:connect(C), + try + Fun(C) + catch Type:Error:Trace -> + emqtt:stop(C), + erlang:raise(Type, Error, Trace) + end. + +repeat(_Fun, 0) -> + ok; +repeat(Fun, N) -> + Fun(), + repeat(Fun, N - 1). From 57cdf8697797362c82cd66942392f10efbc8cb17 Mon Sep 17 00:00:00 2001 From: firest Date: Thu, 31 Mar 2022 16:04:05 +0800 Subject: [PATCH 3/9] fix(exhook): fix elvis error --- apps/emqx_exhook/src/emqx_exhook_metrics.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/emqx_exhook/src/emqx_exhook_metrics.erl b/apps/emqx_exhook/src/emqx_exhook_metrics.erl index f59ad4f65..75dc04709 100644 --- a/apps/emqx_exhook/src/emqx_exhook_metrics.erl +++ b/apps/emqx_exhook/src/emqx_exhook_metrics.erl @@ -34,6 +34,7 @@ , window_rate :: integer() }). +-type metrics() :: #metrics{}. -type server_name() :: emqx_exhook_mgr:server_name(). -type hookpoint() :: emqx_exhook_server:hookpoint(). -type index() :: {server_name(), hookpoint()}. @@ -187,7 +188,7 @@ metrics_aggregate_by_key(Key, MetricsL) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- --spec inc(server_name(), hookpoint(), pos_integer(), #metrics{}) -> ok. +-spec inc(server_name(), hookpoint(), pos_integer(), metrics()) -> ok. inc(Server, Hook, Pos, Default) -> Index = {Server, Hook}, _ = ets:update_counter(?HOOKS_METRICS, From f34fce7c70361a0633c0b7b32426be027ec0d5b0 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Thu, 31 Mar 2022 14:29:42 +0300 Subject: [PATCH 4/9] chore(emqx_modules): improve emqx_topic_metrics coverage --- apps/emqx_modules/src/emqx_topic_metrics.erl | 6 +- .../test/emqx_topic_metrics_SUITE.erl | 114 ++++++++++++++---- 2 files changed, 94 insertions(+), 26 deletions(-) diff --git a/apps/emqx_modules/src/emqx_topic_metrics.erl b/apps/emqx_modules/src/emqx_topic_metrics.erl index 5aa04db07..65409fd61 100644 --- a/apps/emqx_modules/src/emqx_topic_metrics.erl +++ b/apps/emqx_modules/src/emqx_topic_metrics.erl @@ -21,6 +21,7 @@ -include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). -export([ on_message_publish/1, @@ -212,6 +213,7 @@ init([Opts]) -> error("max topic metrics quota exceeded") end end, + ?tp(debug, emqx_topic_metrics_started, #{}), {ok, #state{speeds = lists:foldl(Fun, #{}, Opts)}, hibernate}. handle_call({register, Topic}, _From, State = #state{speeds = Speeds}) -> @@ -262,7 +264,7 @@ handle_call({get_rates, Topic, Metric}, _From, State = #state{speeds = Speeds}) end. handle_cast(Msg, State) -> - ?SLOG(error, #{msg => "unexpected_cast", cast => Msg}), + ?tp(error, emqx_topic_metrics_unexpected_cast, #{cast => Msg}), {noreply, State}. handle_info(ticking, State = #state{speeds = Speeds}) -> @@ -278,7 +280,7 @@ handle_info(ticking, State = #state{speeds = Speeds}) -> erlang:send_after(timer:seconds(?TICKING_INTERVAL), self(), ticking), {noreply, State#state{speeds = NSpeeds}}; handle_info(Info, State) -> - ?SLOG(error, #{msg => "unexpected_info", info => Info}), + ?tp(error, emqx_topic_metrics_unexpected_info, #{info => Info}), {noreply, State}. terminate(_Reason, _State) -> diff --git a/apps/emqx_modules/test/emqx_topic_metrics_SUITE.erl b/apps/emqx_modules/test/emqx_topic_metrics_SUITE.erl index 4d8a340d2..87ec1382c 100644 --- a/apps/emqx_modules/test/emqx_topic_metrics_SUITE.erl +++ b/apps/emqx_modules/test/emqx_topic_metrics_SUITE.erl @@ -19,14 +19,10 @@ -compile(export_all). -compile(nowarn_export_all). --define(TOPIC, << - "" - "\n" - "topic_metrics: []" - "" ->>). +-define(TOPIC, #{<<"topic_metrics">> => []}). -include_lib("eunit/include/eunit.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). all() -> emqx_common_test_helpers:all(?MODULE). @@ -39,8 +35,17 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_common_test_helpers:stop_apps([emqx_modules]). -t_nonexistent_topic_metrics(_) -> +init_per_testcase(_Case, Config) -> emqx_topic_metrics:enable(), + emqx_topic_metrics:deregister_all(), + Config. + +end_per_testcase(_Case, _Config) -> + emqx_topic_metrics:deregister_all(), + emqx_config:put([topic_metrics], []), + emqx_topic_metrics:disable(). + +t_nonexistent_topic_metrics(_) -> ?assertEqual({error, topic_not_found}, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.in')), ?assertEqual({error, topic_not_found}, emqx_topic_metrics:inc(<<"a/b/c">>, 'messages.in')), ?assertEqual({error, topic_not_found}, emqx_topic_metrics:rate(<<"a/b/c">>, 'messages.in')), @@ -56,12 +61,9 @@ t_nonexistent_topic_metrics(_) -> %% emqx_topic_metrics:rates(<<"a/b/c">>, 'invalid.metrics') %% ), - emqx_topic_metrics:deregister(<<"a/b/c">>), - emqx_topic_metrics:disable(). + ok = emqx_topic_metrics:deregister(<<"a/b/c">>). t_topic_metrics(_) -> - emqx_topic_metrics:enable(), - ?assertEqual(false, emqx_topic_metrics:is_registered(<<"a/b/c">>)), ?assertEqual([], emqx_topic_metrics:all_registered_topics()), emqx_topic_metrics:register(<<"a/b/c">>), @@ -78,11 +80,9 @@ t_topic_metrics(_) -> %% #{long => 0, medium => 0, short => 0} %% ), - emqx_topic_metrics:deregister(<<"a/b/c">>), - emqx_topic_metrics:disable(). + ok = emqx_topic_metrics:deregister(<<"a/b/c">>). t_hook(_) -> - emqx_topic_metrics:enable(), emqx_topic_metrics:register(<<"a/b/c">>), ?assertEqual(0, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.in')), @@ -97,19 +97,85 @@ t_hook(_) -> {username, "myuser"} ]), {ok, _} = emqtt:connect(C), - emqtt:publish(C, <<"a/b/c">>, <<"Hello world">>, 0), + emqtt:publish(C, <<"a/b/c">>, <<"Hello world">>, [{qos, 0}]), + emqtt:publish(C, <<"a/b/c">>, <<"Hello world">>, [{qos, 1}]), + emqtt:publish(C, <<"a/b/c">>, <<"Hello world">>, [{qos, 2}]), ct:sleep(100), - ?assertEqual(1, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.in')), + ?assertEqual(3, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.in')), ?assertEqual(1, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.qos0.in')), - ?assertEqual(1, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.dropped')), + ?assertEqual(1, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.qos1.in')), + ?assertEqual(1, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.qos2.in')), + ?assertEqual(3, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.dropped')), - emqtt:subscribe(C, <<"a/b/c">>), - emqtt:publish(C, <<"a/b/c">>, <<"Hello world">>, 0), + emqtt:subscribe(C, <<"a/b/c">>, [{qos, 2}]), ct:sleep(100), - ?assertEqual(2, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.in')), + emqtt:publish(C, <<"a/b/c">>, <<"Hello world">>, [{qos, 0}]), + emqtt:publish(C, <<"a/b/c">>, <<"Hello world">>, [{qos, 1}]), + emqtt:publish(C, <<"a/b/c">>, <<"Hello world">>, [{qos, 2}]), + ct:sleep(100), + ?assertEqual(6, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.in')), ?assertEqual(2, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.qos0.in')), - ?assertEqual(1, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.out')), + ?assertEqual(2, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.qos1.in')), + ?assertEqual(2, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.qos2.in')), + ?assertEqual(3, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.out')), ?assertEqual(1, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.qos0.out')), - ?assertEqual(1, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.dropped')), - emqx_topic_metrics:deregister(<<"a/b/c">>), - emqx_topic_metrics:disable(). + ?assertEqual(1, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.qos1.out')), + ?assertEqual(1, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.qos2.out')), + ?assertEqual(3, emqx_topic_metrics:val(<<"a/b/c">>, 'messages.dropped')), + ok = emqx_topic_metrics:deregister(<<"a/b/c">>). + +t_topic_server_restart(_) -> + emqx_config:put([topic_metrics], [#{topic => <<"a/b/c">>}]), + ?check_trace( + begin + ?wait_async_action( + erlang:exit(whereis(emqx_topic_metrics), kill), + #{?snk_kind := emqx_topic_metrics_started}, + 500 + ) + end, + fun(Trace) -> + ?assertMatch( + [_ | _], + ?of_kind(emqx_topic_metrics_started, Trace) + ) + end + ), + + ?assertEqual( + [<<"a/b/c">>], + emqx_topic_metrics:all_registered_topics() + ). + +t_unknown_messages(_) -> + OldPid = whereis(emqx_topic_metrics), + ?check_trace( + begin + ?wait_async_action( + OldPid ! unknown, + #{?snk_kind := emqx_topic_metrics_unexpected_info}, + 500 + ), + ?wait_async_action( + gen_server:cast(OldPid, unknown), + #{?snk_kind := emqx_topic_metrics_unexpected_cast}, + 500 + ) + end, + fun(Trace) -> + ?assertMatch( + [_ | _], + ?of_kind(emqx_topic_metrics_unexpected_info, Trace) + ), + ?assertMatch( + [_ | _], + ?of_kind(emqx_topic_metrics_unexpected_cast, Trace) + ) + end + ), + + %% emqx_topic_metrics did not crash from unexpected calls + ?assertEqual( + OldPid, + whereis(emqx_topic_metrics) + ). From 12f6b8fab0056d5a3e45a4d307dac22e71776e62 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Thu, 31 Mar 2022 17:01:33 +0300 Subject: [PATCH 5/9] chore(emqx_modules): improve emqx_delayed_api coverage --- .../test/emqx_dashboard_api_test_helpers.erl | 4 + apps/emqx_modules/src/emqx_delayed_api.erl | 20 +-- .../test/emqx_delayed_api_SUITE.erl | 154 +++++++++++++----- 3 files changed, 130 insertions(+), 48 deletions(-) diff --git a/apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl b/apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl index 3bfe9829c..88cf45610 100644 --- a/apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl +++ b/apps/emqx_dashboard/test/emqx_dashboard_api_test_helpers.erl @@ -18,6 +18,7 @@ -export([set_default_config/0, set_default_config/1, + request/2, request/3, request/4, uri/0, @@ -39,6 +40,9 @@ set_default_config(DefaultUsername) -> emqx_config:put([dashboard], Config), ok. +request(Method, Url) -> + request(Method, Url, []). + request(Method, Url, Body) -> request(<<"admin">>, Method, Url, Body). diff --git a/apps/emqx_modules/src/emqx_delayed_api.erl b/apps/emqx_modules/src/emqx_delayed_api.erl index 57028c462..4029646af 100644 --- a/apps/emqx_modules/src/emqx_delayed_api.erl +++ b/apps/emqx_modules/src/emqx_delayed_api.erl @@ -24,7 +24,7 @@ -import(hoconsc, [mk/2, ref/1, ref/2]). -define(MAX_PAYLOAD_LENGTH, 2048). --define(PAYLOAD_TOO_LARGE, 'PAYLOAD_TOO_LARGE'). +-define(PAYLOAD_TOO_LARGE, <<"PAYLOAD_TOO_LARGE">>). -export([ status/2, @@ -49,8 +49,6 @@ -define(MESSAGE_ID_NOT_FOUND, 'MESSAGE_ID_NOT_FOUND'). -define(MESSAGE_ID_SCHEMA_ERROR, 'MESSAGE_ID_SCHEMA_ERROR'). -define(INVALID_NODE, 'INVALID_NODE'). -%% 1MB = 1024 x 1024 --define(MAX_PAYLOAD_SIZE, 1048576). api_spec() -> emqx_dashboard_swagger:spec(?MODULE). @@ -106,7 +104,7 @@ schema("/mqtt/delayed/messages/:node/:msgid") -> responses => #{ 200 => ref("message_without_payload"), 400 => emqx_dashboard_swagger:error_codes( - [?MESSAGE_ID_SCHEMA_ERROR], + [?MESSAGE_ID_SCHEMA_ERROR, ?INVALID_NODE], <<"Bad MsgId format">> ), 404 => emqx_dashboard_swagger:error_codes( @@ -129,7 +127,7 @@ schema("/mqtt/delayed/messages/:node/:msgid") -> responses => #{ 204 => <<"Delete delayed message success">>, 400 => emqx_dashboard_swagger:error_codes( - [?MESSAGE_ID_SCHEMA_ERROR], + [?MESSAGE_ID_SCHEMA_ERROR, ?INVALID_NODE], <<"Bad MsgId format">> ), 404 => emqx_dashboard_swagger:error_codes( @@ -175,7 +173,7 @@ fields("message_without_payload") -> ]; fields("message") -> PayloadDesc = io_lib:format( - "Payload, base64 encode. Payload will be ~p if length large than ~p", + "Payload, base64 encoded. Payload will be set to ~p if its length is larger than ~p", [?PAYLOAD_TOO_LARGE, ?MAX_PAYLOAD_LENGTH] ), fields("message_without_payload") ++ @@ -193,7 +191,7 @@ delayed_messages(get, #{query_string := Qs}) -> {200, emqx_delayed:cluster_list(Qs)}. delayed_message(get, #{bindings := #{node := NodeBin, msgid := HexId}}) -> - MaybeNode = make_maybe(NodeBin, invalid_node, fun erlang:binary_to_atom/1), + MaybeNode = make_maybe(NodeBin, invalid_node, fun erlang:binary_to_existing_atom/1), MaybeId = make_maybe(HexId, id_schema_error, fun emqx_guid:from_hexstr/1), with_maybe( [MaybeNode, MaybeId], @@ -201,14 +199,16 @@ delayed_message(get, #{bindings := #{node := NodeBin, msgid := HexId}}) -> case emqx_delayed:get_delayed_message(Node, Id) of {ok, Message} -> Payload = maps:get(payload, Message), - case erlang:byte_size(Payload) > ?MAX_PAYLOAD_SIZE of + case erlang:byte_size(Payload) > ?MAX_PAYLOAD_LENGTH of true -> - {200, Message}; + {200, Message#{payload => ?PAYLOAD_TOO_LARGE}}; _ -> {200, Message#{payload => base64:encode(Payload)}} end; {error, not_found} -> - {404, generate_http_code_map(not_found, Id)} + {404, generate_http_code_map(not_found, Id)}; + {badrpc, _} -> + {400, generate_http_code_map(invalid_node, Id)} end end ); diff --git a/apps/emqx_modules/test/emqx_delayed_api_SUITE.erl b/apps/emqx_modules/test/emqx_delayed_api_SUITE.erl index 98dd3fb25..f703dcf13 100644 --- a/apps/emqx_modules/test/emqx_delayed_api_SUITE.erl +++ b/apps/emqx_modules/test/emqx_delayed_api_SUITE.erl @@ -22,72 +22,75 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("emqx/include/emqx.hrl"). --define(CLUSTER_RPC_SHARD, emqx_cluster_rpc_shard). +-define(BASE_CONF, #{<<"dealyed">> => <<"true">>, + <<"max_delayed_messages">> => <<"0">> + }). --import(emqx_mgmt_api_test_util, [request_api/2, request_api/5, api_path/1, auth_header_/0]). +-import(emqx_dashboard_api_test_helpers, [request/2, request/3, uri/1]). all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - application:load(emqx_conf), - ok = ekka:start(), - ok = mria:start(), - ok = mria_rlog:wait_for_shards([?CLUSTER_RPC_SHARD], infinity), - emqx_config:put([dealyed], #{enable => true, max_delayed_messages => 10}), - meck:new(emqx_config, [non_strict, passthrough, no_history, no_link]), - meck:expect( - emqx_config, - get_schema_mod, - fun - (delayed) -> emqx_conf_schema; - (Any) -> meck:passthrough(Any) - end - ), + ok = emqx_common_test_helpers:load_config(emqx_modules_schema, ?BASE_CONF), - ok = emqx_delayed:mnesia(boot), - emqx_mgmt_api_test_util:init_suite([emqx_modules]), + ok = emqx_common_test_helpers:start_apps( + [emqx_conf, emqx_modules, emqx_dashboard], + fun set_special_configs/1 + ), emqx_delayed:enable(), Config. end_per_suite(Config) -> - ekka:stop(), - mria:stop(), - mria_mnesia:delete_schema(), - meck:unload(emqx_config), ok = emqx_delayed:disable(), - emqx_mgmt_api_test_util:end_suite([emqx_modules]), + emqx_common_test_helpers:stop_apps([emqx_conf, emqx_dashboard, 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 %%------------------------------------------------------------------------------ t_status(_Config) -> - Path = api_path(["mqtt", "delayed"]), - Auth = emqx_mgmt_api_test_util:auth_header_(), - {ok, R1} = request_api( + Path = uri(["mqtt", "delayed"]), + {ok, 200, R1} = request( put, Path, - "", - Auth, #{enable => false, max_delayed_messages => 10} ), ?assertMatch(#{enable := false, max_delayed_messages := 10}, decode_json(R1)), - {ok, R2} = request_api( + {ok, 200, R2} = request( put, Path, - "", - Auth, #{enable => true, max_delayed_messages => 12} ), ?assertMatch(#{enable := true, max_delayed_messages := 12}, decode_json(R2)), - {ok, ConfJson} = request_api(get, Path), + ?assertMatch( + {ok, 200, _}, + request( + put, + Path, + #{enable => true} + )), + + ?assertMatch( + {ok, 400, _}, + request( + put, + Path, + #{enable => true, max_delayed_messages => -5} + )), + + {ok, 200, ConfJson} = request(get, Path), ReturnConf = decode_json(ConfJson), ?assertMatch(#{enable := true, max_delayed_messages := 12}, ReturnConf). @@ -131,25 +134,100 @@ t_messages(_) -> ), MsgId = maps:get(msgid, First), - {ok, LookupMsg} = request_api( + {ok, 200, LookupMsg} = request( get, - api_path(["mqtt", "delayed", "messages", node(), MsgId]) + uri(["mqtt", "delayed", "messages", node(), MsgId]) ), ?assertEqual(MsgId, maps:get(msgid, decode_json(LookupMsg))), - {ok, _} = request_api( - delete, - api_path(["mqtt", "delayed", "messages", node(), MsgId]) + ?assertMatch( + {ok, 404, _}, + request( + get, + uri(["mqtt", "delayed", "messages", node(), emqx_guid:to_hexstr(emqx_guid:gen())]) + ) + ), + + ?assertMatch( + {ok, 400, _}, + request( + get, + uri(["mqtt", "delayed", "messages", node(), "invalid_msg_id"]) + ) + ), + + ?assertMatch( + {ok, 400, _}, + request( + get, + uri(["mqtt", "delayed", "messages", atom_to_list('unknownnode@127.0.0.1'), MsgId]) + ) + ), + + ?assertMatch( + {ok, 400, _}, + request( + get, + uri(["mqtt", "delayed", "messages", "some_unknown_atom", MsgId]) + ) + ), + + ?assertMatch( + {ok, 404, _}, + request( + delete, + uri(["mqtt", "delayed", "messages", node(), emqx_guid:to_hexstr(emqx_guid:gen())]) + ) + ), + + ?assertMatch( + {ok, 204, _}, + request( + delete, + uri(["mqtt", "delayed", "messages", node(), MsgId]) + ) ), _ = get_messages(4), ok = emqtt:disconnect(C1). +t_large_payload(_) -> + clear_all_record(), + + {ok, C1} = emqtt:start_link([{clean_start, true}]), + {ok, _} = emqtt:connect(C1), + timer:sleep(500), + Topic = <<"$delayed/123/msgs">>, + emqtt:publish( + C1, + Topic, + iolist_to_binary([<<"x">> || _ <- lists:seq(1, 5000)]), + [{qos, 0}, {retain, true}] + ), + + timer:sleep(500), + + [#{msgid := MsgId}] = get_messages(1), + + {ok, 200, Msg} = request( + get, + uri(["mqtt", "delayed", "messages", node(), MsgId]) + ), + + ?assertMatch( + #{ + payload := <<"PAYLOAD_TOO_LARGE">>, + topic := <<"msgs">> + }, + decode_json(Msg) + ). + %%-------------------------------------------------------------------- %% HTTP Request %%-------------------------------------------------------------------- + decode_json(Data) -> BinJson = emqx_json:decode(Data, [return_maps]), emqx_map_lib:unsafe_atom_key_map(BinJson). @@ -158,7 +236,7 @@ clear_all_record() -> ets:delete_all_objects(emqx_delayed). get_messages(Len) -> - {ok, MsgsJson} = request_api(get, api_path(["mqtt", "delayed", "messages"])), + {ok, 200, MsgsJson} = request(get, uri(["mqtt", "delayed", "messages"])), #{data := Msgs} = decode_json(MsgsJson), MsgLen = erlang:length(Msgs), ?assert( From ce437ac5b22da3dfe19872780c689c62dc90b575 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Thu, 31 Mar 2022 20:29:46 +0300 Subject: [PATCH 6/9] chore(emqx_modules): improve emqx_delayed coverage --- apps/emqx_modules/src/emqx_delayed.erl | 15 +-- .../src/proto/emqx_delayed_proto_v1.erl | 8 +- apps/emqx_modules/test/emqx_delayed_SUITE.erl | 104 +++++++++++++++++- .../test/emqx_delayed_api_SUITE.erl | 44 ++++---- 4 files changed, 131 insertions(+), 40 deletions(-) diff --git a/apps/emqx_modules/src/emqx_delayed.erl b/apps/emqx_modules/src/emqx_delayed.erl index be7ca3ba3..8adf86e5d 100644 --- a/apps/emqx_modules/src/emqx_delayed.erl +++ b/apps/emqx_modules/src/emqx_delayed.erl @@ -21,6 +21,7 @@ -include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/types.hrl"). -include_lib("emqx/include/logger.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). %% Mnesia bootstrap -export([mnesia/1]). @@ -275,17 +276,13 @@ init([Opts]) -> handle_call({set_max_delayed_messages, Max}, _From, State) -> {reply, ok, State#{max_delayed_messages => Max}}; handle_call( - {store, DelayedMsg = #delayed_message{key = Key}}, - _From, - State = #{max_delayed_messages := 0} + {store, DelayedMsg = #delayed_message{key = Key}}, _From, State = #{max_delayed_messages := 0} ) -> ok = mria:dirty_write(?TAB, DelayedMsg), emqx_metrics:inc('messages.delayed'), {reply, ok, ensure_publish_timer(Key, State)}; handle_call( - {store, DelayedMsg = #delayed_message{key = Key}}, - _From, - State = #{max_delayed_messages := Max} + {store, DelayedMsg = #delayed_message{key = Key}}, _From, State = #{max_delayed_messages := Max} ) -> Size = mnesia:table_info(?TAB, size), case Size >= Max of @@ -303,11 +300,11 @@ handle_call(disable, _From, State) -> emqx_hooks:del('message.publish', {?MODULE, on_message_publish}), {reply, ok, State}; handle_call(Req, _From, State) -> - ?SLOG(error, #{msg => "unexpected_call", call => Req}), + ?tp(error, emqx_delayed_unexpected_call, #{call => Req}), {reply, ignored, State}. handle_cast(Msg, State) -> - ?SLOG(error, #{msg => "unexpected_cast", cast => Msg}), + ?tp(error, emqx_delayed_unexpected_cast, #{cast => Msg}), {noreply, State}. %% Do Publish... @@ -320,7 +317,7 @@ handle_info(stats, State = #{stats_fun := StatsFun}) -> StatsFun(delayed_count()), {noreply, State#{stats_timer := StatsTimer}, hibernate}; handle_info(Info, State) -> - ?SLOG(error, #{msg => "unexpected_info", info => Info}), + ?tp(error, emqx_delayed_unexpected_info, #{info => Info}), {noreply, State}. terminate(_Reason, #{publish_timer := PublishTimer, stats_timer := StatsTimer}) -> diff --git a/apps/emqx_modules/src/proto/emqx_delayed_proto_v1.erl b/apps/emqx_modules/src/proto/emqx_delayed_proto_v1.erl index 4c4a55dc4..28eec64ee 100644 --- a/apps/emqx_modules/src/proto/emqx_delayed_proto_v1.erl +++ b/apps/emqx_modules/src/proto/emqx_delayed_proto_v1.erl @@ -29,9 +29,9 @@ introduced_in() -> "5.0.0". -spec get_delayed_message(node(), binary()) -> emqx_delayed:with_id_return(map()) | emqx_rpc:badrpc(). -get_delayed_message(Node, HexId) -> - rpc:call(Node, emqx_delayed, get_delayed_message, [HexId]). +get_delayed_message(Node, Id) -> + rpc:call(Node, emqx_delayed, get_delayed_message, [Id]). -spec delete_delayed_message(node(), binary()) -> emqx_delayed:with_id_return() | emqx_rpc:badrpc(). -delete_delayed_message(Node, HexId) -> - rpc:call(Node, emqx_delayed, delete_delayed_message, [HexId]). +delete_delayed_message(Node, Id) -> + rpc:call(Node, emqx_delayed, delete_delayed_message, [Id]). diff --git a/apps/emqx_modules/test/emqx_delayed_SUITE.erl b/apps/emqx_modules/test/emqx_delayed_SUITE.erl index 5bde45c4b..9e98807d9 100644 --- a/apps/emqx_modules/test/emqx_delayed_SUITE.erl +++ b/apps/emqx_modules/test/emqx_delayed_SUITE.erl @@ -43,6 +43,16 @@ init_per_suite(Config) -> end_per_suite(_) -> emqx_common_test_helpers:stop_apps([emqx_modules]). +init_per_testcase(t_load_case, Config) -> + Config; +init_per_testcase(_Case, Config) -> + {atomic, ok} = mria:clear_table(emqx_delayed), + ok = emqx_delayed:enable(), + Config. + +end_per_testcase(_Case, _Config) -> + ok = emqx_delayed:disable(). + %%-------------------------------------------------------------------- %% Test cases %%-------------------------------------------------------------------- @@ -57,7 +67,6 @@ t_load_case(_) -> ok. t_delayed_message(_) -> - ok = emqx_delayed:enable(), DelayedMsg = emqx_message:make(?MODULE, 1, <<"$delayed/1/publish">>, <<"delayed_m">>), ?assertEqual( {stop, DelayedMsg#message{topic = <<"publish">>, headers = #{allow_publish => false}}}, @@ -67,11 +76,94 @@ t_delayed_message(_) -> Msg = emqx_message:make(?MODULE, 1, <<"no_delayed_msg">>, <<"no_delayed">>), ?assertEqual({ok, Msg}, on_message_publish(Msg)), - [Key] = mnesia:dirty_all_keys(emqx_delayed), - [#delayed_message{msg = #message{payload = Payload}}] = mnesia:dirty_read({emqx_delayed, Key}), + [#delayed_message{msg = #message{payload = Payload}}] = ets:tab2list(emqx_delayed), ?assertEqual(<<"delayed_m">>, Payload), - timer:sleep(5000), + ct:sleep(2000), EmptyKey = mnesia:dirty_all_keys(emqx_delayed), - ?assertEqual([], EmptyKey), - ok = emqx_delayed:disable(). + ?assertEqual([], EmptyKey). + +t_delayed_message_abs_time(_) -> + Ts0 = integer_to_binary(erlang:system_time(second) + 1), + DelayedMsg0 = emqx_message:make( + ?MODULE, 1, <<"$delayed/", Ts0/binary, "/publish">>, <<"delayed_abs">> + ), + _ = on_message_publish(DelayedMsg0), + + ?assertMatch( + [#delayed_message{msg = #message{payload = <<"delayed_abs">>}}], + ets:tab2list(emqx_delayed) + ), + + ct:sleep(2000), + + ?assertMatch( + [], + ets:tab2list(emqx_delayed) + ), + + Ts1 = integer_to_binary(erlang:system_time(second) + 10000000), + DelayedMsg1 = emqx_message:make( + ?MODULE, 1, <<"$delayed/", Ts1/binary, "/publish">>, <<"delayed_abs">> + ), + + ?assertError( + invalid_delayed_timestamp, + on_message_publish(DelayedMsg1) + ). + +t_list(_) -> + Ts0 = integer_to_binary(erlang:system_time(second) + 1), + DelayedMsg0 = emqx_message:make( + ?MODULE, 1, <<"$delayed/", Ts0/binary, "/publish">>, <<"delayed_abs">> + ), + _ = on_message_publish(DelayedMsg0), + + ?assertMatch( + #{data := [#{topic := <<"publish">>}]}, + emqx_delayed:list(#{}) + ). + +t_max(_) -> + emqx_delayed:set_max_delayed_messages(1), + + DelayedMsg0 = emqx_message:make(?MODULE, 1, <<"$delayed/10/t0">>, <<"delayed0">>), + DelayedMsg1 = emqx_message:make(?MODULE, 1, <<"$delayed/10/t1">>, <<"delayed1">>), + _ = on_message_publish(DelayedMsg0), + _ = on_message_publish(DelayedMsg1), + + ?assertMatch( + #{data := [#{topic := <<"t0">>}]}, + emqx_delayed:list(#{}) + ). + +t_cluster(_) -> + DelayedMsg = emqx_message:make(?MODULE, 1, <<"$delayed/1/publish">>, <<"delayed">>), + Id = emqx_message:id(DelayedMsg), + _ = on_message_publish(DelayedMsg), + + ?assertMatch( + {ok, _}, + emqx_delayed_proto_v1:get_delayed_message(node(), Id) + ), + + ?assertEqual( + emqx_delayed:get_delayed_message(Id), + emqx_delayed_proto_v1:get_delayed_message(node(), Id) + ), + + ok = emqx_delayed_proto_v1:delete_delayed_message(node(), Id), + + ?assertMatch( + {error, _}, + emqx_delayed:get_delayed_message(Id) + ). + +t_unknown_messages(_) -> + OldPid = whereis(emqx_delayed), + OldPid ! unknown, + ok = gen_server:cast(OldPid, unknown), + ?assertEqual( + ignored, + gen_server:call(OldPid, unknown) + ). diff --git a/apps/emqx_modules/test/emqx_delayed_api_SUITE.erl b/apps/emqx_modules/test/emqx_delayed_api_SUITE.erl index f703dcf13..41c1e10b9 100644 --- a/apps/emqx_modules/test/emqx_delayed_api_SUITE.erl +++ b/apps/emqx_modules/test/emqx_delayed_api_SUITE.erl @@ -20,11 +20,11 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). --include_lib("emqx/include/emqx.hrl"). --define(BASE_CONF, #{<<"dealyed">> => <<"true">>, - <<"max_delayed_messages">> => <<"0">> - }). +-define(BASE_CONF, #{ + <<"dealyed">> => <<"true">>, + <<"max_delayed_messages">> => <<"0">> +}). -import(emqx_dashboard_api_test_helpers, [request/2, request/3, uri/1]). @@ -75,20 +75,22 @@ t_status(_Config) -> ?assertMatch(#{enable := true, max_delayed_messages := 12}, decode_json(R2)), ?assertMatch( - {ok, 200, _}, - request( - put, - Path, - #{enable => true} - )), + {ok, 200, _}, + request( + put, + Path, + #{enable => true} + ) + ), ?assertMatch( - {ok, 400, _}, - request( - put, - Path, - #{enable => true, max_delayed_messages => -5} - )), + {ok, 400, _}, + request( + put, + Path, + #{enable => true, max_delayed_messages => -5} + ) + ), {ok, 200, ConfJson} = request(get, Path), ReturnConf = decode_json(ConfJson), @@ -201,11 +203,11 @@ t_large_payload(_) -> timer:sleep(500), Topic = <<"$delayed/123/msgs">>, emqtt:publish( - C1, - Topic, - iolist_to_binary([<<"x">> || _ <- lists:seq(1, 5000)]), - [{qos, 0}, {retain, true}] - ), + C1, + Topic, + iolist_to_binary([<<"x">> || _ <- lists:seq(1, 5000)]), + [{qos, 0}, {retain, true}] + ), timer:sleep(500), From aae2d01582b980302f30913522eedf1d9d3d217c Mon Sep 17 00:00:00 2001 From: JimMoen Date: Fri, 1 Apr 2022 02:09:42 +0800 Subject: [PATCH 7/9] style: erlfmt apps/emqx_authn --- apps/emqx_authn/rebar.config | 37 +- apps/emqx_authn/src/emqx_authn.app.src | 24 +- apps/emqx_authn/src/emqx_authn.erl | 49 +- apps/emqx_authn/src/emqx_authn_api.erl | 951 +++++++++++------- apps/emqx_authn/src/emqx_authn_app.erl | 38 +- .../src/emqx_authn_password_hashing.erl | 150 +-- apps/emqx_authn/src/emqx_authn_schema.erl | 95 +- apps/emqx_authn/src/emqx_authn_sup.erl | 7 +- apps/emqx_authn/src/emqx_authn_utils.erl | 82 +- apps/emqx_authn/test/emqx_authn_api_SUITE.erl | 439 ++++---- .../emqx_authn/test/emqx_authn_http_SUITE.erl | 483 +++++---- .../test/emqx_authn_http_test_server.erl | 51 +- .../test/emqx_authn_https_SUITE.erl | 109 +- apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl | 225 +++-- .../test/emqx_authn_mnesia_SUITE.erl | 146 +-- .../test/emqx_authn_mongo_SUITE.erl | 486 ++++----- .../test/emqx_authn_mongo_tls_SUITE.erl | 175 ++-- .../test/emqx_authn_mqtt_test_client.erl | 64 +- .../test/emqx_authn_mysql_SUITE.erl | 551 +++++----- .../test/emqx_authn_mysql_tls_SUITE.erl | 107 +- .../emqx_authn_password_hashing_SUITE.erl | 208 ++-- .../test/emqx_authn_pgsql_SUITE.erl | 584 ++++++----- .../test/emqx_authn_pgsql_tls_SUITE.erl | 102 +- .../test/emqx_authn_redis_SUITE.erl | 550 +++++----- .../test/emqx_authn_redis_tls_SUITE.erl | 99 +- apps/emqx_authn/test/emqx_authn_test_lib.erl | 20 +- ...emqx_enhanced_authn_scram_mnesia_SUITE.erl | 274 ++--- 27 files changed, 3509 insertions(+), 2597 deletions(-) diff --git a/apps/emqx_authn/rebar.config b/apps/emqx_authn/rebar.config index d17f1fb90..8fd9cea0f 100644 --- a/apps/emqx_authn/rebar.config +++ b/apps/emqx_authn/rebar.config @@ -1,23 +1,32 @@ %% -*- mode: erlang -*- -{deps, - [ {emqx, {path, "../emqx"}} - , {emqx_connector, {path, "../emqx_connector"}} - ]}. +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_connector, {path, "../emqx_connector"}} +]}. {edoc_opts, [{preprocess, true}]}. -{erl_opts, [warn_unused_vars, - warn_shadow_vars, - warnings_as_errors, - warn_unused_import, - warn_obsolete_guard, - debug_info, - {parse_transform}]}. +{erl_opts, [ + warn_unused_vars, + warn_shadow_vars, + warnings_as_errors, + warn_unused_import, + warn_obsolete_guard, + debug_info, + {parse_transform} +]}. -{xref_checks, [undefined_function_calls, undefined_functions, - locals_not_used, deprecated_function_calls, - warnings_as_errors, deprecated_functions]}. +{xref_checks, [ + undefined_function_calls, + undefined_functions, + locals_not_used, + deprecated_function_calls, + warnings_as_errors, + deprecated_functions +]}. {cover_enabled, true}. {cover_opts, [verbose]}. {cover_export_enabled, true}. + +{project_plugins, [erlfmt]}. diff --git a/apps/emqx_authn/src/emqx_authn.app.src b/apps/emqx_authn/src/emqx_authn.app.src index 799478be8..d32b74e49 100644 --- a/apps/emqx_authn/src/emqx_authn.app.src +++ b/apps/emqx_authn/src/emqx_authn.app.src @@ -1,13 +1,13 @@ %% -*- mode: erlang -*- -{application, emqx_authn, - [{description, "EMQX Authentication"}, - {vsn, "0.1.0"}, - {modules, []}, - {registered, [emqx_authn_sup, emqx_authn_registry]}, - {applications, [kernel,stdlib,emqx_resource,ehttpc,epgsql,mysql,jose]}, - {mod, {emqx_authn_app,[]}}, - {env, []}, - {licenses, ["Apache-2.0"]}, - {maintainers, ["EMQX Team "]}, - {links, [{"Homepage", "https://emqx.io/"}]} - ]}. +{application, emqx_authn, [ + {description, "EMQX Authentication"}, + {vsn, "0.1.0"}, + {modules, []}, + {registered, [emqx_authn_sup, emqx_authn_registry]}, + {applications, [kernel, stdlib, emqx_resource, ehttpc, epgsql, mysql, jose]}, + {mod, {emqx_authn_app, []}}, + {env, []}, + {licenses, ["Apache-2.0"]}, + {maintainers, ["EMQX Team "]}, + {links, [{"Homepage", "https://emqx.io/"}]} +]}. diff --git a/apps/emqx_authn/src/emqx_authn.erl b/apps/emqx_authn/src/emqx_authn.erl index f984c06ec..f93212bcf 100644 --- a/apps/emqx_authn/src/emqx_authn.erl +++ b/apps/emqx_authn/src/emqx_authn.erl @@ -16,28 +16,31 @@ -module(emqx_authn). --export([ providers/0 - , check_config/1 - , check_config/2 - , check_configs/1 - ]). +-export([ + providers/0, + check_config/1, + check_config/2, + check_configs/1 +]). -include("emqx_authn.hrl"). providers() -> - [ {{'password_based', 'built_in_database'}, emqx_authn_mnesia} - , {{'password_based', mysql}, emqx_authn_mysql} - , {{'password_based', postgresql}, emqx_authn_pgsql} - , {{'password_based', mongodb}, emqx_authn_mongodb} - , {{'password_based', redis}, emqx_authn_redis} - , {{'password_based', 'http'}, emqx_authn_http} - , {jwt, emqx_authn_jwt} - , {{scram, 'built_in_database'}, emqx_enhanced_authn_scram_mnesia} + [ + {{'password_based', 'built_in_database'}, emqx_authn_mnesia}, + {{'password_based', mysql}, emqx_authn_mysql}, + {{'password_based', postgresql}, emqx_authn_pgsql}, + {{'password_based', mongodb}, emqx_authn_mongodb}, + {{'password_based', redis}, emqx_authn_redis}, + {{'password_based', 'http'}, emqx_authn_http}, + {jwt, emqx_authn_jwt}, + {{scram, 'built_in_database'}, emqx_enhanced_authn_scram_mnesia} ]. check_configs(C) when is_map(C) -> check_configs([C]); -check_configs([]) -> []; +check_configs([]) -> + []; check_configs([Config | Configs]) -> [check_config(Config) | check_configs(Configs)]. @@ -51,22 +54,26 @@ check_config(Config, Opts) -> end. do_check_config(#{<<"mechanism">> := Mec} = Config, Opts) -> - Key = case maps:get(<<"backend">>, Config, false) of - false -> atom(Mec); - Backend -> {atom(Mec), atom(Backend)} - end, + Key = + case maps:get(<<"backend">>, Config, false) of + false -> atom(Mec); + Backend -> {atom(Mec), atom(Backend)} + end, case lists:keyfind(Key, 1, providers()) of false -> throw({unknown_handler, Key}); {_, ProviderModule} -> - hocon_tconf:check_plain(ProviderModule, #{?CONF_NS_BINARY => Config}, - Opts#{atom_key => true}) + hocon_tconf:check_plain( + ProviderModule, + #{?CONF_NS_BINARY => Config}, + Opts#{atom_key => true} + ) end. atom(Bin) -> try binary_to_existing_atom(Bin, utf8) catch - _ : _ -> + _:_ -> throw({unknown_auth_provider, Bin}) end. diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index 35352dc2a..927fe4072 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -33,113 +33,120 @@ % Swagger --define(API_TAGS_GLOBAL, [?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY, - <<"authentication config(global)">>]). --define(API_TAGS_SINGLE, [?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY, - <<"authentication config(single listener)">>]). +-define(API_TAGS_GLOBAL, [ + ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY, + <<"authentication config(global)">> +]). +-define(API_TAGS_SINGLE, [ + ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY, + <<"authentication config(single listener)">> +]). --export([ api_spec/0 - , paths/0 - , schema/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1 +]). --export([ roots/0 - , fields/1 - ]). +-export([ + roots/0, + fields/1 +]). --export([ authenticators/2 - , authenticator/2 - , authenticator_status/2 - , listener_authenticators/2 - , listener_authenticator/2 - , listener_authenticator_status/2 - , authenticator_move/2 - , listener_authenticator_move/2 - , authenticator_import_users/2 - , listener_authenticator_import_users/2 - , authenticator_users/2 - , authenticator_user/2 - , listener_authenticator_users/2 - , listener_authenticator_user/2 - , lookup_from_local_node/2 - , lookup_from_all_nodes/2 - ]). +-export([ + authenticators/2, + authenticator/2, + authenticator_status/2, + listener_authenticators/2, + listener_authenticator/2, + listener_authenticator_status/2, + authenticator_move/2, + listener_authenticator_move/2, + authenticator_import_users/2, + listener_authenticator_import_users/2, + authenticator_users/2, + authenticator_user/2, + listener_authenticator_users/2, + listener_authenticator_user/2, + lookup_from_local_node/2, + lookup_from_all_nodes/2 +]). --export([ authenticator_examples/0 - , request_move_examples/0 - , request_import_users_examples/0 - , request_user_create_examples/0 - , request_user_update_examples/0 - , response_user_examples/0 - , response_users_example/0 - ]). +-export([ + authenticator_examples/0, + request_move_examples/0, + request_import_users_examples/0, + request_user_create_examples/0, + request_user_update_examples/0, + response_user_examples/0, + response_users_example/0 +]). %% export these funcs for gateway --export([ list_users/3 - , add_user/3 - , delete_user/3 - , find_user/3 - , update_user/4 - , serialize_error/1 - ]). +-export([ + list_users/3, + add_user/3, + delete_user/3, + find_user/3, + update_user/4, + serialize_error/1 +]). -elvis([{elvis_style, god_modules, disable}]). api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). -paths() -> [ "/authentication" - , "/authentication/:id" - , "/authentication/:id/status" - , "/authentication/:id/move" - , "/authentication/:id/import_users" - , "/authentication/:id/users" - , "/authentication/:id/users/:user_id" +paths() -> + [ + "/authentication", + "/authentication/:id", + "/authentication/:id/status", + "/authentication/:id/move", + "/authentication/:id/import_users", + "/authentication/:id/users", + "/authentication/:id/users/:user_id", - , "/listeners/:listener_id/authentication" - , "/listeners/:listener_id/authentication/:id" - , "/listeners/:listener_id/authentication/:id/status" - , "/listeners/:listener_id/authentication/:id/move" - , "/listeners/:listener_id/authentication/:id/import_users" - , "/listeners/:listener_id/authentication/:id/users" - , "/listeners/:listener_id/authentication/:id/users/:user_id" - ]. + "/listeners/:listener_id/authentication", + "/listeners/:listener_id/authentication/:id", + "/listeners/:listener_id/authentication/:id/status", + "/listeners/:listener_id/authentication/:id/move", + "/listeners/:listener_id/authentication/:id/import_users", + "/listeners/:listener_id/authentication/:id/users", + "/listeners/:listener_id/authentication/:id/users/:user_id" + ]. -roots() -> [ request_user_create - , request_user_update - , request_move - , request_import_users - , response_user - , response_users - ]. +roots() -> + [ + request_user_create, + request_user_update, + request_move, + request_import_users, + response_user, + response_users + ]. fields(request_user_create) -> [ {user_id, binary()} | fields(request_user_update) ]; - fields(request_user_update) -> [ {password, binary()}, {is_superuser, mk(boolean(), #{default => false, required => false})} ]; - fields(request_move) -> [{position, binary()}]; - fields(request_import_users) -> [{filename, binary()}]; - fields(response_user) -> [ {user_id, binary()}, {is_superuser, mk(boolean(), #{default => false, required => false})} ]; - fields(response_users) -> paginated_list_type(ref(response_user)); - fields(pagination_meta) -> [ {page, non_neg_integer()}, @@ -156,7 +163,8 @@ schema("/authentication") -> responses => #{ 200 => emqx_dashboard_swagger:schema_with_example( hoconsc:array(emqx_authn_schema:authenticator_type()), - authenticator_array_example()) + authenticator_array_example() + ) } }, post => #{ @@ -164,17 +172,18 @@ schema("/authentication") -> description => <<"Create authenticator for global authentication">>, 'requestBody' => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), - authenticator_examples()), + authenticator_examples() + ), responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), - authenticator_examples()), + authenticator_examples() + ), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), 409 => error_codes([?ALREADY_EXISTS], <<"ALREADY_EXISTS">>) } } }; - schema("/authentication/:id") -> #{ 'operationId' => authenticator, @@ -185,7 +194,8 @@ schema("/authentication/:id") -> responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), - authenticator_examples()), + authenticator_examples() + ), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } }, @@ -200,7 +210,8 @@ schema("/authentication/:id") -> responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), - authenticator_examples()), + authenticator_examples() + ), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), 404 => error_codes([?NOT_FOUND], <<"Not Found">>), 409 => error_codes([?ALREADY_EXISTS], <<"ALREADY_EXISTS">>) @@ -216,7 +227,6 @@ schema("/authentication/:id") -> } } }; - schema("/authentication/:id/status") -> #{ 'operationId' => authenticator_status, @@ -227,12 +237,12 @@ schema("/authentication/:id/status") -> responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( hoconsc:ref(emqx_authn_schema, "metrics_status_fields"), - status_metrics_example()), + status_metrics_example() + ), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) } } }; - schema("/listeners/:listener_id/authentication") -> #{ 'operationId' => listener_authenticators, @@ -243,7 +253,8 @@ schema("/listeners/:listener_id/authentication") -> responses => #{ 200 => emqx_dashboard_swagger:schema_with_example( hoconsc:array(emqx_authn_schema:authenticator_type()), - authenticator_array_example()) + authenticator_array_example() + ) } }, post => #{ @@ -257,13 +268,13 @@ schema("/listeners/:listener_id/authentication") -> responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), - authenticator_examples()), + authenticator_examples() + ), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), 409 => error_codes([?ALREADY_EXISTS], <<"ALREADY_EXISTS">>) } } }; - schema("/listeners/:listener_id/authentication/:id") -> #{ 'operationId' => listener_authenticator, @@ -274,7 +285,8 @@ schema("/listeners/:listener_id/authentication/:id") -> responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), - authenticator_examples()), + authenticator_examples() + ), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } }, @@ -284,11 +296,13 @@ schema("/listeners/:listener_id/authentication/:id") -> parameters => [param_listener_id(), param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), - authenticator_examples()), + authenticator_examples() + ), responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), - authenticator_examples()), + authenticator_examples() + ), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), 404 => error_codes([?NOT_FOUND], <<"Not Found">>), 409 => error_codes([?ALREADY_EXISTS], <<"ALREADY_EXISTS">>) @@ -304,7 +318,6 @@ schema("/listeners/:listener_id/authentication/:id") -> } } }; - schema("/listeners/:listener_id/authentication/:id/status") -> #{ 'operationId' => listener_authenticator_status, @@ -315,12 +328,12 @@ schema("/listeners/:listener_id/authentication/:id/status") -> responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( hoconsc:ref(emqx_authn_schema, "metrics_status_fields"), - status_metrics_example()), + status_metrics_example() + ), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) } } }; - schema("/authentication/:id/move") -> #{ 'operationId' => authenticator_move, @@ -330,7 +343,8 @@ schema("/authentication/:id/move") -> parameters => [param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_move), - request_move_examples()), + request_move_examples() + ), responses => #{ 204 => <<"Authenticator moved">>, 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), @@ -338,7 +352,6 @@ schema("/authentication/:id/move") -> } } }; - schema("/listeners/:listener_id/authentication/:id/move") -> #{ 'operationId' => listener_authenticator_move, @@ -348,7 +361,8 @@ schema("/listeners/:listener_id/authentication/:id/move") -> parameters => [param_listener_id(), param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_move), - request_move_examples()), + request_move_examples() + ), responses => #{ 204 => <<"Authenticator moved">>, 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), @@ -356,7 +370,6 @@ schema("/listeners/:listener_id/authentication/:id/move") -> } } }; - schema("/authentication/:id/import_users") -> #{ 'operationId' => authenticator_import_users, @@ -366,7 +379,8 @@ schema("/authentication/:id/import_users") -> parameters => [param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_import_users), - request_import_users_examples()), + request_import_users_examples() + ), responses => #{ 204 => <<"Users imported">>, 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), @@ -374,7 +388,6 @@ schema("/authentication/:id/import_users") -> } } }; - schema("/listeners/:listener_id/authentication/:id/import_users") -> #{ 'operationId' => listener_authenticator_import_users, @@ -384,7 +397,8 @@ schema("/listeners/:listener_id/authentication/:id/import_users") -> parameters => [param_listener_id(), param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_import_users), - request_import_users_examples()), + request_import_users_examples() + ), responses => #{ 204 => <<"Users imported">>, 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), @@ -392,7 +406,6 @@ schema("/listeners/:listener_id/authentication/:id/import_users") -> } } }; - schema("/authentication/:id/users") -> #{ 'operationId' => authenticator_users, @@ -402,11 +415,13 @@ schema("/authentication/:id/users") -> parameters => [param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_user_create), - request_user_create_examples()), + request_user_create_examples() + ), responses => #{ 201 => emqx_dashboard_swagger:schema_with_examples( ref(response_user), - response_user_examples()), + response_user_examples() + ), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } @@ -418,23 +433,28 @@ schema("/authentication/:id/users") -> param_auth_id(), {page, mk(integer(), #{in => query, desc => <<"Page Index">>, required => false})}, {limit, mk(integer(), #{in => query, desc => <<"Page Limit">>, required => false})}, - {like_username, mk(binary(), #{ in => query - , desc => <<"Fuzzy search username">> - , required => false})}, - {like_clientid, mk(binary(), #{ in => query - , desc => <<"Fuzzy search clientid">> - , required => false})} + {like_username, + mk(binary(), #{ + in => query, + desc => <<"Fuzzy search username">>, + required => false + })}, + {like_clientid, + mk(binary(), #{ + in => query, + desc => <<"Fuzzy search clientid">>, + required => false + })} ], responses => #{ 200 => emqx_dashboard_swagger:schema_with_example( ref(response_users), - response_users_example()), + response_users_example() + ), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } - } }; - schema("/listeners/:listener_id/authentication/:id/users") -> #{ 'operationId' => listener_authenticator_users, @@ -444,11 +464,13 @@ schema("/listeners/:listener_id/authentication/:id/users") -> parameters => [param_auth_id(), param_listener_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_user_create), - request_user_create_examples()), + request_user_create_examples() + ), responses => #{ 201 => emqx_dashboard_swagger:schema_with_examples( ref(response_user), - response_user_examples()), + response_user_examples() + ), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } @@ -457,20 +479,20 @@ schema("/listeners/:listener_id/authentication/:id/users") -> tags => ?API_TAGS_SINGLE, description => <<"List users in authenticator in listener authentication chain">>, parameters => [ - param_listener_id(), param_auth_id(), + param_listener_id(), + param_auth_id(), {page, mk(integer(), #{in => query, desc => <<"Page Index">>, required => false})}, {limit, mk(integer(), #{in => query, desc => <<"Page Limit">>, required => false})} ], responses => #{ 200 => emqx_dashboard_swagger:schema_with_example( ref(response_users), - response_users_example()), + response_users_example() + ), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } - } }; - schema("/authentication/:id/users/:user_id") -> #{ 'operationId' => authenticator_user, @@ -481,7 +503,8 @@ schema("/authentication/:id/users/:user_id") -> responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( ref(response_user), - response_user_examples()), + response_user_examples() + ), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } }, @@ -491,11 +514,13 @@ schema("/authentication/:id/users/:user_id") -> parameters => [param_auth_id(), param_user_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_user_update), - request_user_update_examples()), + request_user_update_examples() + ), responses => #{ 200 => emqx_dashboard_swagger:schema_with_example( ref(response_user), - response_user_examples()), + response_user_examples() + ), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } @@ -510,7 +535,6 @@ schema("/authentication/:id/users/:user_id") -> } } }; - schema("/listeners/:listener_id/authentication/:id/users/:user_id") -> #{ 'operationId' => listener_authenticator_user, @@ -521,7 +545,8 @@ schema("/listeners/:listener_id/authentication/:id/users/:user_id") -> responses => #{ 200 => emqx_dashboard_swagger:schema_with_example( ref(response_user), - response_user_examples()), + response_user_examples() + ), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } }, @@ -531,15 +556,16 @@ schema("/listeners/:listener_id/authentication/:id/users/:user_id") -> parameters => [param_listener_id(), param_auth_id(), param_user_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_example( ref(request_user_update), - request_user_update_examples()), + request_user_update_examples() + ), responses => #{ 200 => emqx_dashboard_swagger:schema_with_example( ref(response_user), - response_user_examples()), + response_user_examples() + ), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } - }, delete => #{ tags => ?API_TAGS_SINGLE, @@ -582,16 +608,13 @@ param_user_id() -> authenticators(post, #{body := Config}) -> create_authenticator([authentication], ?GLOBAL, Config); - authenticators(get, _Params) -> list_authenticators([authentication]). authenticator(get, #{bindings := #{id := AuthenticatorID}}) -> list_authenticator(?GLOBAL, [authentication], AuthenticatorID); - authenticator(put, #{bindings := #{id := AuthenticatorID}, body := Config}) -> update_authenticator([authentication], ?GLOBAL, AuthenticatorID, Config); - authenticator(delete, #{bindings := #{id := AuthenticatorID}}) -> delete_authenticator([authentication], ?GLOBAL, AuthenticatorID). @@ -599,74 +622,118 @@ authenticator_status(get, #{bindings := #{id := AuthenticatorID}}) -> lookup_from_all_nodes(?GLOBAL, AuthenticatorID). listener_authenticators(post, #{bindings := #{listener_id := ListenerID}, body := Config}) -> - with_listener(ListenerID, - fun(Type, Name, ChainName) -> - create_authenticator([listeners, Type, Name, authentication], - ChainName, - Config) - end); - + with_listener( + ListenerID, + fun(Type, Name, ChainName) -> + create_authenticator( + [listeners, Type, Name, authentication], + ChainName, + Config + ) + end + ); listener_authenticators(get, #{bindings := #{listener_id := ListenerID}}) -> - with_listener(ListenerID, - fun(Type, Name, _) -> - list_authenticators([listeners, Type, Name, authentication]) - end). + with_listener( + ListenerID, + fun(Type, Name, _) -> + list_authenticators([listeners, Type, Name, authentication]) + end + ). listener_authenticator(get, #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}}) -> - with_listener(ListenerID, - fun(Type, Name, ChainName) -> - list_authenticator(ChainName, [listeners, Type, Name, authentication], - AuthenticatorID) - end); -listener_authenticator(put, - #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}, - body := Config}) -> - with_listener(ListenerID, - fun(Type, Name, ChainName) -> - update_authenticator([listeners, Type, Name, authentication], - ChainName, - AuthenticatorID, - Config) - end); -listener_authenticator(delete, - #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}}) -> - with_listener(ListenerID, - fun(Type, Name, ChainName) -> - delete_authenticator([listeners, Type, Name, authentication], - ChainName, - AuthenticatorID) - end). + with_listener( + ListenerID, + fun(Type, Name, ChainName) -> + list_authenticator( + ChainName, + [listeners, Type, Name, authentication], + AuthenticatorID + ) + end + ); +listener_authenticator( + put, + #{ + bindings := #{listener_id := ListenerID, id := AuthenticatorID}, + body := Config + } +) -> + with_listener( + ListenerID, + fun(Type, Name, ChainName) -> + update_authenticator( + [listeners, Type, Name, authentication], + ChainName, + AuthenticatorID, + Config + ) + end + ); +listener_authenticator( + delete, + #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}} +) -> + with_listener( + ListenerID, + fun(Type, Name, ChainName) -> + delete_authenticator( + [listeners, Type, Name, authentication], + ChainName, + AuthenticatorID + ) + end + ). -listener_authenticator_status(get, - #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}}) -> - with_listener(ListenerID, - fun(_, _, ChainName) -> - lookup_from_all_nodes(ChainName, AuthenticatorID) - end). +listener_authenticator_status( + get, + #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}} +) -> + with_listener( + ListenerID, + fun(_, _, ChainName) -> + lookup_from_all_nodes(ChainName, AuthenticatorID) + end + ). -authenticator_move(post, - #{bindings := #{id := AuthenticatorID}, - body := #{<<"position">> := Position}}) -> +authenticator_move( + post, + #{ + bindings := #{id := AuthenticatorID}, + body := #{<<"position">> := Position} + } +) -> move_authenticator([authentication], ?GLOBAL, AuthenticatorID, Position); authenticator_move(post, #{bindings := #{id := _}, body := _}) -> serialize_error({missing_parameter, position}). -listener_authenticator_move(post, - #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}, - body := #{<<"position">> := Position}}) -> - with_listener(ListenerID, - fun(Type, Name, ChainName) -> - move_authenticator([listeners, Type, Name, authentication], - ChainName, - AuthenticatorID, - Position) - end); +listener_authenticator_move( + post, + #{ + bindings := #{listener_id := ListenerID, id := AuthenticatorID}, + body := #{<<"position">> := Position} + } +) -> + with_listener( + ListenerID, + fun(Type, Name, ChainName) -> + move_authenticator( + [listeners, Type, Name, authentication], + ChainName, + AuthenticatorID, + Position + ) + end + ); listener_authenticator_move(post, #{bindings := #{listener_id := _, id := _}, body := _}) -> serialize_error({missing_parameter, position}). -authenticator_import_users(post, - #{bindings := #{id := AuthenticatorID}, - body := #{<<"filename">> := Filename}}) -> +authenticator_import_users( + post, + #{ + bindings := #{id := AuthenticatorID}, + body := #{<<"filename">> := Filename} + } +) -> case emqx_authentication:import_users(?GLOBAL, AuthenticatorID, Filename) of ok -> {204}; {error, Reason} -> serialize_error(Reason) @@ -675,17 +742,21 @@ authenticator_import_users(post, #{bindings := #{id := _}, body := _}) -> serialize_error({missing_parameter, filename}). listener_authenticator_import_users( - post, - #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}, - body := #{<<"filename">> := Filename}}) -> + post, + #{ + bindings := #{listener_id := ListenerID, id := AuthenticatorID}, + body := #{<<"filename">> := Filename} + } +) -> with_chain( - ListenerID, - fun(ChainName) -> - case emqx_authentication:import_users(ChainName, AuthenticatorID, Filename) of - ok -> {204}; - {error, Reason} -> serialize_error(Reason) - end - end); + ListenerID, + fun(ChainName) -> + case emqx_authentication:import_users(ChainName, AuthenticatorID, Filename) of + ok -> {204}; + {error, Reason} -> serialize_error(Reason) + end + end + ); listener_authenticator_import_users(post, #{bindings := #{listener_id := _, id := _}, body := _}) -> serialize_error({missing_parameter, filename}). @@ -694,48 +765,86 @@ authenticator_users(post, #{bindings := #{id := AuthenticatorID}, body := UserIn authenticator_users(get, #{bindings := #{id := AuthenticatorID}, query_string := QueryString}) -> list_users(?GLOBAL, AuthenticatorID, QueryString). -authenticator_user(put, #{bindings := #{id := AuthenticatorID, - user_id := UserID}, body := UserInfo}) -> +authenticator_user(put, #{ + bindings := #{ + id := AuthenticatorID, + user_id := UserID + }, + body := UserInfo +}) -> update_user(?GLOBAL, AuthenticatorID, UserID, UserInfo); authenticator_user(get, #{bindings := #{id := AuthenticatorID, user_id := UserID}}) -> find_user(?GLOBAL, AuthenticatorID, UserID); authenticator_user(delete, #{bindings := #{id := AuthenticatorID, user_id := UserID}}) -> delete_user(?GLOBAL, AuthenticatorID, UserID). -listener_authenticator_users(post, #{bindings := #{listener_id := ListenerID, - id := AuthenticatorID}, body := UserInfo}) -> - with_chain(ListenerID, - fun(ChainName) -> - add_user(ChainName, AuthenticatorID, UserInfo) - end); -listener_authenticator_users(get, #{bindings := #{listener_id := ListenerID, - id := AuthenticatorID}, query_string := PageParams}) -> - with_chain(ListenerID, - fun(ChainName) -> - list_users(ChainName, AuthenticatorID, PageParams) - end). +listener_authenticator_users(post, #{ + bindings := #{ + listener_id := ListenerID, + id := AuthenticatorID + }, + body := UserInfo +}) -> + with_chain( + ListenerID, + fun(ChainName) -> + add_user(ChainName, AuthenticatorID, UserInfo) + end + ); +listener_authenticator_users(get, #{ + bindings := #{ + listener_id := ListenerID, + id := AuthenticatorID + }, + query_string := PageParams +}) -> + with_chain( + ListenerID, + fun(ChainName) -> + list_users(ChainName, AuthenticatorID, PageParams) + end + ). -listener_authenticator_user(put, #{bindings := #{listener_id := ListenerID, - id := AuthenticatorID, - user_id := UserID}, body := UserInfo}) -> - with_chain(ListenerID, - fun(ChainName) -> - update_user(ChainName, AuthenticatorID, UserID, UserInfo) - end); -listener_authenticator_user(get, #{bindings := #{listener_id := ListenerID, - id := AuthenticatorID, - user_id := UserID}}) -> - with_chain(ListenerID, - fun(ChainName) -> - find_user(ChainName, AuthenticatorID, UserID) - end); -listener_authenticator_user(delete, #{bindings := #{listener_id := ListenerID, - id := AuthenticatorID, - user_id := UserID}}) -> - with_chain(ListenerID, - fun(ChainName) -> - delete_user(ChainName, AuthenticatorID, UserID) - end). +listener_authenticator_user(put, #{ + bindings := #{ + listener_id := ListenerID, + id := AuthenticatorID, + user_id := UserID + }, + body := UserInfo +}) -> + with_chain( + ListenerID, + fun(ChainName) -> + update_user(ChainName, AuthenticatorID, UserID, UserInfo) + end + ); +listener_authenticator_user(get, #{ + bindings := #{ + listener_id := ListenerID, + id := AuthenticatorID, + user_id := UserID + } +}) -> + with_chain( + ListenerID, + fun(ChainName) -> + find_user(ChainName, AuthenticatorID, UserID) + end + ); +listener_authenticator_user(delete, #{ + bindings := #{ + listener_id := ListenerID, + id := AuthenticatorID, + user_id := UserID + } +}) -> + with_chain( + ListenerID, + fun(ChainName) -> + delete_user(ChainName, AuthenticatorID, UserID) + end + ). %%------------------------------------------------------------------------------ %% Internal functions @@ -768,7 +877,7 @@ find_listener(ListenerID) -> with_chain(ListenerID, Fun) -> {ok, ChainNames} = emqx_authentication:list_chain_names(), ListenerChainName = - [ Name || Name <- ChainNames, atom_to_binary(Name) =:= ListenerID ], + [Name || Name <- ChainNames, atom_to_binary(Name) =:= ListenerID], case ListenerChainName of [ChainName] -> Fun(ChainName); @@ -778,8 +887,10 @@ with_chain(ListenerID, Fun) -> create_authenticator(ConfKeyPath, ChainName, Config) -> case update_config(ConfKeyPath, {create_authenticator, ChainName, Config}) of - {ok, #{post_config_update := #{emqx_authentication := #{id := ID}}, - raw_config := AuthenticatorsConfig}} -> + {ok, #{ + post_config_update := #{emqx_authentication := #{id := ID}}, + raw_config := AuthenticatorsConfig + }} -> {ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig), {200, maps:put(id, ID, convert_certs(fill_defaults(AuthenticatorConfig)))}; {error, {_PrePostConfigUpdate, emqx_authentication, Reason}} -> @@ -790,18 +901,21 @@ create_authenticator(ConfKeyPath, ChainName, Config) -> list_authenticators(ConfKeyPath) -> AuthenticatorsConfig = get_raw_config_with_defaults(ConfKeyPath), - NAuthenticators = [ maps:put( - id, - emqx_authentication:authenticator_id(AuthenticatorConfig), - convert_certs(AuthenticatorConfig)) - || AuthenticatorConfig <- AuthenticatorsConfig], + NAuthenticators = [ + maps:put( + id, + emqx_authentication:authenticator_id(AuthenticatorConfig), + convert_certs(AuthenticatorConfig) + ) + || AuthenticatorConfig <- AuthenticatorsConfig + ], {200, NAuthenticators}. list_authenticator(_, ConfKeyPath, AuthenticatorID) -> AuthenticatorsConfig = get_raw_config_with_defaults(ConfKeyPath), case find_config(AuthenticatorID, AuthenticatorsConfig) of {ok, AuthenticatorConfig} -> - {200, maps:put(id, AuthenticatorID, convert_certs(AuthenticatorConfig))}; + {200, maps:put(id, AuthenticatorID, convert_certs(AuthenticatorConfig))}; {error, Reason} -> serialize_error(Reason) end. @@ -811,24 +925,28 @@ lookup_from_local_node(ChainName, AuthenticatorID) -> case emqx_authentication:lookup_authenticator(ChainName, AuthenticatorID) of {ok, #{provider := Provider, state := State}} -> case lists:member(Provider, resource_provider()) of - false -> {error, {NodeId, resource_unsupport_metrics_and_status}}; + false -> + {error, {NodeId, resource_unsupport_metrics_and_status}}; true -> #{resource_id := ResourceId} = State, case emqx_resource:get_instance(ResourceId) of - {error, not_found} -> {error, {NodeId, not_found_resource}}; - {ok, _, #{ status := Status, metrics := Metrics }} -> + {error, not_found} -> + {error, {NodeId, not_found_resource}}; + {ok, _, #{status := Status, metrics := Metrics}} -> {ok, {NodeId, Status, Metrics}} end end; - {error, Reason} -> {error, {NodeId, Reason}} + {error, Reason} -> + {error, {NodeId, Reason}} end. resource_provider() -> - [ emqx_authn_mysql, - emqx_authn_pgsql, - emqx_authn_mongodb, - emqx_authn_redis, - emqx_authn_http + [ + emqx_authn_mysql, + emqx_authn_pgsql, + emqx_authn_mongodb, + emqx_authn_redis, + emqx_authn_http ]. lookup_from_all_nodes(ChainName, AuthenticatorID) -> @@ -836,38 +954,45 @@ lookup_from_all_nodes(ChainName, AuthenticatorID) -> case is_ok(emqx_authn_proto_v1:lookup_from_all_nodes(Nodes, ChainName, AuthenticatorID)) of {ok, ResList} -> {StatusMap, MetricsMap, _} = make_result_map(ResList), - AggregateStatus = aggregate_status(maps:values(StatusMap)), - AggregateMetrics = aggregate_metrics(maps:values(MetricsMap)), - Fun = fun (_, V1) -> restructure_map(V1) end, - MKMap = fun (Name) -> fun ({Key, Val}) -> #{ node => Key, Name => Val } end end, - HelpFun = fun (M, Name) -> lists:map(MKMap(Name), maps:to_list(M)) end, - case AggregateStatus of - empty_metrics_and_status -> - {400, #{code => <<"BAD_REQUEST">>, - message => <<"Resource Not Support Status">>}}; - _ -> {200, #{node_status => HelpFun(StatusMap, status), - node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics), - status => AggregateStatus, - metrics => restructure_map(AggregateMetrics) - } - } - end; + AggregateStatus = aggregate_status(maps:values(StatusMap)), + AggregateMetrics = aggregate_metrics(maps:values(MetricsMap)), + Fun = fun(_, V1) -> restructure_map(V1) end, + MKMap = fun(Name) -> fun({Key, Val}) -> #{node => Key, Name => Val} end end, + HelpFun = fun(M, Name) -> lists:map(MKMap(Name), maps:to_list(M)) end, + case AggregateStatus of + empty_metrics_and_status -> + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"Resource Not Support Status">> + }}; + _ -> + {200, #{ + node_status => HelpFun(StatusMap, status), + node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics), + status => AggregateStatus, + metrics => restructure_map(AggregateMetrics) + }} + end; {error, ErrL} -> - {500, #{code => <<"INTERNAL_ERROR">>, - message => list_to_binary(io_lib:format("~p", [ErrL]))}} + {500, #{ + code => <<"INTERNAL_ERROR">>, + message => list_to_binary(io_lib:format("~p", [ErrL])) + }} end. -aggregate_status([]) -> empty_metrics_and_status; +aggregate_status([]) -> + empty_metrics_and_status; aggregate_status(AllStatus) -> - Head = fun ([A | _]) -> A end, + Head = fun([A | _]) -> A end, HeadVal = Head(AllStatus), - AllRes = lists:all(fun (Val) -> Val == HeadVal end, AllStatus), + AllRes = lists:all(fun(Val) -> Val == HeadVal end, AllStatus), case AllRes of true -> HeadVal; false -> inconsistent end. -aggregate_metrics([]) -> empty_metrics_and_status; +aggregate_metrics([]) -> + empty_metrics_and_status; aggregate_metrics([HeadMetrics | AllMetrics]) -> CombinerFun = fun ComFun(Val1, Val2) -> @@ -876,8 +1001,9 @@ aggregate_metrics([HeadMetrics | AllMetrics]) -> false -> Val1 + Val2 end end, - Fun = fun (ElemMap, AccMap) -> - emqx_map_lib:merge_with(CombinerFun, ElemMap, AccMap) end, + Fun = fun(ElemMap, AccMap) -> + emqx_map_lib:merge_with(CombinerFun, ElemMap, AccMap) + end, lists:foldl(Fun, HeadMetrics, AllMetrics). make_result_map(ResList) -> @@ -885,48 +1011,57 @@ make_result_map(ResList) -> fun(Elem, {StatusMap, MetricsMap, ErrorMap}) -> case Elem of {ok, {NodeId, Status, Metrics}} -> - {maps:put(NodeId, Status, StatusMap), - maps:put(NodeId, Metrics, MetricsMap), - ErrorMap + { + maps:put(NodeId, Status, StatusMap), + maps:put(NodeId, Metrics, MetricsMap), + ErrorMap }; {error, {NodeId, Reason}} -> - {StatusMap, - MetricsMap, - maps:put(NodeId, Reason, ErrorMap) - } + {StatusMap, MetricsMap, maps:put(NodeId, Reason, ErrorMap)} end end, lists:foldl(Fun, {maps:new(), maps:new(), maps:new()}, ResList). -restructure_map(#{counters := #{failed := Failed, matched := Match, success := Succ}, - rate := #{matched := #{current := Rate, last5m := Rate5m, max := RateMax} - } - } - ) -> - #{matched => Match, - success => Succ, - failed => Failed, - rate => Rate, - rate_last5m => Rate5m, - rate_max => RateMax - }; +restructure_map(#{ + counters := #{failed := Failed, matched := Match, success := Succ}, + rate := #{matched := #{current := Rate, last5m := Rate5m, max := RateMax}} +}) -> + #{ + matched => Match, + success => Succ, + failed => Failed, + rate => Rate, + rate_last5m => Rate5m, + rate_max => RateMax + }; restructure_map(Error) -> - Error. + Error. is_ok(ResL) -> - case lists:filter(fun({ok, _}) -> false; (_) -> true end, ResL) of + case + lists:filter( + fun + ({ok, _}) -> false; + (_) -> true + end, + ResL + ) + of [] -> {ok, [Res || {ok, Res} <- ResL]}; ErrL -> {error, ErrL} end. update_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Config) -> - case update_config(ConfKeyPath, - {update_authenticator, - ChainName, - AuthenticatorID, - Config}) of - {ok, #{post_config_update := #{emqx_authentication := #{id := ID}}, - raw_config := AuthenticatorsConfig}} -> + case + update_config( + ConfKeyPath, + {update_authenticator, ChainName, AuthenticatorID, Config} + ) + of + {ok, #{ + post_config_update := #{emqx_authentication := #{id := ID}}, + raw_config := AuthenticatorsConfig + }} -> {ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig), {200, maps:put(id, ID, convert_certs(fill_defaults(AuthenticatorConfig)))}; {error, {_PrePostConfigUpdate, emqx_authentication, Reason}} -> @@ -948,9 +1083,12 @@ delete_authenticator(ConfKeyPath, ChainName, AuthenticatorID) -> move_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Position) -> case parse_position(Position) of {ok, NPosition} -> - case update_config( - ConfKeyPath, - {move_authenticator, ChainName, AuthenticatorID, NPosition}) of + case + update_config( + ConfKeyPath, + {move_authenticator, ChainName, AuthenticatorID, NPosition} + ) + of {ok, _} -> {204}; {error, {_PrePostConfigUpdate, emqx_authentication, Reason}} -> @@ -962,14 +1100,23 @@ move_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Position) -> serialize_error(Reason) end. -add_user(ChainName, - AuthenticatorID, - #{<<"user_id">> := UserID, <<"password">> := Password} = UserInfo) -> +add_user( + ChainName, + AuthenticatorID, + #{<<"user_id">> := UserID, <<"password">> := Password} = UserInfo +) -> IsSuperuser = maps:get(<<"is_superuser">>, UserInfo, false), - case emqx_authentication:add_user(ChainName, AuthenticatorID, - #{ user_id => UserID - , password => Password - , is_superuser => IsSuperuser}) of + case + emqx_authentication:add_user( + ChainName, + AuthenticatorID, + #{ + user_id => UserID, + password => Password, + is_superuser => IsSuperuser + } + ) + of {ok, User} -> {201, User}; {error, Reason} -> @@ -1015,8 +1162,10 @@ list_users(ChainName, AuthenticatorID, QueryString) -> emqx_mgmt_util:generate_response(Response). update_config(Path, ConfigRequest) -> - emqx_conf:update(Path, ConfigRequest, #{rawconf_with_defaults => true, - override_to => cluster}). + emqx_conf:update(Path, ConfigRequest, #{ + rawconf_with_defaults => true, + override_to => cluster + }). get_raw_config_with_defaults(ConfKeyPath) -> NConfKeyPath = [atom_to_binary(Key, utf8) || Key <- ConfKeyPath], @@ -1024,10 +1173,12 @@ get_raw_config_with_defaults(ConfKeyPath) -> ensure_list(fill_defaults(RawConfig)). find_config(AuthenticatorID, AuthenticatorsConfig) -> - MatchingACs - = [AC - || AC <- ensure_list(AuthenticatorsConfig), - AuthenticatorID =:= emqx_authentication:authenticator_id(AC)], + MatchingACs = + [ + AC + || AC <- ensure_list(AuthenticatorsConfig), + AuthenticatorID =:= emqx_authentication:authenticator_id(AC) + ], case MatchingACs of [] -> {error, {not_found, {authenticator, AuthenticatorID}}}; [AuthenticatorConfig] -> {ok, AuthenticatorConfig} @@ -1039,69 +1190,108 @@ fill_defaults(Config) -> emqx_authn:check_config(Config, #{only_fill_defaults => true}). convert_certs(#{ssl := #{enable := true} = SSLOpts} = Config) -> - NSSLOpts = lists:foldl(fun(K, Acc) -> - case maps:get(K, Acc, undefined) of - undefined -> Acc; - Filename -> - {ok, Bin} = file:read_file(Filename), - Acc#{K => Bin} - end - end, SSLOpts, [certfile, keyfile, cacertfile]), + NSSLOpts = lists:foldl( + fun(K, Acc) -> + case maps:get(K, Acc, undefined) of + undefined -> + Acc; + Filename -> + {ok, Bin} = file:read_file(Filename), + Acc#{K => Bin} + end + end, + SSLOpts, + [certfile, keyfile, cacertfile] + ), Config#{ssl => NSSLOpts}; convert_certs(Config) -> Config. serialize_error({user_error, not_found}) -> - {404, #{code => <<"NOT_FOUND">>, - message => binfmt("User not found", [])}}; + {404, #{ + code => <<"NOT_FOUND">>, + message => binfmt("User not found", []) + }}; serialize_error({user_error, already_exist}) -> - {409, #{code => <<"ALREADY_EXISTS">>, - message => binfmt("User already exists", [])}}; + {409, #{ + code => <<"ALREADY_EXISTS">>, + message => binfmt("User already exists", []) + }}; serialize_error({user_error, Reason}) -> - {400, #{code => <<"BAD_REQUEST">>, - message => binfmt("User error: ~p", [Reason])}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => binfmt("User error: ~p", [Reason]) + }}; serialize_error({not_found, {authenticator, ID}}) -> - {404, #{code => <<"NOT_FOUND">>, - message => binfmt("Authenticator '~ts' does not exist", [ID]) }}; + {404, #{ + code => <<"NOT_FOUND">>, + message => binfmt("Authenticator '~ts' does not exist", [ID]) + }}; serialize_error({not_found, {listener, ID}}) -> - {404, #{code => <<"NOT_FOUND">>, - message => binfmt("Listener '~ts' does not exist", [ID])}}; + {404, #{ + code => <<"NOT_FOUND">>, + message => binfmt("Listener '~ts' does not exist", [ID]) + }}; serialize_error({not_found, {chain, ?GLOBAL}}) -> - {404, #{code => <<"NOT_FOUND">>, - message => <<"Authenticator not found in the 'global' scope">>}}; + {404, #{ + code => <<"NOT_FOUND">>, + message => <<"Authenticator not found in the 'global' scope">> + }}; serialize_error({not_found, {chain, Name}}) -> - {400, #{code => <<"BAD_REQUEST">>, - message => binfmt("No authentication has been created for listener ~p", [Name])}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => binfmt("No authentication has been created for listener ~p", [Name]) + }}; serialize_error({already_exists, {authenticator, ID}}) -> - {409, #{code => <<"ALREADY_EXISTS">>, - message => binfmt("Authenticator '~ts' already exist", [ID])}}; + {409, #{ + code => <<"ALREADY_EXISTS">>, + message => binfmt("Authenticator '~ts' already exist", [ID]) + }}; serialize_error(no_available_provider) -> - {400, #{code => <<"BAD_REQUEST">>, - message => <<"Unsupported authentication type">>}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"Unsupported authentication type">> + }}; serialize_error(change_of_authentication_type_is_not_allowed) -> - {400, #{code => <<"BAD_REQUEST">>, - message => <<"Change of authentication type is not allowed">>}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"Change of authentication type is not allowed">> + }}; serialize_error(unsupported_operation) -> - {400, #{code => <<"BAD_REQUEST">>, - message => <<"Operation not supported in this authentication type">>}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"Operation not supported in this authentication type">> + }}; serialize_error({bad_ssl_config, Details}) -> - {400, #{code => <<"BAD_REQUEST">>, - message => binfmt("bad_ssl_config ~p", [Details])}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => binfmt("bad_ssl_config ~p", [Details]) + }}; serialize_error({missing_parameter, Detail}) -> - {400, #{code => <<"BAD_REQUEST">>, - message => binfmt("Missing required parameter: ~p", [Detail])}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => binfmt("Missing required parameter: ~p", [Detail]) + }}; serialize_error({invalid_parameter, Name}) -> - {400, #{code => <<"BAD_REQUEST">>, - message => binfmt("Invalid value for '~p'", [Name])}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => binfmt("Invalid value for '~p'", [Name]) + }}; serialize_error({unknown_authn_type, Type}) -> - {400, #{code => <<"BAD_REQUEST">>, - message => binfmt("Unknown type '~p'", [Type])}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => binfmt("Unknown type '~p'", [Type]) + }}; serialize_error({bad_authenticator_config, Reason}) -> - {400, #{code => <<"BAD_REQUEST">>, - message => binfmt("Bad authenticator config ~p", [Reason])}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => binfmt("Bad authenticator config ~p", [Reason]) + }}; serialize_error(Reason) -> - {400, #{code => <<"BAD_REQUEST">>, - message => binfmt("~p", [Reason])}}. + {400, #{ + code => <<"BAD_REQUEST">>, + message => binfmt("~p", [Reason]) + }}. parse_position(<<"front">>) -> {ok, ?CMD_MOVE_FRONT}; @@ -1212,29 +1402,36 @@ authenticator_examples() -> }. status_metrics_example() -> - #{ metrics => #{ matched => 0, - success => 0, - failed => 0, - rate => 0.0, - rate_last5m => 0.0, - rate_max => 0.0 - }, - node_metrics => [ #{node => node(), - metrics => #{ matched => 0, - success => 0, - failed => 0, - rate => 0.0, - rate_last5m => 0.0, - rate_max => 0.0 - } - } - ], - status => connected, - node_status => [ #{node => node(), - status => connected - } - ] - }. + #{ + metrics => #{ + matched => 0, + success => 0, + failed => 0, + rate => 0.0, + rate_last5m => 0.0, + rate_max => 0.0 + }, + node_metrics => [ + #{ + node => node(), + metrics => #{ + matched => 0, + success => 0, + failed => 0, + rate => 0.0, + rate_last5m => 0.0, + rate_max => 0.0 + } + } + ], + status => connected, + node_status => [ + #{ + node => node(), + status => connected + } + ] + }. request_user_create_examples() -> #{ diff --git a/apps/emqx_authn/src/emqx_authn_app.erl b/apps/emqx_authn/src/emqx_authn_app.erl index c5a3e73bd..f761bfe33 100644 --- a/apps/emqx_authn/src/emqx_authn_app.erl +++ b/apps/emqx_authn/src/emqx_authn_app.erl @@ -21,9 +21,10 @@ -behaviour(application). %% Application callbacks --export([ start/2 - , stop/1 - ]). +-export([ + start/2, + stop/1 +]). -include_lib("emqx/include/emqx_authentication.hrl"). @@ -51,13 +52,15 @@ initialize() -> ok = ?AUTHN:register_providers(emqx_authn:providers()), lists:foreach( - fun({ChainName, RawAuthConfigs}) -> - AuthConfig = emqx_authn:check_configs(RawAuthConfigs), - ?AUTHN:initialize_authentication( - ChainName, - AuthConfig) - end, - chain_configs()). + fun({ChainName, RawAuthConfigs}) -> + AuthConfig = emqx_authn:check_configs(RawAuthConfigs), + ?AUTHN:initialize_authentication( + ChainName, + AuthConfig + ) + end, + chain_configs() + ). deinitialize() -> ok = ?AUTHN:deregister_providers(provider_types()), @@ -71,15 +74,16 @@ global_chain_config() -> listener_chain_configs() -> lists:map( - fun({ListenerID, _}) -> - {ListenerID, emqx:get_raw_config(auth_config_path(ListenerID), [])} - end, - emqx_listeners:list()). + fun({ListenerID, _}) -> + {ListenerID, emqx:get_raw_config(auth_config_path(ListenerID), [])} + end, + emqx_listeners:list() + ). auth_config_path(ListenerID) -> - [<<"listeners">>] - ++ binary:split(atom_to_binary(ListenerID), <<":">>) - ++ [?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY]. + [<<"listeners">>] ++ + binary:split(atom_to_binary(ListenerID), <<":">>) ++ + [?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY]. provider_types() -> lists:map(fun({Type, _Module}) -> Type end, emqx_authn:providers()). diff --git a/apps/emqx_authn/src/emqx_authn_password_hashing.erl b/apps/emqx_authn/src/emqx_authn_password_hashing.erl index 7a3573ca9..7435fe9c9 100644 --- a/apps/emqx_authn/src/emqx_authn_password_hashing.erl +++ b/apps/emqx_authn/src/emqx_authn_password_hashing.erl @@ -18,21 +18,25 @@ -include_lib("typerefl/include/types.hrl"). --type(simple_algorithm_name() :: plain | md5 | sha | sha256 | sha512). --type(salt_position() :: prefix | suffix). +-type simple_algorithm_name() :: plain | md5 | sha | sha256 | sha512. +-type salt_position() :: prefix | suffix. --type(simple_algorithm() :: #{name := simple_algorithm_name(), - salt_position := salt_position()}). +-type simple_algorithm() :: #{ + name := simple_algorithm_name(), + salt_position := salt_position() +}. --type(bcrypt_algorithm() :: #{name := bcrypt}). --type(bcrypt_algorithm_rw() :: #{name := bcrypt, salt_rounds := integer()}). +-type bcrypt_algorithm() :: #{name := bcrypt}. +-type bcrypt_algorithm_rw() :: #{name := bcrypt, salt_rounds := integer()}. --type(pbkdf2_algorithm() :: #{name := pbkdf2, - mac_fun := emqx_passwd:pbkdf2_mac_fun(), - iterations := pos_integer()}). +-type pbkdf2_algorithm() :: #{ + name := pbkdf2, + mac_fun := emqx_passwd:pbkdf2_mac_fun(), + iterations := pos_integer() +}. --type(algorithm() :: simple_algorithm() | pbkdf2_algorithm() | bcrypt_algorithm()). --type(algorithm_rw() :: simple_algorithm() | pbkdf2_algorithm() | bcrypt_algorithm_rw()). +-type algorithm() :: simple_algorithm() | pbkdf2_algorithm() | bcrypt_algorithm(). +-type algorithm_rw() :: simple_algorithm() | pbkdf2_algorithm() | bcrypt_algorithm_rw(). %%------------------------------------------------------------------------------ %% Hocon Schema @@ -40,37 +44,44 @@ -behaviour(hocon_schema). --export([roots/0, - fields/1, - namespace/0]). +-export([ + roots/0, + fields/1, + namespace/0 +]). --export([type_ro/1, - type_rw/1]). +-export([ + type_ro/1, + type_rw/1 +]). --export([init/1, - gen_salt/1, - hash/2, - check_password/4]). +-export([ + init/1, + gen_salt/1, + hash/2, + check_password/4 +]). namespace() -> "authn-hash". roots() -> [pbkdf2, bcrypt, bcrypt_rw, other_algorithms]. fields(bcrypt_rw) -> fields(bcrypt) ++ - [{salt_rounds, fun salt_rounds/1}]; - + [{salt_rounds, fun salt_rounds/1}]; fields(bcrypt) -> [{name, {enum, [bcrypt]}}]; - fields(pbkdf2) -> - [{name, {enum, [pbkdf2]}}, - {mac_fun, {enum, [md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512]}}, - {iterations, integer()}, - {dk_length, fun dk_length/1}]; - + [ + {name, {enum, [pbkdf2]}}, + {mac_fun, {enum, [md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512]}}, + {iterations, integer()}, + {dk_length, fun dk_length/1} + ]; fields(other_algorithms) -> - [{name, {enum, [plain, md5, sha, sha256, sha512]}}, - {salt_position, fun salt_position/1}]. + [ + {name, {enum, [plain, md5, sha, sha256, sha512]}}, + {salt_position, fun salt_position/1} + ]. salt_position(type) -> {enum, [prefix, suffix]}; salt_position(desc) -> "Specifies whether the password salt is stored as a prefix or the suffix."; @@ -89,47 +100,56 @@ dk_length(_) -> undefined. type_rw(type) -> hoconsc:union(rw_refs()); -type_rw(default) -> #{<<"name">> => sha256, <<"salt_position">> => prefix}; -type_rw(_) -> undefined. +type_rw(default) -> + #{<<"name">> => sha256, <<"salt_position">> => prefix}; +type_rw(_) -> + undefined. type_ro(type) -> hoconsc:union(ro_refs()); -type_ro(default) -> #{<<"name">> => sha256, <<"salt_position">> => prefix}; -type_ro(_) -> undefined. +type_ro(default) -> + #{<<"name">> => sha256, <<"salt_position">> => prefix}; +type_ro(_) -> + undefined. %%------------------------------------------------------------------------------ %% APIs %%------------------------------------------------------------------------------ --spec(init(algorithm()) -> ok). +-spec init(algorithm()) -> ok. init(#{name := bcrypt}) -> {ok, _} = application:ensure_all_started(bcrypt), ok; init(#{name := _Other}) -> ok. - --spec(gen_salt(algorithm_rw()) -> emqx_passwd:salt()). +-spec gen_salt(algorithm_rw()) -> emqx_passwd:salt(). gen_salt(#{name := plain}) -> <<>>; -gen_salt(#{name := bcrypt, - salt_rounds := Rounds}) -> +gen_salt(#{ + name := bcrypt, + salt_rounds := Rounds +}) -> {ok, Salt} = bcrypt:gen_salt(Rounds), list_to_binary(Salt); gen_salt(#{name := Other}) when Other =/= plain, Other =/= bcrypt -> <> = crypto:strong_rand_bytes(16), iolist_to_binary(io_lib:format("~32.16.0b", [X])). - --spec(hash(algorithm_rw(), emqx_passwd:password()) -> {emqx_passwd:hash(), emqx_passwd:salt()}). +-spec hash(algorithm_rw(), emqx_passwd:password()) -> {emqx_passwd:hash(), emqx_passwd:salt()}. hash(#{name := bcrypt, salt_rounds := _} = Algorithm, Password) -> Salt0 = gen_salt(Algorithm), Hash = emqx_passwd:hash({bcrypt, Salt0}, Password), Salt = Hash, {Hash, Salt}; -hash(#{name := pbkdf2, - mac_fun := MacFun, - iterations := Iterations} = Algorithm, Password) -> +hash( + #{ + name := pbkdf2, + mac_fun := MacFun, + iterations := Iterations + } = Algorithm, + Password +) -> Salt = gen_salt(Algorithm), DKLength = maps:get(dk_length, Algorithm, undefined), Hash = emqx_passwd:hash({pbkdf2, MacFun, Salt, Iterations, DKLength}, Password), @@ -139,18 +159,24 @@ hash(#{name := Other, salt_position := SaltPosition} = Algorithm, Password) -> Hash = emqx_passwd:hash({Other, Salt, SaltPosition}, Password), {Hash, Salt}. - --spec(check_password( - algorithm(), - emqx_passwd:salt(), - emqx_passwd:hash(), - emqx_passwd:password()) -> boolean()). +-spec check_password( + algorithm(), + emqx_passwd:salt(), + emqx_passwd:hash(), + emqx_passwd:password() +) -> boolean(). check_password(#{name := bcrypt}, _Salt, PasswordHash, Password) -> emqx_passwd:check_pass({bcrypt, PasswordHash}, PasswordHash, Password); -check_password(#{name := pbkdf2, - mac_fun := MacFun, - iterations := Iterations} = Algorithm, - Salt, PasswordHash, Password) -> +check_password( + #{ + name := pbkdf2, + mac_fun := MacFun, + iterations := Iterations + } = Algorithm, + Salt, + PasswordHash, + Password +) -> DKLength = maps:get(dk_length, Algorithm, undefined), emqx_passwd:check_pass({pbkdf2, MacFun, Salt, Iterations, DKLength}, PasswordHash, Password); check_password(#{name := Other, salt_position := SaltPosition}, Salt, PasswordHash, Password) -> @@ -161,11 +187,15 @@ check_password(#{name := Other, salt_position := SaltPosition}, Salt, PasswordHa %%------------------------------------------------------------------------------ rw_refs() -> - [hoconsc:ref(?MODULE, bcrypt_rw), - hoconsc:ref(?MODULE, pbkdf2), - hoconsc:ref(?MODULE, other_algorithms)]. + [ + hoconsc:ref(?MODULE, bcrypt_rw), + hoconsc:ref(?MODULE, pbkdf2), + hoconsc:ref(?MODULE, other_algorithms) + ]. ro_refs() -> - [hoconsc:ref(?MODULE, bcrypt), - hoconsc:ref(?MODULE, pbkdf2), - hoconsc:ref(?MODULE, other_algorithms)]. + [ + hoconsc:ref(?MODULE, bcrypt), + hoconsc:ref(?MODULE, pbkdf2), + hoconsc:ref(?MODULE, other_algorithms) + ]. diff --git a/apps/emqx_authn/src/emqx_authn_schema.erl b/apps/emqx_authn/src/emqx_authn_schema.erl index b8f8dacfd..0c227973b 100644 --- a/apps/emqx_authn/src/emqx_authn_schema.erl +++ b/apps/emqx_authn/src/emqx_authn_schema.erl @@ -20,21 +20,20 @@ -include_lib("typerefl/include/types.hrl"). -import(hoconsc, [mk/2, ref/2]). --export([ common_fields/0 - , roots/0 - , fields/1 - , authenticator_type/0 - , root_type/0 - , mechanism/1 - , backend/1 - ]). +-export([ + common_fields/0, + roots/0, + fields/1, + authenticator_type/0, + root_type/0, + mechanism/1, + backend/1 +]). roots() -> []. - common_fields() -> - [ {enable, fun enable/1} - ]. + [{enable, fun enable/1}]. enable(type) -> boolean(); enable(default) -> true; @@ -54,46 +53,60 @@ root_type() -> hoconsc:array(authenticator_type()). mechanism(Name) -> - hoconsc:mk(hoconsc:enum([Name]), - #{ required => true - , desc => "Authentication mechanism." - }). + hoconsc:mk( + hoconsc:enum([Name]), + #{ + required => true, + desc => "Authentication mechanism." + } + ). backend(Name) -> - hoconsc:mk(hoconsc:enum([Name]), - #{ required => true - , desc => "Backend type." - }). + hoconsc:mk( + hoconsc:enum([Name]), + #{ + required => true, + desc => "Backend type." + } + ). fields("metrics_status_fields") -> - [ {"metrics", mk(ref(?MODULE, "metrics"), #{desc => "The metrics of the resource"})} - , {"node_metrics", mk(hoconsc:array(ref(?MODULE, "node_metrics")), - #{ desc => "The metrics of the resource for each node" - })} - , {"status", mk(status(), #{desc => "The status of the resource"})} - , {"node_status", mk(hoconsc:array(ref(?MODULE, "node_status")), - #{ desc => "The status of the resource for each node" - })} + [ + {"metrics", mk(ref(?MODULE, "metrics"), #{desc => "The metrics of the resource"})}, + {"node_metrics", + mk( + hoconsc:array(ref(?MODULE, "node_metrics")), + #{desc => "The metrics of the resource for each node"} + )}, + {"status", mk(status(), #{desc => "The status of the resource"})}, + {"node_status", + mk( + hoconsc:array(ref(?MODULE, "node_status")), + #{desc => "The status of the resource for each node"} + )} ]; - fields("metrics") -> - [ {"matched", mk(integer(), #{desc => "Count of this resource is queried"})} - , {"success", mk(integer(), #{desc => "Count of query success"})} - , {"failed", mk(integer(), #{desc => "Count of query failed"})} - , {"rate", mk(float(), #{desc => "The rate of matched, times/second"})} - , {"rate_max", mk(float(), #{desc => "The max rate of matched, times/second"})} - , {"rate_last5m", mk(float(), - #{desc => "The average rate of matched in the last 5 minutes, times/second"})} + [ + {"matched", mk(integer(), #{desc => "Count of this resource is queried"})}, + {"success", mk(integer(), #{desc => "Count of query success"})}, + {"failed", mk(integer(), #{desc => "Count of query failed"})}, + {"rate", mk(float(), #{desc => "The rate of matched, times/second"})}, + {"rate_max", mk(float(), #{desc => "The max rate of matched, times/second"})}, + {"rate_last5m", + mk( + float(), + #{desc => "The average rate of matched in the last 5 minutes, times/second"} + )} ]; - fields("node_metrics") -> - [ node_name() - , {"metrics", mk(ref(?MODULE, "metrics"), #{})} + [ + node_name(), + {"metrics", mk(ref(?MODULE, "metrics"), #{})} ]; - fields("node_status") -> - [ node_name() - , {"status", mk(status(), #{desc => "Status of the node."})} + [ + node_name(), + {"status", mk(status(), #{desc => "Status of the node."})} ]. status() -> diff --git a/apps/emqx_authn/src/emqx_authn_sup.erl b/apps/emqx_authn/src/emqx_authn_sup.erl index 168d60db3..d88acfacc 100644 --- a/apps/emqx_authn/src/emqx_authn_sup.erl +++ b/apps/emqx_authn/src/emqx_authn_sup.erl @@ -18,9 +18,10 @@ -behaviour(supervisor). --export([ start_link/0 - , init/1 - ]). +-export([ + start_link/0, + init/1 +]). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). diff --git a/apps/emqx_authn/src/emqx_authn_utils.erl b/apps/emqx_authn/src/emqx_authn_utils.erl index 7f17215c1..5f2e0100e 100644 --- a/apps/emqx_authn/src/emqx_authn_utils.erl +++ b/apps/emqx_authn/src/emqx_authn_utils.erl @@ -19,26 +19,29 @@ -include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("emqx_authn.hrl"). --export([ check_password_from_selected_map/3 - , parse_deep/1 - , parse_str/1 - , parse_sql/2 - , render_deep/2 - , render_str/2 - , render_sql_params/2 - , is_superuser/1 - , bin/1 - , ensure_apps_started/1 - , cleanup_resources/0 - , make_resource_id/1 - ]). +-export([ + check_password_from_selected_map/3, + parse_deep/1, + parse_str/1, + parse_sql/2, + render_deep/2, + render_str/2, + render_sql_params/2, + is_superuser/1, + bin/1, + ensure_apps_started/1, + cleanup_resources/0, + make_resource_id/1 +]). --define(AUTHN_PLACEHOLDERS, [?PH_USERNAME, - ?PH_CLIENTID, - ?PH_PASSWORD, - ?PH_PEERHOST, - ?PH_CERT_SUBJECT, - ?PH_CERT_CN_NAME]). +-define(AUTHN_PLACEHOLDERS, [ + ?PH_USERNAME, + ?PH_CLIENTID, + ?PH_PASSWORD, + ?PH_PEERHOST, + ?PH_CERT_SUBJECT, + ?PH_CERT_CN_NAME +]). %%------------------------------------------------------------------------------ %% APIs @@ -47,12 +50,12 @@ check_password_from_selected_map(_Algorithm, _Selected, undefined) -> {error, bad_username_or_password}; check_password_from_selected_map( - Algorithm, #{<<"password_hash">> := Hash} = Selected, Password) -> + Algorithm, #{<<"password_hash">> := Hash} = Selected, Password +) -> Salt = maps:get(<<"salt">>, Selected, <<>>), case emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, Password) of true -> ok; - false -> - {error, bad_username_or_password} + false -> {error, bad_username_or_password} end. parse_deep(Template) -> @@ -63,27 +66,33 @@ parse_str(Template) -> parse_sql(Template, ReplaceWith) -> emqx_placeholder:preproc_sql( - Template, - #{replace_with => ReplaceWith, - placeholders => ?AUTHN_PLACEHOLDERS}). + Template, + #{ + replace_with => ReplaceWith, + placeholders => ?AUTHN_PLACEHOLDERS + } + ). render_deep(Template, Credential) -> emqx_placeholder:proc_tmpl_deep( - Template, - Credential, - #{return => full_binary, var_trans => fun handle_var/2}). + Template, + Credential, + #{return => full_binary, var_trans => fun handle_var/2} + ). render_str(Template, Credential) -> emqx_placeholder:proc_tmpl( - Template, - Credential, - #{return => full_binary, var_trans => fun handle_var/2}). + Template, + Credential, + #{return => full_binary, var_trans => fun handle_var/2} + ). render_sql_params(ParamList, Credential) -> emqx_placeholder:proc_tmpl( - ParamList, - Credential, - #{return => rawlist, var_trans => fun handle_sql_var/2}). + ParamList, + Credential, + #{return => rawlist, var_trans => fun handle_sql_var/2} + ). is_superuser(#{<<"is_superuser">> := <<"">>}) -> #{is_superuser => false}; @@ -114,8 +123,9 @@ bin(X) -> X. cleanup_resources() -> lists:foreach( - fun emqx_resource:remove_local/1, - emqx_resource:list_group_instances(?RESOURCE_GROUP)). + fun emqx_resource:remove_local/1, + emqx_resource:list_group_instances(?RESOURCE_GROUP) + ). make_resource_id(Name) -> NameBin = bin(Name), diff --git a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl b/apps/emqx_authn/test/emqx_authn_api_SUITE.erl index c28a9dc33..7889143f3 100644 --- a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_api_SUITE.erl @@ -26,12 +26,12 @@ -define(TCP_DEFAULT, 'tcp:default'). --define( - assertAuthenticatorsMatch(Guard, Path), +-define(assertAuthenticatorsMatch(Guard, Path), (fun() -> {ok, 200, Response} = request(get, uri(Path)), ?assertMatch(Guard, jiffy:decode(Response, [return_maps])) - end)()). + end)() +). all() -> emqx_common_test_helpers:all(?MODULE). @@ -42,12 +42,14 @@ groups() -> init_per_testcase(_, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authn_test_lib:delete_authenticators( - [?CONF_NS_ATOM], - ?GLOBAL), + [?CONF_NS_ATOM], + ?GLOBAL + ), emqx_authn_test_lib:delete_authenticators( - [listeners, tcp, default, ?CONF_NS_ATOM], - ?TCP_DEFAULT), + [listeners, tcp, default, ?CONF_NS_ATOM], + ?TCP_DEFAULT + ), {atomic, ok} = mria:clear_table(emqx_authn_mnesia), Config. @@ -55,8 +57,9 @@ init_per_testcase(_, Config) -> init_per_suite(Config) -> _ = application:load(emqx_conf), ok = emqx_common_test_helpers:start_apps( - [emqx_authn, emqx_dashboard], - fun set_special_configs/1), + [emqx_authn, emqx_dashboard], + fun set_special_configs/1 + ), ?AUTHN:delete_chain(?GLOBAL), {ok, Chains} = ?AUTHN:list_chains(), @@ -117,108 +120,132 @@ t_listener_authenticator_import_users(_) -> test_authenticator_import_users(["listeners", ?TCP_DEFAULT]). test_authenticators(PathPrefix) -> - ValidConfig = emqx_authn_test_lib:http_example(), {ok, 200, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS]), - ValidConfig), + post, + uri(PathPrefix ++ [?CONF_NS]), + ValidConfig + ), {ok, 409, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS]), - ValidConfig), + post, + uri(PathPrefix ++ [?CONF_NS]), + ValidConfig + ), InvalidConfig0 = ValidConfig#{method => <<"delete">>}, {ok, 400, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS]), - InvalidConfig0), + post, + uri(PathPrefix ++ [?CONF_NS]), + InvalidConfig0 + ), - InvalidConfig1 = ValidConfig#{method => <<"get">>, - headers => #{<<"content-type">> => <<"application/json">>}}, + InvalidConfig1 = ValidConfig#{ + method => <<"get">>, + headers => #{<<"content-type">> => <<"application/json">>} + }, {ok, 400, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS]), - InvalidConfig1), + post, + uri(PathPrefix ++ [?CONF_NS]), + InvalidConfig1 + ), ?assertAuthenticatorsMatch( - [#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}], - PathPrefix ++ [?CONF_NS]). + [#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}], + PathPrefix ++ [?CONF_NS] + ). test_authenticator(PathPrefix) -> ValidConfig0 = emqx_authn_test_lib:http_example(), {ok, 200, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS]), - ValidConfig0), + post, + uri(PathPrefix ++ [?CONF_NS]), + ValidConfig0 + ), {ok, 200, _} = request( - get, - uri(PathPrefix ++ [?CONF_NS, "password_based:http"])), + get, + uri(PathPrefix ++ [?CONF_NS, "password_based:http"]) + ), {ok, 200, Res} = request( - get, - uri(PathPrefix ++ [?CONF_NS, "password_based:http", "status"])), + get, + uri(PathPrefix ++ [?CONF_NS, "password_based:http", "status"]) + ), {ok, RList} = emqx_json:safe_decode(Res), - Snd = fun ({_, Val}) -> Val end, + Snd = fun({_, Val}) -> Val end, LookupVal = fun LookupV(List, RestJson) -> - case List of - [Name] -> Snd(lists:keyfind(Name, 1, RestJson)); - [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson))) - end - end, - LookFun = fun (List) -> LookupVal(List, RList) end, - MetricsList = [{<<"failed">>, 0}, - {<<"matched">>, 0}, - {<<"rate">>, 0.0}, - {<<"rate_last5m">>, 0.0}, - {<<"rate_max">>, 0.0}, - {<<"success">>, 0}], - EqualFun = fun ({M, V}) -> - ?assertEqual(V, LookFun([<<"metrics">>, - M] - ) - ) end, + case List of + [Name] -> Snd(lists:keyfind(Name, 1, RestJson)); + [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson))) + end + end, + LookFun = fun(List) -> LookupVal(List, RList) end, + MetricsList = [ + {<<"failed">>, 0}, + {<<"matched">>, 0}, + {<<"rate">>, 0.0}, + {<<"rate_last5m">>, 0.0}, + {<<"rate_max">>, 0.0}, + {<<"success">>, 0} + ], + EqualFun = fun({M, V}) -> + ?assertEqual( + V, + LookFun([ + <<"metrics">>, + M + ]) + ) + end, lists:map(EqualFun, MetricsList), - ?assertEqual(<<"connected">>, - LookFun([<<"status">> - ])), + ?assertEqual( + <<"connected">>, + LookFun([<<"status">>]) + ), {ok, 404, _} = request( - get, - uri(PathPrefix ++ [?CONF_NS, "password_based:redis"])), - + get, + uri(PathPrefix ++ [?CONF_NS, "password_based:redis"]) + ), {ok, 404, _} = request( - put, - uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database"]), - emqx_authn_test_lib:built_in_database_example()), + put, + uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database"]), + emqx_authn_test_lib:built_in_database_example() + ), InvalidConfig0 = ValidConfig0#{method => <<"delete">>}, {ok, 400, _} = request( - put, - uri(PathPrefix ++ [?CONF_NS, "password_based:http"]), - InvalidConfig0), + put, + uri(PathPrefix ++ [?CONF_NS, "password_based:http"]), + InvalidConfig0 + ), - InvalidConfig1 = ValidConfig0#{method => <<"get">>, - headers => #{<<"content-type">> => <<"application/json">>}}, + InvalidConfig1 = ValidConfig0#{ + method => <<"get">>, + headers => #{<<"content-type">> => <<"application/json">>} + }, {ok, 400, _} = request( - put, - uri(PathPrefix ++ [?CONF_NS, "password_based:http"]), - InvalidConfig1), + put, + uri(PathPrefix ++ [?CONF_NS, "password_based:http"]), + InvalidConfig1 + ), ValidConfig1 = ValidConfig0#{pool_size => 9}, {ok, 200, _} = request( - put, - uri(PathPrefix ++ [?CONF_NS, "password_based:http"]), - ValidConfig1), + put, + uri(PathPrefix ++ [?CONF_NS, "password_based:http"]), + ValidConfig1 + ), {ok, 404, _} = request( - delete, - uri(PathPrefix ++ [?CONF_NS, "password_based:redis"])), + delete, + uri(PathPrefix ++ [?CONF_NS, "password_based:redis"]) + ), {ok, 204, _} = request( - delete, - uri(PathPrefix ++ [?CONF_NS, "password_based:http"])), + delete, + uri(PathPrefix ++ [?CONF_NS, "password_based:http"]) + ), ?assertAuthenticatorsMatch([], PathPrefix ++ [?CONF_NS]). @@ -226,64 +253,78 @@ test_authenticator_users(PathPrefix) -> UsersUri = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "users"]), {ok, 200, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS]), - emqx_authn_test_lib:built_in_database_example()), + post, + uri(PathPrefix ++ [?CONF_NS]), + emqx_authn_test_lib:built_in_database_example() + ), InvalidUsers = [ - #{clientid => <<"u1">>, password => <<"p1">>}, - #{user_id => <<"u2">>}, - #{user_id => <<"u3">>, password => <<"p3">>, foobar => <<"foobar">>}], + #{clientid => <<"u1">>, password => <<"p1">>}, + #{user_id => <<"u2">>}, + #{user_id => <<"u3">>, password => <<"p3">>, foobar => <<"foobar">>} + ], lists:foreach( - fun(User) -> {ok, 400, _} = request(post, UsersUri, User) end, - InvalidUsers), - + fun(User) -> {ok, 400, _} = request(post, UsersUri, User) end, + InvalidUsers + ), ValidUsers = [ - #{user_id => <<"u1">>, password => <<"p1">>}, - #{user_id => <<"u2">>, password => <<"p2">>, is_superuser => true}, - #{user_id => <<"u3">>, password => <<"p3">>}], + #{user_id => <<"u1">>, password => <<"p1">>}, + #{user_id => <<"u2">>, password => <<"p2">>, is_superuser => true}, + #{user_id => <<"u3">>, password => <<"p3">>} + ], lists:foreach( - fun(User) -> - {ok, 201, UserData} = request(post, UsersUri, User), - CreatedUser = jiffy:decode(UserData, [return_maps]), - ?assertMatch(#{<<"user_id">> := _}, CreatedUser) - end, - ValidUsers), + fun(User) -> + {ok, 201, UserData} = request(post, UsersUri, User), + CreatedUser = jiffy:decode(UserData, [return_maps]), + ?assertMatch(#{<<"user_id">> := _}, CreatedUser) + end, + ValidUsers + ), {ok, 200, Page1Data} = request(get, UsersUri ++ "?page=1&limit=2"), - #{<<"data">> := Page1Users, - <<"meta">> := - #{<<"page">> := 1, - <<"limit">> := 2, - <<"count">> := 3}} = - jiffy:decode(Page1Data, [return_maps]), + #{ + <<"data">> := Page1Users, + <<"meta">> := + #{ + <<"page">> := 1, + <<"limit">> := 2, + <<"count">> := 3 + } + } = + jiffy:decode(Page1Data, [return_maps]), {ok, 200, Page2Data} = request(get, UsersUri ++ "?page=2&limit=2"), - #{<<"data">> := Page2Users, - <<"meta">> := - #{<<"page">> := 2, - <<"limit">> := 2, - <<"count">> := 3}} = jiffy:decode(Page2Data, [return_maps]), + #{ + <<"data">> := Page2Users, + <<"meta">> := + #{ + <<"page">> := 2, + <<"limit">> := 2, + <<"count">> := 3 + } + } = jiffy:decode(Page2Data, [return_maps]), ?assertEqual(2, length(Page1Users)), ?assertEqual(1, length(Page2Users)), ?assertEqual( - [<<"u1">>, <<"u2">>, <<"u3">>], - lists:usort([ UserId || #{<<"user_id">> := UserId} <- Page1Users ++ Page2Users])). + [<<"u1">>, <<"u2">>, <<"u3">>], + lists:usort([UserId || #{<<"user_id">> := UserId} <- Page1Users ++ Page2Users]) + ). test_authenticator_user(PathPrefix) -> UsersUri = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "users"]), {ok, 200, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS]), - emqx_authn_test_lib:built_in_database_example()), + post, + uri(PathPrefix ++ [?CONF_NS]), + emqx_authn_test_lib:built_in_database_example() + ), User = #{user_id => <<"u1">>, password => <<"p1">>}, {ok, 201, _} = request(post, UsersUri, User), @@ -299,141 +340,161 @@ test_authenticator_user(PathPrefix) -> ?assertNotMatch(#{<<"password">> := _}, FetchedUser), ValidUserUpdates = [ - #{password => <<"p1">>}, - #{password => <<"p1">>, is_superuser => true}], + #{password => <<"p1">>}, + #{password => <<"p1">>, is_superuser => true} + ], lists:foreach( - fun(UserUpdate) -> {ok, 200, _} = request(put, UsersUri ++ "/u1", UserUpdate) end, - ValidUserUpdates), + fun(UserUpdate) -> {ok, 200, _} = request(put, UsersUri ++ "/u1", UserUpdate) end, + ValidUserUpdates + ), InvalidUserUpdates = [#{user_id => <<"u1">>, password => <<"p1">>}], lists:foreach( - fun(UserUpdate) -> {ok, 400, _} = request(put, UsersUri ++ "/u1", UserUpdate) end, - InvalidUserUpdates), + fun(UserUpdate) -> {ok, 400, _} = request(put, UsersUri ++ "/u1", UserUpdate) end, + InvalidUserUpdates + ), {ok, 404, _} = request(delete, UsersUri ++ "/u123"), {ok, 204, _} = request(delete, UsersUri ++ "/u1"). test_authenticator_move(PathPrefix) -> AuthenticatorConfs = [ - emqx_authn_test_lib:http_example(), - emqx_authn_test_lib:jwt_example(), - emqx_authn_test_lib:built_in_database_example() - ], + emqx_authn_test_lib:http_example(), + emqx_authn_test_lib:jwt_example(), + emqx_authn_test_lib:built_in_database_example() + ], lists:foreach( - fun(Conf) -> - {ok, 200, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS]), - Conf) - end, - AuthenticatorConfs), + fun(Conf) -> + {ok, 200, _} = request( + post, + uri(PathPrefix ++ [?CONF_NS]), + Conf + ) + end, + AuthenticatorConfs + ), ?assertAuthenticatorsMatch( - [ - #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}, - #{<<"mechanism">> := <<"jwt">>}, - #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>} - ], - PathPrefix ++ [?CONF_NS]), + [ + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}, + #{<<"mechanism">> := <<"jwt">>}, + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>} + ], + PathPrefix ++ [?CONF_NS] + ), %% Invalid moves {ok, 400, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), - #{position => <<"up">>}), + post, + uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), + #{position => <<"up">>} + ), {ok, 400, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), - #{}), + post, + uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), + #{} + ), {ok, 404, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), - #{position => <<"before:invalid">>}), + post, + uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), + #{position => <<"before:invalid">>} + ), {ok, 404, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), - #{position => <<"before:password_based:redis">>}), + post, + uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), + #{position => <<"before:password_based:redis">>} + ), {ok, 404, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), - #{position => <<"before:password_based:redis">>}), + post, + uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), + #{position => <<"before:password_based:redis">>} + ), %% Valid moves %% test front {ok, 204, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), - #{position => <<"front">>}), + post, + uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), + #{position => <<"front">>} + ), ?assertAuthenticatorsMatch( - [ - #{<<"mechanism">> := <<"jwt">>}, - #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}, - #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>} - ], - PathPrefix ++ [?CONF_NS]), + [ + #{<<"mechanism">> := <<"jwt">>}, + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}, + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>} + ], + PathPrefix ++ [?CONF_NS] + ), %% test rear {ok, 204, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), - #{position => <<"rear">>}), + post, + uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), + #{position => <<"rear">>} + ), ?assertAuthenticatorsMatch( - [ - #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}, - #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>}, - #{<<"mechanism">> := <<"jwt">>} - ], - PathPrefix ++ [?CONF_NS]), + [ + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}, + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>}, + #{<<"mechanism">> := <<"jwt">>} + ], + PathPrefix ++ [?CONF_NS] + ), %% test before {ok, 204, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), - #{position => <<"before:password_based:built_in_database">>}), + post, + uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), + #{position => <<"before:password_based:built_in_database">>} + ), ?assertAuthenticatorsMatch( - [ - #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}, - #{<<"mechanism">> := <<"jwt">>}, - #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>} - ], - PathPrefix ++ [?CONF_NS]), + [ + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}, + #{<<"mechanism">> := <<"jwt">>}, + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>} + ], + PathPrefix ++ [?CONF_NS] + ), %% test after {ok, 204, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS, "password_based%3Abuilt_in_database", "move"]), - #{position => <<"after:password_based:http">>}), + post, + uri(PathPrefix ++ [?CONF_NS, "password_based%3Abuilt_in_database", "move"]), + #{position => <<"after:password_based:http">>} + ), ?assertAuthenticatorsMatch( - [ - #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}, - #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>}, - #{<<"mechanism">> := <<"jwt">>} - ], - PathPrefix ++ [?CONF_NS]). + [ + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}, + #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>}, + #{<<"mechanism">> := <<"jwt">>} + ], + PathPrefix ++ [?CONF_NS] + ). test_authenticator_import_users(PathPrefix) -> ImportUri = uri( - PathPrefix ++ - [?CONF_NS, "password_based:built_in_database", "import_users"]), - + PathPrefix ++ + [?CONF_NS, "password_based:built_in_database", "import_users"] + ), {ok, 200, _} = request( - post, - uri(PathPrefix ++ [?CONF_NS]), - emqx_authn_test_lib:built_in_database_example()), + post, + uri(PathPrefix ++ [?CONF_NS]), + emqx_authn_test_lib:built_in_database_example() + ), {ok, 400, _} = request(post, ImportUri, #{}), diff --git a/apps/emqx_authn/test/emqx_authn_http_SUITE.erl b/apps/emqx_authn/test/emqx_authn_http_SUITE.erl index c42aef019..2ccb0c9df 100644 --- a/apps/emqx_authn/test/emqx_authn_http_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_http_SUITE.erl @@ -28,12 +28,12 @@ -define(HTTP_PORT, 33333). -define(HTTP_PATH, "/auth"). --define(CREDENTIALS, #{username => <<"plain">>, - password => <<"plain">>, - listener => 'tcp:default', - protocol => mqtt - }). - +-define(CREDENTIALS, #{ + username => <<"plain">>, + password => <<"plain">>, + listener => 'tcp:default', + protocol => mqtt +}). all() -> emqx_common_test_helpers:all(?MODULE). @@ -46,8 +46,9 @@ init_per_suite(Config) -> end_per_suite(_) -> emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), emqx_common_test_helpers:stop_apps([emqx_authn]), application:stop(cowboy), ok. @@ -55,8 +56,9 @@ end_per_suite(_) -> init_per_testcase(_Case, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), {ok, _} = emqx_authn_http_test_server:start_link(?HTTP_PORT, ?HTTP_PATH), Config. @@ -71,8 +73,9 @@ t_create(_Config) -> AuthConfig = raw_http_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), {ok, [#{provider := emqx_authn_http}]} = emqx_authentication:list_authenticators(?GLOBAL). @@ -81,83 +84,96 @@ t_create_invalid(_Config) -> InvalidConfigs = [ - AuthConfig#{headers => []}, - AuthConfig#{method => delete} + AuthConfig#{headers => []}, + AuthConfig#{method => delete} ], lists:foreach( - fun(Config) -> - ct:pal("creating authenticator with invalid config: ~p", [Config]), - {error, _} = - try - emqx:update_config( + fun(Config) -> + ct:pal("creating authenticator with invalid config: ~p", [Config]), + {error, _} = + try + emqx:update_config( ?PATH, - {create_authenticator, ?GLOBAL, Config}) - catch - throw:Error -> - {error, Error} - end, - {ok, []} = emqx_authentication:list_authenticators(?GLOBAL) - end, - InvalidConfigs). + {create_authenticator, ?GLOBAL, Config} + ) + catch + throw:Error -> + {error, Error} + end, + {ok, []} = emqx_authentication:list_authenticators(?GLOBAL) + end, + InvalidConfigs + ). t_authenticate(_Config) -> ok = lists:foreach( - fun(Sample) -> - ct:pal("test_user_auth sample: ~p", [Sample]), - test_user_auth(Sample) - end, - samples()). + fun(Sample) -> + ct:pal("test_user_auth sample: ~p", [Sample]), + test_user_auth(Sample) + end, + samples() + ). -test_user_auth(#{handler := Handler, - config_params := SpecificConfgParams, - result := Result}) -> +test_user_auth(#{ + handler := Handler, + config_params := SpecificConfgParams, + result := Result +}) -> AuthConfig = maps:merge(raw_http_auth_config(), SpecificConfgParams), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), ok = emqx_authn_http_test_server:set_handler(Handler), ?assertEqual(Result, emqx_access_control:authenticate(?CREDENTIALS)), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL). + [authentication], + ?GLOBAL + ). t_destroy(_Config) -> AuthConfig = raw_http_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), ok = emqx_authn_http_test_server:set_handler( - fun(Req0, State) -> - Req = cowboy_req:reply(200, Req0), - {ok, Req, State} - end), + fun(Req0, State) -> + Req = cowboy_req:reply(200, Req0), + {ok, Req, State} + end + ), - {ok, [#{provider := emqx_authn_http, state := State}]} - = emqx_authentication:list_authenticators(?GLOBAL), + {ok, [#{provider := emqx_authn_http, state := State}]} = + emqx_authentication:list_authenticators(?GLOBAL), Credentials = maps:with([username, password], ?CREDENTIALS), {ok, _} = emqx_authn_http:authenticate( - Credentials, - State), + Credentials, + State + ), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), % Authenticator should not be usable anymore ?assertMatch( - ignore, - emqx_authn_http:authenticate( - Credentials, - State)). + ignore, + emqx_authn_http:authenticate( + Credentials, + State + ) + ). t_update(_Config) -> CorrectConfig = raw_http_auth_config(), @@ -165,74 +181,80 @@ t_update(_Config) -> CorrectConfig#{url => <<"http://127.0.0.1:33333/invalid">>}, {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, IncorrectConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, IncorrectConfig} + ), ok = emqx_authn_http_test_server:set_handler( - fun(Req0, State) -> - Req = cowboy_req:reply(200, Req0), - {ok, Req, State} - end), + fun(Req0, State) -> + Req = cowboy_req:reply(200, Req0), + {ok, Req, State} + end + ), {error, not_authorized} = emqx_access_control:authenticate(?CREDENTIALS), % We update with config with correct query, provider should update and work properly {ok, _} = emqx:update_config( - ?PATH, - {update_authenticator, ?GLOBAL, <<"password_based:http">>, CorrectConfig}), + ?PATH, + {update_authenticator, ?GLOBAL, <<"password_based:http">>, CorrectConfig} + ), - {ok,_} = emqx_access_control:authenticate(?CREDENTIALS). + {ok, _} = emqx_access_control:authenticate(?CREDENTIALS). t_is_superuser(_Config) -> Config = raw_http_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, Config}), + ?PATH, + {create_authenticator, ?GLOBAL, Config} + ), Checks = [ - {json, <<"0">>, false}, - {json, <<"">>, false}, - {json, null, false}, - {json, 0, false}, + {json, <<"0">>, false}, + {json, <<"">>, false}, + {json, null, false}, + {json, 0, false}, - {json, <<"1">>, true}, - {json, <<"val">>, true}, - {json, 1, true}, - {json, 123, true}, + {json, <<"1">>, true}, + {json, <<"val">>, true}, + {json, 1, true}, + {json, 123, true}, - {form, <<"0">>, false}, - {form, <<"">>, false}, + {form, <<"0">>, false}, + {form, <<"">>, false}, - {form, <<"1">>, true}, - {form, <<"val">>, true} - ], + {form, <<"1">>, true}, + {form, <<"val">>, true} + ], lists:foreach(fun test_is_superuser/1, Checks). test_is_superuser({Kind, Value, ExpectedValue}) -> - - {ContentType, Res} = case Kind of - json -> - {<<"application/json">>, - jiffy:encode(#{is_superuser => Value})}; - form -> - {<<"application/x-www-form-urlencoded">>, - iolist_to_binary([<<"is_superuser=">>, Value])} - end, + {ContentType, Res} = + case Kind of + json -> + {<<"application/json">>, jiffy:encode(#{is_superuser => Value})}; + form -> + {<<"application/x-www-form-urlencoded">>, + iolist_to_binary([<<"is_superuser=">>, Value])} + end, ok = emqx_authn_http_test_server:set_handler( - fun(Req0, State) -> - Req = cowboy_req:reply( - 200, - #{<<"content-type">> => ContentType}, - Res, - Req0), - {ok, Req, State} - end), + fun(Req0, State) -> + Req = cowboy_req:reply( + 200, + #{<<"content-type">> => ContentType}, + Res, + Req0 + ), + {ok, Req, State} + end + ), ?assertMatch( - {ok, #{is_superuser := ExpectedValue}}, - emqx_access_control:authenticate(?CREDENTIALS)). + {ok, #{is_superuser := ExpectedValue}}, + emqx_access_control:authenticate(?CREDENTIALS) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -252,138 +274,159 @@ raw_http_auth_config() -> samples() -> [ - %% simple get request - #{handler => fun(Req0, State) -> - #{username := <<"plain">>, - password := <<"plain">> - } = cowboy_req:match_qs([username, password], Req0), + %% simple get request + #{ + handler => fun(Req0, State) -> + #{ + username := <<"plain">>, + password := <<"plain">> + } = cowboy_req:match_qs([username, password], Req0), - Req = cowboy_req:reply(200, Req0), - {ok, Req, State} - end, - config_params => #{}, - result => {ok,#{is_superuser => false}} - }, + Req = cowboy_req:reply(200, Req0), + {ok, Req, State} + end, + config_params => #{}, + result => {ok, #{is_superuser => false}} + }, - %% get request with json body response - #{handler => fun(Req0, State) -> - Req = cowboy_req:reply( - 200, - #{<<"content-type">> => <<"application/json">>}, - jiffy:encode(#{is_superuser => true}), - Req0), - {ok, Req, State} - end, - config_params => #{}, - result => {ok,#{is_superuser => true, user_property => #{}}} - }, + %% get request with json body response + #{ + handler => fun(Req0, State) -> + Req = cowboy_req:reply( + 200, + #{<<"content-type">> => <<"application/json">>}, + jiffy:encode(#{is_superuser => true}), + Req0 + ), + {ok, Req, State} + end, + config_params => #{}, + result => {ok, #{is_superuser => true, user_property => #{}}} + }, - %% get request with url-form-encoded body response - #{handler => fun(Req0, State) -> - Req = cowboy_req:reply( - 200, - #{<<"content-type">> => - <<"application/x-www-form-urlencoded">>}, - <<"is_superuser=true">>, - Req0), - {ok, Req, State} - end, - config_params => #{}, - result => {ok,#{is_superuser => true, user_property => #{}}} - }, + %% get request with url-form-encoded body response + #{ + handler => fun(Req0, State) -> + Req = cowboy_req:reply( + 200, + #{ + <<"content-type">> => + <<"application/x-www-form-urlencoded">> + }, + <<"is_superuser=true">>, + Req0 + ), + {ok, Req, State} + end, + config_params => #{}, + result => {ok, #{is_superuser => true, user_property => #{}}} + }, - %% get request with response of unknown encoding - #{handler => fun(Req0, State) -> - Req = cowboy_req:reply( - 200, - #{<<"content-type">> => - <<"test/plain">>}, - <<"is_superuser=true">>, - Req0), - {ok, Req, State} - end, - config_params => #{}, - result => {ok,#{is_superuser => false}} - }, + %% get request with response of unknown encoding + #{ + handler => fun(Req0, State) -> + Req = cowboy_req:reply( + 200, + #{ + <<"content-type">> => + <<"test/plain">> + }, + <<"is_superuser=true">>, + Req0 + ), + {ok, Req, State} + end, + config_params => #{}, + result => {ok, #{is_superuser => false}} + }, - %% simple post request, application/json - #{handler => fun(Req0, State) -> - {ok, RawBody, Req1} = cowboy_req:read_body(Req0), - #{<<"username">> := <<"plain">>, - <<"password">> := <<"plain">> - } = jiffy:decode(RawBody, [return_maps]), - Req = cowboy_req:reply(200, Req1), - {ok, Req, State} - end, - config_params => #{ - method => post, - headers => #{<<"content-type">> => <<"application/json">>} - }, - result => {ok,#{is_superuser => false}} - }, + %% simple post request, application/json + #{ + handler => fun(Req0, State) -> + {ok, RawBody, Req1} = cowboy_req:read_body(Req0), + #{ + <<"username">> := <<"plain">>, + <<"password">> := <<"plain">> + } = jiffy:decode(RawBody, [return_maps]), + Req = cowboy_req:reply(200, Req1), + {ok, Req, State} + end, + config_params => #{ + method => post, + headers => #{<<"content-type">> => <<"application/json">>} + }, + result => {ok, #{is_superuser => false}} + }, - %% simple post request, application/x-www-form-urlencoded - #{handler => fun(Req0, State) -> - {ok, PostVars, Req1} = cowboy_req:read_urlencoded_body(Req0), - #{<<"username">> := <<"plain">>, - <<"password">> := <<"plain">> - } = maps:from_list(PostVars), - Req = cowboy_req:reply(200, Req1), - {ok, Req, State} - end, - config_params => #{ - method => post, - headers => #{<<"content-type">> => - <<"application/x-www-form-urlencoded">>} - }, - result => {ok,#{is_superuser => false}} - } + %% simple post request, application/x-www-form-urlencoded + #{ + handler => fun(Req0, State) -> + {ok, PostVars, Req1} = cowboy_req:read_urlencoded_body(Req0), + #{ + <<"username">> := <<"plain">>, + <<"password">> := <<"plain">> + } = maps:from_list(PostVars), + Req = cowboy_req:reply(200, Req1), + {ok, Req, State} + end, + config_params => #{ + method => post, + headers => #{ + <<"content-type">> => + <<"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}} + }, - %% 204 code - #{handler => fun(Req0, State) -> - Req = cowboy_req:reply(204, Req0), - {ok, Req, State} - end, - config_params => #{}, - result => {ok,#{is_superuser => false}} - }, + %% custom headers + #{ + handler => fun(Req0, State) -> + <<"Test Value">> = cowboy_req:header(<<"x-test-header">>, Req0), + Req = cowboy_req:reply(200, Req0), + {ok, Req, State} + end, + config_params => #{}, + result => {ok, #{is_superuser => false}} + }, - %% custom headers - #{handler => fun(Req0, State) -> - <<"Test Value">> = cowboy_req:header(<<"x-test-header">>, Req0), - Req = cowboy_req:reply(200, Req0), - {ok, Req, State} - end, - config_params => #{}, - result => {ok,#{is_superuser => false}} - }, + %% 400 code + #{ + handler => fun(Req0, State) -> + Req = cowboy_req:reply(400, Req0), + {ok, Req, State} + end, + config_params => #{}, + result => {error, not_authorized} + }, - %% 400 code - #{handler => fun(Req0, State) -> - Req = cowboy_req:reply(400, Req0), - {ok, Req, State} - end, - config_params => #{}, - result => {error,not_authorized} - }, + %% 500 code + #{ + handler => fun(Req0, State) -> + Req = cowboy_req:reply(500, Req0), + {ok, Req, State} + end, + config_params => #{}, + result => {error, not_authorized} + }, - %% 500 code - #{handler => fun(Req0, State) -> - Req = cowboy_req:reply(500, Req0), - {ok, Req, State} - end, - config_params => #{}, - result => {error,not_authorized} - }, - - %% Handling error - #{handler => fun(Req0, State) -> - error(woops), - {ok, Req0, State} - end, - config_params => #{}, - result => {error,not_authorized} - } + %% Handling error + #{ + handler => fun(Req0, State) -> + error(woops), + {ok, Req0, State} + end, + config_params => #{}, + result => {error, not_authorized} + } ]. start_apps(Apps) -> diff --git a/apps/emqx_authn/test/emqx_authn_http_test_server.erl b/apps/emqx_authn/test/emqx_authn_http_test_server.erl index e3050a3ae..5f0b3e93b 100644 --- a/apps/emqx_authn/test/emqx_authn_http_test_server.erl +++ b/apps/emqx_authn/test/emqx_authn_http_test_server.erl @@ -26,11 +26,12 @@ -export([init/1]). % API --export([start_link/2, - start_link/3, - stop/0, - set_handler/1 - ]). +-export([ + start_link/2, + start_link/3, + stop/0, + set_handler/1 +]). %%------------------------------------------------------------------------------ %% API @@ -55,10 +56,11 @@ set_handler(F) when is_function(F, 2) -> init([Port, Path, SSLOpts]) -> Dispatch = cowboy_router:compile( - [ - {'_', [{Path, ?MODULE, []}]} - ]), - + [ + {'_', [{Path, ?MODULE, []}]} + ] + ), + ProtoOpts = #{env => #{dispatch => Dispatch}}, Tab = ets:new(?MODULE, [set, named_table, public]), @@ -83,23 +85,28 @@ init(Req, State) -> %%------------------------------------------------------------------------------ transport_settings(Port, false) -> - TransOpts = #{socket_opts => [{port, Port}], - connection_type => supervisor}, + TransOpts = #{ + socket_opts => [{port, Port}], + connection_type => supervisor + }, {ranch_tcp, TransOpts, cowboy_clear}; - transport_settings(Port, SSLOpts) -> - TransOpts = #{socket_opts => [{port, Port}, - {next_protocols_advertised, [<<"h2">>, <<"http/1.1">>]}, - {alpn_preferred_protocols, [<<"h2">>, <<"http/1.1">>]} - | SSLOpts], - connection_type => supervisor}, + TransOpts = #{ + socket_opts => [ + {port, Port}, + {next_protocols_advertised, [<<"h2">>, <<"http/1.1">>]}, + {alpn_preferred_protocols, [<<"h2">>, <<"http/1.1">>]} + | SSLOpts + ], + connection_type => supervisor + }, {ranch_ssl, TransOpts, cowboy_tls}. default_handler(Req0, State) -> Req = cowboy_req:reply( - 400, - #{<<"content-type">> => <<"text/plain">>}, - <<"">>, - Req0), + 400, + #{<<"content-type">> => <<"text/plain">>}, + <<"">>, + Req0 + ), {ok, Req, State}. - diff --git a/apps/emqx_authn/test/emqx_authn_https_SUITE.erl b/apps/emqx_authn/test/emqx_authn_https_SUITE.erl index 481b5477c..b67f040ea 100644 --- a/apps/emqx_authn/test/emqx_authn_https_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_https_SUITE.erl @@ -28,12 +28,12 @@ -define(HTTPS_PORT, 33333). -define(HTTPS_PATH, "/auth"). --define(CREDENTIALS, #{username => <<"plain">>, - password => <<"plain">>, - listener => 'tcp:default', - protocol => mqtt - }). - +-define(CREDENTIALS, #{ + username => <<"plain">>, + password => <<"plain">>, + listener => 'tcp:default', + protocol => mqtt +}). all() -> emqx_common_test_helpers:all(?MODULE). @@ -46,8 +46,9 @@ init_per_suite(Config) -> end_per_suite(_) -> emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), emqx_common_test_helpers:stop_apps([emqx_authn]), application:stop(cowboy), ok. @@ -55,8 +56,9 @@ end_per_suite(_) -> init_per_testcase(_Case, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), {ok, _} = emqx_authn_http_test_server:start_link(?HTTPS_PORT, ?HTTPS_PATH, server_ssl_opts()), ok = emqx_authn_http_test_server:set_handler(fun cowboy_handler/2), Config. @@ -70,46 +72,62 @@ end_per_testcase(_Case, _Config) -> t_create(_Config) -> {ok, _} = create_https_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.2">>], - <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]}), + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.2">>], + <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>] + } + ), ?assertMatch( - {ok, _}, - emqx_access_control:authenticate(?CREDENTIALS)). + {ok, _}, + emqx_access_control:authenticate(?CREDENTIALS) + ). t_create_invalid_domain(_Config) -> {ok, _} = create_https_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server-unknown-host">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.2">>], - <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]}), + #{ + <<"server_name_indication">> => <<"authn-server-unknown-host">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.2">>], + <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>] + } + ), ?assertEqual( - {error, not_authorized}, - emqx_access_control:authenticate(?CREDENTIALS)). + {error, not_authorized}, + emqx_access_control:authenticate(?CREDENTIALS) + ). t_create_invalid_version(_Config) -> {ok, _} = create_https_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.1">>]}), + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.1">>] + } + ), ?assertEqual( - {error, not_authorized}, - emqx_access_control:authenticate(?CREDENTIALS)). + {error, not_authorized}, + emqx_access_control:authenticate(?CREDENTIALS) + ). t_create_invalid_ciphers(_Config) -> {ok, _} = create_https_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.2">>], - <<"ciphers">> => [<<"ECDHE-ECDSA-AES256-SHA384">>]}), + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.2">>], + <<"ciphers">> => [<<"ECDHE-ECDSA-AES256-SHA384">>] + } + ), ?assertEqual( - {error, not_authorized}, - emqx_access_control:authenticate(?CREDENTIALS)). + {error, not_authorized}, + emqx_access_control:authenticate(?CREDENTIALS) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -121,8 +139,9 @@ create_https_auth_with_ssl_opts(SpecificSSLOpts) -> raw_https_auth_config(SpecificSSLOpts) -> SSLOpts = maps:merge( - emqx_authn_test_lib:client_ssl_cert_opts(), - #{enable => <<"true">>}), + emqx_authn_test_lib:client_ssl_cert_opts(), + #{enable => <<"true">>} + ), #{ mechanism => <<"password_based">>, enable => <<"true">>, @@ -133,7 +152,7 @@ raw_https_auth_config(SpecificSSLOpts) -> body => #{<<"username">> => ?PH_USERNAME, <<"password">> => ?PH_PASSWORD}, headers => #{<<"X-Test-Header">> => <<"Test Value">>}, ssl => maps:merge(SSLOpts, SpecificSSLOpts) - }. + }. start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). @@ -147,15 +166,17 @@ cert_path(FileName) -> cowboy_handler(Req0, State) -> Req = cowboy_req:reply( - 200, - Req0), + 200, + Req0 + ), {ok, Req, State}. server_ssl_opts() -> - [{keyfile, cert_path("server.key")}, - {certfile, cert_path("server.crt")}, - {cacertfile, cert_path("ca.crt")}, - {verify, verify_none}, - {versions, ['tlsv1.2', 'tlsv1.3']}, - {ciphers, ["ECDHE-RSA-AES256-GCM-SHA384", "TLS_CHACHA20_POLY1305_SHA256"]} + [ + {keyfile, cert_path("server.key")}, + {certfile, cert_path("server.crt")}, + {cacertfile, cert_path("ca.crt")}, + {verify, verify_none}, + {versions, ['tlsv1.2', 'tlsv1.3']}, + {ciphers, ["ECDHE-RSA-AES256-GCM-SHA384", "TLS_CHACHA20_POLY1305_SHA256"]} ]. diff --git a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl index ae62530e3..a7b1d3f55 100644 --- a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl @@ -28,7 +28,6 @@ -define(JWKS_PORT, 33333). -define(JWKS_PATH, "/jwks.json"). - all() -> emqx_common_test_helpers:all(?MODULE). @@ -51,24 +50,30 @@ end_per_suite(_) -> t_jwt_authenticator_hmac_based(_) -> Secret = <<"abcdef">>, - Config = #{mechanism => jwt, - use_jwks => false, - algorithm => 'hmac-based', - secret => Secret, - secret_base64_encoded => false, - verify_claims => []}, + Config = #{ + mechanism => jwt, + use_jwks => false, + algorithm => 'hmac-based', + secret => Secret, + secret_base64_encoded => false, + verify_claims => [] + }, {ok, State} = emqx_authn_jwt:create(?AUTHN_ID, Config), Payload = #{<<"username">> => <<"myuser">>}, JWS = generate_jws('hmac-based', Payload, Secret), - Credential = #{username => <<"myuser">>, - password => JWS}, + Credential = #{ + username => <<"myuser">>, + password => JWS + }, ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State)), Payload1 = #{<<"username">> => <<"myuser">>, <<"is_superuser">> => true}, JWS1 = generate_jws('hmac-based', Payload1, Secret), - Credential1 = #{username => <<"myuser">>, - password => JWS1}, + Credential1 = #{ + username => <<"myuser">>, + password => JWS1 + }, ?assertEqual({ok, #{is_superuser => true}}, emqx_authn_jwt:authenticate(Credential1, State)), BadJWS = generate_jws('hmac-based', Payload, <<"bad_secret">>), @@ -76,59 +81,84 @@ t_jwt_authenticator_hmac_based(_) -> ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential2, State)), %% secret_base64_encoded - Config2 = Config#{secret => base64:encode(Secret), - secret_base64_encoded => true}, + Config2 = Config#{ + secret => base64:encode(Secret), + secret_base64_encoded => true + }, {ok, State2} = emqx_authn_jwt:update(Config2, State), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State2)), %% invalid secret - BadConfig = Config#{secret => <<"emqxsecret">>, - secret_base64_encoded => true}, + BadConfig = Config#{ + secret => <<"emqxsecret">>, + secret_base64_encoded => true + }, {error, {invalid_parameter, secret}} = emqx_authn_jwt:create(?AUTHN_ID, BadConfig), Config3 = Config#{verify_claims => [{<<"username">>, <<"${username}">>}]}, {ok, State3} = emqx_authn_jwt:update(Config3, State2), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State3)), - ?assertEqual({error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential#{username => <<"otheruser">>}, State3)), + ?assertEqual( + {error, bad_username_or_password}, + emqx_authn_jwt:authenticate(Credential#{username => <<"otheruser">>}, State3) + ), %% Expiration - Payload3 = #{ <<"username">> => <<"myuser">> - , <<"exp">> => erlang:system_time(second) - 60}, + Payload3 = #{ + <<"username">> => <<"myuser">>, + <<"exp">> => erlang:system_time(second) - 60 + }, JWS3 = generate_jws('hmac-based', Payload3, Secret), Credential3 = Credential#{password => JWS3}, - ?assertEqual({error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential3, State3)), + ?assertEqual( + {error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential3, State3) + ), - Payload4 = #{ <<"username">> => <<"myuser">> - , <<"exp">> => erlang:system_time(second) + 60}, + Payload4 = #{ + <<"username">> => <<"myuser">>, + <<"exp">> => erlang:system_time(second) + 60 + }, JWS4 = generate_jws('hmac-based', Payload4, Secret), Credential4 = Credential#{password => JWS4}, ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential4, State3)), %% Issued At - Payload5 = #{ <<"username">> => <<"myuser">> - , <<"iat">> => erlang:system_time(second) - 60}, + Payload5 = #{ + <<"username">> => <<"myuser">>, + <<"iat">> => erlang:system_time(second) - 60 + }, JWS5 = generate_jws('hmac-based', Payload5, Secret), Credential5 = Credential#{password => JWS5}, ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential5, State3)), - Payload6 = #{ <<"username">> => <<"myuser">> - , <<"iat">> => erlang:system_time(second) + 60}, + Payload6 = #{ + <<"username">> => <<"myuser">>, + <<"iat">> => erlang:system_time(second) + 60 + }, JWS6 = generate_jws('hmac-based', Payload6, Secret), Credential6 = Credential#{password => JWS6}, - ?assertEqual({error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential6, State3)), + ?assertEqual( + {error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential6, State3) + ), %% Not Before - Payload7 = #{ <<"username">> => <<"myuser">> - , <<"nbf">> => erlang:system_time(second) - 60}, + Payload7 = #{ + <<"username">> => <<"myuser">>, + <<"nbf">> => erlang:system_time(second) - 60 + }, JWS7 = generate_jws('hmac-based', Payload7, Secret), Credential7 = Credential6#{password => JWS7}, ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential7, State3)), - Payload8 = #{ <<"username">> => <<"myuser">> - , <<"nbf">> => erlang:system_time(second) + 60}, + Payload8 = #{ + <<"username">> => <<"myuser">>, + <<"nbf">> => erlang:system_time(second) + 60 + }, JWS8 = generate_jws('hmac-based', Payload8, Secret), Credential8 = Credential#{password => JWS8}, - ?assertEqual({error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential8, State3)), + ?assertEqual( + {error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential8, State3) + ), ?assertEqual(ok, emqx_authn_jwt:destroy(State3)), ok. @@ -136,19 +166,25 @@ t_jwt_authenticator_hmac_based(_) -> t_jwt_authenticator_public_key(_) -> PublicKey = test_rsa_key(public), PrivateKey = test_rsa_key(private), - Config = #{mechanism => jwt, - use_jwks => false, - algorithm => 'public-key', - certificate => PublicKey, - verify_claims => []}, + Config = #{ + mechanism => jwt, + use_jwks => false, + algorithm => 'public-key', + certificate => PublicKey, + verify_claims => [] + }, {ok, State} = emqx_authn_jwt:create(?AUTHN_ID, Config), Payload = #{<<"username">> => <<"myuser">>}, JWS = generate_jws('public-key', Payload, PrivateKey), - Credential = #{username => <<"myuser">>, - password => JWS}, + Credential = #{ + username => <<"myuser">>, + password => JWS + }, ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State)), - ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State)), + ?assertEqual( + ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State) + ), ?assertEqual(ok, emqx_authn_jwt:destroy(State)), ok. @@ -160,63 +196,77 @@ t_jwks_renewal(_Config) -> PrivateKey = test_rsa_key(private), Payload = #{<<"username">> => <<"myuser">>}, JWS = generate_jws('public-key', Payload, PrivateKey), - Credential = #{username => <<"myuser">>, - password => JWS}, - - BadConfig0 = #{mechanism => jwt, - algorithm => 'public-key', - ssl => #{enable => false}, - verify_claims => [], + Credential = #{ + username => <<"myuser">>, + password => JWS + }, - use_jwks => true, - endpoint => "https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT + 1) ++ ?JWKS_PATH, - refresh_interval => 1000 - }, + BadConfig0 = #{ + mechanism => jwt, + algorithm => 'public-key', + ssl => #{enable => false}, + verify_claims => [], + + use_jwks => true, + endpoint => "https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT + 1) ++ ?JWKS_PATH, + refresh_interval => 1000 + }, ok = snabbkaffe:start_trace(), {{ok, State0}, _} = ?wait_async_action( - emqx_authn_jwt:create(?AUTHN_ID, BadConfig0), - #{?snk_kind := jwks_endpoint_response}, - 10000), + emqx_authn_jwt:create(?AUTHN_ID, BadConfig0), + #{?snk_kind := jwks_endpoint_response}, + 10000 + ), ok = snabbkaffe:stop(), ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential, State0)), - ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State0)), + ?assertEqual( + ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State0) + ), ClientSSLOpts = client_ssl_opts(), BadClientSSLOpts = ClientSSLOpts#{server_name_indication => "authn-server-unknown-host"}, - BadConfig1 = BadConfig0#{endpoint => - "https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT) ++ ?JWKS_PATH, - ssl => BadClientSSLOpts}, + BadConfig1 = BadConfig0#{ + endpoint => + "https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT) ++ ?JWKS_PATH, + ssl => BadClientSSLOpts + }, ok = snabbkaffe:start_trace(), {{ok, State1}, _} = ?wait_async_action( - emqx_authn_jwt:create(?AUTHN_ID, BadConfig1), - #{?snk_kind := jwks_endpoint_response}, - 10000), + emqx_authn_jwt:create(?AUTHN_ID, BadConfig1), + #{?snk_kind := jwks_endpoint_response}, + 10000 + ), ok = snabbkaffe:stop(), ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential, State1)), - ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State0)), + ?assertEqual( + ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State0) + ), GoodConfig = BadConfig1#{ssl => ClientSSLOpts}, ok = snabbkaffe:start_trace(), {{ok, State2}, _} = ?wait_async_action( - emqx_authn_jwt:update(GoodConfig, State1), - #{?snk_kind := jwks_endpoint_response}, - 10000), + emqx_authn_jwt:update(GoodConfig, State1), + #{?snk_kind := jwks_endpoint_response}, + 10000 + ), ok = snabbkaffe:stop(), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State2)), - ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State2)), + ?assertEqual( + ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State2) + ), ?assertEqual(ok, emqx_authn_jwt:destroy(State2)), ok = emqx_authn_http_test_server:stop(). @@ -229,15 +279,15 @@ jwks_handler(Req0, State) -> JWK = jose_jwk:from_pem_file(test_rsa_key(public)), JWKS = jose_jwk_set:to_map([JWK], #{}), Req = cowboy_req:reply( - 200, - #{<<"content-type">> => <<"application/json">>}, - jiffy:encode(JWKS), - Req0), + 200, + #{<<"content-type">> => <<"application/json">>}, + jiffy:encode(JWKS), + Req0 + ), {ok, Req, State}. test_rsa_key(public) -> data_file("public_key.pem"); - test_rsa_key(private) -> data_file("private_key.pem"). @@ -250,32 +300,37 @@ cert_file(Name) -> generate_jws('hmac-based', Payload, Secret) -> JWK = jose_jwk:from_oct(Secret), - Header = #{ <<"alg">> => <<"HS256">> - , <<"typ">> => <<"JWT">> - }, + Header = #{ + <<"alg">> => <<"HS256">>, + <<"typ">> => <<"JWT">> + }, Signed = jose_jwt:sign(JWK, Header, Payload), {_, JWS} = jose_jws:compact(Signed), JWS; generate_jws('public-key', Payload, PrivateKey) -> JWK = jose_jwk:from_pem_file(PrivateKey), - Header = #{ <<"alg">> => <<"RS256">> - , <<"typ">> => <<"JWT">> - }, + Header = #{ + <<"alg">> => <<"RS256">>, + <<"typ">> => <<"JWT">> + }, Signed = jose_jwt:sign(JWK, Header, Payload), {_, JWS} = jose_jws:compact(Signed), JWS. client_ssl_opts() -> maps:merge( - emqx_authn_test_lib:client_ssl_cert_opts(), - #{enable => true, - verify => verify_peer, - server_name_indication => "authn-server" - }). + emqx_authn_test_lib:client_ssl_cert_opts(), + #{ + enable => true, + verify => verify_peer, + server_name_indication => "authn-server" + } + ). server_ssl_opts() -> - [{keyfile, cert_file("server.key")}, - {certfile, cert_file("server.crt")}, - {cacertfile, cert_file("ca.crt")}, - {verify, verify_none} + [ + {keyfile, cert_file("server.key")}, + {certfile, cert_file("server.crt")}, + {cacertfile, cert_file("ca.crt")}, + {verify, verify_none} ]. diff --git a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl index ff380f850..0c3f1a9e8 100644 --- a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl @@ -76,7 +76,8 @@ t_check_schema(_Config) -> ?assertException( throw, {emqx_authn_mnesia, _}, - hocon_tconf:check_plain(emqx_authn_mnesia, ?CONF(ConfigNotOk))). + hocon_tconf:check_plain(emqx_authn_mnesia, ?CONF(ConfigNotOk)) + ). t_create(_) -> Config0 = config(), @@ -110,7 +111,7 @@ t_destroy(_) -> ok = emqx_authn_mnesia:destroy(State0), {ok, State1} = emqx_authn_mnesia:create(?AUTHN_ID, Config), - {error,not_found} = emqx_authn_mnesia:lookup_user(<<"u">>, State1), + {error, not_found} = emqx_authn_mnesia:lookup_user(<<"u">>, State1), {ok, _} = emqx_authn_mnesia:lookup_user(<<"u">>, StateOther). t_authenticate(_) -> @@ -121,14 +122,17 @@ t_authenticate(_) -> {ok, _} = emqx_authn_mnesia:add_user(User, State), {ok, _} = emqx_authn_mnesia:authenticate( - #{username => <<"u">>, password => <<"p">>}, - State), + #{username => <<"u">>, password => <<"p">>}, + State + ), {error, bad_username_or_password} = emqx_authn_mnesia:authenticate( - #{username => <<"u">>, password => <<"badpass">>}, - State), + #{username => <<"u">>, password => <<"badpass">>}, + State + ), ignore = emqx_authn_mnesia:authenticate( - #{clientid => <<"u">>, password => <<"p">>}, - State). + #{clientid => <<"u">>, password => <<"p">>}, + State + ). t_add_user(_) -> Config = config(), @@ -157,16 +161,19 @@ t_update_user(_) -> {ok, _} = emqx_authn_mnesia:add_user(User, State), {error, not_found} = emqx_authn_mnesia:update_user(<<"u1">>, #{password => <<"p1">>}, State), - {ok, - #{user_id := <<"u">>, - is_superuser := true}} = emqx_authn_mnesia:update_user( - <<"u">>, - #{password => <<"p1">>, is_superuser => true}, - State), + {ok, #{ + user_id := <<"u">>, + is_superuser := true + }} = emqx_authn_mnesia:update_user( + <<"u">>, + #{password => <<"p1">>, is_superuser => true}, + State + ), {ok, _} = emqx_authn_mnesia:authenticate( - #{username => <<"u">>, password => <<"p1">>}, - State), + #{username => <<"u">>, password => <<"p1">>}, + State + ), {ok, #{is_superuser := true}} = emqx_authn_mnesia:lookup_user(<<"u">>, State). @@ -174,31 +181,47 @@ t_list_users(_) -> Config = config(), {ok, State} = emqx_authn_mnesia:create(?AUTHN_ID, Config), - Users = [#{user_id => <<"u1">>, password => <<"p">>}, - #{user_id => <<"u2">>, password => <<"p">>}, - #{user_id => <<"u3">>, password => <<"p">>}], + Users = [ + #{user_id => <<"u1">>, password => <<"p">>}, + #{user_id => <<"u2">>, password => <<"p">>}, + #{user_id => <<"u3">>, password => <<"p">>} + ], lists:foreach( - fun(U) -> {ok, _} = emqx_authn_mnesia:add_user(U, State) end, - Users), + fun(U) -> {ok, _} = emqx_authn_mnesia:add_user(U, State) end, + Users + ), - #{data := [#{is_superuser := false,user_id := _}, - #{is_superuser := false,user_id := _}], - meta := #{page := 1, limit := 2, count := 3}} = emqx_authn_mnesia:list_users( - #{<<"page">> => 1, <<"limit">> => 2}, - State), + #{ + data := [ + #{is_superuser := false, user_id := _}, + #{is_superuser := false, user_id := _} + ], + meta := #{page := 1, limit := 2, count := 3} + } = emqx_authn_mnesia:list_users( + #{<<"page">> => 1, <<"limit">> => 2}, + State + ), - #{data := [#{is_superuser := false,user_id := _}], - meta := #{page := 2, limit := 2, count := 3}} = emqx_authn_mnesia:list_users( - #{<<"page">> => 2, <<"limit">> => 2}, - State), + #{ + data := [#{is_superuser := false, user_id := _}], + meta := #{page := 2, limit := 2, count := 3} + } = emqx_authn_mnesia:list_users( + #{<<"page">> => 2, <<"limit">> => 2}, + State + ), - #{data := [#{is_superuser := false,user_id := <<"u3">>}], - meta := #{page := 1, limit := 20, count := 1}} = emqx_authn_mnesia:list_users( - #{ <<"page">> => 1 - , <<"limit">> => 20 - , <<"like_username">> => <<"3">>}, - State). + #{ + data := [#{is_superuser := false, user_id := <<"u3">>}], + meta := #{page := 1, limit := 20, count := 1} + } = emqx_authn_mnesia:list_users( + #{ + <<"page">> => 1, + <<"limit">> => 20, + <<"like_username">> => <<"3">> + }, + State + ). t_import_users(_) -> Config0 = config(), @@ -206,36 +229,44 @@ t_import_users(_) -> {ok, State} = emqx_authn_mnesia:create(?AUTHN_ID, Config), ok = emqx_authn_mnesia:import_users( - data_filename(<<"user-credentials.json">>), - State), + data_filename(<<"user-credentials.json">>), + State + ), ok = emqx_authn_mnesia:import_users( - data_filename(<<"user-credentials.csv">>), - State), + data_filename(<<"user-credentials.csv">>), + State + ), {error, {unsupported_file_format, _}} = emqx_authn_mnesia:import_users( - <<"/file/with/unknown.extension">>, - State), + <<"/file/with/unknown.extension">>, + State + ), {error, unknown_file_format} = emqx_authn_mnesia:import_users( - <<"/file/with/no/extension">>, - State), + <<"/file/with/no/extension">>, + State + ), {error, enoent} = emqx_authn_mnesia:import_users( - <<"/file/that/not/exist.json">>, - State), + <<"/file/that/not/exist.json">>, + State + ), {error, bad_format} = emqx_authn_mnesia:import_users( - data_filename(<<"user-credentials-malformed-0.json">>), - State), + data_filename(<<"user-credentials-malformed-0.json">>), + State + ), {error, {_, invalid_json}} = emqx_authn_mnesia:import_users( - data_filename(<<"user-credentials-malformed-1.json">>), - State), + data_filename(<<"user-credentials-malformed-1.json">>), + State + ), {error, bad_format} = emqx_authn_mnesia:import_users( - data_filename(<<"user-credentials-malformed.csv">>), - State). + data_filename(<<"user-credentials-malformed.csv">>), + State + ). %%------------------------------------------------------------------------------ %% Helpers @@ -246,7 +277,10 @@ data_filename(Name) -> filename:join([Dir, <<"data">>, Name]). config() -> - #{user_id_type => username, - password_hash_algorithm => #{name => bcrypt, - salt_rounds => 8} - }. + #{ + user_id_type => username, + password_hash_algorithm => #{ + name => bcrypt, + salt_rounds => 8 + } + }. diff --git a/apps/emqx_authn/test/emqx_authn_mongo_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mongo_SUITE.erl index 13e39dcd8..ffd3dfd39 100644 --- a/apps/emqx_authn/test/emqx_authn_mongo_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mongo_SUITE.erl @@ -24,7 +24,6 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). - -define(MONGO_HOST, "mongo"). -define(MONGO_CLIENT, 'emqx_authn_mongo_SUITE_client'). @@ -37,8 +36,9 @@ init_per_testcase(_TestCase, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), {ok, _} = mc_worker_api:connect(mongo_config()), Config. @@ -58,8 +58,9 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), ok = stop_apps([emqx_resource, emqx_connector]), ok = emqx_common_test_helpers:stop_apps([emqx_authn]). @@ -71,8 +72,9 @@ t_create(_Config) -> AuthConfig = raw_mongo_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), {ok, [#{provider := emqx_authn_mongodb}]} = emqx_authentication:list_authenticators(?GLOBAL). @@ -81,79 +83,93 @@ t_create_invalid(_Config) -> InvalidConfigs = [ - AuthConfig#{mongo_type => <<"unknown">>}, - AuthConfig#{selector => <<"{ \"username\": \"${username}\" }">>}, - AuthConfig#{w_mode => <<"unknown">>} + AuthConfig#{mongo_type => <<"unknown">>}, + AuthConfig#{selector => <<"{ \"username\": \"${username}\" }">>}, + AuthConfig#{w_mode => <<"unknown">>} ], lists:foreach( - fun(Config) -> - {error, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, Config}), + fun(Config) -> + {error, _} = emqx:update_config( + ?PATH, + {create_authenticator, ?GLOBAL, Config} + ), - {ok, []} = emqx_authentication:list_authenticators(?GLOBAL) - end, - InvalidConfigs). + {ok, []} = emqx_authentication:list_authenticators(?GLOBAL) + end, + InvalidConfigs + ). t_authenticate(_Config) -> ok = init_seeds(), ok = lists:foreach( - fun(Sample) -> - ct:pal("test_user_auth sample: ~p", [Sample]), - test_user_auth(Sample) - end, - user_seeds()), + fun(Sample) -> + ct:pal("test_user_auth sample: ~p", [Sample]), + test_user_auth(Sample) + end, + user_seeds() + ), ok = drop_seeds(). -test_user_auth(#{credentials := Credentials0, - config_params := SpecificConfigParams, - result := Result}) -> +test_user_auth(#{ + credentials := Credentials0, + config_params := SpecificConfigParams, + result := Result +}) -> AuthConfig = maps:merge(raw_mongo_auth_config(), SpecificConfigParams), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), Credentials = Credentials0#{ - listener => 'tcp:default', - protocol => mqtt - }, + listener => 'tcp:default', + protocol => mqtt + }, ?assertEqual(Result, emqx_access_control:authenticate(Credentials)), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL). + [authentication], + ?GLOBAL + ). t_destroy(_Config) -> ok = init_seeds(), AuthConfig = raw_mongo_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), - {ok, [#{provider := emqx_authn_mongodb, state := State}]} - = emqx_authentication:list_authenticators(?GLOBAL), + {ok, [#{provider := emqx_authn_mongodb, state := State}]} = + emqx_authentication:list_authenticators(?GLOBAL), {ok, _} = emqx_authn_mongodb:authenticate( - #{username => <<"plain">>, - password => <<"plain">> - }, - State), + #{ + username => <<"plain">>, + password => <<"plain">> + }, + State + ), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), % Authenticator should not be usable anymore ?assertMatch( - ignore, - emqx_authn_mongodb:authenticate( - #{username => <<"plain">>, - password => <<"plain">> - }, - State)), + ignore, + emqx_authn_mongodb:authenticate( + #{ + username => <<"plain">>, + password => <<"plain">> + }, + State + ) + ), ok = drop_seeds(). @@ -164,48 +180,55 @@ t_update(_Config) -> CorrectConfig#{selector => #{<<"wrongfield">> => <<"wrongvalue">>}}, {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, IncorrectConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, IncorrectConfig} + ), {error, not_authorized} = emqx_access_control:authenticate( - #{username => <<"plain">>, - password => <<"plain">>, - listener => 'tcp:default', - protocol => mqtt - }), + #{ + username => <<"plain">>, + password => <<"plain">>, + listener => 'tcp:default', + protocol => mqtt + } + ), % We update with config with correct selector, provider should update and work properly {ok, _} = emqx:update_config( - ?PATH, - {update_authenticator, ?GLOBAL, <<"password_based:mongodb">>, CorrectConfig}), + ?PATH, + {update_authenticator, ?GLOBAL, <<"password_based:mongodb">>, CorrectConfig} + ), - {ok,_} = emqx_access_control:authenticate( - #{username => <<"plain">>, - password => <<"plain">>, - listener => 'tcp:default', - protocol => mqtt - }), + {ok, _} = emqx_access_control:authenticate( + #{ + username => <<"plain">>, + password => <<"plain">>, + listener => 'tcp:default', + protocol => mqtt + } + ), ok = drop_seeds(). t_is_superuser(_Config) -> Config = raw_mongo_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, Config}), + ?PATH, + {create_authenticator, ?GLOBAL, Config} + ), Checks = [ - {<<"0">>, false}, - {<<"">>, false}, - {null, false}, - {false, false}, - {0, false}, + {<<"0">>, false}, + {<<"">>, false}, + {null, false}, + {false, false}, + {0, false}, - {<<"1">>, true}, - {<<"val">>, true}, - {1, true}, - {123, true}, - {true, true} - ], + {<<"1">>, true}, + {<<"val">>, true}, + {1, true}, + {123, true}, + {true, true} + ], lists:foreach(fun test_is_superuser/1, Checks). @@ -213,24 +236,25 @@ test_is_superuser({Value, ExpectedValue}) -> {true, _} = mc_worker_api:delete(?MONGO_CLIENT, <<"users">>, #{}), UserData = #{ - username => <<"user">>, - password_hash => <<"plainsalt">>, - salt => <<"salt">>, - is_superuser => Value - }, + username => <<"user">>, + password_hash => <<"plainsalt">>, + salt => <<"salt">>, + is_superuser => Value + }, {{true, _}, _} = mc_worker_api:insert(?MONGO_CLIENT, <<"users">>, [UserData]), Credentials = #{ - listener => 'tcp:default', - protocol => mqtt, - username => <<"user">>, - password => <<"plain">> - }, + listener => 'tcp:default', + protocol => mqtt, + username => <<"user">>, + password => <<"plain">> + }, ?assertEqual( - {ok, #{is_superuser => ExpectedValue}}, - emqx_access_control:authenticate(Credentials)). + {ok, #{is_superuser => ExpectedValue}}, + emqx_access_control:authenticate(Credentials) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -238,146 +262,160 @@ test_is_superuser({Value, ExpectedValue}) -> raw_mongo_auth_config() -> #{ - mechanism => <<"password_based">>, - password_hash_algorithm => #{name => <<"plain">>, - salt_position => <<"suffix">>}, - enable => <<"true">>, + mechanism => <<"password_based">>, + password_hash_algorithm => #{ + name => <<"plain">>, + salt_position => <<"suffix">> + }, + enable => <<"true">>, - backend => <<"mongodb">>, - mongo_type => <<"single">>, - database => <<"mqtt">>, - collection => <<"users">>, - server => mongo_server(), - w_mode => <<"unsafe">>, + backend => <<"mongodb">>, + mongo_type => <<"single">>, + database => <<"mqtt">>, + collection => <<"users">>, + server => mongo_server(), + w_mode => <<"unsafe">>, - selector => #{<<"username">> => <<"${username}">>}, - password_hash_field => <<"password_hash">>, - salt_field => <<"salt">>, - is_superuser_field => <<"is_superuser">> - }. + selector => #{<<"username">> => <<"${username}">>}, + password_hash_field => <<"password_hash">>, + salt_field => <<"salt">>, + is_superuser_field => <<"is_superuser">> + }. user_seeds() -> - [#{data => #{ - username => <<"plain">>, - password_hash => <<"plainsalt">>, - salt => <<"salt">>, - is_superuser => <<"1">> - }, - credentials => #{ - username => <<"plain">>, - password => <<"plain">> - }, - config_params => #{ - }, - result => {ok,#{is_superuser => true}} - }, - - #{data => #{ - username => <<"md5">>, - password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>, - salt => <<"salt">>, - is_superuser => <<"0">> - }, - credentials => #{ - username => <<"md5">>, - password => <<"md5">> - }, - config_params => #{ - password_hash_algorithm => #{name => <<"md5">>, - salt_position => <<"suffix">> } - }, - result => {ok,#{is_superuser => false}} - }, - - #{data => #{ - username => <<"sha256">>, - password_hash => <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>, - salt => <<"salt">>, - is_superuser => 1 + [ + #{ + data => #{ + username => <<"plain">>, + password_hash => <<"plainsalt">>, + salt => <<"salt">>, + is_superuser => <<"1">> + }, + credentials => #{ + username => <<"plain">>, + password => <<"plain">> + }, + config_params => #{}, + result => {ok, #{is_superuser => true}} }, - credentials => #{ - clientid => <<"sha256">>, - password => <<"sha256">> - }, - config_params => #{ - selector => #{<<"username">> => <<"${clientid}">>}, - password_hash_algorithm => #{name => <<"sha256">>, - salt_position => <<"prefix">>} - }, - result => {ok,#{is_superuser => true}} - }, - #{data => #{ - username => <<"bcrypt">>, - password_hash => - <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, - salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, - is_superuser => 0 - }, - credentials => #{ - username => <<"bcrypt">>, - password => <<"bcrypt">> - }, - config_params => #{ - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {ok,#{is_superuser => false}} - }, + #{ + data => #{ + username => <<"md5">>, + password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>, + salt => <<"salt">>, + is_superuser => <<"0">> + }, + credentials => #{ + username => <<"md5">>, + password => <<"md5">> + }, + config_params => #{ + password_hash_algorithm => #{ + name => <<"md5">>, + salt_position => <<"suffix">> + } + }, + result => {ok, #{is_superuser => false}} + }, - #{data => #{ - username => <<"bcrypt0">>, - password_hash => - <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, - salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, - is_superuser => <<"0">> - }, - credentials => #{ - username => <<"bcrypt0">>, - password => <<"bcrypt">> - }, - config_params => #{ - % clientid variable & username credentials - selector => #{<<"username">> => <<"${clientid}">>}, - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {error,not_authorized} - }, + #{ + data => #{ + username => <<"sha256">>, + password_hash => + <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>, + salt => <<"salt">>, + is_superuser => 1 + }, + credentials => #{ + clientid => <<"sha256">>, + password => <<"sha256">> + }, + config_params => #{ + selector => #{<<"username">> => <<"${clientid}">>}, + password_hash_algorithm => #{ + name => <<"sha256">>, + salt_position => <<"prefix">> + } + }, + result => {ok, #{is_superuser => true}} + }, - #{data => #{ - username => <<"bcrypt1">>, - password_hash => - <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, - salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, - is_superuser => <<"0">> - }, - credentials => #{ - username => <<"bcrypt1">>, - password => <<"bcrypt">> - }, - config_params => #{ - selector => #{<<"userid">> => <<"${clientid}">>}, - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {error,not_authorized} - }, + #{ + data => #{ + username => <<"bcrypt">>, + password_hash => + <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, + salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, + is_superuser => 0 + }, + credentials => #{ + username => <<"bcrypt">>, + password => <<"bcrypt">> + }, + config_params => #{ + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {ok, #{is_superuser => false}} + }, - #{data => #{ - username => <<"bcrypt2">>, - password_hash => - <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, - salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, - is_superuser => <<"0">> - }, - credentials => #{ - username => <<"bcrypt2">>, - % Wrong password - password => <<"wrongpass">> - }, - config_params => #{ - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {error,bad_username_or_password} - } + #{ + data => #{ + username => <<"bcrypt0">>, + password_hash => + <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, + salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, + is_superuser => <<"0">> + }, + credentials => #{ + username => <<"bcrypt0">>, + password => <<"bcrypt">> + }, + config_params => #{ + % clientid variable & username credentials + selector => #{<<"username">> => <<"${clientid}">>}, + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {error, not_authorized} + }, + + #{ + data => #{ + username => <<"bcrypt1">>, + password_hash => + <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, + salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, + is_superuser => <<"0">> + }, + credentials => #{ + username => <<"bcrypt1">>, + password => <<"bcrypt">> + }, + config_params => #{ + selector => #{<<"userid">> => <<"${clientid}">>}, + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {error, not_authorized} + }, + + #{ + data => #{ + username => <<"bcrypt2">>, + password_hash => + <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, + salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, + is_superuser => <<"0">> + }, + credentials => #{ + username => <<"bcrypt2">>, + % Wrong password + password => <<"wrongpass">> + }, + config_params => #{ + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {error, bad_username_or_password} + } ]. init_seeds() -> @@ -390,14 +428,14 @@ drop_seeds() -> ok. mongo_server() -> - iolist_to_binary(io_lib:format("~s",[?MONGO_HOST])). + iolist_to_binary(io_lib:format("~s", [?MONGO_HOST])). mongo_config() -> [ - {database, <<"mqtt">>}, - {host, ?MONGO_HOST}, - {port, ?MONGO_DEFAULT_PORT}, - {register, ?MONGO_CLIENT} + {database, <<"mqtt">>}, + {host, ?MONGO_HOST}, + {port, ?MONGO_DEFAULT_PORT}, + {register, ?MONGO_CLIENT} ]. start_apps(Apps) -> diff --git a/apps/emqx_authn/test/emqx_authn_mongo_tls_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mongo_tls_SUITE.erl index 8c612bd20..48cee7509 100644 --- a/apps/emqx_authn/test/emqx_authn_mongo_tls_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mongo_tls_SUITE.erl @@ -25,7 +25,6 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). - -define(MONGO_HOST, "mongo-tls"). -define(PATH, [authentication]). @@ -37,8 +36,9 @@ init_per_testcase(_TestCase, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), Config. init_per_suite(Config) -> @@ -54,8 +54,9 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), ok = stop_apps([emqx_resource, emqx_connector]), ok = emqx_common_test_helpers:stop_apps([emqx_authn]). @@ -72,69 +73,90 @@ end_per_suite(_Config) -> t_create(_Config) -> ?check_trace( - create_mongo_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.2">>], - <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]}), - fun({ok, _}, Trace) -> + create_mongo_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.2">>], + <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>] + } + ), + fun({ok, _}, Trace) -> ?assertMatch( - [ok | _], - ?projection( - status, - ?of_kind(emqx_connector_mongo_health_check, Trace))) - end). - + [ok | _], + ?projection( + status, + ?of_kind(emqx_connector_mongo_health_check, Trace) + ) + ) + end + ). t_create_invalid_server_name(_Config) -> ?check_trace( - create_mongo_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server-unknown-host">>, - <<"verify">> => <<"verify_peer">>}), - fun(_, Trace) -> - ?assertNotEqual( - [ok], - ?projection( - status, - ?of_kind(emqx_connector_mongo_health_check, Trace))) - end). - + create_mongo_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server-unknown-host">>, + <<"verify">> => <<"verify_peer">> + } + ), + fun(_, Trace) -> + ?assertNotEqual( + [ok], + ?projection( + status, + ?of_kind(emqx_connector_mongo_health_check, Trace) + ) + ) + end + ). %% docker-compose-mongo-single-tls.yaml: %% --tlsDisabledProtocols TLS1_0,TLS1_1 t_create_invalid_version(_Config) -> ?check_trace( - create_mongo_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.1">>]}), - fun(_, Trace) -> - ?assertNotEqual( - [ok], - ?projection( - status, - ?of_kind(emqx_connector_mongo_health_check, Trace))) - end). - + create_mongo_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.1">>] + } + ), + fun(_, Trace) -> + ?assertNotEqual( + [ok], + ?projection( + status, + ?of_kind(emqx_connector_mongo_health_check, Trace) + ) + ) + end + ). %% docker-compose-mongo-single-tls.yaml: %% --setParameter opensslCipherConfig='HIGH:!EXPORT:!aNULL:!DHE:!kDHE@STRENGTH' t_invalid_ciphers(_Config) -> ?check_trace( - create_mongo_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.2">>], - <<"ciphers">> => [<<"DHE-RSA-AES256-GCM-SHA384">>]}), - fun(_, Trace) -> - ?assertNotEqual( - [ok], - ?projection( - status, - ?of_kind(emqx_connector_mongo_health_check, Trace))) - end). + create_mongo_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.2">>], + <<"ciphers">> => [<<"DHE-RSA-AES256-GCM-SHA384">>] + } + ), + fun(_, Trace) -> + ?assertNotEqual( + [ok], + ?projection( + status, + ?of_kind(emqx_connector_mongo_health_check, Trace) + ) + ) + end + ). %%------------------------------------------------------------------------------ %% Helpers @@ -148,35 +170,38 @@ create_mongo_auth_with_ssl_opts(SpecificSSLOpts) -> raw_mongo_auth_config(SpecificSSLOpts) -> SSLOpts = maps:merge( - emqx_authn_test_lib:client_ssl_cert_opts(), - #{enable => <<"true">>}), + emqx_authn_test_lib:client_ssl_cert_opts(), + #{enable => <<"true">>} + ), #{ - mechanism => <<"password_based">>, - password_hash_algorithm => #{name => <<"plain">>, - salt_position => <<"suffix">>}, - enable => <<"true">>, + mechanism => <<"password_based">>, + password_hash_algorithm => #{ + name => <<"plain">>, + salt_position => <<"suffix">> + }, + enable => <<"true">>, - backend => <<"mongodb">>, - pool_size => 2, - mongo_type => <<"single">>, - database => <<"mqtt">>, - collection => <<"users">>, - server => mongo_server(), - w_mode => <<"unsafe">>, + backend => <<"mongodb">>, + pool_size => 2, + mongo_type => <<"single">>, + database => <<"mqtt">>, + collection => <<"users">>, + server => mongo_server(), + w_mode => <<"unsafe">>, - selector => #{<<"username">> => <<"${username}">>}, - password_hash_field => <<"password_hash">>, - salt_field => <<"salt">>, - is_superuser_field => <<"is_superuser">>, - topology => #{ - server_selection_timeout_ms => <<"10000ms">> - }, + selector => #{<<"username">> => <<"${username}">>}, + password_hash_field => <<"password_hash">>, + salt_field => <<"salt">>, + is_superuser_field => <<"is_superuser">>, + topology => #{ + server_selection_timeout_ms => <<"10000ms">> + }, - ssl => maps:merge(SSLOpts, SpecificSSLOpts) - }. + ssl => maps:merge(SSLOpts, SpecificSSLOpts) + }. mongo_server() -> - iolist_to_binary(io_lib:format("~s",[?MONGO_HOST])). + iolist_to_binary(io_lib:format("~s", [?MONGO_HOST])). start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authn/test/emqx_authn_mqtt_test_client.erl b/apps/emqx_authn/test/emqx_authn_mqtt_test_client.erl index 162cf3f95..734a22cc7 100644 --- a/apps/emqx_authn/test/emqx_authn_mqtt_test_client.erl +++ b/apps/emqx_authn/test/emqx_authn_mqtt_test_client.erl @@ -21,28 +21,36 @@ -include_lib("emqx/include/emqx_mqtt.hrl"). %% API --export([start_link/2, - stop/1]). +-export([ + start_link/2, + stop/1 +]). -export([send/2]). %% gen_server callbacks --export([init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - terminate/2]). +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2 +]). -define(TIMEOUT, 1000). --define(TCP_OPTIONS, [binary, {packet, raw}, {active, once}, - {nodelay, true}]). +-define(TCP_OPTIONS, [ + binary, + {packet, raw}, + {active, once}, + {nodelay, true} +]). --define(PARSE_OPTIONS, - #{strict_mode => false, - max_size => ?MAX_PACKET_SIZE, - version => ?MQTT_PROTO_V5 - }). +-define(PARSE_OPTIONS, #{ + strict_mode => false, + max_size => ?MAX_PACKET_SIZE, + version => ?MQTT_PROTO_V5 +}). %%-------------------------------------------------------------------- %% API @@ -63,26 +71,30 @@ send(Pid, Packet) -> init([Host, Port, Owner]) -> {ok, Socket} = gen_tcp:connect(Host, Port, ?TCP_OPTIONS, ?TIMEOUT), - {ok, #{owner => Owner, - socket => Socket, - parse_state => emqx_frame:initial_parse_state(?PARSE_OPTIONS) - }}. + {ok, #{ + owner => Owner, + socket => Socket, + parse_state => emqx_frame:initial_parse_state(?PARSE_OPTIONS) + }}. -handle_info({tcp, _Sock, Data}, #{parse_state := PSt, - owner := Owner, - socket := Socket} = St) -> +handle_info( + {tcp, _Sock, Data}, + #{ + parse_state := PSt, + owner := Owner, + socket := Socket + } = St +) -> {NewPSt, Packets} = process_incoming(PSt, Data, []), ok = deliver(Owner, Packets), ok = run_sock(Socket), {noreply, St#{parse_state => NewPSt}}; - handle_info({tcp_closed, _Sock}, St) -> {stop, normal, St}. handle_call({send, Packet}, _From, #{socket := Socket} = St) -> ok = gen_tcp:send(Socket, emqx_frame:serialize(Packet, ?MQTT_PROTO_V5)), {reply, ok, St}; - handle_call(stop, _From, #{socket := Socket} = St) -> ok = gen_tcp:close(Socket), {stop, normal, ok, St}. @@ -100,16 +112,16 @@ terminate(_Reason, _St) -> process_incoming(PSt, Data, Packets) -> case emqx_frame:parse(Data, PSt) of {more, NewPSt} -> - {NewPSt, lists:reverse(Packets)}; + {NewPSt, lists:reverse(Packets)}; {ok, Packet, Rest, NewPSt} -> process_incoming(NewPSt, Rest, [Packet | Packets]) end. -deliver(_Owner, []) -> ok; +deliver(_Owner, []) -> + ok; deliver(Owner, [Packet | Packets]) -> Owner ! {packet, Packet}, deliver(Owner, Packets). - run_sock(Socket) -> inet:setopts(Socket, [{active, once}]). diff --git a/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl index cb8933b40..3d2dba895 100644 --- a/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl @@ -40,8 +40,9 @@ init_per_testcase(_, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), Config. init_per_group(require_seeds, Config) -> @@ -59,11 +60,12 @@ init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps([emqx_authn]), ok = start_apps([emqx_resource, emqx_connector]), {ok, _} = emqx_resource:create_local( - ?MYSQL_RESOURCE, - ?RESOURCE_GROUP, - emqx_connector_mysql, - mysql_config(), - #{}), + ?MYSQL_RESOURCE, + ?RESOURCE_GROUP, + emqx_connector_mysql, + mysql_config(), + #{} + ), Config; false -> {skip, no_mysql} @@ -71,8 +73,9 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), ok = emqx_resource:remove_local(?MYSQL_RESOURCE), ok = stop_apps([emqx_resource, emqx_connector]), ok = emqx_common_test_helpers:stop_apps([emqx_authn]). @@ -85,8 +88,9 @@ t_create(_Config) -> AuthConfig = raw_mysql_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), {ok, [#{provider := emqx_authn_mysql}]} = emqx_authentication:list_authenticators(?GLOBAL), emqx_authn_test_lib:delete_config(?ResourceID). @@ -96,108 +100,132 @@ t_create_invalid(_Config) -> InvalidConfigs = [ - maps:without([server], AuthConfig), - AuthConfig#{server => <<"unknownhost:3333">>}, - AuthConfig#{password => <<"wrongpass">>}, - AuthConfig#{database => <<"wrongdatabase">>} + maps:without([server], AuthConfig), + AuthConfig#{server => <<"unknownhost:3333">>}, + AuthConfig#{password => <<"wrongpass">>}, + AuthConfig#{database => <<"wrongdatabase">>} ], lists:foreach( - fun(Config) -> - {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, Config}), - emqx_authn_test_lib:delete_config(?ResourceID), - {ok, _} = emqx_authentication:list_authenticators(?GLOBAL) - end, - InvalidConfigs). + fun(Config) -> + {ok, _} = emqx:update_config( + ?PATH, + {create_authenticator, ?GLOBAL, Config} + ), + emqx_authn_test_lib:delete_config(?ResourceID), + {ok, _} = emqx_authentication:list_authenticators(?GLOBAL) + end, + InvalidConfigs + ). t_authenticate(_Config) -> ok = lists:foreach( - fun(Sample) -> - ct:pal("test_user_auth sample: ~p", [Sample]), - test_user_auth(Sample) - end, - user_seeds()). + fun(Sample) -> + ct:pal("test_user_auth sample: ~p", [Sample]), + test_user_auth(Sample) + end, + user_seeds() + ). -test_user_auth(#{credentials := Credentials0, - config_params := SpecificConfigParams, - result := Result}) -> +test_user_auth(#{ + credentials := Credentials0, + config_params := SpecificConfigParams, + result := Result +}) -> AuthConfig = maps:merge(raw_mysql_auth_config(), SpecificConfigParams), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), Credentials = Credentials0#{ - listener => 'tcp:default', - protocol => mqtt - }, + listener => 'tcp:default', + protocol => mqtt + }, ?assertEqual(Result, emqx_access_control:authenticate(Credentials)), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL). + [authentication], + ?GLOBAL + ). t_destroy(_Config) -> AuthConfig = raw_mysql_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), - {ok, [#{provider := emqx_authn_mysql, state := State}]} - = emqx_authentication:list_authenticators(?GLOBAL), + {ok, [#{provider := emqx_authn_mysql, state := State}]} = + emqx_authentication:list_authenticators(?GLOBAL), {ok, _} = emqx_authn_mysql:authenticate( - #{username => <<"plain">>, - password => <<"plain">> - }, - State), + #{ + username => <<"plain">>, + password => <<"plain">> + }, + State + ), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), % Authenticator should not be usable anymore ?assertMatch( - ignore, - emqx_authn_mysql:authenticate( - #{username => <<"plain">>, - password => <<"plain">> - }, - State)). + ignore, + emqx_authn_mysql:authenticate( + #{ + username => <<"plain">>, + password => <<"plain">> + }, + State + ) + ). t_update(_Config) -> CorrectConfig = raw_mysql_auth_config(), IncorrectConfig = CorrectConfig#{ - query => <<"SELECT password_hash, salt, is_superuser_str as is_superuser - FROM wrong_table where username = ${username} LIMIT 1">>}, + query => + << + "SELECT password_hash, salt, is_superuser_str as is_superuser\n" + " FROM wrong_table where username = ${username} LIMIT 1" + >> + }, {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, IncorrectConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, IncorrectConfig} + ), {error, not_authorized} = emqx_access_control:authenticate( - #{username => <<"plain">>, - password => <<"plain">>, - listener => 'tcp:default', - protocol => mqtt - }), + #{ + username => <<"plain">>, + password => <<"plain">>, + listener => 'tcp:default', + protocol => mqtt + } + ), % We update with config with correct query, provider should update and work properly {ok, _} = emqx:update_config( - ?PATH, - {update_authenticator, ?GLOBAL, <<"password_based:mysql">>, CorrectConfig}), + ?PATH, + {update_authenticator, ?GLOBAL, <<"password_based:mysql">>, CorrectConfig} + ), - {ok,_} = emqx_access_control:authenticate( - #{username => <<"plain">>, - password => <<"plain">>, - listener => 'tcp:default', - protocol => mqtt - }). + {ok, _} = emqx_access_control:authenticate( + #{ + username => <<"plain">>, + password => <<"plain">>, + listener => 'tcp:default', + protocol => mqtt + } + ). %%------------------------------------------------------------------------------ %% Helpers @@ -205,207 +233,248 @@ t_update(_Config) -> raw_mysql_auth_config() -> #{ - mechanism => <<"password_based">>, - password_hash_algorithm => #{name => <<"plain">>, - salt_position => <<"suffix">>}, - enable => <<"true">>, + mechanism => <<"password_based">>, + password_hash_algorithm => #{ + name => <<"plain">>, + salt_position => <<"suffix">> + }, + enable => <<"true">>, - backend => <<"mysql">>, - database => <<"mqtt">>, - username => <<"root">>, - password => <<"public">>, + backend => <<"mysql">>, + database => <<"mqtt">>, + username => <<"root">>, + password => <<"public">>, - query => <<"SELECT password_hash, salt, is_superuser_str as is_superuser - FROM users where username = ${username} LIMIT 1">>, - server => mysql_server() - }. + query => + << + "SELECT password_hash, salt, is_superuser_str as is_superuser\n" + " FROM users where username = ${username} LIMIT 1" + >>, + server => mysql_server() + }. user_seeds() -> - [#{data => #{ - username => "plain", - password_hash => "plainsalt", - salt => "salt", - is_superuser_str => "1" - }, - credentials => #{ - username => <<"plain">>, - password => <<"plain">>}, - config_params => #{}, - result => {ok,#{is_superuser => true}} - }, - - #{data => #{ - username => "md5", - password_hash => "9b4d0c43d206d48279e69b9ad7132e22", - salt => "salt", - is_superuser_str => "0" - }, - credentials => #{ - username => <<"md5">>, - password => <<"md5">> - }, - config_params => #{ - password_hash_algorithm => #{name => <<"md5">>, - salt_position => <<"suffix">>} - }, - result => {ok,#{is_superuser => false}} - }, - - #{data => #{ - username => "sha256", - password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf", - salt => "salt", - is_superuser_int => 1 + [ + #{ + data => #{ + username => "plain", + password_hash => "plainsalt", + salt => "salt", + is_superuser_str => "1" + }, + credentials => #{ + username => <<"plain">>, + password => <<"plain">> + }, + config_params => #{}, + result => {ok, #{is_superuser => true}} }, - credentials => #{ - clientid => <<"sha256">>, - password => <<"sha256">> - }, - config_params => #{ - query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser - FROM users where username = ${clientid} LIMIT 1">>, - password_hash_algorithm => #{name => <<"sha256">>, - salt_position => <<"prefix">>} - }, - result => {ok,#{is_superuser => true}} - }, - #{data => #{ - username => <<"bcrypt">>, - password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", - salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", - is_superuser_int => 0 - }, - credentials => #{ - username => <<"bcrypt">>, - password => <<"bcrypt">> - }, - config_params => #{ - query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser - FROM users where username = ${username} LIMIT 1">>, - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {ok,#{is_superuser => false}} - }, + #{ + data => #{ + username => "md5", + password_hash => "9b4d0c43d206d48279e69b9ad7132e22", + salt => "salt", + is_superuser_str => "0" + }, + credentials => #{ + username => <<"md5">>, + password => <<"md5">> + }, + config_params => #{ + password_hash_algorithm => #{ + name => <<"md5">>, + salt_position => <<"suffix">> + } + }, + result => {ok, #{is_superuser => false}} + }, - #{data => #{ - username => <<"bcrypt">>, - password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", - salt => "$2b$12$wtY3h20mUjjmeaClpqZVve" - }, - credentials => #{ - username => <<"bcrypt">>, - password => <<"bcrypt">> - }, - config_params => #{ - query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser - FROM users where username = ${username} LIMIT 1">>, - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {ok,#{is_superuser => false}} - }, + #{ + data => #{ + username => "sha256", + password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf", + salt => "salt", + is_superuser_int => 1 + }, + credentials => #{ + clientid => <<"sha256">>, + password => <<"sha256">> + }, + config_params => #{ + query => + << + "SELECT password_hash, salt, is_superuser_int as is_superuser\n" + " FROM users where username = ${clientid} LIMIT 1" + >>, + password_hash_algorithm => #{ + name => <<"sha256">>, + salt_position => <<"prefix">> + } + }, + result => {ok, #{is_superuser => true}} + }, - #{data => #{ - username => <<"bcrypt0">>, - password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", - salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", - is_superuser_str => "0" - }, - credentials => #{ - username => <<"bcrypt0">>, - password => <<"bcrypt">> - }, - config_params => #{ - % clientid variable & username credentials - query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser - FROM users where username = ${clientid} LIMIT 1">>, - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {error,not_authorized} - }, + #{ + data => #{ + username => <<"bcrypt">>, + password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", + salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", + is_superuser_int => 0 + }, + credentials => #{ + username => <<"bcrypt">>, + password => <<"bcrypt">> + }, + config_params => #{ + query => + << + "SELECT password_hash, salt, is_superuser_int as is_superuser\n" + " FROM users where username = ${username} LIMIT 1" + >>, + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {ok, #{is_superuser => false}} + }, - #{data => #{ - username => <<"bcrypt1">>, - password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", - salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", - is_superuser_str => "0" - }, - credentials => #{ - username => <<"bcrypt1">>, - password => <<"bcrypt">> - }, - config_params => #{ - % Bad keys in query - query => <<"SELECT 1 AS unknown_field - FROM users where username = ${username} LIMIT 1">>, - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {error,not_authorized} - }, + #{ + data => #{ + username => <<"bcrypt">>, + password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", + salt => "$2b$12$wtY3h20mUjjmeaClpqZVve" + }, + credentials => #{ + username => <<"bcrypt">>, + password => <<"bcrypt">> + }, + config_params => #{ + query => + << + "SELECT password_hash, salt, is_superuser_int as is_superuser\n" + " FROM users where username = ${username} LIMIT 1" + >>, + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {ok, #{is_superuser => false}} + }, - #{data => #{ - username => <<"bcrypt2">>, - password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", - salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", - is_superuser => "0" - }, - credentials => #{ - username => <<"bcrypt2">>, - % Wrong password - password => <<"wrongpass">> - }, - config_params => #{ - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {error,bad_username_or_password} - } + #{ + data => #{ + username => <<"bcrypt0">>, + password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", + salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", + is_superuser_str => "0" + }, + credentials => #{ + username => <<"bcrypt0">>, + password => <<"bcrypt">> + }, + config_params => #{ + % clientid variable & username credentials + query => + << + "SELECT password_hash, salt, is_superuser_int as is_superuser\n" + " FROM users where username = ${clientid} LIMIT 1" + >>, + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {error, not_authorized} + }, + + #{ + data => #{ + username => <<"bcrypt1">>, + password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", + salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", + is_superuser_str => "0" + }, + credentials => #{ + username => <<"bcrypt1">>, + password => <<"bcrypt">> + }, + config_params => #{ + % Bad keys in query + query => + << + "SELECT 1 AS unknown_field\n" + " FROM users where username = ${username} LIMIT 1" + >>, + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {error, not_authorized} + }, + + #{ + data => #{ + username => <<"bcrypt2">>, + password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", + salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", + is_superuser => "0" + }, + credentials => #{ + username => <<"bcrypt2">>, + % Wrong password + password => <<"wrongpass">> + }, + config_params => #{ + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {error, bad_username_or_password} + } ]. init_seeds() -> ok = drop_seeds(), - ok = q("CREATE TABLE users( - username VARCHAR(255), - password_hash VARCHAR(255), - salt VARCHAR(255), - is_superuser_str VARCHAR(255), - is_superuser_int TINYINT)"), + ok = q( + "CREATE TABLE users(\n" + " username VARCHAR(255),\n" + " password_hash VARCHAR(255),\n" + " salt VARCHAR(255),\n" + " is_superuser_str VARCHAR(255),\n" + " is_superuser_int TINYINT)" + ), Fields = [username, password_hash, salt, is_superuser_str, is_superuser_int], - InsertQuery = "INSERT INTO users(username, password_hash, salt, " - " is_superuser_str, is_superuser_int) VALUES(?, ?, ?, ?, ?)", + InsertQuery = + "INSERT INTO users(username, password_hash, salt, " + " is_superuser_str, is_superuser_int) VALUES(?, ?, ?, ?, ?)", lists:foreach( - fun(#{data := Values}) -> - Params = [maps:get(F, Values, null) || F <- Fields], - ok = q(InsertQuery, Params) - end, - user_seeds()). + fun(#{data := Values}) -> + Params = [maps:get(F, Values, null) || F <- Fields], + ok = q(InsertQuery, Params) + end, + user_seeds() + ). q(Sql) -> emqx_resource:query( - ?MYSQL_RESOURCE, - {sql, Sql}). + ?MYSQL_RESOURCE, + {sql, Sql} + ). q(Sql, Params) -> emqx_resource:query( - ?MYSQL_RESOURCE, - {sql, Sql, Params}). + ?MYSQL_RESOURCE, + {sql, Sql, Params} + ). drop_seeds() -> ok = q("DROP TABLE IF EXISTS users"). mysql_server() -> - iolist_to_binary(io_lib:format("~s",[?MYSQL_HOST])). + iolist_to_binary(io_lib:format("~s", [?MYSQL_HOST])). mysql_config() -> - #{auto_reconnect => true, - database => <<"mqtt">>, - username => <<"root">>, - password => <<"public">>, - pool_size => 8, - server => {?MYSQL_HOST, ?MYSQL_DEFAULT_PORT}, - ssl => #{enable => false} - }. + #{ + auto_reconnect => true, + database => <<"mqtt">>, + username => <<"root">>, + password => <<"public">>, + pool_size => 8, + server => {?MYSQL_HOST, ?MYSQL_DEFAULT_PORT}, + ssl => #{enable => false} + }. start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authn/test/emqx_authn_mysql_tls_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mysql_tls_SUITE.erl index e1404eb60..d7d655246 100644 --- a/apps/emqx_authn/test/emqx_authn_mysql_tls_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mysql_tls_SUITE.erl @@ -39,8 +39,9 @@ init_per_testcase(_, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), Config. init_per_suite(Config) -> @@ -56,8 +57,9 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), ok = stop_apps([emqx_resource, emqx_connector]), ok = emqx_common_test_helpers:stop_apps([emqx_authn]). @@ -70,38 +72,53 @@ t_create(_Config) -> %% -connect authn-server:3306 -starttls mysql \ %% -cert client.crt -key client.key -CAfile ca.crt ?assertMatch( - {ok, _}, - create_mysql_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.2">>], - <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]})). + {ok, _}, + create_mysql_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.2">>], + <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>] + } + ) + ). t_create_invalid(_Config) -> - %% invalid server_name ?assertMatch( - {ok, _}, - create_mysql_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server-unknown-host">>, - <<"verify">> => <<"verify_peer">>})), + {ok, _}, + create_mysql_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server-unknown-host">>, + <<"verify">> => <<"verify_peer">> + } + ) + ), emqx_authn_test_lib:delete_config(?ResourceID), %% incompatible versions ?assertMatch( - {ok, _}, - create_mysql_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.1">>]})), + {ok, _}, + create_mysql_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.1">>] + } + ) + ), emqx_authn_test_lib:delete_config(?ResourceID), %% incompatible ciphers ?assertMatch( - {ok, _}, - create_mysql_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.2">>], - <<"ciphers">> => [<<"ECDHE-ECDSA-AES128-GCM-SHA256">>]})). + {ok, _}, + create_mysql_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.2">>], + <<"ciphers">> => [<<"ECDHE-ECDSA-AES128-GCM-SHA256">>] + } + ) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -113,27 +130,33 @@ create_mysql_auth_with_ssl_opts(SpecificSSLOpts) -> raw_mysql_auth_config(SpecificSSLOpts) -> SSLOpts = maps:merge( - emqx_authn_test_lib:client_ssl_cert_opts(), - #{enable => <<"true">>}), + emqx_authn_test_lib:client_ssl_cert_opts(), + #{enable => <<"true">>} + ), #{ - mechanism => <<"password_based">>, - password_hash_algorithm => #{name => <<"plain">>, - salt_position => <<"suffix">>}, - enable => <<"true">>, + mechanism => <<"password_based">>, + password_hash_algorithm => #{ + name => <<"plain">>, + salt_position => <<"suffix">> + }, + enable => <<"true">>, - backend => <<"mysql">>, - database => <<"mqtt">>, - username => <<"root">>, - password => <<"public">>, + backend => <<"mysql">>, + database => <<"mqtt">>, + username => <<"root">>, + password => <<"public">>, - query => <<"SELECT password_hash, salt, is_superuser_str as is_superuser - FROM users where username = ${username} LIMIT 1">>, - server => mysql_server(), - ssl => maps:merge(SSLOpts, SpecificSSLOpts) - }. + query => + << + "SELECT password_hash, salt, is_superuser_str as is_superuser\n" + " FROM users where username = ${username} LIMIT 1" + >>, + server => mysql_server(), + ssl => maps:merge(SSLOpts, SpecificSSLOpts) + }. mysql_server() -> - iolist_to_binary(io_lib:format("~s",[?MYSQL_HOST])). + iolist_to_binary(io_lib:format("~s", [?MYSQL_HOST])). start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authn/test/emqx_authn_password_hashing_SUITE.erl b/apps/emqx_authn/test/emqx_authn_password_hashing_SUITE.erl index 71ff2ff38..0052fcfe1 100644 --- a/apps/emqx_authn/test/emqx_authn_password_hashing_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_password_hashing_SUITE.erl @@ -38,118 +38,150 @@ end_per_suite(_Config) -> ok. t_gen_salt(_Config) -> - Algorithms = [#{name => Type, salt_position => suffix} || Type <- ?SIMPLE_HASHES] - ++ [#{name => bcrypt, salt_rounds => 10}], + Algorithms = + [#{name => Type, salt_position => suffix} || Type <- ?SIMPLE_HASHES] ++ + [#{name => bcrypt, salt_rounds => 10}], lists:foreach( - fun(Algorithm) -> - Salt = emqx_authn_password_hashing:gen_salt(Algorithm), - ct:pal("gen_salt(~p): ~p", [Algorithm, Salt]), - ?assert(is_binary(Salt)) - end, - Algorithms). + fun(Algorithm) -> + Salt = emqx_authn_password_hashing:gen_salt(Algorithm), + ct:pal("gen_salt(~p): ~p", [Algorithm, Salt]), + ?assert(is_binary(Salt)) + end, + Algorithms + ). t_init(_Config) -> - Algorithms = [#{name => Type, salt_position => suffix} || Type <- ?SIMPLE_HASHES] - ++ [#{name => bcrypt, salt_rounds => 10}], + Algorithms = + [#{name => Type, salt_position => suffix} || Type <- ?SIMPLE_HASHES] ++ + [#{name => bcrypt, salt_rounds => 10}], lists:foreach( - fun(Algorithm) -> - ok = emqx_authn_password_hashing:init(Algorithm) - end, - Algorithms). + fun(Algorithm) -> + ok = emqx_authn_password_hashing:init(Algorithm) + end, + Algorithms + ). t_check_password(_Config) -> lists:foreach( - fun test_check_password/1, - hash_examples()). + fun test_check_password/1, + hash_examples() + ). -test_check_password(#{ - password_hash := Hash, - salt := Salt, - password := Password, - password_hash_algorithm := Algorithm - } = Sample) -> +test_check_password( + #{ + password_hash := Hash, + salt := Salt, + password := Password, + password_hash_algorithm := Algorithm + } = Sample +) -> ct:pal("t_check_password sample: ~p", [Sample]), true = emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, Password), false = emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, <<"wrongpass">>). t_hash(_Config) -> lists:foreach( - fun test_hash/1, - hash_examples()). + fun test_hash/1, + hash_examples() + ). -test_hash(#{password := Password, - password_hash_algorithm := Algorithm - } = Sample) -> +test_hash( + #{ + password := Password, + password_hash_algorithm := Algorithm + } = Sample +) -> ct:pal("t_hash sample: ~p", [Sample]), {Hash, Salt} = emqx_authn_password_hashing:hash(Algorithm, Password), true = emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, Password). hash_examples() -> - [#{ - password_hash => <<"plainsalt">>, - salt => <<"salt">>, - password => <<"plain">>, - password_hash_algorithm => #{name => plain, - salt_position => suffix} - }, - #{ - password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>, - salt => <<"salt">>, - password => <<"md5">>, - password_hash_algorithm => #{name => md5, - salt_position => suffix} - }, - #{ - password_hash => <<"c665d4c0a9e5498806b7d9fd0b417d272853660e">>, - salt => <<"salt">>, - password => <<"sha">>, - password_hash_algorithm => #{name => sha, - salt_position => prefix} - }, - #{ - password_hash => <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>, - salt => <<"salt">>, - password => <<"sha256">>, - password_hash_algorithm => #{name => sha256, - salt_position => prefix} - }, - #{ - password_hash => <<"a1509ab67bfacbad020927b5ac9d91e9100a82e33a0ebb01459367ce921c0aa8" - "157aa5652f94bc84fa3babc08283e44887d61c48bcf8ad7bcb3259ee7d0eafcd">>, - salt => <<"salt">>, - password => <<"sha512">>, - password_hash_algorithm => #{name => sha512, - salt_position => prefix} - }, - #{ - password_hash => <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, - salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, - password => <<"bcrypt">>, + [ + #{ + password_hash => <<"plainsalt">>, + salt => <<"salt">>, + password => <<"plain">>, + password_hash_algorithm => #{ + name => plain, + salt_position => suffix + } + }, + #{ + password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>, + salt => <<"salt">>, + password => <<"md5">>, + password_hash_algorithm => #{ + name => md5, + salt_position => suffix + } + }, + #{ + password_hash => <<"c665d4c0a9e5498806b7d9fd0b417d272853660e">>, + salt => <<"salt">>, + password => <<"sha">>, + password_hash_algorithm => #{ + name => sha, + salt_position => prefix + } + }, + #{ + password_hash => <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>, + salt => <<"salt">>, + password => <<"sha256">>, + password_hash_algorithm => #{ + name => sha256, + salt_position => prefix + } + }, + #{ + password_hash => << + "a1509ab67bfacbad020927b5ac9d91e9100a82e33a0ebb01459367ce921c0aa8" + "157aa5652f94bc84fa3babc08283e44887d61c48bcf8ad7bcb3259ee7d0eafcd" + >>, + salt => <<"salt">>, + password => <<"sha512">>, + password_hash_algorithm => #{ + name => sha512, + salt_position => prefix + } + }, + #{ + password_hash => <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, + salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, + password => <<"bcrypt">>, - password_hash_algorithm => #{name => bcrypt, - salt_rounds => 10} - }, + password_hash_algorithm => #{ + name => bcrypt, + salt_rounds => 10 + } + }, - #{ - password_hash => <<"01dbee7f4a9e243e988b62c73cda935d" - "a05378b93244ec8f48a99e61ad799d86">>, - salt => <<"ATHENA.MIT.EDUraeburn">>, - password => <<"password">>, + #{ + password_hash => << + "01dbee7f4a9e243e988b62c73cda935d" + "a05378b93244ec8f48a99e61ad799d86" + >>, + salt => <<"ATHENA.MIT.EDUraeburn">>, + password => <<"password">>, - password_hash_algorithm => #{name => pbkdf2, - iterations => 2, - dk_length => 32, - mac_fun => sha} - }, - #{ - password_hash => <<"01dbee7f4a9e243e988b62c73cda935da05378b9">>, - salt => <<"ATHENA.MIT.EDUraeburn">>, - password => <<"password">>, + password_hash_algorithm => #{ + name => pbkdf2, + iterations => 2, + dk_length => 32, + mac_fun => sha + } + }, + #{ + password_hash => <<"01dbee7f4a9e243e988b62c73cda935da05378b9">>, + salt => <<"ATHENA.MIT.EDUraeburn">>, + password => <<"password">>, - password_hash_algorithm => #{name => pbkdf2, - iterations => 2, - mac_fun => sha} - } + password_hash_algorithm => #{ + name => pbkdf2, + iterations => 2, + mac_fun => sha + } + } ]. diff --git a/apps/emqx_authn/test/emqx_authn_pgsql_SUITE.erl b/apps/emqx_authn/test/emqx_authn_pgsql_SUITE.erl index 4b5606735..269825dce 100644 --- a/apps/emqx_authn/test/emqx_authn_pgsql_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_pgsql_SUITE.erl @@ -41,8 +41,9 @@ init_per_testcase(_, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), Config. init_per_group(require_seeds, Config) -> @@ -60,11 +61,12 @@ init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps([emqx_authn]), ok = start_apps([emqx_resource, emqx_connector]), {ok, _} = emqx_resource:create_local( - ?PGSQL_RESOURCE, - ?RESOURCE_GROUP, - emqx_connector_pgsql, - pgsql_config(), - #{}), + ?PGSQL_RESOURCE, + ?RESOURCE_GROUP, + emqx_connector_pgsql, + pgsql_config(), + #{} + ), Config; false -> {skip, no_pgsql} @@ -72,8 +74,9 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), ok = emqx_resource:remove_local(?PGSQL_RESOURCE), ok = stop_apps([emqx_resource, emqx_connector]), ok = emqx_common_test_helpers:stop_apps([emqx_authn]). @@ -86,8 +89,9 @@ t_create(_Config) -> AuthConfig = raw_pgsql_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), {ok, [#{provider := emqx_authn_pgsql}]} = emqx_authentication:list_authenticators(?GLOBAL), emqx_authn_test_lib:delete_config(?ResourceID). @@ -97,131 +101,156 @@ t_create_invalid(_Config) -> InvalidConfigs = [ - maps:without([server], AuthConfig), - AuthConfig#{server => <<"unknownhost:3333">>}, - AuthConfig#{password => <<"wrongpass">>}, - AuthConfig#{database => <<"wrongdatabase">>} + maps:without([server], AuthConfig), + AuthConfig#{server => <<"unknownhost:3333">>}, + AuthConfig#{password => <<"wrongpass">>}, + AuthConfig#{database => <<"wrongdatabase">>} ], lists:foreach( - fun(Config) -> - {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, Config}), - emqx_authn_test_lib:delete_config(?ResourceID), - {ok, []} = emqx_authentication:list_authenticators(?GLOBAL) - end, - InvalidConfigs). + fun(Config) -> + {ok, _} = emqx:update_config( + ?PATH, + {create_authenticator, ?GLOBAL, Config} + ), + emqx_authn_test_lib:delete_config(?ResourceID), + {ok, []} = emqx_authentication:list_authenticators(?GLOBAL) + end, + InvalidConfigs + ). t_authenticate(_Config) -> ok = lists:foreach( - fun(Sample) -> - ct:pal("test_user_auth sample: ~p", [Sample]), - test_user_auth(Sample) - end, - user_seeds()). + fun(Sample) -> + ct:pal("test_user_auth sample: ~p", [Sample]), + test_user_auth(Sample) + end, + user_seeds() + ). -test_user_auth(#{credentials := Credentials0, - config_params := SpecificConfigParams, - result := Result}) -> +test_user_auth(#{ + credentials := Credentials0, + config_params := SpecificConfigParams, + result := Result +}) -> AuthConfig = maps:merge(raw_pgsql_auth_config(), SpecificConfigParams), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), Credentials = Credentials0#{ - listener => 'tcp:default', - protocol => mqtt - }, + listener => 'tcp:default', + protocol => mqtt + }, ?assertEqual(Result, emqx_access_control:authenticate(Credentials)), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL). + [authentication], + ?GLOBAL + ). t_destroy(_Config) -> AuthConfig = raw_pgsql_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), - {ok, [#{provider := emqx_authn_pgsql, state := State}]} - = emqx_authentication:list_authenticators(?GLOBAL), + {ok, [#{provider := emqx_authn_pgsql, state := State}]} = + emqx_authentication:list_authenticators(?GLOBAL), {ok, _} = emqx_authn_pgsql:authenticate( - #{username => <<"plain">>, - password => <<"plain">> - }, - State), + #{ + username => <<"plain">>, + password => <<"plain">> + }, + State + ), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), % Authenticator should not be usable anymore ?assertMatch( - ignore, - emqx_authn_pgsql:authenticate( - #{username => <<"plain">>, - password => <<"plain">> - }, - State)). + ignore, + emqx_authn_pgsql:authenticate( + #{ + username => <<"plain">>, + password => <<"plain">> + }, + State + ) + ). t_update(_Config) -> CorrectConfig = raw_pgsql_auth_config(), IncorrectConfig = CorrectConfig#{ - query => <<"SELECT password_hash, salt, is_superuser_str as is_superuser - FROM users where username = ${username} LIMIT 0">>}, + query => + << + "SELECT password_hash, salt, is_superuser_str as is_superuser\n" + " FROM users where username = ${username} LIMIT 0" + >> + }, {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, IncorrectConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, IncorrectConfig} + ), {error, not_authorized} = emqx_access_control:authenticate( - #{username => <<"plain">>, - password => <<"plain">>, - listener => 'tcp:default', - protocol => mqtt - }), + #{ + username => <<"plain">>, + password => <<"plain">>, + listener => 'tcp:default', + protocol => mqtt + } + ), % We update with config with correct query, provider should update and work properly {ok, _} = emqx:update_config( - ?PATH, - {update_authenticator, ?GLOBAL, <<"password_based:postgresql">>, CorrectConfig}), + ?PATH, + {update_authenticator, ?GLOBAL, <<"password_based:postgresql">>, CorrectConfig} + ), - {ok,_} = emqx_access_control:authenticate( - #{username => <<"plain">>, - password => <<"plain">>, - listener => 'tcp:default', - protocol => mqtt - }). + {ok, _} = emqx_access_control:authenticate( + #{ + username => <<"plain">>, + password => <<"plain">>, + listener => 'tcp:default', + protocol => mqtt + } + ). t_is_superuser(_Config) -> Config = raw_pgsql_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, Config}), + ?PATH, + {create_authenticator, ?GLOBAL, Config} + ), Checks = [ - {is_superuser_str, "0", false}, - {is_superuser_str, "", false}, - {is_superuser_str, null, false}, - {is_superuser_str, "1", true}, - {is_superuser_str, "val", true}, + {is_superuser_str, "0", false}, + {is_superuser_str, "", false}, + {is_superuser_str, null, false}, + {is_superuser_str, "1", true}, + {is_superuser_str, "val", true}, - {is_superuser_int, 0, false}, - {is_superuser_int, null, false}, - {is_superuser_int, 1, true}, - {is_superuser_int, 123, true}, + {is_superuser_int, 0, false}, + {is_superuser_int, null, false}, + {is_superuser_int, 1, true}, + {is_superuser_int, 123, true}, - {is_superuser_bool, false, false}, - {is_superuser_bool, null, false}, - {is_superuser_bool, true, true} - ], + {is_superuser_bool, false, false}, + {is_superuser_bool, null, false}, + {is_superuser_bool, true, true} + ], lists:foreach(fun test_is_superuser/1, Checks). @@ -229,32 +258,36 @@ test_is_superuser({Field, Value, ExpectedValue}) -> {ok, _} = q("DELETE FROM users"), UserData = #{ - username => "user", - password_hash => "plainsalt", - salt => "salt", - Field => Value - }, + username => "user", + password_hash => "plainsalt", + salt => "salt", + Field => Value + }, ok = create_user(UserData), - Query = "SELECT password_hash, salt, " ++ atom_to_list(Field) ++ " as is_superuser " - "FROM users where username = ${username} LIMIT 1", + Query = + "SELECT password_hash, salt, " ++ atom_to_list(Field) ++ + " as is_superuser " + "FROM users where username = ${username} LIMIT 1", Config = maps:put(query, Query, raw_pgsql_auth_config()), {ok, _} = emqx:update_config( - ?PATH, - {update_authenticator, ?GLOBAL, <<"password_based:postgresql">>, Config}), + ?PATH, + {update_authenticator, ?GLOBAL, <<"password_based:postgresql">>, Config} + ), Credentials = #{ - listener => 'tcp:default', - protocol => mqtt, - username => <<"user">>, - password => <<"plain">> - }, + listener => 'tcp:default', + protocol => mqtt, + username => <<"user">>, + password => <<"plain">> + }, ?assertEqual( - {ok, #{is_superuser => ExpectedValue}}, - emqx_access_control:authenticate(Credentials)). + {ok, #{is_superuser => ExpectedValue}}, + emqx_access_control:authenticate(Credentials) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -262,167 +295,201 @@ test_is_superuser({Field, Value, ExpectedValue}) -> raw_pgsql_auth_config() -> #{ - mechanism => <<"password_based">>, - password_hash_algorithm => #{name => <<"plain">>, - salt_position => <<"suffix">>}, - enable => <<"true">>, + mechanism => <<"password_based">>, + password_hash_algorithm => #{ + name => <<"plain">>, + salt_position => <<"suffix">> + }, + enable => <<"true">>, - backend => <<"postgresql">>, - database => <<"mqtt">>, - username => <<"root">>, - password => <<"public">>, + backend => <<"postgresql">>, + database => <<"mqtt">>, + username => <<"root">>, + password => <<"public">>, - query => <<"SELECT password_hash, salt, is_superuser_str as is_superuser - FROM users where username = ${username} LIMIT 1">>, - server => pgsql_server() - }. + query => + << + "SELECT password_hash, salt, is_superuser_str as is_superuser\n" + " FROM users where username = ${username} LIMIT 1" + >>, + server => pgsql_server() + }. user_seeds() -> - [#{data => #{ - username => "plain", - password_hash => "plainsalt", - salt => "salt", - is_superuser_str => "1" - }, - credentials => #{ - username => <<"plain">>, - password => <<"plain">>}, - config_params => #{}, - result => {ok,#{is_superuser => true}} - }, - - #{data => #{ - username => "md5", - password_hash => "9b4d0c43d206d48279e69b9ad7132e22", - salt => "salt", - is_superuser_str => "0" - }, - credentials => #{ - username => <<"md5">>, - password => <<"md5">> - }, - config_params => #{ - password_hash_algorithm => #{name => <<"md5">>, - salt_position => <<"suffix">>} - }, - result => {ok,#{is_superuser => false}} - }, - - #{data => #{ - username => "sha256", - password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf", - salt => "salt", - is_superuser_int => 1 + [ + #{ + data => #{ + username => "plain", + password_hash => "plainsalt", + salt => "salt", + is_superuser_str => "1" + }, + credentials => #{ + username => <<"plain">>, + password => <<"plain">> + }, + config_params => #{}, + result => {ok, #{is_superuser => true}} }, - credentials => #{ - clientid => <<"sha256">>, - password => <<"sha256">> - }, - config_params => #{ - query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser - FROM users where username = ${clientid} LIMIT 1">>, - password_hash_algorithm => #{name => <<"sha256">>, - salt_position => <<"prefix">>} - }, - result => {ok,#{is_superuser => true}} - }, - #{data => #{ - username => <<"bcrypt">>, - password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", - salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", - is_superuser_int => 0 - }, - credentials => #{ - username => <<"bcrypt">>, - password => <<"bcrypt">> - }, - config_params => #{ - query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser - FROM users where username = ${username} LIMIT 1">>, - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {ok,#{is_superuser => false}} - }, + #{ + data => #{ + username => "md5", + password_hash => "9b4d0c43d206d48279e69b9ad7132e22", + salt => "salt", + is_superuser_str => "0" + }, + credentials => #{ + username => <<"md5">>, + password => <<"md5">> + }, + config_params => #{ + password_hash_algorithm => #{ + name => <<"md5">>, + salt_position => <<"suffix">> + } + }, + result => {ok, #{is_superuser => false}} + }, - #{data => #{ - username => <<"bcrypt0">>, - password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", - salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", - is_superuser_str => "0" - }, - credentials => #{ - username => <<"bcrypt0">>, - password => <<"bcrypt">> - }, - config_params => #{ - % clientid variable & username credentials - query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser - FROM users where username = ${clientid} LIMIT 1">>, - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {error,not_authorized} - }, + #{ + data => #{ + username => "sha256", + password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf", + salt => "salt", + is_superuser_int => 1 + }, + credentials => #{ + clientid => <<"sha256">>, + password => <<"sha256">> + }, + config_params => #{ + query => + << + "SELECT password_hash, salt, is_superuser_int as is_superuser\n" + " FROM users where username = ${clientid} LIMIT 1" + >>, + password_hash_algorithm => #{ + name => <<"sha256">>, + salt_position => <<"prefix">> + } + }, + result => {ok, #{is_superuser => true}} + }, - #{data => #{ - username => <<"bcrypt1">>, - password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", - salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", - is_superuser_str => "0" - }, - credentials => #{ - username => <<"bcrypt1">>, - password => <<"bcrypt">> - }, - config_params => #{ - % Bad keys in query - query => <<"SELECT 1 AS unknown_field - FROM users where username = ${username} LIMIT 1">>, - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {error,not_authorized} - }, + #{ + data => #{ + username => <<"bcrypt">>, + password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", + salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", + is_superuser_int => 0 + }, + credentials => #{ + username => <<"bcrypt">>, + password => <<"bcrypt">> + }, + config_params => #{ + query => + << + "SELECT password_hash, salt, is_superuser_int as is_superuser\n" + " FROM users where username = ${username} LIMIT 1" + >>, + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {ok, #{is_superuser => false}} + }, - #{data => #{ - username => <<"bcrypt2">>, - password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", - salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", - is_superuser => "0" - }, - credentials => #{ - username => <<"bcrypt2">>, - % Wrong password - password => <<"wrongpass">> - }, - config_params => #{ - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {error,bad_username_or_password} - } + #{ + data => #{ + username => <<"bcrypt0">>, + password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", + salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", + is_superuser_str => "0" + }, + credentials => #{ + username => <<"bcrypt0">>, + password => <<"bcrypt">> + }, + config_params => #{ + % clientid variable & username credentials + query => + << + "SELECT password_hash, salt, is_superuser_int as is_superuser\n" + " FROM users where username = ${clientid} LIMIT 1" + >>, + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {error, not_authorized} + }, + + #{ + data => #{ + username => <<"bcrypt1">>, + password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", + salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", + is_superuser_str => "0" + }, + credentials => #{ + username => <<"bcrypt1">>, + password => <<"bcrypt">> + }, + config_params => #{ + % Bad keys in query + query => + << + "SELECT 1 AS unknown_field\n" + " FROM users where username = ${username} LIMIT 1" + >>, + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {error, not_authorized} + }, + + #{ + data => #{ + username => <<"bcrypt2">>, + password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", + salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", + is_superuser => "0" + }, + credentials => #{ + username => <<"bcrypt2">>, + % Wrong password + password => <<"wrongpass">> + }, + config_params => #{ + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {error, bad_username_or_password} + } ]. init_seeds() -> ok = drop_seeds(), - {ok, _, _} = q("CREATE TABLE users( - username varchar(255), - password_hash varchar(255), - salt varchar(255), - is_superuser_str varchar(255), - is_superuser_int smallint, - is_superuser_bool boolean)"), + {ok, _, _} = q( + "CREATE TABLE users(\n" + " username varchar(255),\n" + " password_hash varchar(255),\n" + " salt varchar(255),\n" + " is_superuser_str varchar(255),\n" + " is_superuser_int smallint,\n" + " is_superuser_bool boolean)" + ), lists:foreach( - fun(#{data := Values}) -> - ok = create_user(Values) - end, - user_seeds()). + fun(#{data := Values}) -> + ok = create_user(Values) + end, + user_seeds() + ). create_user(Values) -> Fields = [username, password_hash, salt, is_superuser_str, is_superuser_int, is_superuser_bool], - InsertQuery = "INSERT INTO users(username, password_hash, salt," - "is_superuser_str, is_superuser_int, is_superuser_bool) " - "VALUES($1, $2, $3, $4, $5, $6)", + InsertQuery = + "INSERT INTO users(username, password_hash, salt," + "is_superuser_str, is_superuser_int, is_superuser_bool) " + "VALUES($1, $2, $3, $4, $5, $6)", Params = [maps:get(F, Values, null) || F <- Fields], {ok, 1} = q(InsertQuery, Params), @@ -430,30 +497,33 @@ create_user(Values) -> q(Sql) -> emqx_resource:query( - ?PGSQL_RESOURCE, - {query, Sql}). + ?PGSQL_RESOURCE, + {query, Sql} + ). q(Sql, Params) -> emqx_resource:query( - ?PGSQL_RESOURCE, - {query, Sql, Params}). + ?PGSQL_RESOURCE, + {query, Sql, Params} + ). drop_seeds() -> {ok, _, _} = q("DROP TABLE IF EXISTS users"), ok. pgsql_server() -> - iolist_to_binary(io_lib:format("~s",[?PGSQL_HOST])). + iolist_to_binary(io_lib:format("~s", [?PGSQL_HOST])). pgsql_config() -> - #{auto_reconnect => true, - database => <<"mqtt">>, - username => <<"root">>, - password => <<"public">>, - pool_size => 8, - server => {?PGSQL_HOST, ?PGSQL_DEFAULT_PORT}, - ssl => #{enable => false} - }. + #{ + auto_reconnect => true, + database => <<"mqtt">>, + username => <<"root">>, + password => <<"public">>, + pool_size => 8, + server => {?PGSQL_HOST, ?PGSQL_DEFAULT_PORT}, + ssl => #{enable => false} + }. start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authn/test/emqx_authn_pgsql_tls_SUITE.erl b/apps/emqx_authn/test/emqx_authn_pgsql_tls_SUITE.erl index 0039b7239..300cd748e 100644 --- a/apps/emqx_authn/test/emqx_authn_pgsql_tls_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_pgsql_tls_SUITE.erl @@ -39,8 +39,9 @@ init_per_testcase(_, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), Config. init_per_suite(Config) -> @@ -56,8 +57,9 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), ok = stop_apps([emqx_resource, emqx_connector]), ok = emqx_common_test_helpers:stop_apps([emqx_authn]). @@ -70,38 +72,53 @@ t_create(_Config) -> %% -starttls postgres -connect authn-server:5432 \ %% -cert client.crt -key client.key -CAfile ca.crt ?assertMatch( - {ok, _}, - create_pgsql_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.2">>], - <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]})). + {ok, _}, + create_pgsql_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.2">>], + <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>] + } + ) + ). t_create_invalid(_Config) -> - %% invalid server_name ?assertMatch( - {ok, _}, - create_pgsql_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server-unknown-host">>, - <<"verify">> => <<"verify_peer">>})), + {ok, _}, + create_pgsql_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server-unknown-host">>, + <<"verify">> => <<"verify_peer">> + } + ) + ), emqx_authn_test_lib:delete_config(?ResourceID), %% incompatible versions ?assertMatch( - {ok, _}, - create_pgsql_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.1">>]})), + {ok, _}, + create_pgsql_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.1">>] + } + ) + ), emqx_authn_test_lib:delete_config(?ResourceID), %% incompatible ciphers ?assertMatch( - {ok, _}, - create_pgsql_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.2">>], - <<"ciphers">> => [<<"ECDHE-ECDSA-AES128-GCM-SHA256">>]})). + {ok, _}, + create_pgsql_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.2">>], + <<"ciphers">> => [<<"ECDHE-ECDSA-AES128-GCM-SHA256">>] + } + ) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -113,26 +130,29 @@ create_pgsql_auth_with_ssl_opts(SpecificSSLOpts) -> raw_pgsql_auth_config(SpecificSSLOpts) -> SSLOpts = maps:merge( - emqx_authn_test_lib:client_ssl_cert_opts(), - #{enable => <<"true">>}), + emqx_authn_test_lib:client_ssl_cert_opts(), + #{enable => <<"true">>} + ), #{ - mechanism => <<"password_based">>, - password_hash_algorithm => #{name => <<"plain">>, - salt_position => <<"suffix">>}, - enable => <<"true">>, + mechanism => <<"password_based">>, + password_hash_algorithm => #{ + name => <<"plain">>, + salt_position => <<"suffix">> + }, + enable => <<"true">>, - backend => <<"postgresql">>, - database => <<"mqtt">>, - username => <<"root">>, - password => <<"public">>, + backend => <<"postgresql">>, + database => <<"mqtt">>, + username => <<"root">>, + password => <<"public">>, - query => <<"SELECT 1">>, - server => pgsql_server(), - ssl => maps:merge(SSLOpts, SpecificSSLOpts) - }. + query => <<"SELECT 1">>, + server => pgsql_server(), + ssl => maps:merge(SSLOpts, SpecificSSLOpts) + }. pgsql_server() -> - iolist_to_binary(io_lib:format("~s",[?PGSQL_HOST])). + iolist_to_binary(io_lib:format("~s", [?PGSQL_HOST])). start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authn/test/emqx_authn_redis_SUITE.erl b/apps/emqx_authn/test/emqx_authn_redis_SUITE.erl index 2cf476be7..ade356a3c 100644 --- a/apps/emqx_authn/test/emqx_authn_redis_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_redis_SUITE.erl @@ -40,8 +40,9 @@ init_per_testcase(_, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), Config. init_per_group(require_seeds, Config) -> @@ -59,11 +60,12 @@ init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps([emqx_authn]), ok = start_apps([emqx_resource, emqx_connector]), {ok, _} = emqx_resource:create_local( - ?REDIS_RESOURCE, - ?RESOURCE_GROUP, - emqx_connector_redis, - redis_config(), - #{}), + ?REDIS_RESOURCE, + ?RESOURCE_GROUP, + emqx_connector_redis, + redis_config(), + #{} + ), Config; false -> {skip, no_redis} @@ -71,8 +73,9 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), ok = emqx_resource:remove_local(?REDIS_RESOURCE), ok = stop_apps([emqx_resource, emqx_connector]), ok = emqx_common_test_helpers:stop_apps([emqx_authn]). @@ -86,8 +89,9 @@ t_create(_Config) -> AuthConfig = raw_redis_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), {ok, [#{provider := emqx_authn_redis}]} = emqx_authentication:list_authenticators(?GLOBAL). @@ -95,126 +99,152 @@ t_create_invalid(_Config) -> AuthConfig = raw_redis_auth_config(), InvalidConfigs = [ - AuthConfig#{ - cmd => <<"MGET password_hash:${username} salt:${username}">>}, - AuthConfig#{ - cmd => <<"HMGET mqtt_user:${username} password_hash invalid_field">>}, - AuthConfig#{ - cmd => <<"HMGET mqtt_user:${username} salt is_superuser">>} + AuthConfig#{ + cmd => <<"MGET password_hash:${username} salt:${username}">> + }, + AuthConfig#{ + cmd => <<"HMGET mqtt_user:${username} password_hash invalid_field">> + }, + AuthConfig#{ + cmd => <<"HMGET mqtt_user:${username} salt is_superuser">> + } ], lists:foreach( - fun(Config) -> - {error, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, Config}), + fun(Config) -> + {error, _} = emqx:update_config( + ?PATH, + {create_authenticator, ?GLOBAL, Config} + ), - {ok, []} = emqx_authentication:list_authenticators(?GLOBAL) - end, - InvalidConfigs), + {ok, []} = emqx_authentication:list_authenticators(?GLOBAL) + end, + InvalidConfigs + ), InvalidConfigs1 = [ - maps:without([server], AuthConfig), - AuthConfig#{server => <<"unknownhost:3333">>}, - AuthConfig#{password => <<"wrongpass">>}, - AuthConfig#{database => <<"5678">>} + maps:without([server], AuthConfig), + AuthConfig#{server => <<"unknownhost:3333">>}, + AuthConfig#{password => <<"wrongpass">>}, + AuthConfig#{database => <<"5678">>} ], lists:foreach( - fun(Config) -> - {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, Config}), - emqx_authn_test_lib:delete_config(?ResourceID), - {ok, []} = emqx_authentication:list_authenticators(?GLOBAL) - end, - InvalidConfigs1). + fun(Config) -> + {ok, _} = emqx:update_config( + ?PATH, + {create_authenticator, ?GLOBAL, Config} + ), + emqx_authn_test_lib:delete_config(?ResourceID), + {ok, []} = emqx_authentication:list_authenticators(?GLOBAL) + end, + InvalidConfigs1 + ). t_authenticate(_Config) -> ok = lists:foreach( - fun(Sample) -> - ct:pal("test_user_auth sample: ~p", [Sample]), - test_user_auth(Sample) - end, - user_seeds()). + fun(Sample) -> + ct:pal("test_user_auth sample: ~p", [Sample]), + test_user_auth(Sample) + end, + user_seeds() + ). -test_user_auth(#{credentials := Credentials0, - config_params := SpecificConfigParams, - result := Result}) -> +test_user_auth(#{ + credentials := Credentials0, + config_params := SpecificConfigParams, + result := Result +}) -> AuthConfig = maps:merge(raw_redis_auth_config(), SpecificConfigParams), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), Credentials = Credentials0#{ - listener => 'tcp:default', - protocol => mqtt - }, + listener => 'tcp:default', + protocol => mqtt + }, ?assertEqual(Result, emqx_access_control:authenticate(Credentials)), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL). + [authentication], + ?GLOBAL + ). t_destroy(_Config) -> AuthConfig = raw_redis_auth_config(), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, AuthConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, AuthConfig} + ), - {ok, [#{provider := emqx_authn_redis, state := State}]} - = emqx_authentication:list_authenticators(?GLOBAL), + {ok, [#{provider := emqx_authn_redis, state := State}]} = + emqx_authentication:list_authenticators(?GLOBAL), {ok, _} = emqx_authn_redis:authenticate( - #{username => <<"plain">>, - password => <<"plain">> - }, - State), + #{ + username => <<"plain">>, + password => <<"plain">> + }, + State + ), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), % Authenticator should not be usable anymore ?assertMatch( - ignore, - emqx_authn_redis:authenticate( - #{username => <<"plain">>, - password => <<"plain">> - }, - State)). + ignore, + emqx_authn_redis:authenticate( + #{ + username => <<"plain">>, + password => <<"plain">> + }, + State + ) + ). t_update(_Config) -> CorrectConfig = raw_redis_auth_config(), IncorrectConfig = CorrectConfig#{ - cmd => <<"HMGET invalid_key:${username} password_hash salt is_superuser">>}, + cmd => <<"HMGET invalid_key:${username} password_hash salt is_superuser">> + }, {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, IncorrectConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, IncorrectConfig} + ), {error, not_authorized} = emqx_access_control:authenticate( - #{username => <<"plain">>, - password => <<"plain">>, - listener => 'tcp:default', - protocol => mqtt - }), + #{ + username => <<"plain">>, + password => <<"plain">>, + listener => 'tcp:default', + protocol => mqtt + } + ), % We update with config with correct query, provider should update and work properly {ok, _} = emqx:update_config( - ?PATH, - {update_authenticator, ?GLOBAL, <<"password_based:redis">>, CorrectConfig}), + ?PATH, + {update_authenticator, ?GLOBAL, <<"password_based:redis">>, CorrectConfig} + ), - {ok,_} = emqx_access_control:authenticate( - #{username => <<"plain">>, - password => <<"plain">>, - listener => 'tcp:default', - protocol => mqtt - }). + {ok, _} = emqx_access_control:authenticate( + #{ + username => <<"plain">>, + password => <<"plain">>, + listener => 'tcp:default', + protocol => mqtt + } + ). %%------------------------------------------------------------------------------ %% Helpers @@ -222,194 +252,218 @@ t_update(_Config) -> raw_redis_auth_config() -> #{ - mechanism => <<"password_based">>, - password_hash_algorithm => #{name => <<"plain">>, - salt_position => <<"suffix">>}, - enable => <<"true">>, + mechanism => <<"password_based">>, + password_hash_algorithm => #{ + name => <<"plain">>, + salt_position => <<"suffix">> + }, + enable => <<"true">>, - backend => <<"redis">>, - cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>, - database => <<"1">>, - password => <<"public">>, - server => redis_server() - }. + backend => <<"redis">>, + cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>, + database => <<"1">>, + password => <<"public">>, + server => redis_server() + }. user_seeds() -> - [#{data => #{ - password_hash => <<"plainsalt">>, - salt => <<"salt">>, - is_superuser => <<"1">> - }, - credentials => #{ - username => <<"plain">>, - password => <<"plain">>}, - key => <<"mqtt_user:plain">>, - config_params => #{}, - result => {ok,#{is_superuser => true}} - }, - - #{data => #{ - password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>, - salt => <<"salt">>, - is_superuser => <<"0">> - }, - credentials => #{ - username => <<"md5">>, - password => <<"md5">> - }, - key => <<"mqtt_user:md5">>, - config_params => #{ - password_hash_algorithm => #{name => <<"md5">>, - salt_position => <<"suffix">>} - }, - result => {ok,#{is_superuser => false}} - }, - - #{data => #{ - password_hash => <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>, - salt => <<"salt">>, - is_superuser => <<"1">> + [ + #{ + data => #{ + password_hash => <<"plainsalt">>, + salt => <<"salt">>, + is_superuser => <<"1">> + }, + credentials => #{ + username => <<"plain">>, + password => <<"plain">> + }, + key => <<"mqtt_user:plain">>, + config_params => #{}, + result => {ok, #{is_superuser => true}} }, - credentials => #{ - clientid => <<"sha256">>, - password => <<"sha256">> - }, - key => <<"mqtt_user:sha256">>, - config_params => #{ - cmd => <<"HMGET mqtt_user:${clientid} password_hash salt is_superuser">>, - password_hash_algorithm => #{name => <<"sha256">>, - salt_position => <<"prefix">>} - }, - result => {ok,#{is_superuser => true}} - }, - #{data => #{ - password_hash => - <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, - salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, - is_superuser => <<"0">> - }, - credentials => #{ - username => <<"bcrypt">>, - password => <<"bcrypt">> - }, - key => <<"mqtt_user:bcrypt">>, - config_params => #{ - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {ok,#{is_superuser => false}} - }, - #{data => #{ - password_hash => <<"01dbee7f4a9e243e988b62c73cda935da05378b9">>, - salt => <<"ATHENA.MIT.EDUraeburn">>, - is_superuser => <<"0">> - }, - credentials => #{ - username => <<"pbkdf2">>, - password => <<"password">> - }, - key => <<"mqtt_user:pbkdf2">>, - config_params => #{ - password_hash_algorithm => #{name => <<"pbkdf2">>, - iterations => 2, - mac_fun => sha - } - }, - result => {ok,#{is_superuser => false}} - }, - #{data => #{ - password_hash => - <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, - salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, - is_superuser => <<"0">> - }, - credentials => #{ - username => <<"bcrypt0">>, - password => <<"bcrypt">> - }, - key => <<"mqtt_user:bcrypt0">>, - config_params => #{ - % clientid variable & username credentials - cmd => <<"HMGET mqtt_client:${clientid} password_hash salt is_superuser">>, - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {error,not_authorized} - }, + #{ + data => #{ + password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>, + salt => <<"salt">>, + is_superuser => <<"0">> + }, + credentials => #{ + username => <<"md5">>, + password => <<"md5">> + }, + key => <<"mqtt_user:md5">>, + config_params => #{ + password_hash_algorithm => #{ + name => <<"md5">>, + salt_position => <<"suffix">> + } + }, + result => {ok, #{is_superuser => false}} + }, - #{data => #{ - password_hash => - <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, - salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, - is_superuser => <<"0">> - }, - credentials => #{ - username => <<"bcrypt1">>, - password => <<"bcrypt">> - }, - key => <<"mqtt_user:bcrypt1">>, - config_params => #{ - % Bad key in cmd - cmd => <<"HMGET badkey:${username} password_hash salt is_superuser">>, - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {error,not_authorized} - }, + #{ + data => #{ + password_hash => + <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>, + salt => <<"salt">>, + is_superuser => <<"1">> + }, + credentials => #{ + clientid => <<"sha256">>, + password => <<"sha256">> + }, + key => <<"mqtt_user:sha256">>, + config_params => #{ + cmd => <<"HMGET mqtt_user:${clientid} password_hash salt is_superuser">>, + password_hash_algorithm => #{ + name => <<"sha256">>, + salt_position => <<"prefix">> + } + }, + result => {ok, #{is_superuser => true}} + }, - #{data => #{ - password_hash => + #{ + data => #{ + password_hash => <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, - salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, - is_superuser => <<"0">> - }, - credentials => #{ - username => <<"bcrypt2">>, - % Wrong password - password => <<"wrongpass">> - }, - key => <<"mqtt_user:bcrypt2">>, - config_params => #{ - cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>, - password_hash_algorithm => #{name => <<"bcrypt">>} - }, - result => {error,bad_username_or_password} - } + salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, + is_superuser => <<"0">> + }, + credentials => #{ + username => <<"bcrypt">>, + password => <<"bcrypt">> + }, + key => <<"mqtt_user:bcrypt">>, + config_params => #{ + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {ok, #{is_superuser => false}} + }, + #{ + data => #{ + password_hash => <<"01dbee7f4a9e243e988b62c73cda935da05378b9">>, + salt => <<"ATHENA.MIT.EDUraeburn">>, + is_superuser => <<"0">> + }, + credentials => #{ + username => <<"pbkdf2">>, + password => <<"password">> + }, + key => <<"mqtt_user:pbkdf2">>, + config_params => #{ + password_hash_algorithm => #{ + name => <<"pbkdf2">>, + iterations => 2, + mac_fun => sha + } + }, + result => {ok, #{is_superuser => false}} + }, + #{ + data => #{ + password_hash => + <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, + salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, + is_superuser => <<"0">> + }, + credentials => #{ + username => <<"bcrypt0">>, + password => <<"bcrypt">> + }, + key => <<"mqtt_user:bcrypt0">>, + config_params => #{ + % clientid variable & username credentials + cmd => <<"HMGET mqtt_client:${clientid} password_hash salt is_superuser">>, + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {error, not_authorized} + }, + + #{ + data => #{ + password_hash => + <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, + salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, + is_superuser => <<"0">> + }, + credentials => #{ + username => <<"bcrypt1">>, + password => <<"bcrypt">> + }, + key => <<"mqtt_user:bcrypt1">>, + config_params => #{ + % Bad key in cmd + cmd => <<"HMGET badkey:${username} password_hash salt is_superuser">>, + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {error, not_authorized} + }, + + #{ + data => #{ + password_hash => + <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, + salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, + is_superuser => <<"0">> + }, + credentials => #{ + username => <<"bcrypt2">>, + % Wrong password + password => <<"wrongpass">> + }, + key => <<"mqtt_user:bcrypt2">>, + config_params => #{ + cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>, + password_hash_algorithm => #{name => <<"bcrypt">>} + }, + result => {error, bad_username_or_password} + } ]. init_seeds() -> ok = drop_seeds(), lists:foreach( - fun(#{key := UserKey, data := Values}) -> - lists:foreach(fun({Key, Value}) -> - q(["HSET", UserKey, atom_to_list(Key), Value]) - end, - maps:to_list(Values)) - end, - user_seeds()). + fun(#{key := UserKey, data := Values}) -> + lists:foreach( + fun({Key, Value}) -> + q(["HSET", UserKey, atom_to_list(Key), Value]) + end, + maps:to_list(Values) + ) + end, + user_seeds() + ). q(Command) -> emqx_resource:query( - ?REDIS_RESOURCE, - {cmd, Command}). + ?REDIS_RESOURCE, + {cmd, Command} + ). drop_seeds() -> lists:foreach( - fun(#{key := UserKey}) -> - q(["DEL", UserKey]) - end, - user_seeds()). + fun(#{key := UserKey}) -> + q(["DEL", UserKey]) + end, + user_seeds() + ). redis_server() -> - iolist_to_binary(io_lib:format("~s",[?REDIS_HOST])). + iolist_to_binary(io_lib:format("~s", [?REDIS_HOST])). redis_config() -> - #{auto_reconnect => true, - database => 1, - pool_size => 8, - redis_type => single, - password => "public", - server => {?REDIS_HOST, ?REDIS_DEFAULT_PORT}, - ssl => #{enable => false} - }. + #{ + auto_reconnect => true, + database => 1, + pool_size => 8, + redis_type => single, + password => "public", + server => {?REDIS_HOST, ?REDIS_DEFAULT_PORT}, + ssl => #{enable => false} + }. start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authn/test/emqx_authn_redis_tls_SUITE.erl b/apps/emqx_authn/test/emqx_authn_redis_tls_SUITE.erl index 177877e25..604495d8c 100644 --- a/apps/emqx_authn/test/emqx_authn_redis_tls_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_redis_tls_SUITE.erl @@ -39,8 +39,9 @@ init_per_testcase(_, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), Config. init_per_suite(Config) -> @@ -56,8 +57,9 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), ok = stop_apps([emqx_resource, emqx_connector]), ok = emqx_common_test_helpers:stop_apps([emqx_authn]). @@ -67,39 +69,55 @@ end_per_suite(_Config) -> t_create(_Config) -> ?assertMatch( - {ok, _}, - create_redis_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.3">>], - <<"ciphers">> => [<<"TLS_CHACHA20_POLY1305_SHA256">>]})). + {ok, _}, + create_redis_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.3">>], + <<"ciphers">> => [<<"TLS_CHACHA20_POLY1305_SHA256">>] + } + ) + ). t_create_invalid(_Config) -> %% invalid server_name ?assertMatch( - {ok, _}, - create_redis_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server-unknown-host">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.3">>], - <<"ciphers">> => [<<"TLS_CHACHA20_POLY1305_SHA256">>]})), + {ok, _}, + create_redis_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server-unknown-host">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.3">>], + <<"ciphers">> => [<<"TLS_CHACHA20_POLY1305_SHA256">>] + } + ) + ), %% incompatible versions ?assertMatch( {error, _}, create_redis_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.1">>, <<"tlsv1.2">>]})), + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.1">>, <<"tlsv1.2">>] + } + ) + ), %% incompatible ciphers ?assertMatch( - {error, _}, - create_redis_auth_with_ssl_opts( - #{<<"server_name_indication">> => <<"authn-server">>, - <<"verify">> => <<"verify_peer">>, - <<"versions">> => [<<"tlsv1.3">>], - <<"ciphers">> => [<<"TLS_AES_128_GCM_SHA256">>]})). + {error, _}, + create_redis_auth_with_ssl_opts( + #{ + <<"server_name_indication">> => <<"authn-server">>, + <<"verify">> => <<"verify_peer">>, + <<"versions">> => [<<"tlsv1.3">>], + <<"ciphers">> => [<<"TLS_AES_128_GCM_SHA256">>] + } + ) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -111,24 +129,27 @@ create_redis_auth_with_ssl_opts(SpecificSSLOpts) -> raw_redis_auth_config(SpecificSSLOpts) -> SSLOpts = maps:merge( - emqx_authn_test_lib:client_ssl_cert_opts(), - #{enable => <<"true">>}), + emqx_authn_test_lib:client_ssl_cert_opts(), + #{enable => <<"true">>} + ), #{ - mechanism => <<"password_based">>, - password_hash_algorithm => #{name => <<"plain">>, - salt_position => <<"suffix">>}, - enable => <<"true">>, + mechanism => <<"password_based">>, + password_hash_algorithm => #{ + name => <<"plain">>, + salt_position => <<"suffix">> + }, + enable => <<"true">>, - backend => <<"redis">>, - cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>, - database => <<"1">>, - password => <<"public">>, - server => redis_server(), - ssl => maps:merge(SSLOpts, SpecificSSLOpts) - }. + backend => <<"redis">>, + cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>, + database => <<"1">>, + password => <<"public">>, + server => redis_server(), + ssl => maps:merge(SSLOpts, SpecificSSLOpts) + }. redis_server() -> - iolist_to_binary(io_lib:format("~s:~b",[?REDIS_HOST, ?REDIS_TLS_PORT])). + iolist_to_binary(io_lib:format("~s:~b", [?REDIS_HOST, ?REDIS_TLS_PORT])). start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authn/test/emqx_authn_test_lib.erl b/apps/emqx_authn/test/emqx_authn_test_lib.erl index affa4aa77..9e162b718 100644 --- a/apps/emqx_authn/test/emqx_authn_test_lib.erl +++ b/apps/emqx_authn/test/emqx_authn_test_lib.erl @@ -36,16 +36,19 @@ jwt_example() -> delete_authenticators(Path, Chain) -> case emqx_authentication:list_authenticators(Chain) of - {error, _} -> ok; + {error, _} -> + ok; {ok, Authenticators} -> lists:foreach( fun(#{id := ID}) -> emqx:update_config( Path, {delete_authenticator, Chain, ID}, - #{rawconf_with_defaults => true}) + #{rawconf_with_defaults => true} + ) end, - Authenticators) + Authenticators + ) end. delete_config(ID) -> @@ -53,10 +56,13 @@ delete_config(ID) -> emqx:update_config( [authentication], {delete_authenticator, ?GLOBAL, ID}, - #{rawconf_with_defaults => false}). + #{rawconf_with_defaults => false} + ). client_ssl_cert_opts() -> Dir = code:lib_dir(emqx_authn, test), - #{keyfile => filename:join([Dir, "data/certs", "client.key"]), - certfile => filename:join([Dir, "data/certs", "client.crt"]), - cacertfile => filename:join([Dir, "data/certs", "ca.crt"])}. + #{ + keyfile => filename:join([Dir, "data/certs", "client.key"]), + certfile => filename:join([Dir, "data/certs", "client.crt"]), + cacertfile => filename:join([Dir, "data/certs", "ca.crt"]) + }. diff --git a/apps/emqx_authn/test/emqx_enhanced_authn_scram_mnesia_SUITE.erl b/apps/emqx_authn/test/emqx_enhanced_authn_scram_mnesia_SUITE.erl index b36b8ea56..8e1963791 100644 --- a/apps/emqx_authn/test/emqx_enhanced_authn_scram_mnesia_SUITE.erl +++ b/apps/emqx_authn/test/emqx_enhanced_authn_scram_mnesia_SUITE.erl @@ -26,14 +26,16 @@ -define(PATH, [authentication]). --define(USER_MAP, #{user_id := _, - is_superuser := _}). +-define(USER_MAP, #{ + user_id := _, + is_superuser := _ +}). all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> - _ = application:load(emqx_conf), + _ = application:load(emqx_conf), ok = emqx_common_test_helpers:start_apps([emqx_authn]), Config. @@ -44,8 +46,9 @@ init_per_testcase(_Case, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), mria:clear_table(emqx_enhanced_authn_scram_mnesia), emqx_authn_test_lib:delete_authenticators( - [authentication], - ?GLOBAL), + [authentication], + ?GLOBAL + ), Config. end_per_testcase(_Case, Config) -> @@ -64,23 +67,25 @@ t_create(_Config) -> }, {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, ValidConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, ValidConfig} + ), - {ok, [#{provider := emqx_enhanced_authn_scram_mnesia}]} - = emqx_authentication:list_authenticators(?GLOBAL). + {ok, [#{provider := emqx_enhanced_authn_scram_mnesia}]} = + emqx_authentication:list_authenticators(?GLOBAL). t_create_invalid(_Config) -> InvalidConfig = #{ - <<"mechanism">> => <<"scram">>, - <<"backend">> => <<"built_in_database">>, - <<"algorithm">> => <<"sha271828">>, - <<"iteration_count">> => <<"4096">> - }, + <<"mechanism">> => <<"scram">>, + <<"backend">> => <<"built_in_database">>, + <<"algorithm">> => <<"sha271828">>, + <<"iteration_count">> => <<"4096">> + }, {error, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, InvalidConfig}), + ?PATH, + {create_authenticator, ?GLOBAL, InvalidConfig} + ), {ok, []} = emqx_authentication:list_authenticators(?GLOBAL). @@ -96,39 +101,47 @@ t_authenticate(_Config) -> ClientFirstMessage = esasl_scram:client_first_message(Username), ConnectPacket = ?CONNECT_PACKET( - #mqtt_packet_connect{ - proto_ver = ?MQTT_PROTO_V5, - properties = #{ - 'Authentication-Method' => <<"SCRAM-SHA-512">>, - 'Authentication-Data' => ClientFirstMessage - } - }), + #mqtt_packet_connect{ + proto_ver = ?MQTT_PROTO_V5, + properties = #{ + 'Authentication-Method' => <<"SCRAM-SHA-512">>, + 'Authentication-Data' => ClientFirstMessage + } + } + ), ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket), ?AUTH_PACKET( - ?RC_CONTINUE_AUTHENTICATION, - #{'Authentication-Data' := ServerFirstMessage}) = receive_packet(), + ?RC_CONTINUE_AUTHENTICATION, + #{'Authentication-Data' := ServerFirstMessage} + ) = receive_packet(), {continue, ClientFinalMessage, ClientCache} = esasl_scram:check_server_first_message( ServerFirstMessage, - #{client_first_message => ClientFirstMessage, - password => Password, - algorithm => Algorithm} + #{ + client_first_message => ClientFirstMessage, + password => Password, + algorithm => Algorithm + } ), AuthContinuePacket = ?AUTH_PACKET( - ?RC_CONTINUE_AUTHENTICATION, - #{'Authentication-Method' => <<"SCRAM-SHA-512">>, - 'Authentication-Data' => ClientFinalMessage}), + ?RC_CONTINUE_AUTHENTICATION, + #{ + 'Authentication-Method' => <<"SCRAM-SHA-512">>, + 'Authentication-Data' => ClientFinalMessage + } + ), ok = emqx_authn_mqtt_test_client:send(Pid, AuthContinuePacket), ?CONNACK_PACKET( - ?RC_SUCCESS, - _, - #{'Authentication-Data' := ServerFinalMessage}) = receive_packet(), + ?RC_SUCCESS, + _, + #{'Authentication-Data' := ServerFinalMessage} + ) = receive_packet(), ok = esasl_scram:check_server_final_message( ServerFinalMessage, ClientCache#{algorithm => Algorithm} @@ -146,13 +159,14 @@ t_authenticate_bad_username(_Config) -> ClientFirstMessage = esasl_scram:client_first_message(<<"badusername">>), ConnectPacket = ?CONNECT_PACKET( - #mqtt_packet_connect{ - proto_ver = ?MQTT_PROTO_V5, - properties = #{ - 'Authentication-Method' => <<"SCRAM-SHA-512">>, - 'Authentication-Data' => ClientFirstMessage - } - }), + #mqtt_packet_connect{ + proto_ver = ?MQTT_PROTO_V5, + properties = #{ + 'Authentication-Method' => <<"SCRAM-SHA-512">>, + 'Authentication-Data' => ClientFirstMessage + } + } + ), ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket), @@ -170,32 +184,39 @@ t_authenticate_bad_password(_Config) -> ClientFirstMessage = esasl_scram:client_first_message(Username), ConnectPacket = ?CONNECT_PACKET( - #mqtt_packet_connect{ - proto_ver = ?MQTT_PROTO_V5, - properties = #{ - 'Authentication-Method' => <<"SCRAM-SHA-512">>, - 'Authentication-Data' => ClientFirstMessage - } - }), + #mqtt_packet_connect{ + proto_ver = ?MQTT_PROTO_V5, + properties = #{ + 'Authentication-Method' => <<"SCRAM-SHA-512">>, + 'Authentication-Data' => ClientFirstMessage + } + } + ), ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket), ?AUTH_PACKET( - ?RC_CONTINUE_AUTHENTICATION, - #{'Authentication-Data' := ServerFirstMessage}) = receive_packet(), + ?RC_CONTINUE_AUTHENTICATION, + #{'Authentication-Data' := ServerFirstMessage} + ) = receive_packet(), {continue, ClientFinalMessage, _ClientCache} = esasl_scram:check_server_first_message( ServerFirstMessage, - #{client_first_message => ClientFirstMessage, - password => <<"badpassword">>, - algorithm => Algorithm} + #{ + client_first_message => ClientFirstMessage, + password => <<"badpassword">>, + algorithm => Algorithm + } ), AuthContinuePacket = ?AUTH_PACKET( - ?RC_CONTINUE_AUTHENTICATION, - #{'Authentication-Method' => <<"SCRAM-SHA-512">>, - 'Authentication-Data' => ClientFinalMessage}), + ?RC_CONTINUE_AUTHENTICATION, + #{ + 'Authentication-Method' => <<"SCRAM-SHA-512">>, + 'Authentication-Data' => ClientFinalMessage + } + ), ok = emqx_authn_mqtt_test_client:send(Pid, AuthContinuePacket), @@ -218,7 +239,7 @@ t_destroy(_) -> ok = emqx_enhanced_authn_scram_mnesia:destroy(State0), {ok, State1} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config), - {error,not_found} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State1), + {error, not_found} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State1), {ok, _} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, StateOther). t_add_user(_) -> @@ -248,12 +269,14 @@ t_update_user(_) -> {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(User, State), {ok, #{is_superuser := false}} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State), - {ok, - #{user_id := <<"u">>, - is_superuser := true}} = emqx_enhanced_authn_scram_mnesia:update_user( - <<"u">>, - #{password => <<"p1">>, is_superuser => true}, - State), + {ok, #{ + user_id := <<"u">>, + is_superuser := true + }} = emqx_enhanced_authn_scram_mnesia:update_user( + <<"u">>, + #{password => <<"p1">>, is_superuser => true}, + State + ), {ok, #{is_superuser := true}} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State). @@ -261,29 +284,47 @@ t_list_users(_) -> Config = config(), {ok, State} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config), - Users = [#{user_id => <<"u1">>, password => <<"p">>}, - #{user_id => <<"u2">>, password => <<"p">>}, - #{user_id => <<"u3">>, password => <<"p">>}], + Users = [ + #{user_id => <<"u1">>, password => <<"p">>}, + #{user_id => <<"u2">>, password => <<"p">>}, + #{user_id => <<"u3">>, password => <<"p">>} + ], lists:foreach( - fun(U) -> {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(U, State) end, - Users), + fun(U) -> {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(U, State) end, + Users + ), - #{data := [?USER_MAP, ?USER_MAP], - meta := #{page := 1, limit := 2, count := 3}} = emqx_enhanced_authn_scram_mnesia:list_users( - #{<<"page">> => 1, <<"limit">> => 2}, - State), - #{data := [?USER_MAP], - meta := #{page := 2, limit := 2, count := 3}} = emqx_enhanced_authn_scram_mnesia:list_users( - #{<<"page">> => 2, <<"limit">> => 2}, - State), - #{data := [#{user_id := <<"u1">>, - is_superuser := _}], - meta := #{page := 1, limit := 3, count := 1}} = emqx_enhanced_authn_scram_mnesia:list_users( - #{ <<"page">> => 1 - , <<"limit">> => 3 - , <<"like_username">> => <<"1">>}, - State). + #{ + data := [?USER_MAP, ?USER_MAP], + meta := #{page := 1, limit := 2, count := 3} + } = emqx_enhanced_authn_scram_mnesia:list_users( + #{<<"page">> => 1, <<"limit">> => 2}, + State + ), + #{ + data := [?USER_MAP], + meta := #{page := 2, limit := 2, count := 3} + } = emqx_enhanced_authn_scram_mnesia:list_users( + #{<<"page">> => 2, <<"limit">> => 2}, + State + ), + #{ + data := [ + #{ + user_id := <<"u1">>, + is_superuser := _ + } + ], + meta := #{page := 1, limit := 3, count := 1} + } = emqx_enhanced_authn_scram_mnesia:list_users( + #{ + <<"page">> => 1, + <<"limit">> => 3, + <<"like_username">> => <<"1">> + }, + State + ). t_is_superuser(_Config) -> ok = test_is_superuser(#{is_superuser => false}, false), @@ -297,36 +338,44 @@ test_is_superuser(UserInfo, ExpectedIsSuperuser) -> Username = <<"u">>, Password = <<"p">>, - UserInfo0 = UserInfo#{user_id => Username, - password => Password}, + UserInfo0 = UserInfo#{ + user_id => Username, + password => Password + }, {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(UserInfo0, State), ClientFirstMessage = esasl_scram:client_first_message(Username), - {continue, ServerFirstMessage, ServerCache} - = emqx_enhanced_authn_scram_mnesia:authenticate( - #{auth_method => <<"SCRAM-SHA-512">>, - auth_data => ClientFirstMessage, - auth_cache => #{} - }, - State), + {continue, ServerFirstMessage, ServerCache} = + emqx_enhanced_authn_scram_mnesia:authenticate( + #{ + auth_method => <<"SCRAM-SHA-512">>, + auth_data => ClientFirstMessage, + auth_cache => #{} + }, + State + ), {continue, ClientFinalMessage, ClientCache} = esasl_scram:check_server_first_message( ServerFirstMessage, - #{client_first_message => ClientFirstMessage, - password => Password, - algorithm => sha512} + #{ + client_first_message => ClientFirstMessage, + password => Password, + algorithm => sha512 + } ), - {ok, UserInfo1, ServerFinalMessage} - = emqx_enhanced_authn_scram_mnesia:authenticate( - #{auth_method => <<"SCRAM-SHA-512">>, - auth_data => ClientFinalMessage, - auth_cache => ServerCache - }, - State), + {ok, UserInfo1, ServerFinalMessage} = + emqx_enhanced_authn_scram_mnesia:authenticate( + #{ + auth_method => <<"SCRAM-SHA-512">>, + auth_data => ClientFinalMessage, + auth_cache => ServerCache + }, + State + ), ok = esasl_scram:check_server_final_message( ServerFinalMessage, ClientCache#{algorithm => sha512} @@ -336,18 +385,17 @@ test_is_superuser(UserInfo, ExpectedIsSuperuser) -> ok = emqx_enhanced_authn_scram_mnesia:destroy(State). - %%------------------------------------------------------------------------------ %% Helpers %%------------------------------------------------------------------------------ config() -> #{ - mechanism => <<"scram">>, - backend => <<"built_in_database">>, - algorithm => sha512, - iteration_count => 4096 - }. + mechanism => <<"scram">>, + backend => <<"built_in_database">>, + algorithm => sha512, + iteration_count => 4096 + }. raw_config(Algorithm) -> #{ @@ -361,14 +409,16 @@ init_auth(Username, Password, Algorithm) -> Config = raw_config(Algorithm), {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, Config}), + ?PATH, + {create_authenticator, ?GLOBAL, Config} + ), {ok, [#{state := State}]} = emqx_authentication:list_authenticators(?GLOBAL), emqx_enhanced_authn_scram_mnesia:add_user( - #{user_id => Username, password => Password}, - State). + #{user_id => Username, password => Password}, + State + ). receive_packet() -> receive From 82559b9b089c6cfadf54b7ce5143156dd403876f Mon Sep 17 00:00:00 2001 From: JimMoen Date: Fri, 1 Apr 2022 02:11:51 +0800 Subject: [PATCH 8/9] style: erlfmt apps/emqx_authz --- apps/emqx_authz/include/emqx_authz.hrl | 128 +-- apps/emqx_authz/rebar.config | 11 +- apps/emqx_authz/src/emqx_authz.app.src | 32 +- apps/emqx_authz/src/emqx_authz.erl | 184 ++-- apps/emqx_authz/src/emqx_authz_api_mnesia.erl | 792 +++++++++++------- apps/emqx_authz/src/emqx_authz_api_schema.erl | 204 +++-- .../src/emqx_authz_api_settings.erl | 54 +- .../emqx_authz/src/emqx_authz_api_sources.erl | 643 ++++++++------ apps/emqx_authz/src/emqx_authz_file.erl | 38 +- apps/emqx_authz/src/emqx_authz_http.erl | 148 ++-- apps/emqx_authz/src/emqx_authz_mnesia.erl | 137 +-- apps/emqx_authz/src/emqx_authz_mongodb.erl | 87 +- apps/emqx_authz/src/emqx_authz_mysql.erl | 91 +- apps/emqx_authz/src/emqx_authz_postgresql.erl | 96 ++- apps/emqx_authz/src/emqx_authz_redis.erl | 71 +- apps/emqx_authz/src/emqx_authz_rule.erl | 147 ++-- apps/emqx_authz/src/emqx_authz_schema.erl | 337 ++++---- apps/emqx_authz/src/emqx_authz_sup.erl | 8 +- apps/emqx_authz/src/emqx_authz_utils.erl | 74 +- apps/emqx_authz/test/emqx_authz_SUITE.erl | 301 ++++--- .../test/emqx_authz_api_mnesia_SUITE.erl | 314 ++++--- .../test/emqx_authz_api_settings_SUITE.erl | 50 +- .../test/emqx_authz_api_sources_SUITE.erl | 509 ++++++----- .../emqx_authz/test/emqx_authz_file_SUITE.erl | 91 +- .../emqx_authz/test/emqx_authz_http_SUITE.erl | 406 +++++---- .../test/emqx_authz_http_test_server.erl | 32 +- .../test/emqx_authz_mnesia_SUITE.erl | 109 +-- .../test/emqx_authz_mongodb_SUITE.erl | 223 +++-- .../test/emqx_authz_mysql_SUITE.erl | 325 ++++--- .../test/emqx_authz_postgresql_SUITE.erl | 331 +++++--- .../test/emqx_authz_redis_SUITE.erl | 204 +++-- .../emqx_authz/test/emqx_authz_rule_SUITE.erl | 359 +++++--- apps/emqx_authz/test/emqx_authz_test_lib.erl | 169 ++-- 33 files changed, 4042 insertions(+), 2663 deletions(-) diff --git a/apps/emqx_authz/include/emqx_authz.hrl b/apps/emqx_authz/include/emqx_authz.hrl index 13c100ba8..a3fe2d1f9 100644 --- a/apps/emqx_authz/include/emqx_authz.hrl +++ b/apps/emqx_authz/include/emqx_authz.hrl @@ -16,13 +16,15 @@ -define(APP, emqx_authz). --define(ALLOW_DENY(A), ((A =:= allow) orelse (A =:= <<"allow">>) orelse - (A =:= deny) orelse (A =:= <<"deny">>) - )). --define(PUBSUB(A), ((A =:= subscribe) orelse (A =:= <<"subscribe">>) orelse - (A =:= publish) orelse (A =:= <<"publish">>) orelse - (A =:= all) orelse (A =:= <<"all">>) - )). +-define(ALLOW_DENY(A), + ((A =:= allow) orelse (A =:= <<"allow">>) orelse + (A =:= deny) orelse (A =:= <<"deny">>)) +). +-define(PUBSUB(A), + ((A =:= subscribe) orelse (A =:= <<"subscribe">>) orelse + (A =:= publish) orelse (A =:= <<"publish">>) orelse + (A =:= all) orelse (A =:= <<"all">>)) +). %% authz_mnesia -define(ACL_TABLE, emqx_acl). @@ -44,53 +46,69 @@ -define(RE_PLACEHOLDER, "\\$\\{[a-z0-9_]+\\}"). %% API examples --define(USERNAME_RULES_EXAMPLE, #{username => user1, - rules => [ #{topic => <<"test/toopic/1">>, - permission => <<"allow">>, - action => <<"publish">> - } - , #{topic => <<"test/toopic/2">>, - permission => <<"allow">>, - action => <<"subscribe">> - } - , #{topic => <<"eq test/#">>, - permission => <<"deny">>, - action => <<"all">> - } - ] - }). --define(CLIENTID_RULES_EXAMPLE, #{clientid => client1, - rules => [ #{topic => <<"test/toopic/1">>, - permission => <<"allow">>, - action => <<"publish">> - } - , #{topic => <<"test/toopic/2">>, - permission => <<"allow">>, - action => <<"subscribe">> - } - , #{topic => <<"eq test/#">>, - permission => <<"deny">>, - action => <<"all">> - } - ] - }). --define(ALL_RULES_EXAMPLE, #{rules => [ #{topic => <<"test/toopic/1">>, - permission => <<"allow">>, - action => <<"publish">> - } - , #{topic => <<"test/toopic/2">>, - permission => <<"allow">>, - action => <<"subscribe">> - } - , #{topic => <<"eq test/#">>, - permission => <<"deny">>, - action => <<"all">> - } - ] - }). --define(META_EXAMPLE, #{ page => 1 - , limit => 100 - , count => 1 - }). +-define(USERNAME_RULES_EXAMPLE, #{ + username => user1, + rules => [ + #{ + topic => <<"test/toopic/1">>, + permission => <<"allow">>, + action => <<"publish">> + }, + #{ + topic => <<"test/toopic/2">>, + permission => <<"allow">>, + action => <<"subscribe">> + }, + #{ + topic => <<"eq test/#">>, + permission => <<"deny">>, + action => <<"all">> + } + ] +}). +-define(CLIENTID_RULES_EXAMPLE, #{ + clientid => client1, + rules => [ + #{ + topic => <<"test/toopic/1">>, + permission => <<"allow">>, + action => <<"publish">> + }, + #{ + topic => <<"test/toopic/2">>, + permission => <<"allow">>, + action => <<"subscribe">> + }, + #{ + topic => <<"eq test/#">>, + permission => <<"deny">>, + action => <<"all">> + } + ] +}). +-define(ALL_RULES_EXAMPLE, #{ + rules => [ + #{ + topic => <<"test/toopic/1">>, + permission => <<"allow">>, + action => <<"publish">> + }, + #{ + topic => <<"test/toopic/2">>, + permission => <<"allow">>, + action => <<"subscribe">> + }, + #{ + topic => <<"eq test/#">>, + permission => <<"deny">>, + action => <<"all">> + } + ] +}). +-define(META_EXAMPLE, #{ + page => 1, + limit => 100, + count => 1 +}). -define(RESOURCE_GROUP, <<"emqx_authz">>). diff --git a/apps/emqx_authz/rebar.config b/apps/emqx_authz/rebar.config index 2e33f41d5..da2fa7807 100644 --- a/apps/emqx_authz/rebar.config +++ b/apps/emqx_authz/rebar.config @@ -1,11 +1,14 @@ %% -*- mode: erlang -*- {erl_opts, [debug_info, nowarn_unused_import]}. -{deps, [ {emqx, {path, "../emqx"}} - , {emqx_connector, {path, "../emqx_connector"}} - ]}. +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_connector, {path, "../emqx_connector"}} +]}. {shell, [ - % {config, "config/sys.config"}, + % {config, "config/sys.config"}, {apps, [emqx_authz]} ]}. + +{project_plugins, [erlfmt]}. diff --git a/apps/emqx_authz/src/emqx_authz.app.src b/apps/emqx_authz/src/emqx_authz.app.src index ff43b3536..a8e781df9 100644 --- a/apps/emqx_authz/src/emqx_authz.app.src +++ b/apps/emqx_authz/src/emqx_authz.app.src @@ -1,18 +1,18 @@ %% -*- mode: erlang -*- -{application, emqx_authz, - [{description, "An OTP application"}, - {vsn, "0.1.1"}, - {registered, []}, - {mod, {emqx_authz_app, []}}, - {applications, - [kernel, - stdlib, - crypto, - emqx_connector - ]}, - {env,[]}, - {modules, []}, +{application, emqx_authz, [ + {description, "An OTP application"}, + {vsn, "0.1.1"}, + {registered, []}, + {mod, {emqx_authz_app, []}}, + {applications, [ + kernel, + stdlib, + crypto, + emqx_connector + ]}, + {env, []}, + {modules, []}, - {licenses, ["Apache 2.0"]}, - {links, []} - ]}. + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_authz/src/emqx_authz.erl b/apps/emqx_authz/src/emqx_authz.erl index 3e5a53649..40c987e1f 100644 --- a/apps/emqx_authz/src/emqx_authz.erl +++ b/apps/emqx_authz/src/emqx_authz.erl @@ -25,29 +25,30 @@ -compile(nowarn_export_all). -endif. --export([ register_metrics/0 - , init/0 - , deinit/0 - , lookup/0 - , lookup/1 - , move/2 - , update/2 - , authorize/5 - ]). +-export([ + register_metrics/0, + init/0, + deinit/0, + lookup/0, + lookup/1, + move/2, + update/2, + authorize/5 +]). -export([post_config_update/5, pre_config_update/3]). -export([acl_conf_file/0]). --type(source() :: map()). +-type source() :: map(). --type(match_result() :: {matched, allow} | {matched, deny} | nomatch). +-type match_result() :: {matched, allow} | {matched, deny} | nomatch. --type(default_result() :: allow | deny). +-type default_result() :: allow | deny. --type(authz_result() :: {stop, allow} | {ok, deny}). +-type authz_result() :: {stop, allow} | {ok, deny}. --type(sources() :: [source()]). +-type sources() :: [source()]. -define(METRIC_ALLOW, 'client.authorize.allow'). -define(METRIC_DENY, 'client.authorize.deny'). @@ -60,24 +61,25 @@ %% Initialize authz backend. %% Populate the passed configuration map with necessary data, %% like `ResourceID`s --callback(init(source()) -> source()). +-callback init(source()) -> source(). %% Get authz text description. --callback(description() -> string()). +-callback description() -> string(). %% Destroy authz backend. %% Make cleanup of all allocated data. %% An authz backend will not be used after `destroy`. --callback(destroy(source()) -> ok). +-callback destroy(source()) -> ok. %% Authorize client action. --callback(authorize( - emqx_types:clientinfo(), - emqx_types:pubsub(), - emqx_types:topic(), - source()) -> match_result()). +-callback authorize( + emqx_types:clientinfo(), + emqx_types:pubsub(), + emqx_types:topic(), + source() +) -> match_result(). --spec(register_metrics() -> ok). +-spec register_metrics() -> ok. register_metrics() -> lists:foreach(fun emqx_metrics:ensure/1, ?METRICS). @@ -104,13 +106,16 @@ lookup(Type) -> move(Type, ?CMD_MOVE_BEFORE(Before)) -> emqx_authz_utils:update_config( - ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), ?CMD_MOVE_BEFORE(type(Before))}); + ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), ?CMD_MOVE_BEFORE(type(Before))} + ); move(Type, ?CMD_MOVE_AFTER(After)) -> emqx_authz_utils:update_config( - ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), ?CMD_MOVE_AFTER(type(After))}); + ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), ?CMD_MOVE_AFTER(type(After))} + ); move(Type, Position) -> emqx_authz_utils:update_config( - ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), Position}). + ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), Position} + ). update({?CMD_REPLACE, Type}, Sources) -> emqx_authz_utils:update_config(?CONF_KEY_PATH, {{?CMD_REPLACE, type(Type)}, Sources}); @@ -205,7 +210,8 @@ do_move({?CMD_MOVE, Type, ?CMD_MOVE_AFTER(After)}, Sources) -> {S2, Front2, Rear2} = take(After, Front1 ++ Rear1), Front2 ++ [S2, S1] ++ Rear2. -ensure_resource_deleted(#{enable := false}) -> ok; +ensure_resource_deleted(#{enable := false}) -> + ok; ensure_resource_deleted(#{type := Type} = Source) -> Module = authz_module(Type), Module:destroy(Source). @@ -213,17 +219,19 @@ ensure_resource_deleted(#{type := Type} = Source) -> check_dup_types(Sources) -> check_dup_types(Sources, []). -check_dup_types([], _Checked) -> ok; +check_dup_types([], _Checked) -> + ok; check_dup_types([Source | Sources], Checked) -> %% the input might be raw or type-checked result, so lookup both 'type' and <<"type">> %% TODO: check: really? - Type = case maps:get(<<"type">>, Source, maps:get(type, Source, undefined)) of - undefined -> - %% this should never happen if the value is type checked by honcon schema - throw({bad_source_input, Source}); - Type0 -> - type(Type0) - end, + Type = + case maps:get(<<"type">>, Source, maps:get(type, Source, undefined)) of + undefined -> + %% this should never happen if the value is type checked by honcon schema + throw({bad_source_input, Source}); + Type0 -> + type(Type0) + end, case lists:member(Type, Checked) of true -> %% we have made it clear not to support more than one authz instance for each type @@ -240,7 +248,8 @@ init_sources(Sources) -> end, lists:map(fun init_source/1, Sources). -init_source(#{enable := false} = Source) -> Source; +init_source(#{enable := false} = Source) -> + Source; init_source(#{type := Type} = Source) -> Module = authz_module(Type), Module:init(Source). @@ -250,42 +259,63 @@ init_source(#{type := Type} = Source) -> %%-------------------------------------------------------------------- %% @doc Check AuthZ --spec(authorize( emqx_types:clientinfo() - , emqx_types:pubsub() - , emqx_types:topic() - , default_result() - , sources()) - -> authz_result()). -authorize(#{username := Username, - peerhost := IpAddress - } = Client, PubSub, Topic, DefaultResult, Sources) -> +-spec authorize( + emqx_types:clientinfo(), + emqx_types:pubsub(), + emqx_types:topic(), + default_result(), + sources() +) -> + authz_result(). +authorize( + #{ + username := Username, + peerhost := IpAddress + } = Client, + PubSub, + Topic, + DefaultResult, + Sources +) -> case do_authorize(Client, PubSub, Topic, Sources) of - {{matched, allow}, AuthzSource}-> - emqx:run_hook('client.check_authz_complete', - [Client, PubSub, Topic, allow, AuthzSource]), - ?SLOG(info, #{msg => "authorization_permission_allowed", - username => Username, - ipaddr => IpAddress, - topic => Topic}), + {{matched, allow}, AuthzSource} -> + emqx:run_hook( + 'client.check_authz_complete', + [Client, PubSub, Topic, allow, AuthzSource] + ), + ?SLOG(info, #{ + msg => "authorization_permission_allowed", + username => Username, + ipaddr => IpAddress, + topic => Topic + }), emqx_metrics:inc(?METRIC_ALLOW), {stop, allow}; - {{matched, deny}, AuthzSource}-> - emqx:run_hook('client.check_authz_complete', - [Client, PubSub, Topic, deny, AuthzSource]), - ?SLOG(info, #{msg => "authorization_permission_denied", - username => Username, - ipaddr => IpAddress, - topic => Topic}), + {{matched, deny}, AuthzSource} -> + emqx:run_hook( + 'client.check_authz_complete', + [Client, PubSub, Topic, deny, AuthzSource] + ), + ?SLOG(info, #{ + msg => "authorization_permission_denied", + username => Username, + ipaddr => IpAddress, + topic => Topic + }), emqx_metrics:inc(?METRIC_DENY), {stop, deny}; nomatch -> - emqx:run_hook('client.check_authz_complete', - [Client, PubSub, Topic, DefaultResult, default]), - ?SLOG(info, #{msg => "authorization_failed_nomatch", - username => Username, - ipaddr => IpAddress, - topic => Topic, - reason => "no-match rule"}), + emqx:run_hook( + 'client.check_authz_complete', + [Client, PubSub, Topic, DefaultResult, default] + ), + ?SLOG(info, #{ + msg => "authorization_failed_nomatch", + username => Username, + ipaddr => IpAddress, + topic => Topic, + reason => "no-match rule" + }), emqx_metrics:inc(?METRIC_NOMATCH), {stop, DefaultResult} end. @@ -294,8 +324,12 @@ do_authorize(_Client, _PubSub, _Topic, []) -> nomatch; do_authorize(Client, PubSub, Topic, [#{enable := false} | Rest]) -> do_authorize(Client, PubSub, Topic, Rest); -do_authorize(Client, PubSub, Topic, - [Connector = #{type := Type} | Tail] ) -> +do_authorize( + Client, + PubSub, + Topic, + [Connector = #{type := Type} | Tail] +) -> Module = authz_module(Type), case Module:authorize(Client, PubSub, Topic, Connector) of nomatch -> do_authorize(Client, PubSub, Topic, Tail); @@ -311,7 +345,7 @@ take(Type) -> take(Type, lookup()). %% Take the source of give type, the sources list is split into two parts %% front part and rear part. take(Type, Sources) -> - {Front, Rear} = lists:splitwith(fun(T) -> type(T) =/= type(Type) end, Sources), + {Front, Rear} = lists:splitwith(fun(T) -> type(T) =/= type(Type) end, Sources), case Rear =:= [] of true -> throw({not_found_source, Type}); @@ -321,7 +355,7 @@ take(Type, Sources) -> find_action_in_hooks() -> Callbacks = emqx_hooks:lookup('client.authorize'), - [Action] = [Action || {callback,{?MODULE, authorize, _} = Action, _, _} <- Callbacks ], + [Action] = [Action || {callback, {?MODULE, authorize, _} = Action, _, _} <- Callbacks], Action. authz_module('built_in_database') -> @@ -364,8 +398,11 @@ acl_conf_file() -> filename:join([emqx:data_dir(), "authz", "acl.conf"]). maybe_write_certs(#{<<"type">> := Type} = Source) -> - case emqx_tls_lib:ensure_ssl_files( - ssl_file_path(Type), maps:get(<<"ssl">>, Source, undefined)) of + case + emqx_tls_lib:ensure_ssl_files( + ssl_file_path(Type), maps:get(<<"ssl">>, Source, undefined) + ) + of {ok, SSL} -> new_ssl_source(Source, SSL); {error, Reason} -> @@ -380,7 +417,8 @@ clear_certs(OldSource) -> write_file(Filename, Bytes) -> ok = filelib:ensure_dir(Filename), case file:write_file(Filename, Bytes) of - ok -> {ok, iolist_to_binary(Filename)}; + ok -> + {ok, iolist_to_binary(Filename)}; {error, Reason} -> ?SLOG(error, #{filename => Filename, msg => "write_file_error", reason => Reason}), throw(Reason) diff --git a/apps/emqx_authz/src/emqx_authz_api_mnesia.erl b/apps/emqx_authz/src/emqx_authz_api_mnesia.erl index 12459d684..2f06ccdbc 100644 --- a/apps/emqx_authz/src/emqx_authz_api_mnesia.erl +++ b/apps/emqx_authz/src/emqx_authz_api_mnesia.erl @@ -30,25 +30,28 @@ -define(ACL_USERNAME_QSCHEMA, [{<<"like_username">>, binary}]). -define(ACL_CLIENTID_QSCHEMA, [{<<"like_clientid">>, binary}]). - --export([ api_spec/0 - , paths/0 - , schema/1 - , fields/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1, + fields/1 +]). %% operation funs --export([ users/2 - , clients/2 - , user/2 - , client/2 - , all/2 - , purge/2 - ]). +-export([ + users/2, + clients/2, + user/2, + client/2, + all/2, + purge/2 +]). %% query funs --export([ query_username/4 - , query_clientid/4]). +-export([ + query_username/4, + query_clientid/4 +]). -export([format_result/1]). @@ -65,279 +68,399 @@ api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). paths() -> - [ "/authorization/sources/built_in_database/username" - , "/authorization/sources/built_in_database/clientid" - , "/authorization/sources/built_in_database/username/:username" - , "/authorization/sources/built_in_database/clientid/:clientid" - , "/authorization/sources/built_in_database/all" - , "/authorization/sources/built_in_database/purge-all"]. + [ + "/authorization/sources/built_in_database/username", + "/authorization/sources/built_in_database/clientid", + "/authorization/sources/built_in_database/username/:username", + "/authorization/sources/built_in_database/clientid/:clientid", + "/authorization/sources/built_in_database/all", + "/authorization/sources/built_in_database/purge-all" + ]. %%-------------------------------------------------------------------- %% Schema for each URI %%-------------------------------------------------------------------- schema("/authorization/sources/built_in_database/username") -> - #{ 'operationId' => users - , get => - #{ tags => [<<"authorization">>] - , description => <<"Show the list of record for username">> - , parameters => - [ ref(emqx_dashboard_swagger, page) - , ref(emqx_dashboard_swagger, limit) - , { like_username - , mk( binary(), #{ in => query - , required => false - , desc => <<"Fuzzy search `username` as substring">>})} - ] - , responses => - #{ 200 => swagger_with_example( {username_response_data, ?TYPE_REF} - , {username, ?PAGE_QUERY_EXAMPLE}) + #{ + 'operationId' => users, + get => + #{ + tags => [<<"authorization">>], + description => <<"Show the list of record for username">>, + parameters => + [ + ref(emqx_dashboard_swagger, page), + ref(emqx_dashboard_swagger, limit), + {like_username, + mk(binary(), #{ + in => query, + required => false, + desc => <<"Fuzzy search `username` as substring">> + })} + ], + responses => + #{ + 200 => swagger_with_example( + {username_response_data, ?TYPE_REF}, + {username, ?PAGE_QUERY_EXAMPLE} + ) + } + }, + post => + #{ + tags => [<<"authorization">>], + description => <<"Add new records for username">>, + 'requestBody' => swagger_with_example( + {rules_for_username, ?TYPE_ARRAY}, + {username, ?POST_ARRAY_EXAMPLE} + ), + responses => + #{ + 204 => <<"Created">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad username or bad rule schema">> + ) + } } - } - , post => - #{ tags => [<<"authorization">>] - , description => <<"Add new records for username">> - , 'requestBody' => swagger_with_example( {rules_for_username, ?TYPE_ARRAY} - , {username, ?POST_ARRAY_EXAMPLE}) - , responses => - #{ 204 => <<"Created">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad username or bad rule schema">>) - } - } }; schema("/authorization/sources/built_in_database/clientid") -> - #{ 'operationId' => clients - , get => - #{ tags => [<<"authorization">>] - , description => <<"Show the list of record for clientid">> - , parameters => - [ ref(emqx_dashboard_swagger, page) - , ref(emqx_dashboard_swagger, limit) - , { like_clientid - , mk( binary() - , #{ in => query - , required => false - , desc => <<"Fuzzy search `clientid` as substring">>}) + #{ + 'operationId' => clients, + get => + #{ + tags => [<<"authorization">>], + description => <<"Show the list of record for clientid">>, + parameters => + [ + ref(emqx_dashboard_swagger, page), + ref(emqx_dashboard_swagger, limit), + {like_clientid, + mk( + binary(), + #{ + in => query, + required => false, + desc => <<"Fuzzy search `clientid` as substring">> + } + )} + ], + responses => + #{ + 200 => swagger_with_example( + {clientid_response_data, ?TYPE_REF}, + {clientid, ?PAGE_QUERY_EXAMPLE} + ) } - ] - , responses => - #{ 200 => swagger_with_example( {clientid_response_data, ?TYPE_REF} - , {clientid, ?PAGE_QUERY_EXAMPLE}) - } - } - , post => - #{ tags => [<<"authorization">>] - , description => <<"Add new records for clientid">> - , 'requestBody' => swagger_with_example( {rules_for_clientid, ?TYPE_ARRAY} - , {clientid, ?POST_ARRAY_EXAMPLE}) - , responses => - #{ 204 => <<"Created">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad clientid or bad rule schema">>) - } - } - }; -schema("/authorization/sources/built_in_database/username/:username") -> - #{ 'operationId' => user - , get => - #{ tags => [<<"authorization">>] - , description => <<"Get record info for username">> - , parameters => [ref(username)] - , responses => - #{ 200 => swagger_with_example( {rules_for_username, ?TYPE_REF} - , {username, ?PUT_MAP_EXAMPLE}) - , 404 => emqx_dashboard_swagger:error_codes( - [?NOT_FOUND], <<"Not Found">>) - } - } - , put => - #{ tags => [<<"authorization">>] - , description => <<"Set record for username">> - , parameters => [ref(username)] - , 'requestBody' => swagger_with_example( {rules_for_username, ?TYPE_REF} - , {username, ?PUT_MAP_EXAMPLE}) - , responses => - #{ 204 => <<"Updated">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad username or bad rule schema">>) - } - } - , delete => - #{ tags => [<<"authorization">>] - , description => <<"Delete one record for username">> - , parameters => [ref(username)] - , responses => - #{ 204 => <<"Deleted">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad username">>) - , 404 => emqx_dashboard_swagger:error_codes( - [?NOT_FOUND], <<"Username Not Found">>) - } - } - }; -schema("/authorization/sources/built_in_database/clientid/:clientid") -> - #{ 'operationId' => client - , get => - #{ tags => [<<"authorization">>] - , description => <<"Get record info for clientid">> - , parameters => [ref(clientid)] - , responses => - #{ 200 => swagger_with_example( {rules_for_clientid, ?TYPE_REF} - , {clientid, ?PUT_MAP_EXAMPLE}) - , 404 => emqx_dashboard_swagger:error_codes( - [?NOT_FOUND], <<"Not Found">>) - } }, - put => - #{ tags => [<<"authorization">>] - , description => <<"Set record for clientid">> - , parameters => [ref(clientid)] - , 'requestBody' => swagger_with_example( {rules_for_clientid, ?TYPE_REF} - , {clientid, ?PUT_MAP_EXAMPLE}) - , responses => - #{ 204 => <<"Updated">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad clientid or bad rule schema">>) - } + post => + #{ + tags => [<<"authorization">>], + description => <<"Add new records for clientid">>, + 'requestBody' => swagger_with_example( + {rules_for_clientid, ?TYPE_ARRAY}, + {clientid, ?POST_ARRAY_EXAMPLE} + ), + responses => + #{ + 204 => <<"Created">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad clientid or bad rule schema">> + ) + } } - , delete => - #{ tags => [<<"authorization">>] - , description => <<"Delete one record for clientid">> - , parameters => [ref(clientid)] - , responses => - #{ 204 => <<"Deleted">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad clientid">>) - , 404 => emqx_dashboard_swagger:error_codes( - [?NOT_FOUND], <<"ClientID Not Found">>) - } + }; +schema("/authorization/sources/built_in_database/username/:username") -> + #{ + 'operationId' => user, + get => + #{ + tags => [<<"authorization">>], + description => <<"Get record info for username">>, + parameters => [ref(username)], + responses => + #{ + 200 => swagger_with_example( + {rules_for_username, ?TYPE_REF}, + {username, ?PUT_MAP_EXAMPLE} + ), + 404 => emqx_dashboard_swagger:error_codes( + [?NOT_FOUND], <<"Not Found">> + ) + } + }, + put => + #{ + tags => [<<"authorization">>], + description => <<"Set record for username">>, + parameters => [ref(username)], + 'requestBody' => swagger_with_example( + {rules_for_username, ?TYPE_REF}, + {username, ?PUT_MAP_EXAMPLE} + ), + responses => + #{ + 204 => <<"Updated">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad username or bad rule schema">> + ) + } + }, + delete => + #{ + tags => [<<"authorization">>], + description => <<"Delete one record for username">>, + parameters => [ref(username)], + responses => + #{ + 204 => <<"Deleted">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad username">> + ), + 404 => emqx_dashboard_swagger:error_codes( + [?NOT_FOUND], <<"Username Not Found">> + ) + } } - }; + }; +schema("/authorization/sources/built_in_database/clientid/:clientid") -> + #{ + 'operationId' => client, + get => + #{ + tags => [<<"authorization">>], + description => <<"Get record info for clientid">>, + parameters => [ref(clientid)], + responses => + #{ + 200 => swagger_with_example( + {rules_for_clientid, ?TYPE_REF}, + {clientid, ?PUT_MAP_EXAMPLE} + ), + 404 => emqx_dashboard_swagger:error_codes( + [?NOT_FOUND], <<"Not Found">> + ) + } + }, + put => + #{ + tags => [<<"authorization">>], + description => <<"Set record for clientid">>, + parameters => [ref(clientid)], + 'requestBody' => swagger_with_example( + {rules_for_clientid, ?TYPE_REF}, + {clientid, ?PUT_MAP_EXAMPLE} + ), + responses => + #{ + 204 => <<"Updated">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad clientid or bad rule schema">> + ) + } + }, + delete => + #{ + tags => [<<"authorization">>], + description => <<"Delete one record for clientid">>, + parameters => [ref(clientid)], + responses => + #{ + 204 => <<"Deleted">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad clientid">> + ), + 404 => emqx_dashboard_swagger:error_codes( + [?NOT_FOUND], <<"ClientID Not Found">> + ) + } + } + }; schema("/authorization/sources/built_in_database/all") -> - #{ 'operationId' => all - , get => - #{ tags => [<<"authorization">>] - , description => <<"Show the list of rules for all">> - , responses => - #{200 => swagger_with_example({rules, ?TYPE_REF}, {all, ?PUT_MAP_EXAMPLE})} + #{ + 'operationId' => all, + get => + #{ + tags => [<<"authorization">>], + description => <<"Show the list of rules for all">>, + responses => + #{200 => swagger_with_example({rules, ?TYPE_REF}, {all, ?PUT_MAP_EXAMPLE})} + }, + post => + #{ + tags => [<<"authorization">>], + description => << + "Create/Update the list of rules for all. " + "Set a empty list to clean up rules" + >>, + 'requestBody' => + swagger_with_example({rules, ?TYPE_REF}, {all, ?PUT_MAP_EXAMPLE}), + responses => + #{ + 204 => <<"Updated">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad rule schema">> + ) + } } - , post => - #{ tags => [<<"authorization">>] - , description => <<"Create/Update the list of rules for all. " - "Set a empty list to clean up rules">> - , 'requestBody' => - swagger_with_example({rules, ?TYPE_REF}, {all, ?PUT_MAP_EXAMPLE}) - , responses => - #{ 204 => <<"Updated">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad rule schema">>) - } - } - }; + }; schema("/authorization/sources/built_in_database/purge-all") -> - #{ 'operationId' => purge - , delete => - #{ tags => [<<"authorization">>] - , description => <<"Purge all records">> - , responses => - #{ 204 => <<"Deleted">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad Request">>) - } + #{ + 'operationId' => purge, + delete => + #{ + tags => [<<"authorization">>], + description => <<"Purge all records">>, + responses => + #{ + 204 => <<"Deleted">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad Request">> + ) + } } - }. + }. fields(rule_item) -> - [ {topic, mk(string(), - #{ required => true - , desc => <<"Rule on specific topic">> - , example => <<"test/topic/1">> - })} - , {permission, mk(enum([allow, deny]), - #{ desc => <<"Permission">> - , required => true - , example => allow - })} - , {action, mk(enum([publish, subscribe, all]), - #{ required => true - , example => publish - , desc => <<"Authorized action">> - })} + [ + {topic, + mk( + string(), + #{ + required => true, + desc => <<"Rule on specific topic">>, + example => <<"test/topic/1">> + } + )}, + {permission, + mk( + enum([allow, deny]), + #{ + desc => <<"Permission">>, + required => true, + example => allow + } + )}, + {action, + mk( + enum([publish, subscribe, all]), + #{ + required => true, + example => publish, + desc => <<"Authorized action">> + } + )} ]; fields(clientid) -> - [ {clientid, mk(binary(), - #{ in => path - , required => true - , desc => <<"ClientID">> - , example => <<"client1">> - })} + [ + {clientid, + mk( + binary(), + #{ + in => path, + required => true, + desc => <<"ClientID">>, + example => <<"client1">> + } + )} ]; fields(username) -> - [ {username, mk(binary(), - #{ in => path - , required => true - , desc => <<"Username">> - , example => <<"user1">>})} + [ + {username, + mk( + binary(), + #{ + in => path, + required => true, + desc => <<"Username">>, + example => <<"user1">> + } + )} ]; fields(rules_for_username) -> - fields(rules) - ++ fields(username); + fields(rules) ++ + fields(username); fields(username_response_data) -> - [ {data, mk(array(ref(rules_for_username)), #{})} - , {meta, ref(meta)} + [ + {data, mk(array(ref(rules_for_username)), #{})}, + {meta, ref(meta)} ]; fields(rules_for_clientid) -> - fields(rules) - ++ fields(clientid); + fields(rules) ++ + fields(clientid); fields(clientid_response_data) -> - [ {data, mk(array(ref(rules_for_clientid)), #{})} - , {meta, ref(meta)} + [ + {data, mk(array(ref(rules_for_clientid)), #{})}, + {meta, ref(meta)} ]; fields(rules) -> [{rules, mk(array(ref(rule_item)))}]; fields(meta) -> - emqx_dashboard_swagger:fields(page) - ++ emqx_dashboard_swagger:fields(limit) - ++ [{count, mk(integer(), #{example => 1})}]. + emqx_dashboard_swagger:fields(page) ++ + emqx_dashboard_swagger:fields(limit) ++ + [{count, mk(integer(), #{example => 1})}]. %%-------------------------------------------------------------------- %% HTTP API %%-------------------------------------------------------------------- users(get, #{query_string := QueryString}) -> - Response = emqx_mgmt_api:node_query(node(), QueryString, - ?ACL_TABLE, ?ACL_USERNAME_QSCHEMA, ?QUERY_USERNAME_FUN), + Response = emqx_mgmt_api:node_query( + node(), + QueryString, + ?ACL_TABLE, + ?ACL_USERNAME_QSCHEMA, + ?QUERY_USERNAME_FUN + ), emqx_mgmt_util:generate_response(Response); users(post, #{body := Body}) when is_list(Body) -> - lists:foreach(fun(#{<<"username">> := Username, <<"rules">> := Rules}) -> - emqx_authz_mnesia:store_rules({username, Username}, format_rules(Rules)) - end, Body), + lists:foreach( + fun(#{<<"username">> := Username, <<"rules">> := Rules}) -> + emqx_authz_mnesia:store_rules({username, Username}, format_rules(Rules)) + end, + Body + ), {204}. clients(get, #{query_string := QueryString}) -> - Response = emqx_mgmt_api:node_query(node(), QueryString, - ?ACL_TABLE, ?ACL_CLIENTID_QSCHEMA, ?QUERY_CLIENTID_FUN), + Response = emqx_mgmt_api:node_query( + node(), + QueryString, + ?ACL_TABLE, + ?ACL_CLIENTID_QSCHEMA, + ?QUERY_CLIENTID_FUN + ), emqx_mgmt_util:generate_response(Response); clients(post, #{body := Body}) when is_list(Body) -> - lists:foreach(fun(#{<<"clientid">> := ClientID, <<"rules">> := Rules}) -> - emqx_authz_mnesia:store_rules({clientid, ClientID}, format_rules(Rules)) - end, Body), + lists:foreach( + fun(#{<<"clientid">> := ClientID, <<"rules">> := Rules}) -> + emqx_authz_mnesia:store_rules({clientid, ClientID}, format_rules(Rules)) + end, + Body + ), {204}. user(get, #{bindings := #{username := Username}}) -> case emqx_authz_mnesia:get_rules({username, Username}) of - not_found -> {404, #{code => <<"NOT_FOUND">>, message => <<"Not Found">>}}; + not_found -> + {404, #{code => <<"NOT_FOUND">>, message => <<"Not Found">>}}; {ok, Rules} -> - {200, #{username => Username, - rules => [ #{topic => Topic, - action => Action, - permission => Permission - } || {Permission, Action, Topic} <- Rules]} - } + {200, #{ + username => Username, + rules => [ + #{ + topic => Topic, + action => Action, + permission => Permission + } + || {Permission, Action, Topic} <- Rules + ] + }} end; -user(put, #{bindings := #{username := Username}, - body := #{<<"username">> := Username, <<"rules">> := Rules}}) -> +user(put, #{ + bindings := #{username := Username}, + body := #{<<"username">> := Username, <<"rules">> := Rules} +}) -> emqx_authz_mnesia:store_rules({username, Username}, format_rules(Rules)), {204}; user(delete, #{bindings := #{username := Username}}) -> @@ -351,17 +474,25 @@ user(delete, #{bindings := #{username := Username}}) -> client(get, #{bindings := #{clientid := ClientID}}) -> case emqx_authz_mnesia:get_rules({clientid, ClientID}) of - not_found -> {404, #{code => <<"NOT_FOUND">>, message => <<"Not Found">>}}; + not_found -> + {404, #{code => <<"NOT_FOUND">>, message => <<"Not Found">>}}; {ok, Rules} -> - {200, #{clientid => ClientID, - rules => [ #{topic => Topic, - action => Action, - permission => Permission - } || {Permission, Action, Topic} <- Rules]} - } + {200, #{ + clientid => ClientID, + rules => [ + #{ + topic => Topic, + action => Action, + permission => Permission + } + || {Permission, Action, Topic} <- Rules + ] + }} end; -client(put, #{bindings := #{clientid := ClientID}, - body := #{<<"clientid">> := ClientID, <<"rules">> := Rules}}) -> +client(put, #{ + bindings := #{clientid := ClientID}, + body := #{<<"clientid">> := ClientID, <<"rules">> := Rules} +}) -> emqx_authz_mnesia:store_rules({clientid, ClientID}, format_rules(Rules)), {204}; client(delete, #{bindings := #{clientid := ClientID}}) -> @@ -378,11 +509,16 @@ all(get, _) -> not_found -> {200, #{rules => []}}; {ok, Rules} -> - {200, #{rules => [ #{topic => Topic, - action => Action, - permission => Permission - } || {Permission, Action, Topic} <- Rules]} - } + {200, #{ + rules => [ + #{ + topic => Topic, + action => Action, + permission => Permission + } + || {Permission, Action, Topic} <- Rules + ] + }} end; all(post, #{body := #{<<"rules">> := Rules}}) -> emqx_authz_mnesia:store_rules(all, format_rules(Rules)), @@ -394,13 +530,16 @@ purge(delete, _) -> ok = emqx_authz_mnesia:purge_rules(), {204}; [#{<<"enable">> := true}] -> - {400, #{code => <<"BAD_REQUEST">>, - message => - <<"'built_in_database' type source must be disabled before purge.">>}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => + <<"'built_in_database' type source must be disabled before purge.">> + }}; [] -> - {404, #{code => <<"BAD_REQUEST">>, - message => <<"'built_in_database' type source is not found.">> - }} + {404, #{ + code => <<"BAD_REQUEST">>, + message => <<"'built_in_database' type source is not found.">> + }} end. %%-------------------------------------------------------------------- @@ -408,25 +547,43 @@ purge(delete, _) -> query_username(Tab, {_QString, []}, Continuation, Limit) -> Ms = emqx_authz_mnesia:list_username_rules(), - emqx_mgmt_api:select_table_with_count(Tab, Ms, Continuation, Limit, - fun format_result/1); - + emqx_mgmt_api:select_table_with_count( + Tab, + Ms, + Continuation, + Limit, + fun format_result/1 + ); query_username(Tab, {_QString, FuzzyQString}, Continuation, Limit) -> Ms = emqx_authz_mnesia:list_username_rules(), FuzzyFilterFun = fuzzy_filter_fun(FuzzyQString), - emqx_mgmt_api:select_table_with_count(Tab, {Ms, FuzzyFilterFun}, Continuation, Limit, - fun format_result/1). + emqx_mgmt_api:select_table_with_count( + Tab, + {Ms, FuzzyFilterFun}, + Continuation, + Limit, + fun format_result/1 + ). query_clientid(Tab, {_QString, []}, Continuation, Limit) -> Ms = emqx_authz_mnesia:list_clientid_rules(), - emqx_mgmt_api:select_table_with_count(Tab, Ms, Continuation, Limit, - fun format_result/1); - + emqx_mgmt_api:select_table_with_count( + Tab, + Ms, + Continuation, + Limit, + fun format_result/1 + ); query_clientid(Tab, {_QString, FuzzyQString}, Continuation, Limit) -> Ms = emqx_authz_mnesia:list_clientid_rules(), FuzzyFilterFun = fuzzy_filter_fun(FuzzyQString), - emqx_mgmt_api:select_table_with_count(Tab, {Ms, FuzzyFilterFun}, Continuation, Limit, - fun format_result/1). + emqx_mgmt_api:select_table_with_count( + Tab, + {Ms, FuzzyFilterFun}, + Continuation, + Limit, + fun format_result/1 + ). %%-------------------------------------------------------------------- %% Match funcs @@ -434,17 +591,23 @@ query_clientid(Tab, {_QString, FuzzyQString}, Continuation, Limit) -> %% Fuzzy username funcs fuzzy_filter_fun(Fuzzy) -> fun(MsRaws) when is_list(MsRaws) -> - lists:filter( fun(E) -> run_fuzzy_filter(E, Fuzzy) end - , MsRaws) + lists:filter( + fun(E) -> run_fuzzy_filter(E, Fuzzy) end, + MsRaws + ) end. run_fuzzy_filter(_, []) -> true; -run_fuzzy_filter( E = [{username, Username}, _Rule] - , [{username, like, UsernameSubStr} | Fuzzy]) -> +run_fuzzy_filter( + E = [{username, Username}, _Rule], + [{username, like, UsernameSubStr} | Fuzzy] +) -> binary:match(Username, UsernameSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy); -run_fuzzy_filter( E = [{clientid, ClientId}, _Rule] - , [{clientid, like, ClientIdSubStr} | Fuzzy]) -> +run_fuzzy_filter( + E = [{clientid, ClientId}, _Rule], + [{clientid, like, ClientIdSubStr} | Fuzzy] +) -> binary:match(ClientId, ClientIdSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy). %%-------------------------------------------------------------------- @@ -452,31 +615,52 @@ run_fuzzy_filter( E = [{clientid, ClientId}, _Rule] %% format rule from api format_rules(Rules) when is_list(Rules) -> - lists:foldl(fun(#{<<"topic">> := Topic, - <<"action">> := Action, - <<"permission">> := Permission - }, AccIn) when ?PUBSUB(Action) - andalso ?ALLOW_DENY(Permission) -> - AccIn ++ [{ atom(Permission), atom(Action), Topic }] - end, [], Rules). + lists:foldl( + fun( + #{ + <<"topic">> := Topic, + <<"action">> := Action, + <<"permission">> := Permission + }, + AccIn + ) when + ?PUBSUB(Action) andalso + ?ALLOW_DENY(Permission) + -> + AccIn ++ [{atom(Permission), atom(Action), Topic}] + end, + [], + Rules + ). %% format result from mnesia tab format_result([{username, Username}, {rules, Rules}]) -> - #{username => Username, - rules => [ #{topic => Topic, - action => Action, - permission => Permission - } || {Permission, Action, Topic} <- Rules] - }; + #{ + username => Username, + rules => [ + #{ + topic => Topic, + action => Action, + permission => Permission + } + || {Permission, Action, Topic} <- Rules + ] + }; format_result([{clientid, ClientID}, {rules, Rules}]) -> - #{clientid => ClientID, - rules => [ #{topic => Topic, - action => Action, - permission => Permission - } || {Permission, Action, Topic} <- Rules] - }. + #{ + clientid => ClientID, + rules => [ + #{ + topic => Topic, + action => Action, + permission => Permission + } + || {Permission, Action, Topic} <- Rules + ] + }. atom(B) when is_binary(B) -> - try binary_to_existing_atom(B, utf8) + try + binary_to_existing_atom(B, utf8) catch _Error:_Expection -> binary_to_atom(B) end; @@ -488,25 +672,27 @@ atom(A) when is_atom(A) -> A. swagger_with_example({Ref, TypeP}, {_Name, _Type} = Example) -> emqx_dashboard_swagger:schema_with_examples( - case TypeP of - ?TYPE_REF -> ref(?MODULE, Ref); - ?TYPE_ARRAY -> array(ref(?MODULE, Ref)) - end, - rules_example(Example)). + case TypeP of + ?TYPE_REF -> ref(?MODULE, Ref); + ?TYPE_ARRAY -> array(ref(?MODULE, Ref)) + end, + rules_example(Example) + ). rules_example({ExampleName, ExampleType}) -> {Summary, Example} = case ExampleName of username -> {<<"Username">>, ?USERNAME_RULES_EXAMPLE}; clientid -> {<<"ClientID">>, ?CLIENTID_RULES_EXAMPLE}; - all -> {<<"All">>, ?ALL_RULES_EXAMPLE} + all -> {<<"All">>, ?ALL_RULES_EXAMPLE} end, Value = case ExampleType of - ?PAGE_QUERY_EXAMPLE -> #{ - data => [Example], - meta => ?META_EXAMPLE - }; + ?PAGE_QUERY_EXAMPLE -> + #{ + data => [Example], + meta => ?META_EXAMPLE + }; ?PUT_MAP_EXAMPLE -> Example; ?POST_ARRAY_EXAMPLE -> @@ -515,6 +701,6 @@ rules_example({ExampleName, ExampleType}) -> #{ 'password_based:built_in_database' => #{ summary => Summary, - value => Value + value => Value } }. diff --git a/apps/emqx_authz/src/emqx_authz_api_schema.erl b/apps/emqx_authz/src/emqx_authz_api_schema.erl index 35822bd52..78c8539ae 100644 --- a/apps/emqx_authz/src/emqx_authz_api_schema.erl +++ b/apps/emqx_authz/src/emqx_authz_api_schema.erl @@ -25,58 +25,77 @@ -export([fields/1, authz_sources_types/1]). fields(http) -> - authz_common_fields(http) - ++ [ {url, fun url/1} - , {method, #{ type => enum([get, post]) - , default => get}} - , {headers, fun headers/1} - , {body, map([{fuzzy, term(), binary()}])} - , {request_timeout, mk_duration("Request timeout", #{default => "30s"})}] - ++ maps:to_list(maps:without([ base_url - , pool_type], - maps:from_list(emqx_connector_http:fields(config)))); + authz_common_fields(http) ++ + [ + {url, fun url/1}, + {method, #{ + type => enum([get, post]), + default => get + }}, + {headers, fun headers/1}, + {body, map([{fuzzy, term(), binary()}])}, + {request_timeout, mk_duration("Request timeout", #{default => "30s"})} + ] ++ + maps:to_list( + maps:without( + [ + base_url, + pool_type + ], + maps:from_list(emqx_connector_http:fields(config)) + ) + ); fields('built_in_database') -> authz_common_fields('built_in_database'); fields(mongo_single) -> - authz_mongo_common_fields() - ++ emqx_connector_mongo:fields(single); + authz_mongo_common_fields() ++ + emqx_connector_mongo:fields(single); fields(mongo_rs) -> - authz_mongo_common_fields() - ++ emqx_connector_mongo:fields(rs); + authz_mongo_common_fields() ++ + emqx_connector_mongo:fields(rs); fields(mongo_sharded) -> - authz_mongo_common_fields() - ++ emqx_connector_mongo:fields(sharded); + authz_mongo_common_fields() ++ + emqx_connector_mongo:fields(sharded); fields(mysql) -> - authz_common_fields(mysql) - ++ [ {query, #{type => binary()}}] - ++ emqx_connector_mysql:fields(config); + authz_common_fields(mysql) ++ + [{query, #{type => binary()}}] ++ + emqx_connector_mysql:fields(config); fields(postgresql) -> - authz_common_fields(postgresql) - ++ [ {query, #{type => binary()}}] - ++ proplists:delete(named_queries, emqx_connector_pgsql:fields(config)); + authz_common_fields(postgresql) ++ + [{query, #{type => binary()}}] ++ + proplists:delete(named_queries, emqx_connector_pgsql:fields(config)); fields(redis_single) -> - authz_redis_common_fields() - ++ emqx_connector_redis:fields(single); + authz_redis_common_fields() ++ + emqx_connector_redis:fields(single); fields(redis_sentinel) -> - authz_redis_common_fields() - ++ emqx_connector_redis:fields(sentinel); + authz_redis_common_fields() ++ + emqx_connector_redis:fields(sentinel); fields(redis_cluster) -> - authz_redis_common_fields() - ++ emqx_connector_redis:fields(cluster); + authz_redis_common_fields() ++ + emqx_connector_redis:fields(cluster); fields(file) -> - authz_common_fields(file) - ++ [ { rules, #{ type => binary() - , required => true - , example => - <<"{allow,{username,\"^dashboard?\"},","subscribe,[\"$SYS/#\"]}.\n", - "{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">>}} - ]; + authz_common_fields(file) ++ + [ + {rules, #{ + type => binary(), + required => true, + example => + <<"{allow,{username,\"^dashboard?\"},", "subscribe,[\"$SYS/#\"]}.\n", + "{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">> + }} + ]; fields(position) -> - [ { position - , mk( string() - , #{ desc => <<"Where to place the source">> - , required => true - , in => body})}]. + [ + {position, + mk( + string(), + #{ + desc => <<"Where to place the source">>, + required => true, + in => body + } + )} + ]. %%------------------------------------------------------------------------------ %% http type funcs @@ -86,41 +105,52 @@ url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")]; url(required) -> true; url(_) -> undefined. -headers(type) -> map(); +headers(type) -> + map(); headers(converter) -> fun(Headers) -> - maps:merge(default_headers(), transform_header_name(Headers)) + maps:merge(default_headers(), transform_header_name(Headers)) end; -headers(default) -> default_headers(); -headers(_) -> undefined. +headers(default) -> + default_headers(); +headers(_) -> + undefined. %% headers default_headers() -> - maps:put(<<"content-type">>, - <<"application/json">>, - default_headers_no_content_type()). + maps:put( + <<"content-type">>, + <<"application/json">>, + default_headers_no_content_type() + ). default_headers_no_content_type() -> - #{ <<"accept">> => <<"application/json">> - , <<"cache-control">> => <<"no-cache">> - , <<"connection">> => <<"keep-alive">> - , <<"keep-alive">> => <<"timeout=30, max=1000">> - }. + #{ + <<"accept">> => <<"application/json">>, + <<"cache-control">> => <<"no-cache">>, + <<"connection">> => <<"keep-alive">>, + <<"keep-alive">> => <<"timeout=30, max=1000">> + }. transform_header_name(Headers) -> - maps:fold(fun(K0, V, Acc) -> - K = list_to_binary(string:to_lower(to_list(K0))), - maps:put(K, V, Acc) - end, #{}, Headers). + maps:fold( + fun(K0, V, Acc) -> + K = list_to_binary(string:to_lower(to_list(K0))), + maps:put(K, V, Acc) + end, + #{}, + Headers + ). %%------------------------------------------------------------------------------ %% MonogDB type funcs authz_mongo_common_fields() -> authz_common_fields(mongodb) ++ - [ {collection, fun collection/1} - , {selector, fun selector/1} - ]. + [ + {collection, fun collection/1}, + {selector, fun selector/1} + ]. collection(type) -> binary(); collection(_) -> undefined. @@ -133,19 +163,24 @@ selector(_) -> undefined. authz_redis_common_fields() -> authz_common_fields(redis) ++ - [ {cmd, #{ type => binary() - , example => <<"HGETALL mqtt_authz">>}}]. + [ + {cmd, #{ + type => binary(), + example => <<"HGETALL mqtt_authz">> + }} + ]. %%------------------------------------------------------------------------------ %% Authz api type funcs -authz_common_fields(Type) when is_atom(Type)-> - [ {enable, fun enable/1} - , {type, #{ type => enum([Type]) - , default => Type - , in => body - } - } +authz_common_fields(Type) when is_atom(Type) -> + [ + {enable, fun enable/1}, + {type, #{ + type => enum([Type]), + default => Type, + in => body + }} ]. enable(type) -> boolean(); @@ -158,20 +193,25 @@ enable(_) -> undefined. authz_sources_types(Type) -> case Type of - simple -> [mongodb, redis]; - detailed -> [ mongo_single - , mongo_rs - , mongo_sharded - , redis_single - , redis_sentinel - , redis_cluster] - end - ++ - [ http - , 'built_in_database' - , mysql - , postgresql - , file]. + simple -> + [mongodb, redis]; + detailed -> + [ + mongo_single, + mongo_rs, + mongo_sharded, + redis_single, + redis_sentinel, + redis_cluster + ] + end ++ + [ + http, + 'built_in_database', + mysql, + postgresql, + file + ]. to_list(A) when is_atom(A) -> atom_to_list(A); diff --git a/apps/emqx_authz/src/emqx_authz_api_settings.erl b/apps/emqx_authz/src/emqx_authz_api_settings.erl index b4b2ab5cc..cd00d131e 100644 --- a/apps/emqx_authz/src/emqx_authz_api_settings.erl +++ b/apps/emqx_authz/src/emqx_authz_api_settings.erl @@ -20,10 +20,11 @@ -import(hoconsc, [mk/1, ref/2]). --export([ api_spec/0 - , paths/0 - , schema/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1 +]). -export([settings/2]). @@ -40,33 +41,42 @@ paths() -> %%-------------------------------------------------------------------- schema("/authorization/settings") -> - #{ 'operationId' => settings - , get => - #{ description => <<"Get authorization settings">> - , responses => - #{200 => ref_authz_schema()} + #{ + 'operationId' => settings, + get => + #{ + description => <<"Get authorization settings">>, + responses => + #{200 => ref_authz_schema()} + }, + put => + #{ + description => <<"Update authorization settings">>, + 'requestBody' => ref_authz_schema(), + responses => + #{ + 200 => ref_authz_schema(), + 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) + } } - , put => - #{ description => <<"Update authorization settings">> - , 'requestBody' => ref_authz_schema() - , responses => - #{ 200 => ref_authz_schema() - , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>)} - } - }. + }. ref_authz_schema() -> proplists:delete(sources, emqx_conf_schema:fields("authorization")). settings(get, _Params) -> {200, authorization_settings()}; - -settings(put, #{body := #{<<"no_match">> := NoMatch, - <<"deny_action">> := DenyAction, - <<"cache">> := Cache}}) -> +settings(put, #{ + body := #{ + <<"no_match">> := NoMatch, + <<"deny_action">> := DenyAction, + <<"cache">> := Cache + } +}) -> {ok, _} = emqx_authz_utils:update_config([authorization, no_match], NoMatch), {ok, _} = emqx_authz_utils:update_config( - [authorization, deny_action], DenyAction), + [authorization, deny_action], DenyAction + ), {ok, _} = emqx_authz_utils:update_config([authorization, cache], Cache), ok = emqx_authz_cache:drain_cache(), {200, authorization_settings()}. diff --git a/apps/emqx_authz/src/emqx_authz_api_sources.erl b/apps/emqx_authz/src/emqx_authz_api_sources.erl index 22a4f0f86..759fd92f8 100644 --- a/apps/emqx_authz/src/emqx_authz_api_sources.erl +++ b/apps/emqx_authz/src/emqx_authz_api_sources.erl @@ -29,167 +29,226 @@ -define(API_SCHEMA_MODULE, emqx_authz_api_schema). --export([ get_raw_sources/0 - , get_raw_source/1 - , source_status/2 - , lookup_from_local_node/1 - , lookup_from_all_nodes/1 - ]). +-export([ + get_raw_sources/0, + get_raw_source/1, + source_status/2, + lookup_from_local_node/1, + lookup_from_all_nodes/1 +]). --export([ api_spec/0 - , paths/0 - , schema/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1 +]). --export([ sources/2 - , source/2 - , move_source/2 - ]). +-export([ + sources/2, + source/2, + move_source/2 +]). api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). paths() -> - [ "/authorization/sources" - , "/authorization/sources/:type" - , "/authorization/sources/:type/status" - , "/authorization/sources/:type/move"]. + [ + "/authorization/sources", + "/authorization/sources/:type", + "/authorization/sources/:type/status", + "/authorization/sources/:type/move" + ]. %%-------------------------------------------------------------------- %% Schema for each URI %%-------------------------------------------------------------------- schema("/authorization/sources") -> - #{ 'operationId' => sources - , get => - #{ description => <<"List all authorization sources">> - , responses => - #{ 200 => mk( array(hoconsc:union(authz_sources_type_refs())) - , #{desc => <<"Authorization source">>}) - } + #{ + 'operationId' => sources, + get => + #{ + description => <<"List all authorization sources">>, + responses => + #{ + 200 => mk( + array(hoconsc:union(authz_sources_type_refs())), + #{desc => <<"Authorization source">>} + ) + } + }, + post => + #{ + description => <<"Add a new source">>, + 'requestBody' => mk( + hoconsc:union(authz_sources_type_refs()), + #{desc => <<"Source config">>} + ), + responses => + #{ + 204 => <<"Authorization source created successfully">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], + <<"Bad Request">> + ) + } } - , post => - #{ description => <<"Add a new source">> - , 'requestBody' => mk( hoconsc:union(authz_sources_type_refs()) - , #{desc => <<"Source config">>}) - , responses => - #{ 204 => <<"Authorization source created successfully">> - , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], - <<"Bad Request">>) - } - } - }; + }; schema("/authorization/sources/:type") -> - #{ 'operationId' => source - , get => - #{ description => <<"Get a authorization source">> - , parameters => parameters_field() - , responses => - #{ 200 => mk( hoconsc:union(authz_sources_type_refs()) - , #{desc => <<"Authorization source">>}) - , 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) - } + #{ + 'operationId' => source, + get => + #{ + description => <<"Get a authorization source">>, + parameters => parameters_field(), + responses => + #{ + 200 => mk( + hoconsc:union(authz_sources_type_refs()), + #{desc => <<"Authorization source">>} + ), + 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) + } + }, + put => + #{ + description => <<"Update source">>, + parameters => parameters_field(), + 'requestBody' => mk(hoconsc:union(authz_sources_type_refs())), + responses => + #{ + 204 => <<"Authorization source updated successfully">>, + 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) + } + }, + delete => + #{ + description => <<"Delete source">>, + parameters => parameters_field(), + responses => + #{ + 204 => <<"Deleted successfully">>, + 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) + } } - , put => - #{ description => <<"Update source">> - , parameters => parameters_field() - , 'requestBody' => mk(hoconsc:union(authz_sources_type_refs())) - , responses => - #{ 204 => <<"Authorization source updated successfully">> - , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) - } - } - , delete => - #{ description => <<"Delete source">> - , parameters => parameters_field() - , responses => - #{ 204 => <<"Deleted successfully">> - , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) - } - } - }; + }; schema("/authorization/sources/:type/status") -> - #{ 'operationId' => source_status - , get => - #{ description => <<"Get a authorization source">> - , parameters => parameters_field() - , responses => - #{ 200 => emqx_dashboard_swagger:schema_with_examples( - hoconsc:ref(emqx_authn_schema, "metrics_status_fields"), - status_metrics_example()) - , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad request">>) - , 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) - } + #{ + 'operationId' => source_status, + get => + #{ + description => <<"Get a authorization source">>, + parameters => parameters_field(), + responses => + #{ + 200 => emqx_dashboard_swagger:schema_with_examples( + hoconsc:ref(emqx_authn_schema, "metrics_status_fields"), + status_metrics_example() + ), + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad request">> + ), + 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) + } } - }; + }; schema("/authorization/sources/:type/move") -> - #{ 'operationId' => move_source - , post => - #{ description => <<"Change the order of sources">> - , parameters => parameters_field() - , 'requestBody' => - emqx_dashboard_swagger:schema_with_examples( - ref(?API_SCHEMA_MODULE, position), - position_example()) - , responses => - #{ 204 => <<"No Content">> - , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) - , 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) - } + #{ + 'operationId' => move_source, + post => + #{ + description => <<"Change the order of sources">>, + parameters => parameters_field(), + 'requestBody' => + emqx_dashboard_swagger:schema_with_examples( + ref(?API_SCHEMA_MODULE, position), + position_example() + ), + responses => + #{ + 204 => <<"No Content">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad Request">> + ), + 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) + } } - }. - + }. %%-------------------------------------------------------------------- %% Operation functions %%-------------------------------------------------------------------- -sources(Method, #{bindings := #{type := Type} = Bindings } = Req) - when is_atom(Type) -> +sources(Method, #{bindings := #{type := Type} = Bindings} = Req) when + is_atom(Type) +-> sources(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}}); sources(get, _) -> - Sources = lists:foldl(fun (#{<<"type">> := <<"file">>, - <<"enable">> := Enable, <<"path">> := Path}, AccIn) -> - case file:read_file(Path) of - {ok, Rules} -> - lists:append(AccIn, [#{type => file, - enable => Enable, - rules => Rules - }]); - {error, _} -> - lists:append(AccIn, [#{type => file, - enable => Enable, - rules => <<"">> - }]) - end; - (Source, AccIn) -> - lists:append(AccIn, [read_certs(Source)]) - end, [], get_raw_sources()), + Sources = lists:foldl( + fun + ( + #{ + <<"type">> := <<"file">>, + <<"enable">> := Enable, + <<"path">> := Path + }, + AccIn + ) -> + case file:read_file(Path) of + {ok, Rules} -> + lists:append(AccIn, [ + #{ + type => file, + enable => Enable, + rules => Rules + } + ]); + {error, _} -> + lists:append(AccIn, [ + #{ + type => file, + enable => Enable, + rules => <<"">> + } + ]) + end; + (Source, AccIn) -> + lists:append(AccIn, [read_certs(Source)]) + end, + [], + get_raw_sources() + ), {200, #{sources => Sources}}; sources(post, #{body := #{<<"type">> := <<"file">>} = Body}) -> create_authz_file(Body); sources(post, #{body := Body}) -> update_config(?CMD_PREPEND, Body). -source(Method, #{bindings := #{type := Type} = Bindings } = Req) - when is_atom(Type) -> +source(Method, #{bindings := #{type := Type} = Bindings} = Req) when + is_atom(Type) +-> source(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}}); source(get, #{bindings := #{type := Type}}) -> case get_raw_source(Type) of - [] -> {404, #{message => <<"Not found ", Type/binary>>}}; + [] -> + {404, #{message => <<"Not found ", Type/binary>>}}; [#{<<"type">> := <<"file">>, <<"enable">> := Enable, <<"path">> := Path}] -> case file:read_file(Path) of {ok, Rules} -> - {200, #{type => file, - enable => Enable, - rules => Rules - } - }; + {200, #{ + type => file, + enable => Enable, + rules => Rules + }}; {error, Reason} -> - {500, #{code => <<"INTERNAL_ERROR">>, - message => bin(Reason)}} + {500, #{ + code => <<"INTERNAL_ERROR">>, + message => bin(Reason) + }} end; - [Source] -> {200, read_certs(Source)} + [Source] -> + {200, read_certs(Source)} end; source(put, #{bindings := #{type := <<"file">>}, body := #{<<"type">> := <<"file">>} = Body}) -> update_authz_file(Body); @@ -201,44 +260,61 @@ source(delete, #{bindings := #{type := Type}}) -> source_status(get, #{bindings := #{type := Type}}) -> BinType = atom_to_binary(Type, utf8), case get_raw_source(BinType) of - [] -> {404, #{code => <<"NOT_FOUND">>, - message => <<"Not found", BinType/binary>>}}; + [] -> + {404, #{ + code => <<"NOT_FOUND">>, + message => <<"Not found", BinType/binary>> + }}; [#{<<"type">> := <<"file">>}] -> - {400, #{code => <<"BAD_REQUEST">>, - message => <<"Not Support Status">>}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"Not Support Status">> + }}; [_] -> case emqx_authz:lookup(Type) of - #{annotations := #{id := ResourceId }} -> lookup_from_all_nodes(ResourceId); + #{annotations := #{id := ResourceId}} -> lookup_from_all_nodes(ResourceId); _ -> {400, #{code => <<"BAD_REQUEST">>, message => <<"Resource Disable">>}} end end. -move_source(Method, #{bindings := #{type := Type} = Bindings } = Req) - when is_atom(Type) -> +move_source(Method, #{bindings := #{type := Type} = Bindings} = Req) when + is_atom(Type) +-> move_source(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}}); move_source(post, #{bindings := #{type := Type}, body := #{<<"position">> := Position}}) -> - case parse_position(Position) of + case parse_position(Position) of {ok, NPosition} -> try emqx_authz:move(Type, NPosition) of - {ok, _} -> {204}; + {ok, _} -> + {204}; {error, {not_found_source, _Type}} -> - {404, #{code => <<"NOT_FOUND">>, - message => <<"source ", Type/binary, " not found">>}}; + {404, #{ + code => <<"NOT_FOUND">>, + message => <<"source ", Type/binary, " not found">> + }}; {error, {emqx_conf_schema, _}} -> - {400, #{code => <<"BAD_REQUEST">>, - message => <<"BAD_SCHEMA">>}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"BAD_SCHEMA">> + }}; {error, Reason} -> - {400, #{code => <<"BAD_REQUEST">>, - message => bin(Reason)}} + {400, #{ + code => <<"BAD_REQUEST">>, + message => bin(Reason) + }} catch - error : {unknown_authz_source_type, Unknown} -> + error:{unknown_authz_source_type, Unknown} -> NUnknown = bin(Unknown), - {400, #{code => <<"BAD_REQUEST">>, - message => <<"Unknown authz Source Type: ", NUnknown/binary>>}} + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"Unknown authz Source Type: ", NUnknown/binary>> + }} end; {error, Reason} -> - {400, #{code => <<"BAD_REQUEST">>, - message => bin(Reason)}} + {400, #{ + code => <<"BAD_REQUEST">>, + message => bin(Reason) + }} end. %%-------------------------------------------------------------------- @@ -249,46 +325,53 @@ lookup_from_local_node(ResourceId) -> NodeId = node(self()), case emqx_resource:get_instance(ResourceId) of {error, not_found} -> {error, {NodeId, not_found_resource}}; - {ok, _, #{ status := Status, metrics := Metrics }} -> - {ok, {NodeId, Status, Metrics}} + {ok, _, #{status := Status, metrics := Metrics}} -> {ok, {NodeId, Status, Metrics}} end. lookup_from_all_nodes(ResourceId) -> Nodes = mria_mnesia:running_nodes(), case is_ok(emqx_authz_proto_v1:lookup_from_all_nodes(Nodes, ResourceId)) of {ok, ResList} -> - {StatusMap, MetricsMap, _} = make_result_map(ResList), - AggregateStatus = aggregate_status(maps:values(StatusMap)), - AggregateMetrics = aggregate_metrics(maps:values(MetricsMap)), - Fun = fun (_, V1) -> restructure_map(V1) end, - MKMap = fun (Name) -> fun ({Key, Val}) -> #{ node => Key, Name => Val } end end, - HelpFun = fun (M, Name) -> lists:map(MKMap(Name), maps:to_list(M)) end, - case AggregateStatus of - empty_metrics_and_status -> {400, #{code => <<"BAD_REQUEST">>, - message => <<"Resource Not Support Status">>}}; - _ -> {200, #{node_status => HelpFun(StatusMap, status), - node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics), - status => AggregateStatus, - metrics => restructure_map(AggregateMetrics) - } - } - end; + {StatusMap, MetricsMap, _} = make_result_map(ResList), + AggregateStatus = aggregate_status(maps:values(StatusMap)), + AggregateMetrics = aggregate_metrics(maps:values(MetricsMap)), + Fun = fun(_, V1) -> restructure_map(V1) end, + MKMap = fun(Name) -> fun({Key, Val}) -> #{node => Key, Name => Val} end end, + HelpFun = fun(M, Name) -> lists:map(MKMap(Name), maps:to_list(M)) end, + case AggregateStatus of + empty_metrics_and_status -> + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"Resource Not Support Status">> + }}; + _ -> + {200, #{ + node_status => HelpFun(StatusMap, status), + node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics), + status => AggregateStatus, + metrics => restructure_map(AggregateMetrics) + }} + end; {error, ErrL} -> - {500, #{code => <<"INTERNAL_ERROR">>, - message => bin_t(io_lib:format("~p", [ErrL]))}} + {500, #{ + code => <<"INTERNAL_ERROR">>, + message => bin_t(io_lib:format("~p", [ErrL])) + }} end. -aggregate_status([]) -> empty_metrics_and_status; +aggregate_status([]) -> + empty_metrics_and_status; aggregate_status(AllStatus) -> - Head = fun ([A | _]) -> A end, + Head = fun([A | _]) -> A end, HeadVal = Head(AllStatus), - AllRes = lists:all(fun (Val) -> Val == HeadVal end, AllStatus), + AllRes = lists:all(fun(Val) -> Val == HeadVal end, AllStatus), case AllRes of true -> HeadVal; false -> inconsistent end. -aggregate_metrics([]) -> empty_metrics_and_status; +aggregate_metrics([]) -> + empty_metrics_and_status; aggregate_metrics([HeadMetrics | AllMetrics]) -> CombinerFun = fun ComFun(Val1, Val2) -> @@ -297,8 +380,9 @@ aggregate_metrics([HeadMetrics | AllMetrics]) -> false -> Val1 + Val2 end end, - Fun = fun (ElemMap, AccMap) -> - emqx_map_lib:merge_with(CombinerFun, ElemMap, AccMap) end, + Fun = fun(ElemMap, AccMap) -> + emqx_map_lib:merge_with(CombinerFun, ElemMap, AccMap) + end, lists:foldl(Fun, HeadMetrics, AllMetrics). make_result_map(ResList) -> @@ -306,39 +390,45 @@ make_result_map(ResList) -> fun(Elem, {StatusMap, MetricsMap, ErrorMap}) -> case Elem of {ok, {NodeId, Status, Metrics}} -> - {maps:put(NodeId, Status, StatusMap), - maps:put(NodeId, Metrics, MetricsMap), - ErrorMap + { + maps:put(NodeId, Status, StatusMap), + maps:put(NodeId, Metrics, MetricsMap), + ErrorMap }; {error, {NodeId, Reason}} -> - {StatusMap, - MetricsMap, - maps:put(NodeId, Reason, ErrorMap) - } + {StatusMap, MetricsMap, maps:put(NodeId, Reason, ErrorMap)} end end, lists:foldl(Fun, {maps:new(), maps:new(), maps:new()}, ResList). -restructure_map(#{counters := #{failed := Failed, matched := Match, success := Succ}, - rate := #{matched := #{current := Rate, last5m := Rate5m, max := RateMax} - } - } - ) -> - #{matched => Match, - success => Succ, - failed => Failed, - rate => Rate, - rate_last5m => Rate5m, - rate_max => RateMax - }; +restructure_map(#{ + counters := #{failed := Failed, matched := Match, success := Succ}, + rate := #{matched := #{current := Rate, last5m := Rate5m, max := RateMax}} +}) -> + #{ + matched => Match, + success => Succ, + failed => Failed, + rate => Rate, + rate_last5m => Rate5m, + rate_max => RateMax + }; restructure_map(Error) -> - Error. + Error. bin_t(S) when is_list(S) -> list_to_binary(S). is_ok(ResL) -> - case lists:filter(fun({ok, _}) -> false; (_) -> true end, ResL) of + case + lists:filter( + fun + ({ok, _}) -> false; + (_) -> true + end, + ResL + ) + of [] -> {ok, [Res || {ok, Res} <- ResL]}; ErrL -> {error, ErrL} end. @@ -352,43 +442,60 @@ get_raw_sources() -> merge_default_headers(Sources). merge_default_headers(Sources) -> - lists:map(fun(Source) -> - case maps:find(<<"headers">>, Source) of - {ok, Headers} -> - NewHeaders = - case Source of - #{<<"method">> := <<"get">>} -> - (emqx_authz_schema:headers_no_content_type(converter))(Headers); - #{<<"method">> := <<"post">>} -> - (emqx_authz_schema:headers(converter))(Headers); - _ -> Headers - end, - Source#{<<"headers">> => NewHeaders}; - error -> Source - end - end, Sources). + lists:map( + fun(Source) -> + case maps:find(<<"headers">>, Source) of + {ok, Headers} -> + NewHeaders = + case Source of + #{<<"method">> := <<"get">>} -> + (emqx_authz_schema:headers_no_content_type(converter))(Headers); + #{<<"method">> := <<"post">>} -> + (emqx_authz_schema:headers(converter))(Headers); + _ -> + Headers + end, + Source#{<<"headers">> => NewHeaders}; + error -> + Source + end + end, + Sources + ). get_raw_source(Type) -> - lists:filter(fun (#{<<"type">> := T}) -> - T =:= Type - end, get_raw_sources()). + lists:filter( + fun(#{<<"type">> := T}) -> + T =:= Type + end, + get_raw_sources() + ). update_config(Cmd, Sources) -> case emqx_authz:update(Cmd, Sources) of - {ok, _} -> {204}; + {ok, _} -> + {204}; {error, {pre_config_update, emqx_authz, Reason}} -> - {400, #{code => <<"BAD_REQUEST">>, - message => bin(Reason)}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => bin(Reason) + }}; {error, {post_config_update, emqx_authz, Reason}} -> - {400, #{code => <<"BAD_REQUEST">>, - message => bin(Reason)}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => bin(Reason) + }}; %% TODO: The `Reason` may cann't be trans to json term. (i.e. ecpool start failed) {error, {emqx_conf_schema, _}} -> - {400, #{code => <<"BAD_REQUEST">>, - message => <<"BAD_SCHEMA">>}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"BAD_SCHEMA">> + }}; {error, Reason} -> - {400, #{code => <<"BAD_REQUEST">>, - message => bin(Reason)}} + {400, #{ + code => <<"BAD_REQUEST">>, + message => bin(Reason) + }} end. read_certs(#{<<"ssl">> := SSL} = Source) -> @@ -399,12 +506,16 @@ read_certs(#{<<"ssl">> := SSL} = Source) -> {ok, NewSSL} -> Source#{<<"ssl">> => NewSSL} end; -read_certs(Source) -> Source. +read_certs(Source) -> + Source. parameters_field() -> - [ {type, mk( enum(?API_SCHEMA_MODULE:authz_sources_types(simple)) - , #{in => path, desc => <<"Authorization type">>}) - } + [ + {type, + mk( + enum(?API_SCHEMA_MODULE:authz_sources_types(simple)), + #{in => path, desc => <<"Authorization type">>} + )} ]. parse_position(<<"front">>) -> @@ -423,50 +534,68 @@ parse_position(_) -> {error, <<"Invalid parameter. Unknow position">>}. position_example() -> - #{ front => - #{ summary => <<"front example">> - , value => #{<<"position">> => <<"front">>}} - , rear => - #{ summary => <<"rear example">> - , value => #{<<"position">> => <<"rear">>}} - , relative_before => - #{ summary => <<"relative example">> - , value => #{<<"position">> => <<"before:file">>}} - , relative_after => - #{ summary => <<"relative example">> - , value => #{<<"position">> => <<"after:file">>}} - }. + #{ + front => + #{ + summary => <<"front example">>, + value => #{<<"position">> => <<"front">>} + }, + rear => + #{ + summary => <<"rear example">>, + value => #{<<"position">> => <<"rear">>} + }, + relative_before => + #{ + summary => <<"relative example">>, + value => #{<<"position">> => <<"before:file">>} + }, + relative_after => + #{ + summary => <<"relative example">>, + value => #{<<"position">> => <<"after:file">>} + } + }. authz_sources_type_refs() -> - [ref(?API_SCHEMA_MODULE, Type) - || Type <- emqx_authz_api_schema:authz_sources_types(detailed)]. + [ + ref(?API_SCHEMA_MODULE, Type) + || Type <- emqx_authz_api_schema:authz_sources_types(detailed) + ]. bin(Term) -> erlang:iolist_to_binary(io_lib:format("~p", [Term])). status_metrics_example() -> - #{ metrics => #{ matched => 0, - success => 0, - failed => 0, - rate => 0.0, - rate_last5m => 0.0, - rate_max => 0.0 - }, - node_metrics => [ #{node => node(), - metrics => #{ matched => 0, - success => 0, - failed => 0, - rate => 0.0, - rate_last5m => 0.0, - rate_max => 0.0 - } - } - ], - status => connected, - node_status => [ #{node => node(), - status => connected - } - ] - }. + #{ + metrics => #{ + matched => 0, + success => 0, + failed => 0, + rate => 0.0, + rate_last5m => 0.0, + rate_max => 0.0 + }, + node_metrics => [ + #{ + node => node(), + metrics => #{ + matched => 0, + success => 0, + failed => 0, + rate => 0.0, + rate_last5m => 0.0, + rate_max => 0.0 + } + } + ], + status => connected, + node_status => [ + #{ + node => node(), + status => connected + } + ] + }. create_authz_file(Body) -> do_update_authz_file(?CMD_PREPEND, Body). @@ -476,4 +605,4 @@ update_authz_file(Body) -> do_update_authz_file(Cmd, Body) -> %% API update will placed in `authz` subdirectory inside EMQX's `data_dir` - update_config(Cmd, Body). + update_config(Cmd, Body). diff --git a/apps/emqx_authz/src/emqx_authz_file.erl b/apps/emqx_authz/src/emqx_authz_file.erl index 729646714..38ff8447a 100644 --- a/apps/emqx_authz/src/emqx_authz_file.erl +++ b/apps/emqx_authz/src/emqx_authz_file.erl @@ -27,28 +27,32 @@ -endif. %% APIs --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4 +]). description() -> "AuthZ with static rules". init(#{path := Path} = Source) -> - Rules = case file:consult(Path) of - {ok, Terms} -> - [emqx_authz_rule:compile(Term) || Term <- Terms]; - {error, Reason} when is_atom(Reason) -> - ?SLOG(alert, #{msg => failed_to_read_acl_file, - path => Path, - explain => emqx_misc:explain_posix(Reason)}), - throw(failed_to_read_acl_file); - {error, Reason} -> - ?SLOG(alert, #{msg => bad_acl_file_content, path => Path, reason => Reason}), - throw(bad_acl_file_content) - end, + Rules = + case file:consult(Path) of + {ok, Terms} -> + [emqx_authz_rule:compile(Term) || Term <- Terms]; + {error, Reason} when is_atom(Reason) -> + ?SLOG(alert, #{ + msg => failed_to_read_acl_file, + path => Path, + explain => emqx_misc:explain_posix(Reason) + }), + throw(failed_to_read_acl_file); + {error, Reason} -> + ?SLOG(alert, #{msg => bad_acl_file_content, path => Path, reason => Reason}), + throw(bad_acl_file_content) + end, Source#{annotations => #{rules => Rules}}. destroy(_Source) -> ok. diff --git a/apps/emqx_authz/src/emqx_authz_http.erl b/apps/emqx_authz/src/emqx_authz_http.erl index 592c59b03..94dfcecf3 100644 --- a/apps/emqx_authz/src/emqx_authz_http.erl +++ b/apps/emqx_authz/src/emqx_authz_http.erl @@ -24,25 +24,28 @@ -behaviour(emqx_authz). %% AuthZ Callbacks --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - , parse_url/1 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4, + parse_url/1 +]). -ifdef(TEST). -compile(export_all). -compile(nowarn_export_all). -endif. --define(PLACEHOLDERS, [?PH_USERNAME, - ?PH_CLIENTID, - ?PH_PEERHOST, - ?PH_PROTONAME, - ?PH_MOUNTPOINT, - ?PH_TOPIC, - ?PH_ACTION]). +-define(PLACEHOLDERS, [ + ?PH_USERNAME, + ?PH_CLIENTID, + ?PH_PEERHOST, + ?PH_PROTONAME, + ?PH_MOUNTPOINT, + ?PH_TOPIC, + ?PH_ACTION +]). description() -> "AuthZ with http". @@ -57,14 +60,17 @@ init(Config) -> destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -authorize( Client - , PubSub - , Topic - , #{ type := http - , annotations := #{id := ResourceID} - , method := Method - , request_timeout := RequestTimeout - } = 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} -> @@ -78,38 +84,47 @@ authorize( Client {ok, _Status, _Headers, _Body} -> nomatch; {error, Reason} -> - ?SLOG(error, #{msg => "http_server_query_failed", - resource => ResourceID, - reason => Reason}), + ?SLOG(error, #{ + msg => "http_server_query_failed", + resource => ResourceID, + reason => Reason + }), ignore end. -parse_config(#{ url := URL - , method := Method - , headers := Headers - , request_timeout := ReqTimeout - } = Conf) -> +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_template => emqx_authz_utils:parse_deep( - cow_qs:parse_qs(bin(Query)), - ?PLACEHOLDERS) - , body_template => emqx_authz_utils:parse_deep( - maps:to_list(maps:get(body, Conf, #{})), - ?PLACEHOLDERS) - , headers => Headers - , request_timeout => ReqTimeout - %% pool_type default value `random` - , pool_type => random - }. + Conf#{ + method => Method, + base_url => maps:remove(query, BaseURLMap), + base_query_template => emqx_authz_utils:parse_deep( + cow_qs:parse_qs(bin(Query)), + ?PLACEHOLDERS + ), + body_template => emqx_authz_utils:parse_deep( + maps:to_list(maps:get(body, Conf, #{})), + ?PLACEHOLDERS + ), + headers => Headers, + request_timeout => ReqTimeout, + %% pool_type default value `random` + pool_type => random + }. parse_fullpath(RawURL) -> cow_http:parse_fullpath(bin(RawURL)). -parse_url(URL) - when URL =:= undefined -> +parse_url(URL) when + URL =:= undefined +-> #{}; parse_url(URL) -> {ok, URIMap} = emqx_http_lib:uri_parse(URL), @@ -120,28 +135,31 @@ parse_url(URL) -> URIMap end. -generate_request( PubSub - , Topic - , Client - , #{ method := Method - , base_url := #{path := Path} - , base_query_template := BaseQueryTemplate - , headers := Headers - , body_template := BodyTemplate - }) -> +generate_request( + PubSub, + Topic, + Client, + #{ + method := Method, + base_url := #{path := Path}, + base_query_template := BaseQueryTemplate, + headers := Headers, + body_template := BodyTemplate + } +) -> Values = client_vars(Client, PubSub, Topic), Body = emqx_authz_utils:render_deep(BodyTemplate, Values), NBaseQuery = emqx_authz_utils:render_deep(BaseQueryTemplate, Values), case Method of - get -> + 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 - ), + proplists:get_value(<<"Accept">>, Headers, <<"application/json">>), + Body + ), {NPath, Headers, NBody} end. @@ -161,9 +179,13 @@ query_string([], Acc) -> <<>> end; query_string([{K, V} | More], Acc) -> - query_string( More - , [ ["&", emqx_http_lib:uri_encode(K), "=", emqx_http_lib:uri_encode(V)] - | Acc]). + query_string( + More, + [ + ["&", emqx_http_lib:uri_encode(K), "=", emqx_http_lib:uri_encode(V)] + | Acc + ] + ). serialize_body(<<"application/json">>, Body) -> jsx:encode(Body); @@ -172,9 +194,9 @@ serialize_body(<<"application/x-www-form-urlencoded">>, Body) -> client_vars(Client, PubSub, Topic) -> Client#{ - action => PubSub, - topic => Topic - }. + action => PubSub, + topic => Topic + }. bin(A) when is_atom(A) -> atom_to_binary(A, utf8); bin(B) when is_binary(B) -> B; diff --git a/apps/emqx_authz/src/emqx_authz_mnesia.erl b/apps/emqx_authz/src/emqx_authz_mnesia.erl index f02419b0c..0bb85b96a 100644 --- a/apps/emqx_authz/src/emqx_authz_mnesia.erl +++ b/apps/emqx_authz/src/emqx_authz_mnesia.erl @@ -29,38 +29,40 @@ -define(ACL_TABLE_USERNAME, 1). -define(ACL_TABLE_CLIENTID, 2). --type(username() :: {username, binary()}). --type(clientid() :: {clientid, binary()}). --type(who() :: username() | clientid() | all). +-type username() :: {username, binary()}. +-type clientid() :: {clientid, binary()}. +-type who() :: username() | clientid() | all. --type(rule() :: {emqx_authz_rule:permission(), emqx_authz_rule:action(), emqx_topic:topic()}). --type(rules() :: [rule()]). +-type rule() :: {emqx_authz_rule:permission(), emqx_authz_rule:action(), emqx_topic:topic()}. +-type rules() :: [rule()]. -record(emqx_acl, { - who :: ?ACL_TABLE_ALL | {?ACL_TABLE_USERNAME, binary()} | {?ACL_TABLE_CLIENTID, binary()}, - rules :: rules() - }). + who :: ?ACL_TABLE_ALL | {?ACL_TABLE_USERNAME, binary()} | {?ACL_TABLE_CLIENTID, binary()}, + rules :: rules() +}). -behaviour(emqx_authz). %% AuthZ Callbacks --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4 +]). %% Management API --export([ mnesia/1 - , init_tables/0 - , store_rules/2 - , purge_rules/0 - , get_rules/1 - , delete_rules/1 - , list_clientid_rules/0 - , list_username_rules/0 - , record_count/0 - ]). +-export([ + mnesia/1, + init_tables/0, + store_rules/2, + purge_rules/0, + get_rules/1, + delete_rules/1, + list_clientid_rules/0, + list_username_rules/0, + record_count/0 +]). -ifdef(TEST). -compile(export_all). @@ -69,14 +71,15 @@ -boot_mnesia({mnesia, [boot]}). --spec(mnesia(boot | copy) -> ok). +-spec mnesia(boot | copy) -> ok. mnesia(boot) -> ok = mria:create_table(?ACL_TABLE, [ - {type, ordered_set}, - {rlog_shard, ?ACL_SHARDED}, - {storage, disc_copies}, - {attributes, record_info(fields, ?ACL_TABLE)}, - {storage_properties, [{ets, [{read_concurrency, true}]}]}]). + {type, ordered_set}, + {rlog_shard, ?ACL_SHARDED}, + {storage, disc_copies}, + {attributes, record_info(fields, ?ACL_TABLE)}, + {storage_properties, [{ets, [{read_concurrency, true}]}]} + ]). %%-------------------------------------------------------------------- %% emqx_authz callbacks @@ -89,19 +92,25 @@ init(Source) -> Source. destroy(_Source) -> ok. -authorize(#{username := Username, - clientid := Clientid - } = Client, PubSub, Topic, #{type := 'built_in_database'}) -> - - Rules = case mnesia:dirty_read(?ACL_TABLE, {?ACL_TABLE_CLIENTID, Clientid}) of - [] -> []; - [#emqx_acl{rules = Rules0}] when is_list(Rules0) -> Rules0 - end - ++ case mnesia:dirty_read(?ACL_TABLE, {?ACL_TABLE_USERNAME, Username}) of +authorize( + #{ + username := Username, + clientid := Clientid + } = Client, + PubSub, + Topic, + #{type := 'built_in_database'} +) -> + Rules = + case mnesia:dirty_read(?ACL_TABLE, {?ACL_TABLE_CLIENTID, Clientid}) of + [] -> []; + [#emqx_acl{rules = Rules0}] when is_list(Rules0) -> Rules0 + end ++ + case mnesia:dirty_read(?ACL_TABLE, {?ACL_TABLE_USERNAME, Username}) of [] -> []; [#emqx_acl{rules = Rules1}] when is_list(Rules1) -> Rules1 - end - ++ case mnesia:dirty_read(?ACL_TABLE, ?ACL_TABLE_ALL) of + end ++ + case mnesia:dirty_read(?ACL_TABLE, ?ACL_TABLE_ALL) of [] -> []; [#emqx_acl{rules = Rules2}] when is_list(Rules2) -> Rules2 end, @@ -112,12 +121,12 @@ authorize(#{username := Username, %%-------------------------------------------------------------------- %% Init --spec(init_tables() -> ok). +-spec init_tables() -> ok. init_tables() -> ok = mria_rlog:wait_for_shards([?ACL_SHARDED], infinity). %% @doc Update authz rules --spec(store_rules(who(), rules()) -> ok). +-spec store_rules(who(), rules()) -> ok. store_rules({username, Username}, Rules) -> Record = #emqx_acl{who = {?ACL_TABLE_USERNAME, Username}, rules = normalize_rules(Rules)}, mria:dirty_write(Record); @@ -129,16 +138,17 @@ store_rules(all, Rules) -> mria:dirty_write(Record). %% @doc Clean all authz rules for (username & clientid & all) --spec(purge_rules() -> ok). +-spec purge_rules() -> ok. purge_rules() -> ok = lists:foreach( - fun(Key) -> - ok = mria:dirty_delete(?ACL_TABLE, Key) - end, - mnesia:dirty_all_keys(?ACL_TABLE)). + fun(Key) -> + ok = mria:dirty_delete(?ACL_TABLE, Key) + end, + mnesia:dirty_all_keys(?ACL_TABLE) + ). %% @doc Get one record --spec(get_rules(who()) -> {ok, rules()} | not_found). +-spec get_rules(who()) -> {ok, rules()} | not_found. get_rules({username, Username}) -> do_get_rules({?ACL_TABLE_USERNAME, Username}); get_rules({clientid, Clientid}) -> @@ -147,7 +157,7 @@ get_rules(all) -> do_get_rules(?ACL_TABLE_ALL). %% @doc Delete one record --spec(delete_rules(who()) -> ok). +-spec delete_rules(who()) -> ok. delete_rules({username, Username}) -> mria:dirty_delete(?ACL_TABLE, {?ACL_TABLE_USERNAME, Username}); delete_rules({clientid, Clientid}) -> @@ -155,21 +165,23 @@ delete_rules({clientid, Clientid}) -> delete_rules(all) -> mria:dirty_delete(?ACL_TABLE, ?ACL_TABLE_ALL). --spec(list_username_rules() -> ets:match_spec()). +-spec list_username_rules() -> ets:match_spec(). list_username_rules() -> ets:fun2ms( - fun(#emqx_acl{who = {?ACL_TABLE_USERNAME, Username}, rules = Rules}) -> - [{username, Username}, {rules, Rules}] - end). + fun(#emqx_acl{who = {?ACL_TABLE_USERNAME, Username}, rules = Rules}) -> + [{username, Username}, {rules, Rules}] + end + ). --spec(list_clientid_rules() -> ets:match_spec()). +-spec list_clientid_rules() -> ets:match_spec(). list_clientid_rules() -> ets:fun2ms( - fun(#emqx_acl{who = {?ACL_TABLE_CLIENTID, Clientid}, rules = Rules}) -> - [{clientid, Clientid}, {rules, Rules}] - end). + fun(#emqx_acl{who = {?ACL_TABLE_CLIENTID, Clientid}, rules = Rules}) -> + [{clientid, Clientid}, {rules, Rules}] + end + ). --spec(record_count() -> non_neg_integer()). +-spec record_count() -> non_neg_integer(). record_count() -> mnesia:table_info(?ACL_TABLE, size). @@ -181,9 +193,7 @@ normalize_rules(Rules) -> lists:map(fun normalize_rule/1, Rules). normalize_rule({Permission, Action, Topic}) -> - {normalize_permission(Permission), - normalize_action(Action), - normalize_topic(Topic)}; + {normalize_permission(Permission), normalize_action(Action), normalize_topic(Topic)}; normalize_rule(Rule) -> error({invalid_rule, Rule}). @@ -206,8 +216,9 @@ do_get_rules(Key) -> [] -> not_found end. -do_authorize(_Client, _PubSub, _Topic, []) -> nomatch; -do_authorize(Client, PubSub, Topic, [ {Permission, Action, TopicFilter} | Tail]) -> +do_authorize(_Client, _PubSub, _Topic, []) -> + nomatch; +do_authorize(Client, PubSub, Topic, [{Permission, Action, TopicFilter} | Tail]) -> Rule = emqx_authz_rule:compile({Permission, all, Action, [TopicFilter]}), case emqx_authz_rule:match(Client, PubSub, Topic, Rule) of {matched, Permission} -> {matched, Permission}; diff --git a/apps/emqx_authz/src/emqx_authz_mongodb.erl b/apps/emqx_authz/src/emqx_authz_mongodb.erl index 158bff152..2bac33003 100644 --- a/apps/emqx_authz/src/emqx_authz_mongodb.erl +++ b/apps/emqx_authz/src/emqx_authz_mongodb.erl @@ -24,62 +24,83 @@ -behaviour(emqx_authz). %% AuthZ Callbacks --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4 +]). -ifdef(TEST). -compile(export_all). -compile(nowarn_export_all). -endif. --define(PLACEHOLDERS, [?PH_USERNAME, - ?PH_CLIENTID, - ?PH_PEERHOST]). +-define(PLACEHOLDERS, [ + ?PH_USERNAME, + ?PH_CLIENTID, + ?PH_PEERHOST +]). description() -> "AuthZ with MongoDB". init(#{selector := Selector} = Source) -> case emqx_authz_utils:create_resource(emqx_connector_mongo, Source) of - {error, Reason} -> error({load_config_error, Reason}); - {ok, Id} -> Source#{annotations => #{id => Id}, - selector_template => emqx_authz_utils:parse_deep( - Selector, - ?PLACEHOLDERS)} + {error, Reason} -> + error({load_config_error, Reason}); + {ok, Id} -> + Source#{ + annotations => #{id => Id}, + selector_template => emqx_authz_utils:parse_deep( + Selector, + ?PLACEHOLDERS + ) + } end. destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -authorize(Client, PubSub, Topic, - #{collection := Collection, - selector_template := SelectorTemplate, - annotations := #{id := ResourceID} - }) -> +authorize( + Client, + PubSub, + Topic, + #{ + collection := Collection, + selector_template := SelectorTemplate, + annotations := #{id := ResourceID} + } +) -> RenderedSelector = emqx_authz_utils:render_deep(SelectorTemplate, Client), - Result = try - emqx_resource:query(ResourceID, {find, Collection, RenderedSelector, #{}}) - catch - error:Error -> {error, Error} - end, + Result = + try + emqx_resource:query(ResourceID, {find, Collection, RenderedSelector, #{}}) + catch + error:Error -> {error, Error} + end, case Result of {error, Reason} -> - ?SLOG(error, #{msg => "query_mongo_error", - reason => Reason, - collection => Collection, - selector => RenderedSelector, - resource_id => ResourceID}), + ?SLOG(error, #{ + msg => "query_mongo_error", + reason => Reason, + collection => Collection, + selector => RenderedSelector, + resource_id => ResourceID + }), + nomatch; + [] -> nomatch; - [] -> nomatch; Rows -> - Rules = [ emqx_authz_rule:compile({Permission, all, Action, Topics}) - || #{<<"topics">> := Topics, - <<"permission">> := Permission, - <<"action">> := Action} <- Rows], + Rules = [ + emqx_authz_rule:compile({Permission, all, Action, Topics}) + || #{ + <<"topics">> := Topics, + <<"permission">> := Permission, + <<"action">> := Action + } <- Rows + ], do_authorize(Client, PubSub, Topic, Rules) end. diff --git a/apps/emqx_authz/src/emqx_authz_mysql.erl b/apps/emqx_authz/src/emqx_authz_mysql.erl index 31e8dcd98..d88273e14 100644 --- a/apps/emqx_authz/src/emqx_authz_mysql.erl +++ b/apps/emqx_authz/src/emqx_authz_mysql.erl @@ -24,66 +24,89 @@ -behaviour(emqx_authz). %% AuthZ Callbacks --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4 +]). -ifdef(TEST). -compile(export_all). -compile(nowarn_export_all). -endif. --define(PLACEHOLDERS, [?PH_USERNAME, - ?PH_CLIENTID, - ?PH_PEERHOST, - ?PH_CERT_CN_NAME, - ?PH_CERT_SUBJECT]). +-define(PLACEHOLDERS, [ + ?PH_USERNAME, + ?PH_CLIENTID, + ?PH_PEERHOST, + ?PH_CERT_CN_NAME, + ?PH_CERT_SUBJECT +]). description() -> "AuthZ with Mysql". init(#{query := SQL} = Source) -> case emqx_authz_utils:create_resource(emqx_connector_mysql, Source) of - {error, Reason} -> error({load_config_error, Reason}); - {ok, Id} -> Source#{annotations => - #{id => Id, - query => emqx_authz_utils:parse_sql( - SQL, - '?', - ?PLACEHOLDERS)}} + {error, Reason} -> + error({load_config_error, Reason}); + {ok, Id} -> + Source#{ + annotations => + #{ + id => Id, + query => emqx_authz_utils:parse_sql( + SQL, + '?', + ?PLACEHOLDERS + ) + } + } end. destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -authorize(Client, PubSub, Topic, - #{annotations := #{id := ResourceID, - query := {Query, Params} - } - }) -> +authorize( + Client, + PubSub, + Topic, + #{ + annotations := #{ + id := ResourceID, + query := {Query, Params} + } + } +) -> RenderParams = emqx_authz_utils:render_sql_params(Params, Client), case emqx_resource:query(ResourceID, {sql, Query, RenderParams}) of - {ok, _Columns, []} -> nomatch; + {ok, _Columns, []} -> + nomatch; {ok, Columns, Rows} -> do_authorize(Client, PubSub, Topic, Columns, Rows); {error, Reason} -> - ?SLOG(error, #{ msg => "query_mysql_error" - , reason => Reason - , query => Query - , params => RenderParams - , resource_id => ResourceID}), + ?SLOG(error, #{ + msg => "query_mysql_error", + reason => Reason, + query => Query, + params => RenderParams, + resource_id => ResourceID + }), nomatch end. - do_authorize(_Client, _PubSub, _Topic, _Columns, []) -> nomatch; do_authorize(Client, PubSub, Topic, Columns, [Row | Tail]) -> - case emqx_authz_rule:match(Client, PubSub, Topic, - emqx_authz_rule:compile(format_result(Columns, Row)) - ) of + case + emqx_authz_rule:match( + Client, + PubSub, + Topic, + emqx_authz_rule:compile(format_result(Columns, Row)) + ) + of {matched, Permission} -> {matched, Permission}; nomatch -> do_authorize(Client, PubSub, Topic, Columns, Tail) end. @@ -97,5 +120,5 @@ format_result(Columns, Row) -> index(Elem, List) -> index(Elem, List, 1). index(_Elem, [], _Index) -> {error, not_found}; -index(Elem, [ Elem | _List], Index) -> Index; -index(Elem, [ _ | List], Index) -> index(Elem, List, Index + 1). +index(Elem, [Elem | _List], Index) -> Index; +index(Elem, [_ | List], Index) -> index(Elem, List, Index + 1). diff --git a/apps/emqx_authz/src/emqx_authz_postgresql.erl b/apps/emqx_authz/src/emqx_authz_postgresql.erl index 275b39f2b..4305addb7 100644 --- a/apps/emqx_authz/src/emqx_authz_postgresql.erl +++ b/apps/emqx_authz/src/emqx_authz_postgresql.erl @@ -24,42 +24,53 @@ -behaviour(emqx_authz). %% AuthZ Callbacks --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4 +]). -ifdef(TEST). -compile(export_all). -compile(nowarn_export_all). -endif. --define(PLACEHOLDERS, [?PH_USERNAME, - ?PH_CLIENTID, - ?PH_PEERHOST, - ?PH_CERT_CN_NAME, - ?PH_CERT_SUBJECT]). +-define(PLACEHOLDERS, [ + ?PH_USERNAME, + ?PH_CLIENTID, + ?PH_PEERHOST, + ?PH_CERT_CN_NAME, + ?PH_CERT_SUBJECT +]). description() -> "AuthZ with PostgreSQL". init(#{query := SQL0} = Source) -> {SQL, PlaceHolders} = emqx_authz_utils:parse_sql( - SQL0, - '$n', - ?PLACEHOLDERS), + SQL0, + '$n', + ?PLACEHOLDERS + ), ResourceID = emqx_authz_utils:make_resource_id(emqx_connector_pgsql), - case emqx_resource:create_local( + case + emqx_resource:create_local( ResourceID, ?RESOURCE_GROUP, emqx_connector_pgsql, Source#{named_queries => #{ResourceID => SQL}}, - #{}) of + #{} + ) + of {ok, _} -> - Source#{annotations => - #{id => ResourceID, - placeholders => PlaceHolders}}; + Source#{ + annotations => + #{ + id => ResourceID, + placeholders => PlaceHolders + } + }; {error, Reason} -> error({load_config_error, Reason}) end. @@ -67,30 +78,44 @@ init(#{query := SQL0} = Source) -> destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -authorize(Client, PubSub, Topic, - #{annotations := #{id := ResourceID, - placeholders := Placeholders - } - }) -> +authorize( + Client, + PubSub, + Topic, + #{ + annotations := #{ + id := ResourceID, + placeholders := Placeholders + } + } +) -> RenderedParams = emqx_authz_utils:render_sql_params(Placeholders, Client), case emqx_resource:query(ResourceID, {prepared_query, ResourceID, RenderedParams}) of - {ok, _Columns, []} -> nomatch; + {ok, _Columns, []} -> + nomatch; {ok, Columns, Rows} -> do_authorize(Client, PubSub, Topic, Columns, Rows); {error, Reason} -> - ?SLOG(error, #{ msg => "query_postgresql_error" - , reason => Reason - , params => RenderedParams - , resource_id => ResourceID}), + ?SLOG(error, #{ + msg => "query_postgresql_error", + reason => Reason, + params => RenderedParams, + resource_id => ResourceID + }), nomatch end. do_authorize(_Client, _PubSub, _Topic, _Columns, []) -> nomatch; do_authorize(Client, PubSub, Topic, Columns, [Row | Tail]) -> - case emqx_authz_rule:match(Client, PubSub, Topic, - emqx_authz_rule:compile(format_result(Columns, Row)) - ) of + case + emqx_authz_rule:match( + Client, + PubSub, + Topic, + emqx_authz_rule:compile(format_result(Columns, Row)) + ) + of {matched, Permission} -> {matched, Permission}; nomatch -> do_authorize(Client, PubSub, Topic, Columns, Tail) end. @@ -104,6 +129,9 @@ format_result(Columns, Row) -> index(Key, N, TupleList) when is_integer(N) -> Tuple = lists:keyfind(Key, N, TupleList), index(Tuple, TupleList, 1); -index(_Tuple, [], _Index) -> {error, not_found}; -index(Tuple, [Tuple | _TupleList], Index) -> Index; -index(Tuple, [_ | TupleList], Index) -> index(Tuple, TupleList, Index + 1). +index(_Tuple, [], _Index) -> + {error, not_found}; +index(Tuple, [Tuple | _TupleList], Index) -> + Index; +index(Tuple, [_ | TupleList], Index) -> + index(Tuple, TupleList, Index + 1). diff --git a/apps/emqx_authz/src/emqx_authz_redis.erl b/apps/emqx_authz/src/emqx_authz_redis.erl index 7e9ea8325..54ec16475 100644 --- a/apps/emqx_authz/src/emqx_authz_redis.erl +++ b/apps/emqx_authz/src/emqx_authz_redis.erl @@ -24,22 +24,25 @@ -behaviour(emqx_authz). %% AuthZ Callbacks --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4 +]). -ifdef(TEST). -compile(export_all). -compile(nowarn_export_all). -endif. --define(PLACEHOLDERS, [?PH_CERT_CN_NAME, - ?PH_CERT_SUBJECT, - ?PH_PEERHOST, - ?PH_CLIENTID, - ?PH_USERNAME]). +-define(PLACEHOLDERS, [ + ?PH_CERT_CN_NAME, + ?PH_CERT_SUBJECT, + ?PH_PEERHOST, + ?PH_CLIENTID, + ?PH_USERNAME +]). description() -> "AuthZ with Redis". @@ -48,38 +51,54 @@ init(#{cmd := CmdStr} = Source) -> Cmd = tokens(CmdStr), CmdTemplate = emqx_authz_utils:parse_deep(Cmd, ?PLACEHOLDERS), case emqx_authz_utils:create_resource(emqx_connector_redis, Source) of - {error, Reason} -> error({load_config_error, Reason}); - {ok, Id} -> Source#{annotations => #{id => Id}, - cmd_template => CmdTemplate} + {error, Reason} -> + error({load_config_error, Reason}); + {ok, Id} -> + Source#{ + annotations => #{id => Id}, + cmd_template => CmdTemplate + } end. destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -authorize(Client, PubSub, Topic, - #{cmd_template := CmdTemplate, - annotations := #{id := ResourceID} - }) -> +authorize( + Client, + PubSub, + Topic, + #{ + cmd_template := CmdTemplate, + annotations := #{id := ResourceID} + } +) -> Cmd = emqx_authz_utils:render_deep(CmdTemplate, Client), case emqx_resource:query(ResourceID, {cmd, Cmd}) of - {ok, []} -> nomatch; + {ok, []} -> + nomatch; {ok, Rows} -> do_authorize(Client, PubSub, Topic, Rows); {error, Reason} -> - ?SLOG(error, #{ msg => "query_redis_error" - , reason => Reason - , cmd => Cmd - , resource_id => ResourceID}), + ?SLOG(error, #{ + msg => "query_redis_error", + reason => Reason, + cmd => Cmd, + resource_id => ResourceID + }), nomatch end. do_authorize(_Client, _PubSub, _Topic, []) -> nomatch; do_authorize(Client, PubSub, Topic, [TopicFilter, Action | Tail]) -> - case emqx_authz_rule:match( - Client, PubSub, Topic, - emqx_authz_rule:compile({allow, all, Action, [TopicFilter]}) - ) of + case + emqx_authz_rule:match( + Client, + PubSub, + Topic, + emqx_authz_rule:compile({allow, all, Action, [TopicFilter]}) + ) + of {matched, Permission} -> {matched, Permission}; nomatch -> do_authorize(Client, PubSub, Topic, Tail) end. diff --git a/apps/emqx_authz/src/emqx_authz_rule.erl b/apps/emqx_authz/src/emqx_authz_rule.erl index 4333cf0e7..c5ad0b657 100644 --- a/apps/emqx_authz/src/emqx_authz_rule.erl +++ b/apps/emqx_authz/src/emqx_authz_rule.erl @@ -26,50 +26,66 @@ -endif. %% APIs --export([ match/4 - , matches/4 - , compile/1 - ]). +-export([ + match/4, + matches/4, + compile/1 +]). --type(ipaddress() :: {ipaddr, esockd_cidr:cidr_string()} | - {ipaddrs, list(esockd_cidr:cidr_string())}). +-type ipaddress() :: + {ipaddr, esockd_cidr:cidr_string()} + | {ipaddrs, list(esockd_cidr:cidr_string())}. --type(username() :: {username, binary()}). +-type username() :: {username, binary()}. --type(clientid() :: {clientid, binary()}). +-type clientid() :: {clientid, binary()}. --type(who() :: ipaddress() | username() | clientid() | - {'and', [ipaddress() | username() | clientid()]} | - {'or', [ipaddress() | username() | clientid()]} | - all). +-type who() :: + ipaddress() + | username() + | clientid() + | {'and', [ipaddress() | username() | clientid()]} + | {'or', [ipaddress() | username() | clientid()]} + | all. --type(action() :: subscribe | publish | all). --type(permission() :: allow | deny). +-type action() :: subscribe | publish | all. +-type permission() :: allow | deny. --type(rule() :: {permission(), who(), action(), list(emqx_types:topic())}). +-type rule() :: {permission(), who(), action(), list(emqx_types:topic())}. --export_type([ action/0 - , permission/0 - ]). +-export_type([ + action/0, + permission/0 +]). -compile({Permission, all}) - when ?ALLOW_DENY(Permission) -> {Permission, all, all, [compile_topic(<<"#">>)]}; -compile({Permission, Who, Action, TopicFilters}) - when ?ALLOW_DENY(Permission), ?PUBSUB(Action), is_list(TopicFilters) -> - { atom(Permission), compile_who(Who), atom(Action) - , [compile_topic(Topic) || Topic <- TopicFilters]}. +compile({Permission, all}) when + ?ALLOW_DENY(Permission) +-> + {Permission, all, all, [compile_topic(<<"#">>)]}; +compile({Permission, Who, Action, TopicFilters}) when + ?ALLOW_DENY(Permission), ?PUBSUB(Action), is_list(TopicFilters) +-> + {atom(Permission), compile_who(Who), atom(Action), [ + compile_topic(Topic) + || Topic <- TopicFilters + ]}. -compile_who(all) -> all; -compile_who({user, Username}) -> compile_who({username, Username}); +compile_who(all) -> + all; +compile_who({user, Username}) -> + compile_who({username, Username}); compile_who({username, {re, Username}}) -> {ok, MP} = re:compile(bin(Username)), {username, MP}; -compile_who({username, Username}) -> {username, {eq, bin(Username)}}; -compile_who({client, Clientid}) -> compile_who({clientid, Clientid}); +compile_who({username, Username}) -> + {username, {eq, bin(Username)}}; +compile_who({client, Clientid}) -> + compile_who({clientid, Clientid}); compile_who({clientid, {re, Clientid}}) -> {ok, MP} = re:compile(bin(Clientid)), {clientid, MP}; -compile_who({clientid, Clientid}) -> {clientid, {eq, bin(Clientid)}}; +compile_who({clientid, Clientid}) -> + {clientid, {eq, bin(Clientid)}}; compile_who({ipaddr, CIDR}) -> {ipaddr, esockd_cidr:parse(CIDR, true)}; compile_who({ipaddrs, CIDRs}) -> @@ -86,7 +102,7 @@ compile_topic({eq, Topic}) -> compile_topic(Topic) -> Words = emqx_topic:words(bin(Topic)), case pattern(Words) of - true -> {pattern, Words}; + true -> {pattern, Words}; false -> Words end. @@ -94,7 +110,8 @@ pattern(Words) -> lists:member(?PH_USERNAME, Words) orelse lists:member(?PH_CLIENTID, Words). atom(B) when is_binary(B) -> - try binary_to_existing_atom(B, utf8) + try + binary_to_existing_atom(B, utf8) catch _E:_S -> binary_to_atom(B) end; @@ -105,21 +122,24 @@ bin(L) when is_list(L) -> bin(B) when is_binary(B) -> B. --spec(matches(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic(), [rule()]) - -> {matched, allow} | {matched, deny} | nomatch). -matches(_Client, _PubSub, _Topic, []) -> nomatch; +-spec matches(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic(), [rule()]) -> + {matched, allow} | {matched, deny} | nomatch. +matches(_Client, _PubSub, _Topic, []) -> + nomatch; matches(Client, PubSub, Topic, [{Permission, Who, Action, TopicFilters} | Tail]) -> case match(Client, PubSub, Topic, {Permission, Who, Action, TopicFilters}) of nomatch -> matches(Client, PubSub, Topic, Tail); Matched -> Matched end. --spec(match(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic(), rule()) - -> {matched, allow} | {matched, deny} | nomatch). +-spec match(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic(), rule()) -> + {matched, allow} | {matched, deny} | nomatch. match(Client, PubSub, Topic, {Permission, Who, Action, TopicFilters}) -> - case match_action(PubSub, Action) andalso - match_who(Client, Who) andalso - match_topics(Client, Topic, TopicFilters) of + case + match_action(PubSub, Action) andalso + match_who(Client, Who) andalso + match_topics(Client, Topic, TopicFilters) + of true -> {matched, Permission}; _ -> nomatch end. @@ -129,16 +149,19 @@ match_action(subscribe, subscribe) -> true; match_action(_, all) -> true; match_action(_, _) -> false. -match_who(_, all) -> true; +match_who(_, all) -> + true; match_who(#{username := undefined}, {username, _}) -> false; -match_who(#{username := Username}, {username, {eq, Username}}) -> true; +match_who(#{username := Username}, {username, {eq, Username}}) -> + true; match_who(#{username := Username}, {username, {re_pattern, _, _, _, _} = MP}) -> case re:run(Username, MP) of {match, _} -> true; _ -> false end; -match_who(#{clientid := Clientid}, {clientid, {eq, Clientid}}) -> true; +match_who(#{clientid := Clientid}, {clientid, {eq, Clientid}}) -> + true; match_who(#{clientid := Clientid}, {clientid, {re_pattern, _, _, _, _} = MP}) -> case re:run(Clientid, MP) of {match, _} -> true; @@ -151,28 +174,40 @@ match_who(#{peerhost := IpAddress}, {ipaddr, CIDR}) -> match_who(#{peerhost := undefined}, {ipaddrs, _CIDR}) -> false; match_who(#{peerhost := IpAddress}, {ipaddrs, CIDRs}) -> - lists:any(fun(CIDR) -> - esockd_cidr:match(IpAddress, CIDR) - end, CIDRs); + lists:any( + fun(CIDR) -> + esockd_cidr:match(IpAddress, CIDR) + end, + CIDRs + ); match_who(ClientInfo, {'and', Principals}) when is_list(Principals) -> - lists:foldl(fun(Principal, Permission) -> - match_who(ClientInfo, Principal) andalso Permission - end, true, Principals); + lists:foldl( + fun(Principal, Permission) -> + match_who(ClientInfo, Principal) andalso Permission + end, + true, + Principals + ); match_who(ClientInfo, {'or', Principals}) when is_list(Principals) -> - lists:foldl(fun(Principal, Permission) -> - match_who(ClientInfo, Principal) orelse Permission - end, false, Principals); -match_who(_, _) -> false. + lists:foldl( + fun(Principal, Permission) -> + match_who(ClientInfo, Principal) orelse Permission + end, + false, + Principals + ); +match_who(_, _) -> + false. match_topics(_ClientInfo, _Topic, []) -> false; match_topics(ClientInfo, Topic, [{pattern, PatternFilter} | Filters]) -> TopicFilter = feed_var(ClientInfo, PatternFilter), - match_topic(emqx_topic:words(Topic), TopicFilter) - orelse match_topics(ClientInfo, Topic, Filters); + match_topic(emqx_topic:words(Topic), TopicFilter) orelse + match_topics(ClientInfo, Topic, Filters); match_topics(ClientInfo, Topic, [TopicFilter | Filters]) -> - match_topic(emqx_topic:words(Topic), TopicFilter) - orelse match_topics(ClientInfo, Topic, Filters). + match_topic(emqx_topic:words(Topic), TopicFilter) orelse + match_topics(ClientInfo, Topic, Filters). match_topic(Topic, {'eq', TopicFilter}) -> Topic =:= TopicFilter; diff --git a/apps/emqx_authz/src/emqx_authz_schema.erl b/apps/emqx_authz/src/emqx_authz_schema.erl index d787da82d..1e34a2620 100644 --- a/apps/emqx_authz/src/emqx_authz_schema.erl +++ b/apps/emqx_authz/src/emqx_authz_schema.erl @@ -19,22 +19,25 @@ -include_lib("typerefl/include/types.hrl"). -include_lib("emqx_connector/include/emqx_connector.hrl"). --reflect_type([ permission/0 - , action/0 - ]). +-reflect_type([ + permission/0, + action/0 +]). -type action() :: publish | subscribe | all. -type permission() :: allow | deny. --export([ namespace/0 - , roots/0 - , fields/1 - , validations/0 - ]). +-export([ + namespace/0, + roots/0, + fields/1, + validations/0 +]). --export([ headers_no_content_type/1 - , headers/1 - ]). +-export([ + headers_no_content_type/1, + headers/1 +]). -import(emqx_schema, [mk_duration/2]). -include_lib("hocon/include/hoconsc.hrl"). @@ -50,73 +53,84 @@ namespace() -> authz. roots() -> []. fields("authorization") -> - [ {sources, #{type => union_array( - [ hoconsc:ref(?MODULE, file) - , hoconsc:ref(?MODULE, http_get) - , hoconsc:ref(?MODULE, http_post) - , hoconsc:ref(?MODULE, mnesia) - , hoconsc:ref(?MODULE, mongo_single) - , hoconsc:ref(?MODULE, mongo_rs) - , hoconsc:ref(?MODULE, mongo_sharded) - , hoconsc:ref(?MODULE, mysql) - , hoconsc:ref(?MODULE, postgresql) - , hoconsc:ref(?MODULE, redis_single) - , hoconsc:ref(?MODULE, redis_sentinel) - , hoconsc:ref(?MODULE, redis_cluster) - ]), - default => [], - desc => -" -Authorization data sources.
-An array of authorization (ACL) data providers. -It is designed as an array, not a hash-map, so the sources can be -ordered to form a chain of access controls.
- -When authorizing a 'publish' or 'subscribe' action, the configured -sources are checked in order. When checking an ACL source, -in case the client (identified by username or client ID) is not found, -it moves on to the next source. And it stops immediately -once an 'allow' or 'deny' decision is returned.
- -If the client is not found in any of the sources, -the default action configured in 'authorization.no_match' is applied.
- -NOTE: -The source elements are identified by their 'type'. -It is NOT allowed to configure two or more sources of the same type. -" - } - } + [ + {sources, #{ + type => union_array( + [ + hoconsc:ref(?MODULE, file), + hoconsc:ref(?MODULE, http_get), + hoconsc:ref(?MODULE, http_post), + hoconsc:ref(?MODULE, mnesia), + hoconsc:ref(?MODULE, mongo_single), + hoconsc:ref(?MODULE, mongo_rs), + hoconsc:ref(?MODULE, mongo_sharded), + hoconsc:ref(?MODULE, mysql), + hoconsc:ref(?MODULE, postgresql), + hoconsc:ref(?MODULE, redis_single), + hoconsc:ref(?MODULE, redis_sentinel), + hoconsc:ref(?MODULE, redis_cluster) + ] + ), + default => [], + desc => + "\n" + "Authorization data sources.
\n" + "An array of authorization (ACL) data providers.\n" + "It is designed as an array, not a hash-map, so the sources can be\n" + "ordered to form a chain of access controls.
\n" + "\n" + "When authorizing a 'publish' or 'subscribe' action, the configured\n" + "sources are checked in order. When checking an ACL source,\n" + "in case the client (identified by username or client ID) is not found,\n" + "it moves on to the next source. And it stops immediately\n" + "once an 'allow' or 'deny' decision is returned.
\n" + "\n" + "If the client is not found in any of the sources,\n" + "the default action configured in 'authorization.no_match' is applied.
\n" + "\n" + "NOTE:\n" + "The source elements are identified by their 'type'.\n" + "It is NOT allowed to configure two or more sources of the same type.\n" + }} ]; fields(file) -> - [ {type, #{type => file}} - , {enable, #{type => boolean(), - default => true}} - , {path, #{type => string(), - required => true, - desc => " -Path to the file which contains the ACL rules.
-If the file provisioned before starting EMQX node, -it can be placed anywhere as long as EMQX has read access to it. - -In case the rule-set is created from EMQX dashboard or management API, -the file will be placed in `authz` subdirectory inside EMQX's `data_dir`, -and the new rules will override all rules from the old config file. -" - }} + [ + {type, #{type => file}}, + {enable, #{ + type => boolean(), + default => true + }}, + {path, #{ + type => string(), + required => true, + desc => + "\n" + "Path to the file which contains the ACL rules.
\n" + "If the file provisioned before starting EMQX node,\n" + "it can be placed anywhere as long as EMQX has read access to it.\n" + "\n" + "In case the rule-set is created from EMQX dashboard or management API,\n" + "the file will be placed in `authz` subdirectory inside EMQX's `data_dir`,\n" + "and the new rules will override all rules from the old config file.\n" + }} ]; fields(http_get) -> - [ {method, #{type => get, default => post}} - , {headers, fun headers_no_content_type/1} + [ + {method, #{type => get, default => post}}, + {headers, fun headers_no_content_type/1} ] ++ http_common_fields(); fields(http_post) -> - [ {method, #{type => post, default => post}} - , {headers, fun headers/1} + [ + {method, #{type => post, default => post}}, + {headers, fun headers/1} ] ++ http_common_fields(); fields(mnesia) -> - [ {type, #{type => 'built_in_database'}} - , {enable, #{type => boolean(), - default => true}} + [ + {type, #{type => 'built_in_database'}}, + {enable, #{ + type => boolean(), + default => true + }} ]; fields(mongo_single) -> mongo_common_fields() ++ emqx_connector_mongo:fields(single); @@ -126,62 +140,88 @@ fields(mongo_sharded) -> mongo_common_fields() ++ emqx_connector_mongo:fields(sharded); fields(mysql) -> connector_fields(mysql) ++ - [ {query, query()} ]; + [{query, query()}]; fields(postgresql) -> - [ {query, query()} - , {type, #{type => postgresql}} - , {enable, #{type => boolean(), - default => true}} + [ + {query, query()}, + {type, #{type => postgresql}}, + {enable, #{ + type => boolean(), + default => true + }} ] ++ emqx_connector_pgsql:fields(config); fields(redis_single) -> connector_fields(redis, single) ++ - [ {cmd, query()} ]; + [{cmd, query()}]; fields(redis_sentinel) -> connector_fields(redis, sentinel) ++ - [ {cmd, query()} ]; + [{cmd, query()}]; fields(redis_cluster) -> connector_fields(redis, cluster) ++ - [ {cmd, query()} ]. + [{cmd, query()}]. http_common_fields() -> - [ {url, fun url/1} - , {request_timeout, mk_duration("Request timeout", #{default => "30s", desc => "Request timeout."})} - , {body, #{type => map(), required => false, desc => "HTTP request body."}} - ] ++ maps:to_list(maps:without([ base_url - , pool_type], - maps:from_list(connector_fields(http)))). + [ + {url, fun url/1}, + {request_timeout, + mk_duration("Request timeout", #{default => "30s", desc => "Request timeout."})}, + {body, #{type => map(), required => false, desc => "HTTP request body."}} + ] ++ + maps:to_list( + maps:without( + [ + base_url, + pool_type + ], + maps:from_list(connector_fields(http)) + ) + ). mongo_common_fields() -> - [ {collection, #{type => atom(), desc => "`MongoDB` collection containing the authorization data."}} - , {selector, #{type => map(), desc => "MQL query used to select the authorization record."}} - , {type, #{type => mongodb, desc => "Database backend."}} - , {enable, #{type => boolean(), - default => true, - desc => "Enable or disable the backend."}} + [ + {collection, #{ + type => atom(), desc => "`MongoDB` collection containing the authorization data." + }}, + {selector, #{type => map(), desc => "MQL query used to select the authorization record."}}, + {type, #{type => mongodb, desc => "Database backend."}}, + {enable, #{ + type => boolean(), + default => true, + desc => "Enable or disable the backend." + }} ]. validations() -> - [ {check_ssl_opts, fun check_ssl_opts/1} - , {check_headers, fun check_headers/1} + [ + {check_ssl_opts, fun check_ssl_opts/1}, + {check_headers, fun check_headers/1} ]. -headers(type) -> list({binary(), binary()}); -headers(desc) -> "List of HTTP headers."; +headers(type) -> + list({binary(), binary()}); +headers(desc) -> + "List of HTTP headers."; headers(converter) -> fun(Headers) -> maps:to_list(maps:merge(default_headers(), transform_header_name(Headers))) end; -headers(default) -> default_headers(); -headers(_) -> undefined. +headers(default) -> + default_headers(); +headers(_) -> + undefined. -headers_no_content_type(type) -> list({binary(), binary()}); -headers_no_content_type(desc) -> "List of HTTP headers."; +headers_no_content_type(type) -> + list({binary(), binary()}); +headers_no_content_type(desc) -> + "List of HTTP headers."; headers_no_content_type(converter) -> fun(Headers) -> - maps:to_list(maps:merge(default_headers_no_content_type(), transform_header_name(Headers))) + maps:to_list(maps:merge(default_headers_no_content_type(), transform_header_name(Headers))) end; -headers_no_content_type(default) -> default_headers_no_content_type(); -headers_no_content_type(_) -> undefined. +headers_no_content_type(default) -> + default_headers_no_content_type(); +headers_no_content_type(_) -> + undefined. url(type) -> binary(); url(desc) -> "URL of the auth server."; @@ -194,26 +234,34 @@ url(_) -> undefined. %%-------------------------------------------------------------------- default_headers() -> - maps:put(<<"content-type">>, - <<"application/json">>, - default_headers_no_content_type()). + maps:put( + <<"content-type">>, + <<"application/json">>, + default_headers_no_content_type() + ). default_headers_no_content_type() -> - #{ <<"accept">> => <<"application/json">> - , <<"cache-control">> => <<"no-cache">> - , <<"connection">> => <<"keep-alive">> - , <<"keep-alive">> => <<"timeout=30, max=1000">> - }. + #{ + <<"accept">> => <<"application/json">>, + <<"cache-control">> => <<"no-cache">>, + <<"connection">> => <<"keep-alive">>, + <<"keep-alive">> => <<"timeout=30, max=1000">> + }. transform_header_name(Headers) -> - maps:fold(fun(K0, V, Acc) -> - K = list_to_binary(string:to_lower(to_list(K0))), - maps:put(K, V, Acc) - end, #{}, Headers). + maps:fold( + fun(K0, V, Acc) -> + K = list_to_binary(string:to_lower(to_list(K0))), + maps:put(K, V, Acc) + end, + #{}, + Headers + ). check_ssl_opts(Conf) -> case hocon_maps:get("config.url", Conf) of - undefined -> true; + undefined -> + true; Url -> case emqx_authz_http:parse_url(Url) of #{scheme := https} -> @@ -221,19 +269,23 @@ check_ssl_opts(Conf) -> true -> true; _ -> {error, ssl_not_enable} end; - #{scheme := http} -> true; - Bad -> {bad_scheme, Url, Bad} + #{scheme := http} -> + true; + Bad -> + {bad_scheme, Url, Bad} end end. check_headers(Conf) -> case hocon_maps:get("config.method", Conf) of - undefined -> true; + undefined -> + true; Method0 -> Method = to_bin(Method0), Headers = hocon_maps:get("config.headers", Conf), case Method of - <<"post">> -> true; + <<"post">> -> + true; _ when Headers =:= undefined -> true; _ when is_list(Headers) -> case lists:member(<<"content-type">>, Headers) of @@ -247,32 +299,37 @@ union_array(Item) when is_list(Item) -> hoconsc:array(hoconsc:union(Item)). query() -> - #{type => binary(), - desc => "", - validator => fun(S) -> - case size(S) > 0 of - true -> ok; - _ -> {error, "Request query"} - end - end - }. + #{ + type => binary(), + desc => "", + validator => fun(S) -> + case size(S) > 0 of + true -> ok; + _ -> {error, "Request query"} + end + end + }. connector_fields(DB) -> connector_fields(DB, config). connector_fields(DB, Fields) -> - Mod0 = io_lib:format("~ts_~ts",[emqx_connector, DB]), - Mod = try - list_to_existing_atom(Mod0) - catch - error:badarg -> - list_to_atom(Mod0); - error:Reason -> - erlang:error(Reason) - end, - [ {type, #{type => DB, desc => "Database backend."}} - , {enable, #{type => boolean(), - default => true, - desc => "Enable or disable the backend."}} + Mod0 = io_lib:format("~ts_~ts", [emqx_connector, DB]), + Mod = + try + list_to_existing_atom(Mod0) + catch + error:badarg -> + list_to_atom(Mod0); + error:Reason -> + erlang:error(Reason) + end, + [ + {type, #{type => DB, desc => "Database backend."}}, + {enable, #{ + type => boolean(), + default => true, + desc => "Enable or disable the backend." + }} ] ++ erlang:apply(Mod, fields, [Fields]). to_list(A) when is_atom(A) -> diff --git a/apps/emqx_authz/src/emqx_authz_sup.erl b/apps/emqx_authz/src/emqx_authz_sup.erl index ca50f5a83..47a639201 100644 --- a/apps/emqx_authz/src/emqx_authz_sup.erl +++ b/apps/emqx_authz/src/emqx_authz_sup.erl @@ -33,8 +33,10 @@ start_link() -> supervisor:start_link({local, ?SERVER}, ?MODULE, []). init([]) -> - SupFlags = #{strategy => one_for_all, - intensity => 0, - period => 1}, + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, ChildSpecs = [], {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_authz/src/emqx_authz_utils.erl b/apps/emqx_authz/src/emqx_authz_utils.erl index 4a0e447e9..c153dbc1f 100644 --- a/apps/emqx_authz/src/emqx_authz_utils.erl +++ b/apps/emqx_authz/src/emqx_authz_utils.erl @@ -19,15 +19,16 @@ -include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("emqx_authz.hrl"). --export([ cleanup_resources/0 - , make_resource_id/1 - , create_resource/2 - , update_config/2 - , parse_deep/2 - , parse_sql/3 - , render_deep/2 - , render_sql_params/2 - ]). +-export([ + cleanup_resources/0, + make_resource_id/1, + create_resource/2, + update_config/2, + parse_deep/2, + parse_sql/3, + render_deep/2, + render_sql_params/2 +]). %%------------------------------------------------------------------------------ %% APIs @@ -35,10 +36,15 @@ create_resource(Module, Config) -> ResourceID = make_resource_id(Module), - case emqx_resource:create_local(ResourceID, - ?RESOURCE_GROUP, - Module, Config, - #{}) of + case + emqx_resource:create_local( + ResourceID, + ?RESOURCE_GROUP, + Module, + Config, + #{} + ) + of {ok, already_created} -> {ok, ResourceID}; {ok, _} -> {ok, ResourceID}; {error, Reason} -> {error, Reason} @@ -46,37 +52,45 @@ create_resource(Module, Config) -> cleanup_resources() -> lists:foreach( - fun emqx_resource:remove_local/1, - emqx_resource:list_group_instances(?RESOURCE_GROUP)). + fun emqx_resource:remove_local/1, + emqx_resource:list_group_instances(?RESOURCE_GROUP) + ). make_resource_id(Name) -> NameBin = bin(Name), emqx_resource:generate_id(NameBin). update_config(Path, ConfigRequest) -> - emqx_conf:update(Path, ConfigRequest, #{rawconf_with_defaults => true, - override_to => cluster}). + emqx_conf:update(Path, ConfigRequest, #{ + rawconf_with_defaults => true, + override_to => cluster + }). parse_deep(Template, PlaceHolders) -> emqx_placeholder:preproc_tmpl_deep(Template, #{placeholders => PlaceHolders}). parse_sql(Template, ReplaceWith, PlaceHolders) -> emqx_placeholder:preproc_sql( - Template, - #{replace_with => ReplaceWith, - placeholders => PlaceHolders}). + Template, + #{ + replace_with => ReplaceWith, + placeholders => PlaceHolders + } + ). render_deep(Template, Values) -> emqx_placeholder:proc_tmpl_deep( - Template, - client_vars(Values), - #{return => full_binary, var_trans => fun handle_var/2}). + Template, + client_vars(Values), + #{return => full_binary, var_trans => fun handle_var/2} + ). render_sql_params(ParamList, Values) -> emqx_placeholder:proc_tmpl( - ParamList, - client_vars(Values), - #{return => rawlist, var_trans => fun handle_sql_var/2}). + ParamList, + client_vars(Values), + #{return => rawlist, var_trans => fun handle_sql_var/2} + ). %%------------------------------------------------------------------------------ %% Internal functions @@ -84,9 +98,11 @@ render_sql_params(ParamList, Values) -> client_vars(ClientInfo) -> maps:from_list( - lists:map( - fun convert_client_var/1, - maps:to_list(ClientInfo))). + lists:map( + fun convert_client_var/1, + maps:to_list(ClientInfo) + ) + ). convert_client_var({cn, CN}) -> {cert_common_name, CN}; convert_client_var({dn, DN}) -> {cert_subject, DN}; diff --git a/apps/emqx_authz/test/emqx_authz_SUITE.erl b/apps/emqx_authz/test/emqx_authz_SUITE.erl index 2d2648913..40f918727 100644 --- a/apps/emqx_authz/test/emqx_authz_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_SUITE.erl @@ -33,22 +33,29 @@ init_per_suite(Config) -> meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_resource, create_local, fun(_, _, _, _) -> {ok, meck_data} end), meck:expect(emqx_resource, remove_local, fun(_) -> ok end), - meck:expect(emqx_authz, acl_conf_file, - fun() -> - emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") - end), + meck:expect( + emqx_authz, + acl_conf_file, + fun() -> + emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") + end + ), ok = emqx_common_test_helpers:start_apps( - [emqx_connector, emqx_conf, emqx_authz], - fun set_special_configs/1), + [emqx_connector, emqx_conf, emqx_authz], + fun set_special_configs/1 + ), Config. end_per_suite(_Config) -> {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => <<"allow">>, - <<"cache">> => #{<<"enable">> => <<"true">>}, - <<"sources">> => []}), + [authorization], + #{ + <<"no_match">> => <<"allow">>, + <<"cache">> => #{<<"enable">> => <<"true">>}, + <<"sources">> => [] + } + ), ok = stop_apps([emqx_resource]), emqx_common_test_helpers:stop_apps([emqx_authz, emqx_conf]), meck:unload(emqx_resource), @@ -66,64 +73,71 @@ set_special_configs(emqx_authz) -> set_special_configs(_App) -> ok. --define(SOURCE1, #{<<"type">> => <<"http">>, - <<"enable">> => true, - <<"url">> => <<"https://example.com:443/a/b?c=d">>, - <<"headers">> => #{}, - <<"method">> => <<"get">>, - <<"request_timeout">> => 5000 - }). --define(SOURCE2, #{<<"type">> => <<"mongodb">>, - <<"enable">> => true, - <<"mongo_type">> => <<"single">>, - <<"server">> => <<"127.0.0.1:27017">>, - <<"w_mode">> => <<"unsafe">>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"ssl">> => #{<<"enable">> => false}, - <<"collection">> => <<"authz">>, - <<"selector">> => #{<<"a">> => <<"b">>} - }). --define(SOURCE3, #{<<"type">> => <<"mysql">>, - <<"enable">> => true, - <<"server">> => <<"127.0.0.1:27017">>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"username">> => <<"xx">>, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"query">> => <<"abcb">> - }). --define(SOURCE4, #{<<"type">> => <<"postgresql">>, - <<"enable">> => true, - <<"server">> => <<"127.0.0.1:27017">>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"username">> => <<"xx">>, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"query">> => <<"abcb">> - }). --define(SOURCE5, #{<<"type">> => <<"redis">>, - <<"redis_type">> => <<"single">>, - <<"enable">> => true, - <<"server">> => <<"127.0.0.1:27017">>, - <<"pool_size">> => 1, - <<"database">> => 0, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"cmd">> => <<"HGETALL mqtt_authz:", ?PH_USERNAME/binary>> - }). --define(SOURCE6, #{<<"type">> => <<"file">>, - <<"enable">> => true, - <<"rules">> => -<<"{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}." - "\n{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">> - }). - +-define(SOURCE1, #{ + <<"type">> => <<"http">>, + <<"enable">> => true, + <<"url">> => <<"https://example.com:443/a/b?c=d">>, + <<"headers">> => #{}, + <<"method">> => <<"get">>, + <<"request_timeout">> => 5000 +}). +-define(SOURCE2, #{ + <<"type">> => <<"mongodb">>, + <<"enable">> => true, + <<"mongo_type">> => <<"single">>, + <<"server">> => <<"127.0.0.1:27017">>, + <<"w_mode">> => <<"unsafe">>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"ssl">> => #{<<"enable">> => false}, + <<"collection">> => <<"authz">>, + <<"selector">> => #{<<"a">> => <<"b">>} +}). +-define(SOURCE3, #{ + <<"type">> => <<"mysql">>, + <<"enable">> => true, + <<"server">> => <<"127.0.0.1:27017">>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"username">> => <<"xx">>, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false}, + <<"query">> => <<"abcb">> +}). +-define(SOURCE4, #{ + <<"type">> => <<"postgresql">>, + <<"enable">> => true, + <<"server">> => <<"127.0.0.1:27017">>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"username">> => <<"xx">>, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false}, + <<"query">> => <<"abcb">> +}). +-define(SOURCE5, #{ + <<"type">> => <<"redis">>, + <<"redis_type">> => <<"single">>, + <<"enable">> => true, + <<"server">> => <<"127.0.0.1:27017">>, + <<"pool_size">> => 1, + <<"database">> => 0, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false}, + <<"cmd">> => <<"HGETALL mqtt_authz:", ?PH_USERNAME/binary>> +}). +-define(SOURCE6, #{ + <<"type">> => <<"file">>, + <<"enable">> => true, + <<"rules">> => + << + "{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}." + "\n{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}." + >> +}). %%------------------------------------------------------------------------------ %% Testcases @@ -138,95 +152,130 @@ t_update_source(_) -> {ok, _} = emqx_authz:update(?CMD_APPEND, ?SOURCE5), {ok, _} = emqx_authz:update(?CMD_APPEND, ?SOURCE6), - ?assertMatch([ #{type := http, enable := true} - , #{type := mongodb, enable := true} - , #{type := mysql, enable := true} - , #{type := postgresql, enable := true} - , #{type := redis, enable := true} - , #{type := file, enable := true} - ], emqx_conf:get([authorization, sources], [])), + ?assertMatch( + [ + #{type := http, enable := true}, + #{type := mongodb, enable := true}, + #{type := mysql, enable := true}, + #{type := postgresql, enable := true}, + #{type := redis, enable := true}, + #{type := file, enable := true} + ], + emqx_conf:get([authorization, sources], []) + ), - {ok, _} = emqx_authz:update({?CMD_REPLACE, http}, ?SOURCE1#{<<"enable">> := true}), + {ok, _} = emqx_authz:update({?CMD_REPLACE, http}, ?SOURCE1#{<<"enable">> := true}), {ok, _} = emqx_authz:update({?CMD_REPLACE, mongodb}, ?SOURCE2#{<<"enable">> := true}), {ok, _} = emqx_authz:update({?CMD_REPLACE, mysql}, ?SOURCE3#{<<"enable">> := true}), {ok, _} = emqx_authz:update({?CMD_REPLACE, postgresql}, ?SOURCE4#{<<"enable">> := true}), {ok, _} = emqx_authz:update({?CMD_REPLACE, redis}, ?SOURCE5#{<<"enable">> := true}), - {ok, _} = emqx_authz:update({?CMD_REPLACE, file}, ?SOURCE6#{<<"enable">> := true}), + {ok, _} = emqx_authz:update({?CMD_REPLACE, file}, ?SOURCE6#{<<"enable">> := true}), - {ok, _} = emqx_authz:update({?CMD_REPLACE, http}, ?SOURCE1#{<<"enable">> := false}), + {ok, _} = emqx_authz:update({?CMD_REPLACE, http}, ?SOURCE1#{<<"enable">> := false}), {ok, _} = emqx_authz:update({?CMD_REPLACE, mongodb}, ?SOURCE2#{<<"enable">> := false}), {ok, _} = emqx_authz:update({?CMD_REPLACE, mysql}, ?SOURCE3#{<<"enable">> := false}), {ok, _} = emqx_authz:update({?CMD_REPLACE, postgresql}, ?SOURCE4#{<<"enable">> := false}), {ok, _} = emqx_authz:update({?CMD_REPLACE, redis}, ?SOURCE5#{<<"enable">> := false}), - {ok, _} = emqx_authz:update({?CMD_REPLACE, file}, ?SOURCE6#{<<"enable">> := false}), + {ok, _} = emqx_authz:update({?CMD_REPLACE, file}, ?SOURCE6#{<<"enable">> := false}), - ?assertMatch([ #{type := http, enable := false} - , #{type := mongodb, enable := false} - , #{type := mysql, enable := false} - , #{type := postgresql, enable := false} - , #{type := redis, enable := false} - , #{type := file, enable := false} - ], emqx_conf:get([authorization, sources], [])), + ?assertMatch( + [ + #{type := http, enable := false}, + #{type := mongodb, enable := false}, + #{type := mysql, enable := false}, + #{type := postgresql, enable := false}, + #{type := redis, enable := false}, + #{type := file, enable := false} + ], + emqx_conf:get([authorization, sources], []) + ), {ok, _} = emqx_authz:update(?CMD_REPLACE, []). t_delete_source(_) -> {ok, _} = emqx_authz:update(?CMD_REPLACE, [?SOURCE1]), - ?assertMatch([ #{type := http, enable := true} - ], emqx_conf:get([authorization, sources], [])), + ?assertMatch([#{type := http, enable := true}], emqx_conf:get([authorization, sources], [])), {ok, _} = emqx_authz:update({?CMD_DELETE, http}, #{}), ?assertMatch([], emqx_conf:get([authorization, sources], [])). t_move_source(_) -> - {ok, _} = emqx_authz:update(?CMD_REPLACE, - [?SOURCE1, ?SOURCE2, ?SOURCE3, - ?SOURCE4, ?SOURCE5, ?SOURCE6]), - ?assertMatch([ #{type := http} - , #{type := mongodb} - , #{type := mysql} - , #{type := postgresql} - , #{type := redis} - , #{type := file} - ], emqx_authz:lookup()), + {ok, _} = emqx_authz:update( + ?CMD_REPLACE, + [ + ?SOURCE1, + ?SOURCE2, + ?SOURCE3, + ?SOURCE4, + ?SOURCE5, + ?SOURCE6 + ] + ), + ?assertMatch( + [ + #{type := http}, + #{type := mongodb}, + #{type := mysql}, + #{type := postgresql}, + #{type := redis}, + #{type := file} + ], + emqx_authz:lookup() + ), {ok, _} = emqx_authz:move(postgresql, ?CMD_MOVE_FRONT), - ?assertMatch([ #{type := postgresql} - , #{type := http} - , #{type := mongodb} - , #{type := mysql} - , #{type := redis} - , #{type := file} - ], emqx_authz:lookup()), + ?assertMatch( + [ + #{type := postgresql}, + #{type := http}, + #{type := mongodb}, + #{type := mysql}, + #{type := redis}, + #{type := file} + ], + emqx_authz:lookup() + ), {ok, _} = emqx_authz:move(http, ?CMD_MOVE_REAR), - ?assertMatch([ #{type := postgresql} - , #{type := mongodb} - , #{type := mysql} - , #{type := redis} - , #{type := file} - , #{type := http} - ], emqx_authz:lookup()), + ?assertMatch( + [ + #{type := postgresql}, + #{type := mongodb}, + #{type := mysql}, + #{type := redis}, + #{type := file}, + #{type := http} + ], + emqx_authz:lookup() + ), {ok, _} = emqx_authz:move(mysql, ?CMD_MOVE_BEFORE(postgresql)), - ?assertMatch([ #{type := mysql} - , #{type := postgresql} - , #{type := mongodb} - , #{type := redis} - , #{type := file} - , #{type := http} - ], emqx_authz:lookup()), + ?assertMatch( + [ + #{type := mysql}, + #{type := postgresql}, + #{type := mongodb}, + #{type := redis}, + #{type := file}, + #{type := http} + ], + emqx_authz:lookup() + ), {ok, _} = emqx_authz:move(mongodb, ?CMD_MOVE_AFTER(http)), - ?assertMatch([ #{type := mysql} - , #{type := postgresql} - , #{type := redis} - , #{type := file} - , #{type := http} - , #{type := mongodb} - ], emqx_authz:lookup()), + ?assertMatch( + [ + #{type := mysql}, + #{type := postgresql}, + #{type := redis}, + #{type := file}, + #{type := http}, + #{type := mongodb} + ], + emqx_authz:lookup() + ), ok. diff --git a/apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl b/apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl index ee7f68c14..385a10eb9 100644 --- a/apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl @@ -32,16 +32,20 @@ groups() -> init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz, emqx_dashboard], - fun set_special_configs/1), + [emqx_conf, emqx_authz, emqx_dashboard], + fun set_special_configs/1 + ), Config. end_per_suite(_Config) -> {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => <<"allow">>, - <<"cache">> => #{<<"enable">> => <<"true">>}, - <<"sources">> => []}), + [authorization], + #{ + <<"no_match">> => <<"allow">>, + <<"cache">> => #{<<"enable">> => <<"true">>}, + <<"sources">> => [] + } + ), emqx_common_test_helpers:stop_apps([emqx_dashboard, emqx_authz, emqx_conf]), ok. @@ -50,8 +54,10 @@ set_special_configs(emqx_dashboard) -> set_special_configs(emqx_authz) -> {ok, _} = emqx:update_config([authorization, cache, enable], false), {ok, _} = emqx:update_config([authorization, no_match], deny), - {ok, _} = emqx:update_config([authorization, sources], - [#{<<"type">> => <<"built_in_database">>}]), + {ok, _} = emqx:update_config( + [authorization, sources], + [#{<<"type">> => <<"built_in_database">>}] + ), ok; set_special_configs(_App) -> ok. @@ -62,176 +68,248 @@ set_special_configs(_App) -> t_api(_) -> {ok, 204, _} = - request( post - , uri(["authorization", "sources", "built_in_database", "username"]) - , [?USERNAME_RULES_EXAMPLE]), + request( + post, + uri(["authorization", "sources", "built_in_database", "username"]), + [?USERNAME_RULES_EXAMPLE] + ), {ok, 200, Request1} = - request( get - , uri(["authorization", "sources", "built_in_database", "username"]) - , []), - #{<<"data">> := [#{<<"username">> := <<"user1">>, <<"rules">> := Rules1}], - <<"meta">> := #{<<"count">> := 1, - <<"limit">> := 100, - <<"page">> := 1}} = jsx:decode(Request1), + request( + get, + uri(["authorization", "sources", "built_in_database", "username"]), + [] + ), + #{ + <<"data">> := [#{<<"username">> := <<"user1">>, <<"rules">> := Rules1}], + <<"meta">> := #{ + <<"count">> := 1, + <<"limit">> := 100, + <<"page">> := 1 + } + } = jsx:decode(Request1), ?assertEqual(3, length(Rules1)), {ok, 200, Request1_1} = - request( get - , uri([ "authorization" - , "sources" - , "built_in_database" - , "username?page=1&limit=20&like_username=noexist"]) - , []), - #{<<"data">> := [], - <<"meta">> := #{<<"count">> := 0, - <<"limit">> := 20, - <<"page">> := 1}} = jsx:decode(Request1_1), - + request( + get, + uri([ + "authorization", + "sources", + "built_in_database", + "username?page=1&limit=20&like_username=noexist" + ]), + [] + ), + #{ + <<"data">> := [], + <<"meta">> := #{ + <<"count">> := 0, + <<"limit">> := 20, + <<"page">> := 1 + } + } = jsx:decode(Request1_1), {ok, 200, Request2} = - request( get - , uri(["authorization", "sources", "built_in_database", "username", "user1"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "username", "user1"]), + [] + ), #{<<"username">> := <<"user1">>, <<"rules">> := Rules1} = jsx:decode(Request2), - {ok, 204, _} = - request( put - , uri(["authorization", "sources", "built_in_database", "username", "user1"]) - , ?USERNAME_RULES_EXAMPLE#{rules => []}), + request( + put, + uri(["authorization", "sources", "built_in_database", "username", "user1"]), + ?USERNAME_RULES_EXAMPLE#{rules => []} + ), {ok, 200, Request3} = - request( get - , uri(["authorization", "sources", "built_in_database", "username", "user1"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "username", "user1"]), + [] + ), #{<<"username">> := <<"user1">>, <<"rules">> := Rules2} = jsx:decode(Request3), ?assertEqual(0, length(Rules2)), {ok, 204, _} = - request( delete - , uri(["authorization", "sources", "built_in_database", "username", "user1"]) - , []), + request( + delete, + uri(["authorization", "sources", "built_in_database", "username", "user1"]), + [] + ), {ok, 404, _} = - request( get - , uri(["authorization", "sources", "built_in_database", "username", "user1"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "username", "user1"]), + [] + ), {ok, 404, _} = - request( delete - , uri(["authorization", "sources", "built_in_database", "username", "user1"]) - , []), - + request( + delete, + uri(["authorization", "sources", "built_in_database", "username", "user1"]), + [] + ), {ok, 204, _} = - request( post - , uri(["authorization", "sources", "built_in_database", "clientid"]) - , [?CLIENTID_RULES_EXAMPLE]), + request( + post, + uri(["authorization", "sources", "built_in_database", "clientid"]), + [?CLIENTID_RULES_EXAMPLE] + ), {ok, 200, Request4} = - request( get - , uri(["authorization", "sources", "built_in_database", "clientid"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "clientid"]), + [] + ), {ok, 200, Request5} = - request( get - , uri(["authorization", "sources", "built_in_database", "clientid", "client1"]) - , []), - #{<<"data">> := [#{<<"clientid">> := <<"client1">>, <<"rules">> := Rules3}], - <<"meta">> := #{<<"count">> := 1, <<"limit">> := 100, <<"page">> := 1}} - = jsx:decode(Request4), + request( + get, + uri(["authorization", "sources", "built_in_database", "clientid", "client1"]), + [] + ), + #{ + <<"data">> := [#{<<"clientid">> := <<"client1">>, <<"rules">> := Rules3}], + <<"meta">> := #{<<"count">> := 1, <<"limit">> := 100, <<"page">> := 1} + } = + jsx:decode(Request4), #{<<"clientid">> := <<"client1">>, <<"rules">> := Rules3} = jsx:decode(Request5), ?assertEqual(3, length(Rules3)), {ok, 204, _} = - request( put - , uri(["authorization", "sources", "built_in_database", "clientid", "client1"]) - , ?CLIENTID_RULES_EXAMPLE#{rules => []}), + request( + put, + uri(["authorization", "sources", "built_in_database", "clientid", "client1"]), + ?CLIENTID_RULES_EXAMPLE#{rules => []} + ), {ok, 200, Request6} = - request( get - , uri(["authorization", "sources", "built_in_database", "clientid", "client1"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "clientid", "client1"]), + [] + ), #{<<"clientid">> := <<"client1">>, <<"rules">> := Rules4} = jsx:decode(Request6), ?assertEqual(0, length(Rules4)), {ok, 204, _} = - request( delete - , uri(["authorization", "sources", "built_in_database", "clientid", "client1"]) - , []), + request( + delete, + uri(["authorization", "sources", "built_in_database", "clientid", "client1"]), + [] + ), {ok, 404, _} = - request( get - , uri(["authorization", "sources", "built_in_database", "clientid", "client1"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "clientid", "client1"]), + [] + ), {ok, 404, _} = - request( delete - , uri(["authorization", "sources", "built_in_database", "clientid", "client1"]) - , []), - + request( + delete, + uri(["authorization", "sources", "built_in_database", "clientid", "client1"]), + [] + ), {ok, 204, _} = - request( post - , uri(["authorization", "sources", "built_in_database", "all"]) - , ?ALL_RULES_EXAMPLE), + request( + post, + uri(["authorization", "sources", "built_in_database", "all"]), + ?ALL_RULES_EXAMPLE + ), {ok, 200, Request7} = - request( get - , uri(["authorization", "sources", "built_in_database", "all"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "all"]), + [] + ), #{<<"rules">> := Rules5} = jsx:decode(Request7), ?assertEqual(3, length(Rules5)), {ok, 204, _} = - request( post - , uri(["authorization", "sources", "built_in_database", "all"]) + request( + post, + uri(["authorization", "sources", "built_in_database", "all"]), - , ?ALL_RULES_EXAMPLE#{rules => []}), + ?ALL_RULES_EXAMPLE#{rules => []} + ), {ok, 200, Request8} = - request( get - , uri(["authorization", "sources", "built_in_database", "all"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "all"]), + [] + ), #{<<"rules">> := Rules6} = jsx:decode(Request8), ?assertEqual(0, length(Rules6)), {ok, 204, _} = - request( post - , uri(["authorization", "sources", "built_in_database", "username"]) - , [ #{username => erlang:integer_to_binary(N), rules => []} - || N <- lists:seq(1, 20) ]), + request( + post, + uri(["authorization", "sources", "built_in_database", "username"]), + [ + #{username => erlang:integer_to_binary(N), rules => []} + || N <- lists:seq(1, 20) + ] + ), {ok, 200, Request9} = - request( get - , uri(["authorization", "sources", "built_in_database", "username?page=2&limit=5"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "username?page=2&limit=5"]), + [] + ), #{<<"data">> := Data1} = jsx:decode(Request9), ?assertEqual(5, length(Data1)), {ok, 204, _} = - request( post - , uri(["authorization", "sources", "built_in_database", "clientid"]) - , [ #{clientid => erlang:integer_to_binary(N), rules => []} - || N <- lists:seq(1, 20) ]), + request( + post, + uri(["authorization", "sources", "built_in_database", "clientid"]), + [ + #{clientid => erlang:integer_to_binary(N), rules => []} + || N <- lists:seq(1, 20) + ] + ), {ok, 200, Request10} = - request( get - , uri(["authorization", "sources", "built_in_database", "clientid?limit=5"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "clientid?limit=5"]), + [] + ), #{<<"data">> := Data2} = jsx:decode(Request10), ?assertEqual(5, length(Data2)), {ok, 400, Msg1} = - request( delete - , uri(["authorization", "sources", "built_in_database", "purge-all"]) - , []), + request( + delete, + uri(["authorization", "sources", "built_in_database", "purge-all"]), + [] + ), ?assertMatch({match, _}, re:run(Msg1, "must\sbe\sdisabled\sbefore")), {ok, 204, _} = - request( put - , uri(["authorization", "sources", "built_in_database"]) - , #{<<"enable">> => true, <<"type">> => <<"built_in_database">>}), + request( + put, + uri(["authorization", "sources", "built_in_database"]), + #{<<"enable">> => true, <<"type">> => <<"built_in_database">>} + ), %% test idempotence {ok, 204, _} = - request( put - , uri(["authorization", "sources", "built_in_database"]) - , #{<<"enable">> => true, <<"type">> => <<"built_in_database">>}), + request( + put, + uri(["authorization", "sources", "built_in_database"]), + #{<<"enable">> => true, <<"type">> => <<"built_in_database">>} + ), {ok, 204, _} = - request( put - , uri(["authorization", "sources", "built_in_database"]) - , #{<<"enable">> => false, <<"type">> => <<"built_in_database">>}), + request( + put, + uri(["authorization", "sources", "built_in_database"]), + #{<<"enable">> => false, <<"type">> => <<"built_in_database">>} + ), {ok, 204, _} = - request( delete - , uri(["authorization", "sources", "built_in_database", "purge-all"]) - , []), + request( + delete, + uri(["authorization", "sources", "built_in_database", "purge-all"]), + [] + ), ?assertEqual(0, emqx_authz_mnesia:record_count()), ok. diff --git a/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl b/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl index a14535018..b53b7aa1b 100644 --- a/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl @@ -31,16 +31,20 @@ groups() -> init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz, emqx_dashboard], - fun set_special_configs/1), + [emqx_conf, emqx_authz, emqx_dashboard], + fun set_special_configs/1 + ), Config. end_per_suite(_Config) -> {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => <<"allow">>, - <<"cache">> => #{<<"enable">> => <<"true">>}, - <<"sources">> => []}), + [authorization], + #{ + <<"no_match">> => <<"allow">>, + <<"cache">> => #{<<"enable">> => <<"true">>}, + <<"sources">> => [] + } + ), ok = stop_apps([emqx_resource, emqx_connector]), emqx_common_test_helpers:stop_apps([emqx_dashboard, emqx_authz, emqx_conf]), ok. @@ -60,27 +64,29 @@ set_special_configs(_App) -> %%------------------------------------------------------------------------------ t_api(_) -> - Settings1 = #{<<"no_match">> => <<"deny">>, - <<"deny_action">> => <<"disconnect">>, - <<"cache">> => #{ - <<"enable">> => false, - <<"max_size">> => 32, - <<"ttl">> => 60000 - } - }, + Settings1 = #{ + <<"no_match">> => <<"deny">>, + <<"deny_action">> => <<"disconnect">>, + <<"cache">> => #{ + <<"enable">> => false, + <<"max_size">> => 32, + <<"ttl">> => 60000 + } + }, {ok, 200, Result1} = request(put, uri(["authorization", "settings"]), Settings1), {ok, 200, Result1} = request(get, uri(["authorization", "settings"]), []), ?assertEqual(Settings1, jsx:decode(Result1)), - Settings2 = #{<<"no_match">> => <<"allow">>, - <<"deny_action">> => <<"ignore">>, - <<"cache">> => #{ - <<"enable">> => true, - <<"max_size">> => 32, - <<"ttl">> => 60000 - } - }, + Settings2 = #{ + <<"no_match">> => <<"allow">>, + <<"deny_action">> => <<"ignore">>, + <<"cache">> => #{ + <<"enable">> => true, + <<"max_size">> => 32, + <<"ttl">> => 60000 + } + }, {ok, 200, Result2} = request(put, uri(["authorization", "settings"]), Settings2), {ok, 200, Result2} = request(get, uri(["authorization", "settings"]), []), diff --git a/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl b/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl index 737091585..fe445fbf0 100644 --- a/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl @@ -29,62 +29,70 @@ -define(PGSQL_HOST, "pgsql"). -define(REDIS_SINGLE_HOST, "redis"). --define(SOURCE1, #{<<"type">> => <<"http">>, - <<"enable">> => true, - <<"url">> => <<"https://fake.com:443/acl?username=", ?PH_USERNAME/binary>>, - <<"headers">> => #{}, - <<"method">> => <<"get">>, - <<"request_timeout">> => <<"5s">> - }). --define(SOURCE2, #{<<"type">> => <<"mongodb">>, - <<"enable">> => true, - <<"mongo_type">> => <<"single">>, - <<"server">> => <>, - <<"w_mode">> => <<"unsafe">>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"ssl">> => #{<<"enable">> => false}, - <<"collection">> => <<"fake">>, - <<"selector">> => #{<<"a">> => <<"b">>} - }). --define(SOURCE3, #{<<"type">> => <<"mysql">>, - <<"enable">> => true, - <<"server">> => <>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"username">> => <<"xx">>, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"query">> => <<"abcb">> - }). --define(SOURCE4, #{<<"type">> => <<"postgresql">>, - <<"enable">> => true, - <<"server">> => <>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"username">> => <<"xx">>, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"query">> => <<"abcb">> - }). --define(SOURCE5, #{<<"type">> => <<"redis">>, - <<"enable">> => true, - <<"servers">> => <>, - <<"pool_size">> => 1, - <<"database">> => 0, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"cmd">> => <<"HGETALL mqtt_authz:", ?PH_USERNAME/binary>> - }). --define(SOURCE6, #{<<"type">> => <<"file">>, - <<"enable">> => true, - <<"rules">> => -<<"{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}." - "\n{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">> - }). +-define(SOURCE1, #{ + <<"type">> => <<"http">>, + <<"enable">> => true, + <<"url">> => <<"https://fake.com:443/acl?username=", ?PH_USERNAME/binary>>, + <<"headers">> => #{}, + <<"method">> => <<"get">>, + <<"request_timeout">> => <<"5s">> +}). +-define(SOURCE2, #{ + <<"type">> => <<"mongodb">>, + <<"enable">> => true, + <<"mongo_type">> => <<"single">>, + <<"server">> => <>, + <<"w_mode">> => <<"unsafe">>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"ssl">> => #{<<"enable">> => false}, + <<"collection">> => <<"fake">>, + <<"selector">> => #{<<"a">> => <<"b">>} +}). +-define(SOURCE3, #{ + <<"type">> => <<"mysql">>, + <<"enable">> => true, + <<"server">> => <>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"username">> => <<"xx">>, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false}, + <<"query">> => <<"abcb">> +}). +-define(SOURCE4, #{ + <<"type">> => <<"postgresql">>, + <<"enable">> => true, + <<"server">> => <>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"username">> => <<"xx">>, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false}, + <<"query">> => <<"abcb">> +}). +-define(SOURCE5, #{ + <<"type">> => <<"redis">>, + <<"enable">> => true, + <<"servers">> => <>, + <<"pool_size">> => 1, + <<"database">> => 0, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false}, + <<"cmd">> => <<"HGETALL mqtt_authz:", ?PH_USERNAME/binary>> +}). +-define(SOURCE6, #{ + <<"type">> => <<"file">>, + <<"enable">> => true, + <<"rules">> => + << + "{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}." + "\n{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}." + >> +}). -define(MATCH_RSA_KEY, <<"-----BEGIN RSA PRIVATE KEY", _/binary>>). -define(MATCH_CERT, <<"-----BEGIN CERTIFICATE", _/binary>>). @@ -100,24 +108,31 @@ init_per_suite(Config) -> meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_resource, create_local, fun(_, _, _, _) -> {ok, meck_data} end), meck:expect(emqx_resource, health_check, fun(St) -> {ok, St} end), - meck:expect(emqx_resource, remove_local, fun(_) -> ok end ), - meck:expect(emqx_authz, acl_conf_file, - fun() -> - emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") - end), + meck:expect(emqx_resource, remove_local, fun(_) -> ok end), + meck:expect( + emqx_authz, + acl_conf_file, + fun() -> + emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") + end + ), ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz, emqx_dashboard], - fun set_special_configs/1), + [emqx_conf, emqx_authz, emqx_dashboard], + fun set_special_configs/1 + ), ok = start_apps([emqx_resource, emqx_connector]), Config. end_per_suite(_Config) -> {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => <<"allow">>, - <<"cache">> => #{<<"enable">> => <<"true">>}, - <<"sources">> => []}), + [authorization], + #{ + <<"no_match">> => <<"allow">>, + <<"cache">> => #{<<"enable">> => <<"true">>}, + <<"sources">> => [] + } + ), %% resource and connector should be stop first, %% or authz_[mysql|pgsql|redis..]_SUITE would be failed ok = stop_apps([emqx_resource, emqx_connector]), @@ -140,19 +155,24 @@ init_per_testcase(t_api, Config) -> meck:expect(emqx_misc, gen_id, fun() -> "fake" end), meck:new(emqx, [non_strict, passthrough, no_history, no_link]), - meck:expect(emqx, data_dir, - fun() -> - {data_dir, Data} = lists:keyfind(data_dir, 1, Config), - Data - end), + meck:expect( + emqx, + data_dir, + fun() -> + {data_dir, Data} = lists:keyfind(data_dir, 1, Config), + Data + end + ), Config; -init_per_testcase(_, Config) -> Config. +init_per_testcase(_, Config) -> + Config. end_per_testcase(t_api, _Config) -> meck:unload(emqx_misc), meck:unload(emqx), ok; -end_per_testcase(_, _Config) -> ok. +end_per_testcase(_, _Config) -> + ok. %%------------------------------------------------------------------------------ %% Testcases @@ -162,139 +182,190 @@ t_api(_) -> {ok, 200, Result1} = request(get, uri(["authorization", "sources"]), []), ?assertEqual([], get_sources(Result1)), - [ begin {ok, 204, _} = request(post, uri(["authorization", "sources"]), Source) end - || Source <- lists:reverse([?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5, ?SOURCE6])], + [ + begin + {ok, 204, _} = request(post, uri(["authorization", "sources"]), Source) + end + || Source <- lists:reverse([?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5, ?SOURCE6]) + ], {ok, 204, _} = request(post, uri(["authorization", "sources"]), ?SOURCE1), - Snd = fun ({_, Val}) -> Val end, + Snd = fun({_, Val}) -> Val end, LookupVal = fun LookupV(List, RestJson) -> - case List of - [Name] -> Snd(lists:keyfind(Name, 1, RestJson)); - [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson))) - end - end, - EqualFun = fun (RList) -> - fun ({M, V}) -> - ?assertEqual(V, - LookupVal([<<"metrics">>, M], - RList) - ) - end - end, + case List of + [Name] -> Snd(lists:keyfind(Name, 1, RestJson)); + [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson))) + end + end, + EqualFun = fun(RList) -> + fun({M, V}) -> + ?assertEqual( + V, + LookupVal( + [<<"metrics">>, M], + RList + ) + ) + end + end, AssertFun = - fun (ResultJson) -> + fun(ResultJson) -> {ok, RList} = emqx_json:safe_decode(ResultJson), - MetricsList = [{<<"failed">>, 0}, - {<<"matched">>, 0}, - {<<"rate">>, 0.0}, - {<<"rate_last5m">>, 0.0}, - {<<"rate_max">>, 0.0}, - {<<"success">>, 0}], + MetricsList = [ + {<<"failed">>, 0}, + {<<"matched">>, 0}, + {<<"rate">>, 0.0}, + {<<"rate_last5m">>, 0.0}, + {<<"rate_max">>, 0.0}, + {<<"success">>, 0} + ], lists:map(EqualFun(RList), MetricsList) end, {ok, 200, Result2} = request(get, uri(["authorization", "sources"]), []), Sources = get_sources(Result2), - ?assertMatch([ #{<<"type">> := <<"http">>} - , #{<<"type">> := <<"mongodb">>} - , #{<<"type">> := <<"mysql">>} - , #{<<"type">> := <<"postgresql">>} - , #{<<"type">> := <<"redis">>} - , #{<<"type">> := <<"file">>} - ], Sources), + ?assertMatch( + [ + #{<<"type">> := <<"http">>}, + #{<<"type">> := <<"mongodb">>}, + #{<<"type">> := <<"mysql">>}, + #{<<"type">> := <<"postgresql">>}, + #{<<"type">> := <<"redis">>}, + #{<<"type">> := <<"file">>} + ], + Sources + ), ?assert(filelib:is_file(emqx_authz:acl_conf_file())), - {ok, 204, _} = request(put, uri(["authorization", "sources", "http"]), - ?SOURCE1#{<<"enable">> := false}), + {ok, 204, _} = request( + put, + uri(["authorization", "sources", "http"]), + ?SOURCE1#{<<"enable">> := false} + ), {ok, 200, Result3} = request(get, uri(["authorization", "sources", "http"]), []), ?assertMatch(#{<<"type">> := <<"http">>, <<"enable">> := false}, jsx:decode(Result3)), Keyfile = emqx_common_test_helpers:app_path( - emqx, - filename:join(["etc", "certs", "key.pem"])), + emqx, + filename:join(["etc", "certs", "key.pem"]) + ), Certfile = emqx_common_test_helpers:app_path( - emqx, - filename:join(["etc", "certs", "cert.pem"])), + emqx, + filename:join(["etc", "certs", "cert.pem"]) + ), Cacertfile = emqx_common_test_helpers:app_path( - emqx, - filename:join(["etc", "certs", "cacert.pem"])), + emqx, + filename:join(["etc", "certs", "cacert.pem"]) + ), - {ok, 204, _} = request(put, uri(["authorization", "sources", "mongodb"]), - ?SOURCE2#{<<"ssl">> => #{ - <<"enable">> => <<"true">>, - <<"cacertfile">> => Cacertfile, - <<"certfile">> => Certfile, - <<"keyfile">> => Keyfile, - <<"verify">> => <<"verify_none">> - }}), + {ok, 204, _} = request( + put, + uri(["authorization", "sources", "mongodb"]), + ?SOURCE2#{ + <<"ssl">> => #{ + <<"enable">> => <<"true">>, + <<"cacertfile">> => Cacertfile, + <<"certfile">> => Certfile, + <<"keyfile">> => Keyfile, + <<"verify">> => <<"verify_none">> + } + } + ), {ok, 200, Result4} = request(get, uri(["authorization", "sources", "mongodb"]), []), {ok, 200, Status4} = request(get, uri(["authorization", "sources", "mongodb", "status"]), []), AssertFun(Status4), - ?assertMatch(#{<<"type">> := <<"mongodb">>, - <<"ssl">> := #{<<"enable">> := <<"true">>, - <<"cacertfile">> := ?MATCH_CERT, - <<"certfile">> := ?MATCH_CERT, - <<"keyfile">> := ?MATCH_RSA_KEY, - <<"verify">> := <<"verify_none">> - } - }, jsx:decode(Result4)), + ?assertMatch( + #{ + <<"type">> := <<"mongodb">>, + <<"ssl">> := #{ + <<"enable">> := <<"true">>, + <<"cacertfile">> := ?MATCH_CERT, + <<"certfile">> := ?MATCH_CERT, + <<"keyfile">> := ?MATCH_RSA_KEY, + <<"verify">> := <<"verify_none">> + } + }, + jsx:decode(Result4) + ), {ok, Cacert} = file:read_file(Cacertfile), {ok, Cert} = file:read_file(Certfile), {ok, Key} = file:read_file(Keyfile), - {ok, 204, _} = request(put, uri(["authorization", "sources", "mongodb"]), - ?SOURCE2#{<<"ssl">> => #{ - <<"enable">> => <<"true">>, - <<"cacertfile">> => Cacert, - <<"certfile">> => Cert, - <<"keyfile">> => Key, - <<"verify">> => <<"verify_none">> - }}), + {ok, 204, _} = request( + put, + uri(["authorization", "sources", "mongodb"]), + ?SOURCE2#{ + <<"ssl">> => #{ + <<"enable">> => <<"true">>, + <<"cacertfile">> => Cacert, + <<"certfile">> => Cert, + <<"keyfile">> => Key, + <<"verify">> => <<"verify_none">> + } + } + ), {ok, 200, Result5} = request(get, uri(["authorization", "sources", "mongodb"]), []), {ok, 200, Status5} = request(get, uri(["authorization", "sources", "mongodb", "status"]), []), AssertFun(Status5), - ?assertMatch(#{<<"type">> := <<"mongodb">>, - <<"ssl">> := #{<<"enable">> := <<"true">>, - <<"cacertfile">> := ?MATCH_CERT, - <<"certfile">> := ?MATCH_CERT, - <<"keyfile">> := ?MATCH_RSA_KEY, - <<"verify">> := <<"verify_none">> - } - }, jsx:decode(Result5)), + ?assertMatch( + #{ + <<"type">> := <<"mongodb">>, + <<"ssl">> := #{ + <<"enable">> := <<"true">>, + <<"cacertfile">> := ?MATCH_CERT, + <<"certfile">> := ?MATCH_CERT, + <<"keyfile">> := ?MATCH_RSA_KEY, + <<"verify">> := <<"verify_none">> + } + }, + jsx:decode(Result5) + ), - - #{ssl := #{cacertfile := SavedCacertfile, - certfile := SavedCertfile, - keyfile := SavedKeyfile - }} = emqx_authz:lookup(mongodb), + #{ + ssl := #{ + cacertfile := SavedCacertfile, + certfile := SavedCertfile, + keyfile := SavedKeyfile + } + } = emqx_authz:lookup(mongodb), ?assert(filelib:is_file(SavedCacertfile)), ?assert(filelib:is_file(SavedCertfile)), ?assert(filelib:is_file(SavedKeyfile)), {ok, 204, _} = request( - put, - uri(["authorization", "sources", "mysql"]), - ?SOURCE3#{<<"server">> := <<"192.168.1.100:3306">>}), + put, + uri(["authorization", "sources", "mysql"]), + ?SOURCE3#{<<"server">> := <<"192.168.1.100:3306">>} + ), {ok, 204, _} = request( - put, - uri(["authorization", "sources", "postgresql"]), - ?SOURCE4#{<<"server">> := <<"fake">>}), + put, + uri(["authorization", "sources", "postgresql"]), + ?SOURCE4#{<<"server">> := <<"fake">>} + ), {ok, 204, _} = request( - put, - uri(["authorization", "sources", "redis"]), - ?SOURCE5#{<<"servers">> := [<<"192.168.1.100:6379">>, - <<"192.168.1.100:6380">>]}), + put, + uri(["authorization", "sources", "redis"]), + ?SOURCE5#{ + <<"servers">> := [ + <<"192.168.1.100:6379">>, + <<"192.168.1.100:6380">> + ] + } + ), lists:foreach( - fun(#{<<"type">> := Type}) -> - {ok, 204, _} = request( - delete, - uri(["authorization", "sources", binary_to_list(Type)]), - []) - end, Sources), + fun(#{<<"type">> := Type}) -> + {ok, 204, _} = request( + delete, + uri(["authorization", "sources", binary_to_list(Type)]), + [] + ) + end, + Sources + ), {ok, 200, Result6} = request(get, uri(["authorization", "sources"]), []), ?assertEqual([], get_sources(Result6)), ?assertEqual([], emqx:get_config([authorization, sources])), @@ -302,48 +373,80 @@ t_api(_) -> t_move_source(_) -> {ok, _} = emqx_authz:update(replace, [?SOURCE1, ?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5]), - ?assertMatch([ #{type := http} - , #{type := mongodb} - , #{type := mysql} - , #{type := postgresql} - , #{type := redis} - ], emqx_authz:lookup()), + ?assertMatch( + [ + #{type := http}, + #{type := mongodb}, + #{type := mysql}, + #{type := postgresql}, + #{type := redis} + ], + emqx_authz:lookup() + ), - {ok, 204, _} = request(post, uri(["authorization", "sources", "postgresql", "move"]), - #{<<"position">> => <<"front">>}), - ?assertMatch([ #{type := postgresql} - , #{type := http} - , #{type := mongodb} - , #{type := mysql} - , #{type := redis} - ], emqx_authz:lookup()), + {ok, 204, _} = request( + post, + uri(["authorization", "sources", "postgresql", "move"]), + #{<<"position">> => <<"front">>} + ), + ?assertMatch( + [ + #{type := postgresql}, + #{type := http}, + #{type := mongodb}, + #{type := mysql}, + #{type := redis} + ], + emqx_authz:lookup() + ), - {ok, 204, _} = request(post, uri(["authorization", "sources", "http", "move"]), - #{<<"position">> => <<"rear">>}), - ?assertMatch([ #{type := postgresql} - , #{type := mongodb} - , #{type := mysql} - , #{type := redis} - , #{type := http} - ], emqx_authz:lookup()), + {ok, 204, _} = request( + post, + uri(["authorization", "sources", "http", "move"]), + #{<<"position">> => <<"rear">>} + ), + ?assertMatch( + [ + #{type := postgresql}, + #{type := mongodb}, + #{type := mysql}, + #{type := redis}, + #{type := http} + ], + emqx_authz:lookup() + ), - {ok, 204, _} = request(post, uri(["authorization", "sources", "mysql", "move"]), - #{<<"position">> => <<"before:postgresql">>}), - ?assertMatch([ #{type := mysql} - , #{type := postgresql} - , #{type := mongodb} - , #{type := redis} - , #{type := http} - ], emqx_authz:lookup()), + {ok, 204, _} = request( + post, + uri(["authorization", "sources", "mysql", "move"]), + #{<<"position">> => <<"before:postgresql">>} + ), + ?assertMatch( + [ + #{type := mysql}, + #{type := postgresql}, + #{type := mongodb}, + #{type := redis}, + #{type := http} + ], + emqx_authz:lookup() + ), - {ok, 204, _} = request(post, uri(["authorization", "sources", "mongodb", "move"]), - #{<<"position">> => <<"after:http">>}), - ?assertMatch([ #{type := mysql} - , #{type := postgresql} - , #{type := redis} - , #{type := http} - , #{type := mongodb} - ], emqx_authz:lookup()), + {ok, 204, _} = request( + post, + uri(["authorization", "sources", "mongodb", "move"]), + #{<<"position">> => <<"after:http">>} + ), + ?assertMatch( + [ + #{type := mysql}, + #{type := postgresql}, + #{type := redis}, + #{type := http}, + #{type := mongodb} + ], + emqx_authz:lookup() + ), ok. diff --git a/apps/emqx_authz/test/emqx_authz_file_SUITE.erl b/apps/emqx_authz/test/emqx_authz_file_SUITE.erl index fb9849a26..9e7767d1e 100644 --- a/apps/emqx_authz/test/emqx_authz_file_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_file_SUITE.erl @@ -22,12 +22,15 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). --define(RAW_SOURCE, #{<<"type">> => <<"file">>, - <<"enable">> => true, - <<"rules">> => -<<"{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}." - "\n{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">> - }). +-define(RAW_SOURCE, #{ + <<"type">> => <<"file">>, + <<"enable">> => true, + <<"rules">> => + << + "{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}." + "\n{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}." + >> +}). all() -> emqx_common_test_helpers:all(?MODULE). @@ -37,13 +40,17 @@ groups() -> init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), %% meck after authz started - meck:expect(emqx_authz, acl_conf_file, - fun() -> - emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") - end), + meck:expect( + emqx_authz, + acl_conf_file, + fun() -> + emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") + end + ), Config. end_per_suite(_Config) -> @@ -57,7 +64,6 @@ init_per_testcase(_TestCase, Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. @@ -66,43 +72,55 @@ set_special_configs(_) -> %%------------------------------------------------------------------------------ t_ok(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, - ok = setup_config(?RAW_SOURCE#{<<"rules">> => <<"{allow, {user, \"username\"}, publish, [\"t\"]}.">>}), + ok = setup_config(?RAW_SOURCE#{ + <<"rules">> => <<"{allow, {user, \"username\"}, publish, [\"t\"]}.">> + }), io:format("~p", [emqx_authz:acl_conf_file()]), ?assertEqual( - allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), + allow, + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ), ?assertEqual( - deny, - emqx_access_control:authorize(ClientInfo, subscribe, <<"t">>)). + deny, + emqx_access_control:authorize(ClientInfo, subscribe, <<"t">>) + ). t_invalid_file(_Config) -> ?assertMatch( - {error, bad_acl_file_content}, - emqx_authz:update(?CMD_REPLACE, [?RAW_SOURCE#{<<"rules">> => <<"{{invalid term">>}])). + {error, bad_acl_file_content}, + emqx_authz:update(?CMD_REPLACE, [?RAW_SOURCE#{<<"rules">> => <<"{{invalid term">>}]) + ). t_update(_Config) -> - ok = setup_config(?RAW_SOURCE#{<<"rules">> => <<"{allow, {user, \"username\"}, publish, [\"t\"]}.">>}), + ok = setup_config(?RAW_SOURCE#{ + <<"rules">> => <<"{allow, {user, \"username\"}, publish, [\"t\"]}.">> + }), ?assertMatch( - {error, _}, - emqx_authz:update( - {?CMD_REPLACE, file}, - ?RAW_SOURCE#{<<"rules">> => <<"{{invalid term">>})), + {error, _}, + emqx_authz:update( + {?CMD_REPLACE, file}, + ?RAW_SOURCE#{<<"rules">> => <<"{{invalid term">>} + ) + ), ?assertMatch( - {ok, _}, - emqx_authz:update( - {?CMD_REPLACE, file}, ?RAW_SOURCE)). + {ok, _}, + emqx_authz:update( + {?CMD_REPLACE, file}, ?RAW_SOURCE + ) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -110,8 +128,9 @@ t_update(_Config) -> setup_config(SpecialParams) -> emqx_authz_test_lib:setup_config( - ?RAW_SOURCE, - SpecialParams). + ?RAW_SOURCE, + SpecialParams + ). stop_apps(Apps) -> lists:foreach(fun application:stop/1, Apps). diff --git a/apps/emqx_authz/test/emqx_authz_http_SUITE.erl b/apps/emqx_authz/test/emqx_authz_http_SUITE.erl index e6aeb703c..03016f7e2 100644 --- a/apps/emqx_authz/test/emqx_authz_http_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_http_SUITE.erl @@ -32,9 +32,9 @@ all() -> init_per_suite(Config) -> ok = stop_apps([emqx_resource, emqx_connector, cowboy]), ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 - ), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), ok = start_apps([emqx_resource, emqx_connector, cowboy]), Config. @@ -45,7 +45,6 @@ end_per_suite(_Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. @@ -62,251 +61,301 @@ end_per_testcase(_Case, _Config) -> %%------------------------------------------------------------------------------ t_response_handling(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, %% OK, get, no body ok = setup_handler_and_config( - fun(Req0, State) -> - Req = cowboy_req:reply(200, Req0), - {ok, Req, State} - end, - #{}), + fun(Req0, State) -> + Req = cowboy_req:reply(200, Req0), + {ok, Req, State} + end, + #{} + ), allow = emqx_access_control:authorize(ClientInfo, publish, <<"t">>), %% OK, get, body & headers ok = setup_handler_and_config( - fun(Req0, State) -> - Req = cowboy_req:reply( - 200, - #{<<"content-type">> => <<"text/plain">>}, - "Response body", - Req0), - {ok, Req, State} - end, - #{}), + fun(Req0, State) -> + Req = cowboy_req:reply( + 200, + #{<<"content-type">> => <<"text/plain">>}, + "Response body", + Req0 + ), + {ok, Req, State} + end, + #{} + ), ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ), %% OK, get, 204 ok = setup_handler_and_config( - fun(Req0, State) -> - Req = cowboy_req:reply(204, Req0), - {ok, Req, State} - end, - #{}), + fun(Req0, State) -> + Req = cowboy_req:reply(204, Req0), + {ok, Req, State} + end, + #{} + ), ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ), %% Not OK, get, 400 ok = setup_handler_and_config( - fun(Req0, State) -> - Req = cowboy_req:reply(400, Req0), - {ok, Req, State} - end, - #{}), + fun(Req0, State) -> + Req = cowboy_req:reply(400, Req0), + {ok, Req, State} + end, + #{} + ), ?assertEqual( deny, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ), %% Not OK, get, 400 + body & headers ok = setup_handler_and_config( - fun(Req0, State) -> - Req = cowboy_req:reply( - 400, - #{<<"content-type">> => <<"text/plain">>}, - "Response body", - Req0), - {ok, Req, State} - end, - #{}), + fun(Req0, State) -> + Req = cowboy_req:reply( + 400, + #{<<"content-type">> => <<"text/plain">>}, + "Response body", + Req0 + ), + {ok, Req, State} + end, + #{} + ), ?assertEqual( deny, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)). + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ). t_query_params(_Config) -> ok = setup_handler_and_config( - fun(Req0, State) -> - #{username := <<"user name">>, - clientid := <<"client id">>, - peerhost := <<"127.0.0.1">>, - proto_name := <<"MQTT">>, - mountpoint := <<"MOUNTPOINT">>, - topic := <<"t">>, - action := <<"publish">> - } = cowboy_req:match_qs( - [username, - clientid, - peerhost, - proto_name, - mountpoint, - topic, - action], - Req0), - Req = cowboy_req:reply(200, Req0), - {ok, Req, State} - end, - #{<<"url">> => <<"http://127.0.0.1:33333/authz/users/?" - "username=${username}&" - "clientid=${clientid}&" - "peerhost=${peerhost}&" - "proto_name=${proto_name}&" - "mountpoint=${mountpoint}&" - "topic=${topic}&" - "action=${action}">> - }), + fun(Req0, State) -> + #{ + username := <<"user name">>, + clientid := <<"client id">>, + peerhost := <<"127.0.0.1">>, + proto_name := <<"MQTT">>, + mountpoint := <<"MOUNTPOINT">>, + topic := <<"t">>, + action := <<"publish">> + } = cowboy_req:match_qs( + [ + username, + clientid, + peerhost, + proto_name, + mountpoint, + topic, + action + ], + Req0 + ), + Req = cowboy_req:reply(200, Req0), + {ok, Req, State} + end, + #{ + <<"url">> => << + "http://127.0.0.1:33333/authz/users/?" + "username=${username}&" + "clientid=${clientid}&" + "peerhost=${peerhost}&" + "proto_name=${proto_name}&" + "mountpoint=${mountpoint}&" + "topic=${topic}&" + "action=${action}" + >> + } + ), - ClientInfo = #{clientid => <<"client id">>, - username => <<"user name">>, - peerhost => {127,0,0,1}, - protocol => <<"MQTT">>, - mountpoint => <<"MOUNTPOINT">>, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"client id">>, + username => <<"user name">>, + peerhost => {127, 0, 0, 1}, + protocol => <<"MQTT">>, + mountpoint => <<"MOUNTPOINT">>, + zone => default, + listener => {tcp, default} + }, ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)). + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ). t_json_body(_Config) -> ok = setup_handler_and_config( - fun(Req0, State) -> - ?assertEqual( - <<"/authz/users/">>, - cowboy_req:path(Req0)), + fun(Req0, State) -> + ?assertEqual( + <<"/authz/users/">>, + cowboy_req:path(Req0) + ), - {ok, RawBody, Req1} = cowboy_req:read_body(Req0), + {ok, RawBody, Req1} = cowboy_req:read_body(Req0), - ?assertMatch( - #{<<"username">> := <<"user name">>, - <<"CLIENT">> := <<"client id">>, - <<"peerhost">> := <<"127.0.0.1">>, - <<"proto_name">> := <<"MQTT">>, - <<"mountpoint">> := <<"MOUNTPOINT">>, - <<"topic">> := <<"t">>, - <<"action">> := <<"publish">>}, - jiffy:decode(RawBody, [return_maps])), + ?assertMatch( + #{ + <<"username">> := <<"user name">>, + <<"CLIENT">> := <<"client id">>, + <<"peerhost">> := <<"127.0.0.1">>, + <<"proto_name">> := <<"MQTT">>, + <<"mountpoint">> := <<"MOUNTPOINT">>, + <<"topic">> := <<"t">>, + <<"action">> := <<"publish">> + }, + jiffy:decode(RawBody, [return_maps]) + ), - Req = cowboy_req:reply(200, Req1), - {ok, Req, State} - end, - #{<<"method">> => <<"post">>, - <<"body">> => #{<<"username">> => <<"${username}">>, - <<"CLIENT">> => <<"${clientid}">>, - <<"peerhost">> => <<"${peerhost}">>, - <<"proto_name">> => <<"${proto_name}">>, - <<"mountpoint">> => <<"${mountpoint}">>, - <<"topic">> => <<"${topic}">>, - <<"action">> => <<"${action}">>} - }), + Req = cowboy_req:reply(200, Req1), + {ok, Req, State} + end, + #{ + <<"method">> => <<"post">>, + <<"body">> => #{ + <<"username">> => <<"${username}">>, + <<"CLIENT">> => <<"${clientid}">>, + <<"peerhost">> => <<"${peerhost}">>, + <<"proto_name">> => <<"${proto_name}">>, + <<"mountpoint">> => <<"${mountpoint}">>, + <<"topic">> => <<"${topic}">>, + <<"action">> => <<"${action}">> + } + } + ), - ClientInfo = #{clientid => <<"client id">>, - username => <<"user name">>, - peerhost => {127,0,0,1}, - protocol => <<"MQTT">>, - mountpoint => <<"MOUNTPOINT">>, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"client id">>, + username => <<"user name">>, + peerhost => {127, 0, 0, 1}, + protocol => <<"MQTT">>, + mountpoint => <<"MOUNTPOINT">>, + zone => default, + listener => {tcp, default} + }, ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)). - + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ). t_form_body(_Config) -> ok = setup_handler_and_config( - fun(Req0, State) -> - ?assertEqual( - <<"/authz/users/">>, - cowboy_req:path(Req0)), + fun(Req0, State) -> + ?assertEqual( + <<"/authz/users/">>, + cowboy_req:path(Req0) + ), - {ok, [{PostVars, true}], Req1} = cowboy_req:read_urlencoded_body(Req0), + {ok, [{PostVars, true}], Req1} = cowboy_req:read_urlencoded_body(Req0), - ?assertMatch( - #{<<"username">> := <<"user name">>, - <<"clientid">> := <<"client id">>, - <<"peerhost">> := <<"127.0.0.1">>, - <<"proto_name">> := <<"MQTT">>, - <<"mountpoint">> := <<"MOUNTPOINT">>, - <<"topic">> := <<"t">>, - <<"action">> := <<"publish">>}, - jiffy:decode(PostVars, [return_maps])), + ?assertMatch( + #{ + <<"username">> := <<"user name">>, + <<"clientid">> := <<"client id">>, + <<"peerhost">> := <<"127.0.0.1">>, + <<"proto_name">> := <<"MQTT">>, + <<"mountpoint">> := <<"MOUNTPOINT">>, + <<"topic">> := <<"t">>, + <<"action">> := <<"publish">> + }, + jiffy:decode(PostVars, [return_maps]) + ), - Req = cowboy_req:reply(200, Req1), - {ok, Req, State} - end, - #{<<"method">> => <<"post">>, - <<"body">> => #{<<"username">> => <<"${username}">>, - <<"clientid">> => <<"${clientid}">>, - <<"peerhost">> => <<"${peerhost}">>, - <<"proto_name">> => <<"${proto_name}">>, - <<"mountpoint">> => <<"${mountpoint}">>, - <<"topic">> => <<"${topic}">>, - <<"action">> => <<"${action}">>}, - <<"headers">> => #{<<"content-type">> => <<"application/x-www-form-urlencoded">>} - }), + Req = cowboy_req:reply(200, Req1), + {ok, Req, State} + end, + #{ + <<"method">> => <<"post">>, + <<"body">> => #{ + <<"username">> => <<"${username}">>, + <<"clientid">> => <<"${clientid}">>, + <<"peerhost">> => <<"${peerhost}">>, + <<"proto_name">> => <<"${proto_name}">>, + <<"mountpoint">> => <<"${mountpoint}">>, + <<"topic">> => <<"${topic}">>, + <<"action">> => <<"${action}">> + }, + <<"headers">> => #{<<"content-type">> => <<"application/x-www-form-urlencoded">>} + } + ), - ClientInfo = #{clientid => <<"client id">>, - username => <<"user name">>, - peerhost => {127,0,0,1}, - protocol => <<"MQTT">>, - mountpoint => <<"MOUNTPOINT">>, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"client id">>, + username => <<"user name">>, + peerhost => {127, 0, 0, 1}, + protocol => <<"MQTT">>, + mountpoint => <<"MOUNTPOINT">>, + zone => default, + listener => {tcp, default} + }, ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)). - + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ). t_create_replace(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, %% Create with valid URL ok = setup_handler_and_config( - fun(Req0, State) -> - Req = cowboy_req:reply(200, Req0), - {ok, Req, State} - end, - #{<<"url">> => - <<"http://127.0.0.1:33333/authz/users/?topic=${topic}&action=${action}">>}), + fun(Req0, State) -> + Req = cowboy_req:reply(200, Req0), + {ok, Req, State} + end, + #{ + <<"url">> => + <<"http://127.0.0.1:33333/authz/users/?topic=${topic}&action=${action}">> + } + ), ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ), %% Changing to valid config OkConfig = maps:merge( - raw_http_authz_config(), - #{<<"url">> => - <<"http://127.0.0.1:33333/authz/users/?topic=${topic}&action=${action}">>}), + raw_http_authz_config(), + #{ + <<"url">> => + <<"http://127.0.0.1:33333/authz/users/?topic=${topic}&action=${action}">> + } + ), ?assertMatch( {ok, _}, - emqx_authz:update({?CMD_REPLACE, http}, OkConfig)), + emqx_authz:update({?CMD_REPLACE, http}, OkConfig) + ), ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)). + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -324,8 +373,9 @@ raw_http_authz_config() -> setup_handler_and_config(Handler, Config) -> ok = emqx_authz_http_test_server:set_handler(Handler), ok = emqx_authz_test_lib:setup_config( - raw_http_authz_config(), - Config). + raw_http_authz_config(), + Config + ). start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authz/test/emqx_authz_http_test_server.erl b/apps/emqx_authz/test/emqx_authz_http_test_server.erl index d1c3e2959..ea67f363b 100644 --- a/apps/emqx_authz/test/emqx_authz_http_test_server.erl +++ b/apps/emqx_authz/test/emqx_authz_http_test_server.erl @@ -26,10 +26,11 @@ -export([init/1]). % API --export([start_link/2, - stop/0, - set_handler/1 - ]). +-export([ + start_link/2, + stop/0, + set_handler/1 +]). %%------------------------------------------------------------------------------ %% API @@ -51,11 +52,14 @@ set_handler(F) when is_function(F, 2) -> init([Port, Path]) -> Dispatch = cowboy_router:compile( - [ - {'_', [{Path, ?MODULE, []}]} - ]), - TransOpts = #{socket_opts => [{port, Port}], - connection_type => supervisor}, + [ + {'_', [{Path, ?MODULE, []}]} + ] + ), + TransOpts = #{ + socket_opts => [{port, Port}], + connection_type => supervisor + }, ProtoOpts = #{env => #{dispatch => Dispatch}}, Tab = ets:new(?MODULE, [set, named_table, public]), @@ -78,9 +82,9 @@ init(Req, State) -> default_handler(Req0, State) -> Req = cowboy_req:reply( - 400, - #{<<"content-type">> => <<"text/plain">>}, - <<"">>, - Req0), + 400, + #{<<"content-type">> => <<"text/plain">>}, + <<"">>, + Req0 + ), {ok, Req, State}. - diff --git a/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl b/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl index 0f926f1bd..8c0f282e8 100644 --- a/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl @@ -29,8 +29,9 @@ groups() -> init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), Config. end_per_suite(_Config) -> @@ -47,7 +48,6 @@ end_per_testcase(_TestCase, _Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. @@ -64,16 +64,17 @@ t_all_topic_rules(_Config) -> ok = test_topic_rules(all). test_topic_rules(Key) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, SetupSamples = fun(CInfo, Samples) -> - setup_client_samples(CInfo, Samples, Key) - end, + setup_client_samples(CInfo, Samples, Key) + end, ok = emqx_authz_test_lib:test_no_topic_rules(ClientInfo, SetupSamples), @@ -82,41 +83,50 @@ test_topic_rules(Key) -> ok = emqx_authz_test_lib:test_deny_topic_rules(ClientInfo, SetupSamples). t_normalize_rules(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = emqx_authz_mnesia:store_rules( - {username, <<"username">>}, - [{allow, publish, "t"}]), + {username, <<"username">>}, + [{allow, publish, "t"}] + ), ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ), ?assertException( - error, - {invalid_rule, _}, - emqx_authz_mnesia:store_rules( - {username, <<"username">>}, - [[allow, publish, <<"t">>]])), + error, + {invalid_rule, _}, + emqx_authz_mnesia:store_rules( + {username, <<"username">>}, + [[allow, publish, <<"t">>]] + ) + ), ?assertException( - error, - {invalid_rule_action, _}, - emqx_authz_mnesia:store_rules( - {username, <<"username">>}, - [{allow, pub, <<"t">>}])), + error, + {invalid_rule_action, _}, + emqx_authz_mnesia:store_rules( + {username, <<"username">>}, + [{allow, pub, <<"t">>}] + ) + ), ?assertException( - error, - {invalid_rule_permission, _}, - emqx_authz_mnesia:store_rules( - {username, <<"username">>}, - [{accept, publish, <<"t">>}])). + error, + {invalid_rule_permission, _}, + emqx_authz_mnesia:store_rules( + {username, <<"username">>}, + [{accept, publish, <<"t">>}] + ) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -131,20 +141,23 @@ raw_mnesia_authz_config() -> setup_client_samples(ClientInfo, Samples, Key) -> ok = emqx_authz_mnesia:purge_rules(), Rules = lists:flatmap( - fun(#{topics := Topics, permission := Permission, action := Action}) -> - lists:map( - fun(Topic) -> - {binary_to_atom(Permission), binary_to_atom(Action), Topic} - end, - Topics) - end, - Samples), + fun(#{topics := Topics, permission := Permission, action := Action}) -> + lists:map( + fun(Topic) -> + {binary_to_atom(Permission), binary_to_atom(Action), Topic} + end, + Topics + ) + end, + Samples + ), #{username := Username, clientid := ClientId} = ClientInfo, - Who = case Key of - username -> {username, Username}; - clientid -> {clientid, ClientId}; - all -> all - end, + Who = + case Key of + username -> {username, Username}; + clientid -> {clientid, ClientId}; + all -> all + end, ok = emqx_authz_mnesia:store_rules(Who, Rules). setup_config() -> diff --git a/apps/emqx_authz/test/emqx_authz_mongodb_SUITE.erl b/apps/emqx_authz/test/emqx_authz_mongodb_SUITE.erl index 26eaf5a45..e0220488f 100644 --- a/apps/emqx_authz/test/emqx_authz_mongodb_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_mongodb_SUITE.erl @@ -38,9 +38,9 @@ init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?MONGO_HOST, ?MONGO_DEFAULT_PORT) of true -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 - ), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), ok = start_apps([emqx_resource, emqx_connector]), Config; false -> @@ -54,7 +54,6 @@ end_per_suite(_Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. @@ -72,12 +71,13 @@ end_per_testcase(_TestCase, _Config) -> %%------------------------------------------------------------------------------ t_topic_rules(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = emqx_authz_test_lib:test_no_topic_rules(ClientInfo, fun setup_client_samples/2), @@ -87,104 +87,137 @@ t_topic_rules(_Config) -> t_complex_selector(_) -> %% atom and string values also supported - ClientInfo = #{clientid => clientid, - username => "username", - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => clientid, + username => "username", + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, - Samples = [#{<<"x">> => #{<<"u">> => <<"username">>, - <<"c">> => [#{<<"c">> => <<"clientid">>}], - <<"y">> => 1}, - <<"permission">> => <<"allow">>, - <<"action">> => <<"publish">>, - <<"topics">> => [<<"t">>] - }], + Samples = [ + #{ + <<"x">> => #{ + <<"u">> => <<"username">>, + <<"c">> => [#{<<"c">> => <<"clientid">>}], + <<"y">> => 1 + }, + <<"permission">> => <<"allow">>, + <<"action">> => <<"publish">>, + <<"topics">> => [<<"t">>] + } + ], ok = setup_samples(Samples), ok = setup_config( - #{<<"selector">> => #{<<"x">> => #{<<"u">> => <<"${username}">>, - <<"c">> => [#{<<"c">> => <<"${clientid}">>}], - <<"y">> => 1} - } - }), + #{ + <<"selector">> => #{ + <<"x">> => #{ + <<"u">> => <<"${username}">>, + <<"c">> => [#{<<"c">> => <<"${clientid}">>}], + <<"y">> => 1 + } + } + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, publish, <<"t">>}]). + ClientInfo, + [{allow, publish, <<"t">>}] + ). t_mongo_error(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = setup_samples([]), ok = setup_config( - #{<<"selector">> => #{<<"$badoperator">> => <<"$badoperator">>}}), + #{<<"selector">> => #{<<"$badoperator">> => <<"$badoperator">>}} + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{deny, publish, <<"t">>}]). + ClientInfo, + [{deny, publish, <<"t">>}] + ). t_lookups(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - cn => <<"cn">>, - dn => <<"dn">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + cn => <<"cn">>, + dn => <<"dn">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, - ByClientid = #{<<"clientid">> => <<"clientid">>, - <<"topics">> => [<<"a">>], - <<"action">> => <<"all">>, - <<"permission">> => <<"allow">>}, + ByClientid = #{ + <<"clientid">> => <<"clientid">>, + <<"topics">> => [<<"a">>], + <<"action">> => <<"all">>, + <<"permission">> => <<"allow">> + }, ok = setup_samples([ByClientid]), ok = setup_config( - #{<<"selector">> => #{<<"clientid">> => <<"${clientid}">>}}), + #{<<"selector">> => #{<<"clientid">> => <<"${clientid}">>}} + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), - ByPeerhost = #{<<"peerhost">> => <<"127.0.0.1">>, - <<"topics">> => [<<"a">>], - <<"action">> => <<"all">>, - <<"permission">> => <<"allow">>}, + ByPeerhost = #{ + <<"peerhost">> => <<"127.0.0.1">>, + <<"topics">> => [<<"a">>], + <<"action">> => <<"all">>, + <<"permission">> => <<"allow">> + }, ok = setup_samples([ByPeerhost]), ok = setup_config( - #{<<"selector">> => #{<<"peerhost">> => <<"${peerhost}">>}}), + #{<<"selector">> => #{<<"peerhost">> => <<"${peerhost}">>}} + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). t_bad_selector(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - cn => <<"cn">>, - dn => <<"dn">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + cn => <<"cn">>, + dn => <<"dn">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = setup_config( - #{<<"selector">> => #{<<"$in">> => #{<<"a">> => 1}}}), + #{<<"selector">> => #{<<"$in">> => #{<<"a">> => 1}}} + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{deny, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {deny, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). %%------------------------------------------------------------------------------ %% Helpers @@ -201,17 +234,22 @@ setup_samples(AclRecords) -> setup_client_samples(ClientInfo, Samples) -> #{username := Username} = ClientInfo, Records = lists:map( - fun(Sample) -> - #{topics := Topics, - permission := Permission, - action := Action} = Sample, + fun(Sample) -> + #{ + topics := Topics, + permission := Permission, + action := Action + } = Sample, - #{<<"topics">> => Topics, - <<"permission">> => Permission, - <<"action">> => Action, - <<"username">> => Username} - end, - Samples), + #{ + <<"topics">> => Topics, + <<"permission">> => Permission, + <<"action">> => Action, + <<"username">> => Username + } + end, + Samples + ), setup_samples(Records), setup_config(#{<<"selector">> => #{<<"username">> => <<"${username}">>}}). @@ -221,8 +259,9 @@ reset_samples() -> setup_config(SpecialParams) -> emqx_authz_test_lib:setup_config( - raw_mongo_authz_config(), - SpecialParams). + raw_mongo_authz_config(), + SpecialParams + ). raw_mongo_authz_config() -> #{ @@ -238,14 +277,14 @@ raw_mongo_authz_config() -> }. mongo_server() -> - iolist_to_binary(io_lib:format("~s",[?MONGO_HOST])). + iolist_to_binary(io_lib:format("~s", [?MONGO_HOST])). mongo_config() -> [ - {database, <<"mqtt">>}, - {host, ?MONGO_HOST}, - {port, ?MONGO_DEFAULT_PORT}, - {register, ?MONGO_CLIENT} + {database, <<"mqtt">>}, + {host, ?MONGO_HOST}, + {port, ?MONGO_DEFAULT_PORT}, + {register, ?MONGO_CLIENT} ]. start_apps(Apps) -> diff --git a/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl b/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl index 193ed80fa..ce8d03984 100644 --- a/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl @@ -37,16 +37,17 @@ init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?MYSQL_HOST, ?MYSQL_DEFAULT_PORT) of true -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 - ), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), ok = start_apps([emqx_resource, emqx_connector]), {ok, _} = emqx_resource:create_local( - ?MYSQL_RESOURCE, - ?RESOURCE_GROUP, - emqx_connector_mysql, - mysql_config(), - #{}), + ?MYSQL_RESOURCE, + ?RESOURCE_GROUP, + emqx_connector_mysql, + mysql_config(), + #{} + ), Config; false -> {skip, no_mysql} @@ -64,7 +65,6 @@ init_per_testcase(_TestCase, Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. @@ -73,12 +73,13 @@ set_special_configs(_) -> %%------------------------------------------------------------------------------ t_topic_rules(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = emqx_authz_test_lib:test_no_topic_rules(ClientInfo, fun setup_client_samples/2), @@ -86,127 +87,190 @@ t_topic_rules(_Config) -> ok = emqx_authz_test_lib:test_deny_topic_rules(ClientInfo, fun setup_client_samples/2). - t_lookups(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - cn => <<"cn">>, - dn => <<"dn">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + cn => <<"cn">>, + dn => <<"dn">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, %% by clientid ok = init_table(), - ok = q(<<"INSERT INTO acl(clientid, topic, permission, action)" - "VALUES(?, ?, ?, ?)">>, - [<<"clientid">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = q( + << + "INSERT INTO acl(clientid, topic, permission, action)" + "VALUES(?, ?, ?, ?)" + >>, + [<<"clientid">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE clientid = ${clientid}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE clientid = ${clientid}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), %% by peerhost ok = init_table(), - ok = q(<<"INSERT INTO acl(peerhost, topic, permission, action)" - "VALUES(?, ?, ?, ?)">>, - [<<"127.0.0.1">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = q( + << + "INSERT INTO acl(peerhost, topic, permission, action)" + "VALUES(?, ?, ?, ?)" + >>, + [<<"127.0.0.1">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE peerhost = ${peerhost}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE peerhost = ${peerhost}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), %% by cn ok = init_table(), - ok = q(<<"INSERT INTO acl(cn, topic, permission, action)" - "VALUES(?, ?, ?, ?)">>, - [<<"cn">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = q( + << + "INSERT INTO acl(cn, topic, permission, action)" + "VALUES(?, ?, ?, ?)" + >>, + [<<"cn">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE cn = ${cert_common_name}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE cn = ${cert_common_name}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), %% by dn ok = init_table(), - ok = q(<<"INSERT INTO acl(dn, topic, permission, action)" - "VALUES(?, ?, ?, ?)">>, - [<<"dn">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = q( + << + "INSERT INTO acl(dn, topic, permission, action)" + "VALUES(?, ?, ?, ?)" + >>, + [<<"dn">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE dn = ${cert_subject}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE dn = ${cert_subject}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). t_mysql_error(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = setup_config( - #{<<"query">> => <<"SOME INVALID STATEMENT">>}), + #{<<"query">> => <<"SOME INVALID STATEMENT">>} + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{deny, subscribe, <<"a">>}]). - + ClientInfo, + [{deny, subscribe, <<"a">>}] + ). t_create_invalid(_Config) -> BadConfig = maps:merge( - raw_mysql_authz_config(), - #{<<"server">> => <<"255.255.255.255:33333">>}), + raw_mysql_authz_config(), + #{<<"server">> => <<"255.255.255.255:33333">>} + ), {ok, _} = emqx_authz:update(?CMD_REPLACE, [BadConfig]), [_] = emqx_authz:lookup(). t_nonbinary_values(_Config) -> - ClientInfo = #{clientid => clientid, - username => "username", - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, - + ClientInfo = #{ + clientid => clientid, + username => "username", + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = init_table(), - ok = q(<<"INSERT INTO acl(clientid, username, topic, permission, action)" - "VALUES(?, ?, ?, ?, ?)">>, - [<<"clientid">>, <<"username">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = q( + << + "INSERT INTO acl(clientid, username, topic, permission, action)" + "VALUES(?, ?, ?, ?, ?)" + >>, + [<<"clientid">>, <<"username">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE clientid = ${clientid} AND username = ${username}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE clientid = ${clientid} AND username = ${username}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). %%------------------------------------------------------------------------------ %% Helpers @@ -221,33 +285,39 @@ raw_mysql_authz_config() -> <<"username">> => <<"root">>, <<"password">> => <<"public">>, - <<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE username = ${username}">>, + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE username = ${username}" + >>, <<"server">> => mysql_server() }. q(Sql) -> emqx_resource:query( - ?MYSQL_RESOURCE, - {sql, Sql}). + ?MYSQL_RESOURCE, + {sql, Sql} + ). q(Sql, Params) -> emqx_resource:query( - ?MYSQL_RESOURCE, - {sql, Sql, Params}). + ?MYSQL_RESOURCE, + {sql, Sql, Params} + ). init_table() -> ok = drop_table(), - ok = q("CREATE TABLE acl( - username VARCHAR(255), - clientid VARCHAR(255), - peerhost VARCHAR(255), - cn VARCHAR(255), - dn VARCHAR(255), - topic VARCHAR(255), - permission VARCHAR(255), - action VARCHAR(255))"). + ok = q( + "CREATE TABLE acl(\n" + " username VARCHAR(255),\n" + " clientid VARCHAR(255),\n" + " peerhost VARCHAR(255),\n" + " cn VARCHAR(255),\n" + " dn VARCHAR(255),\n" + " topic VARCHAR(255),\n" + " permission VARCHAR(255),\n" + " action VARCHAR(255))" + ). drop_table() -> ok = q("DROP TABLE IF EXISTS acl"). @@ -256,37 +326,50 @@ setup_client_samples(ClientInfo, Samples) -> #{username := Username} = ClientInfo, ok = init_table(), ok = lists:foreach( - fun(#{topics := Topics, permission := Permission, action := Action}) -> - lists:foreach( - fun(Topic) -> - q(<<"INSERT INTO acl(username, topic, permission, action)" - "VALUES(?, ?, ?, ?)">>, - [Username, Topic, Permission, Action]) - end, - Topics) - end, - Samples), + fun(#{topics := Topics, permission := Permission, action := Action}) -> + lists:foreach( + fun(Topic) -> + q( + << + "INSERT INTO acl(username, topic, permission, action)" + "VALUES(?, ?, ?, ?)" + >>, + [Username, Topic, Permission, Action] + ) + end, + Topics + ) + end, + Samples + ), setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE username = ${username}">>}). + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE username = ${username}" + >> + } + ). setup_config(SpecialParams) -> emqx_authz_test_lib:setup_config( - raw_mysql_authz_config(), - SpecialParams). + raw_mysql_authz_config(), + SpecialParams + ). mysql_server() -> - iolist_to_binary(io_lib:format("~s",[?MYSQL_HOST])). + iolist_to_binary(io_lib:format("~s", [?MYSQL_HOST])). mysql_config() -> - #{auto_reconnect => true, - database => <<"mqtt">>, - username => <<"root">>, - password => <<"public">>, - pool_size => 8, - server => {?MYSQL_HOST, ?MYSQL_DEFAULT_PORT}, - ssl => #{enable => false} - }. + #{ + auto_reconnect => true, + database => <<"mqtt">>, + username => <<"root">>, + password => <<"public">>, + pool_size => 8, + server => {?MYSQL_HOST, ?MYSQL_DEFAULT_PORT}, + ssl => #{enable => false} + }. start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl b/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl index 1785656aa..d4aaf7077 100644 --- a/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl @@ -37,16 +37,17 @@ init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?PGSQL_HOST, ?PGSQL_DEFAULT_PORT) of true -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 - ), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), ok = start_apps([emqx_resource, emqx_connector]), {ok, _} = emqx_resource:create_local( - ?PGSQL_RESOURCE, - ?RESOURCE_GROUP, - emqx_connector_pgsql, - pgsql_config(), - #{}), + ?PGSQL_RESOURCE, + ?RESOURCE_GROUP, + emqx_connector_pgsql, + pgsql_config(), + #{} + ), Config; false -> {skip, no_pgsql} @@ -64,7 +65,6 @@ init_per_testcase(_TestCase, Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. @@ -73,12 +73,13 @@ set_special_configs(_) -> %%------------------------------------------------------------------------------ t_topic_rules(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = emqx_authz_test_lib:test_no_topic_rules(ClientInfo, fun setup_client_samples/2), @@ -86,128 +87,195 @@ t_topic_rules(_Config) -> ok = emqx_authz_test_lib:test_deny_topic_rules(ClientInfo, fun setup_client_samples/2). - t_lookups(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - cn => <<"cn">>, - dn => <<"dn">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + cn => <<"cn">>, + dn => <<"dn">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, %% by clientid ok = init_table(), - ok = insert(<<"INSERT INTO acl(clientid, topic, permission, action)" - "VALUES($1, $2, $3, $4)">>, - [<<"clientid">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = insert( + << + "INSERT INTO acl(clientid, topic, permission, action)" + "VALUES($1, $2, $3, $4)" + >>, + [<<"clientid">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE clientid = ${clientid}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE clientid = ${clientid}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), %% by peerhost ok = init_table(), - ok = insert(<<"INSERT INTO acl(peerhost, topic, permission, action)" - "VALUES($1, $2, $3, $4)">>, - [<<"127.0.0.1">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = insert( + << + "INSERT INTO acl(peerhost, topic, permission, action)" + "VALUES($1, $2, $3, $4)" + >>, + [<<"127.0.0.1">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE peerhost = ${peerhost}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE peerhost = ${peerhost}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), %% by cn ok = init_table(), - ok = insert(<<"INSERT INTO acl(cn, topic, permission, action)" - "VALUES($1, $2, $3, $4)">>, - [<<"cn">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = insert( + << + "INSERT INTO acl(cn, topic, permission, action)" + "VALUES($1, $2, $3, $4)" + >>, + [<<"cn">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE cn = ${cert_common_name}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE cn = ${cert_common_name}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), %% by dn ok = init_table(), - ok = insert(<<"INSERT INTO acl(dn, topic, permission, action)" - "VALUES($1, $2, $3, $4)">>, - [<<"dn">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = insert( + << + "INSERT INTO acl(dn, topic, permission, action)" + "VALUES($1, $2, $3, $4)" + >>, + [<<"dn">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE dn = ${cert_subject}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE dn = ${cert_subject}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). t_pgsql_error(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE clientid = ${username}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE clientid = ${username}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{deny, subscribe, <<"a">>}]). - + ClientInfo, + [{deny, subscribe, <<"a">>}] + ). t_create_invalid(_Config) -> BadConfig = maps:merge( - raw_pgsql_authz_config(), - #{<<"server">> => <<"255.255.255.255:33333">>}), + raw_pgsql_authz_config(), + #{<<"server">> => <<"255.255.255.255:33333">>} + ), {ok, _} = emqx_authz:update(?CMD_REPLACE, [BadConfig]), [_] = emqx_authz:lookup(). t_nonbinary_values(_Config) -> - ClientInfo = #{clientid => clientid, - username => "username", - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, - + ClientInfo = #{ + clientid => clientid, + username => "username", + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = init_table(), - ok = insert(<<"INSERT INTO acl(clientid, username, topic, permission, action)" - "VALUES($1, $2, $3, $4, $5)">>, - [<<"clientid">>, <<"username">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = insert( + << + "INSERT INTO acl(clientid, username, topic, permission, action)" + "VALUES($1, $2, $3, $4, $5)" + >>, + [<<"clientid">>, <<"username">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE clientid = ${clientid} AND username = ${username}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE clientid = ${clientid} AND username = ${username}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). %%------------------------------------------------------------------------------ %% Helpers @@ -222,34 +290,40 @@ raw_pgsql_authz_config() -> <<"username">> => <<"root">>, <<"password">> => <<"public">>, - <<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE username = ${username}">>, + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE username = ${username}" + >>, <<"server">> => pgsql_server() }. q(Sql) -> emqx_resource:query( - ?PGSQL_RESOURCE, - {query, Sql}). + ?PGSQL_RESOURCE, + {query, Sql} + ). insert(Sql, Params) -> {ok, _} = emqx_resource:query( - ?PGSQL_RESOURCE, - {query, Sql, Params}), + ?PGSQL_RESOURCE, + {query, Sql, Params} + ), ok. init_table() -> ok = drop_table(), - {ok, _, _} = q("CREATE TABLE acl( - username VARCHAR(255), - clientid VARCHAR(255), - peerhost VARCHAR(255), - cn VARCHAR(255), - dn VARCHAR(255), - topic VARCHAR(255), - permission VARCHAR(255), - action VARCHAR(255))"), + {ok, _, _} = q( + "CREATE TABLE acl(\n" + " username VARCHAR(255),\n" + " clientid VARCHAR(255),\n" + " peerhost VARCHAR(255),\n" + " cn VARCHAR(255),\n" + " dn VARCHAR(255),\n" + " topic VARCHAR(255),\n" + " permission VARCHAR(255),\n" + " action VARCHAR(255))" + ), ok. drop_table() -> @@ -260,37 +334,50 @@ setup_client_samples(ClientInfo, Samples) -> #{username := Username} = ClientInfo, ok = init_table(), ok = lists:foreach( - fun(#{topics := Topics, permission := Permission, action := Action}) -> - lists:foreach( - fun(Topic) -> - insert(<<"INSERT INTO acl(username, topic, permission, action)" - "VALUES($1, $2, $3, $4)">>, - [Username, Topic, Permission, Action]) - end, - Topics) - end, - Samples), + fun(#{topics := Topics, permission := Permission, action := Action}) -> + lists:foreach( + fun(Topic) -> + insert( + << + "INSERT INTO acl(username, topic, permission, action)" + "VALUES($1, $2, $3, $4)" + >>, + [Username, Topic, Permission, Action] + ) + end, + Topics + ) + end, + Samples + ), setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE username = ${username}">>}). + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE username = ${username}" + >> + } + ). setup_config(SpecialParams) -> emqx_authz_test_lib:setup_config( - raw_pgsql_authz_config(), - SpecialParams). + raw_pgsql_authz_config(), + SpecialParams + ). pgsql_server() -> - iolist_to_binary(io_lib:format("~s",[?PGSQL_HOST])). + iolist_to_binary(io_lib:format("~s", [?PGSQL_HOST])). pgsql_config() -> - #{auto_reconnect => true, - database => <<"mqtt">>, - username => <<"root">>, - password => <<"public">>, - pool_size => 8, - server => {?PGSQL_HOST, ?PGSQL_DEFAULT_PORT}, - ssl => #{enable => false} - }. + #{ + auto_reconnect => true, + database => <<"mqtt">>, + username => <<"root">>, + password => <<"public">>, + pool_size => 8, + server => {?PGSQL_HOST, ?PGSQL_DEFAULT_PORT}, + ssl => #{enable => false} + }. start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl b/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl index c0cdef2c9..286dbd286 100644 --- a/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl @@ -38,16 +38,17 @@ init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?REDIS_HOST, ?REDIS_DEFAULT_PORT) of true -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 - ), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), ok = start_apps([emqx_resource, emqx_connector]), {ok, _} = emqx_resource:create_local( - ?REDIS_RESOURCE, - ?RESOURCE_GROUP, - emqx_connector_redis, - redis_config(), - #{}), + ?REDIS_RESOURCE, + ?RESOURCE_GROUP, + emqx_connector_redis, + redis_config(), + #{} + ), Config; false -> {skip, no_redis} @@ -65,109 +66,130 @@ init_per_testcase(_TestCase, Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. - %%------------------------------------------------------------------------------ %% Tests %%------------------------------------------------------------------------------ t_topic_rules(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = emqx_authz_test_lib:test_no_topic_rules(ClientInfo, fun setup_client_samples/2), ok = emqx_authz_test_lib:test_allow_topic_rules(ClientInfo, fun setup_client_samples/2). - t_lookups(_Config) -> - ClientInfo = #{clientid => <<"client id">>, - cn => <<"cn">>, - dn => <<"dn">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"client id">>, + cn => <<"cn">>, + dn => <<"dn">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, - ByClientid = #{<<"mqtt_user:client id">> => - #{<<"a">> => <<"all">>}}, + ByClientid = #{ + <<"mqtt_user:client id">> => + #{<<"a">> => <<"all">>} + }, ok = setup_sample(ByClientid), ok = setup_config(#{<<"cmd">> => <<"HGETALL mqtt_user:${clientid}">>}), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), - ByPeerhost = #{<<"mqtt_user:127.0.0.1">> => - #{<<"a">> => <<"all">>}}, + ByPeerhost = #{ + <<"mqtt_user:127.0.0.1">> => + #{<<"a">> => <<"all">>} + }, ok = setup_sample(ByPeerhost), ok = setup_config(#{<<"cmd">> => <<"HGETALL mqtt_user:${peerhost}">>}), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), - ByCN = #{<<"mqtt_user:cn">> => - #{<<"a">> => <<"all">>}}, + ByCN = #{ + <<"mqtt_user:cn">> => + #{<<"a">> => <<"all">>} + }, ok = setup_sample(ByCN), ok = setup_config(#{<<"cmd">> => <<"HGETALL mqtt_user:${cert_common_name}">>}), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), - - ByDN = #{<<"mqtt_user:dn">> => - #{<<"a">> => <<"all">>}}, + ByDN = #{ + <<"mqtt_user:dn">> => + #{<<"a">> => <<"all">>} + }, ok = setup_sample(ByDN), ok = setup_config(#{<<"cmd">> => <<"HGETALL mqtt_user:${cert_subject}">>}), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). t_create_invalid(_Config) -> AuthzConfig = raw_redis_authz_config(), InvalidConfigs = - [maps:without([<<"server">>], AuthzConfig), - AuthzConfig#{<<"server">> => <<"unknownhost:3333">>}, - AuthzConfig#{<<"password">> => <<"wrongpass">>}, - AuthzConfig#{<<"database">> => <<"5678">>}], + [ + maps:without([<<"server">>], AuthzConfig), + AuthzConfig#{<<"server">> => <<"unknownhost:3333">>}, + AuthzConfig#{<<"password">> => <<"wrongpass">>}, + AuthzConfig#{<<"database">> => <<"5678">>} + ], lists:foreach( - fun(Config) -> + fun(Config) -> {ok, _} = emqx_authz:update(?CMD_REPLACE, [Config]), [_] = emqx_authz:lookup() - - end, - InvalidConfigs). + end, + InvalidConfigs + ). t_redis_error(_Config) -> ok = setup_config(#{<<"cmd">> => <<"INVALID COMMAND">>}), - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, deny = emqx_access_control:authorize(ClientInfo, subscribe, <<"a">>). @@ -178,30 +200,36 @@ t_redis_error(_Config) -> setup_sample(AuthzData) -> {ok, _} = q(["FLUSHDB"]), ok = lists:foreach( - fun({Key, Values}) -> - lists:foreach( - fun({TopicFilter, Action}) -> - q(["HSET", Key, TopicFilter, Action]) - end, - maps:to_list(Values)) - end, - maps:to_list(AuthzData)). + fun({Key, Values}) -> + lists:foreach( + fun({TopicFilter, Action}) -> + q(["HSET", Key, TopicFilter, Action]) + end, + maps:to_list(Values) + ) + end, + maps:to_list(AuthzData) + ). setup_client_samples(ClientInfo, Samples) -> #{username := Username} = ClientInfo, Key = <<"mqtt_user:", Username/binary>>, lists:foreach( - fun(Sample) -> - #{topics := Topics, + fun(Sample) -> + #{ + topics := Topics, permission := <<"allow">>, - action := Action} = Sample, - lists:foreach( + action := Action + } = Sample, + lists:foreach( fun(Topic) -> - q(["HSET", Key, Topic, Action]) + q(["HSET", Key, Topic, Action]) end, - Topics) - end, - Samples), + Topics + ) + end, + Samples + ), setup_config(#{}). setup_config(SpecialParams) -> @@ -221,22 +249,24 @@ raw_redis_authz_config() -> }. redis_server() -> - iolist_to_binary(io_lib:format("~s",[?REDIS_HOST])). + iolist_to_binary(io_lib:format("~s", [?REDIS_HOST])). q(Command) -> emqx_resource:query( - ?REDIS_RESOURCE, - {cmd, Command}). + ?REDIS_RESOURCE, + {cmd, Command} + ). redis_config() -> - #{auto_reconnect => true, - database => 1, - pool_size => 8, - redis_type => single, - password => "public", - server => {?REDIS_HOST, ?REDIS_DEFAULT_PORT}, - ssl => #{enable => false} - }. + #{ + auto_reconnect => true, + database => 1, + pool_size => 8, + redis_type => single, + password => "public", + server => {?REDIS_HOST, ?REDIS_DEFAULT_PORT}, + ssl => #{enable => false} + }. start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authz/test/emqx_authz_rule_SUITE.erl b/apps/emqx_authz/test/emqx_authz_rule_SUITE.erl index 62299621a..eb16c4e50 100644 --- a/apps/emqx_authz/test/emqx_authz_rule_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_rule_SUITE.erl @@ -23,30 +23,38 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). --define(SOURCE1, {deny, all}). --define(SOURCE2, {allow, {ipaddr, "127.0.0.1"}, all, [{eq, "#"}, {eq, "+"}]}). +-define(SOURCE1, {deny, all}). +-define(SOURCE2, {allow, {ipaddr, "127.0.0.1"}, all, [{eq, "#"}, {eq, "+"}]}). -define(SOURCE3, {allow, {ipaddrs, ["127.0.0.1", "192.168.1.0/24"]}, subscribe, [?PH_S_CLIENTID]}). -define(SOURCE4, {allow, {'and', [{client, "test"}, {user, "test"}]}, publish, ["topic/test"]}). --define(SOURCE5, {allow, {'or', - [{username, {re, "^test"}}, - {clientid, {re, "test?"}}]}, - publish, [?PH_S_USERNAME, ?PH_S_CLIENTID]}). +-define(SOURCE5, + {allow, + {'or', [ + {username, {re, "^test"}}, + {clientid, {re, "test?"}} + ]}, + publish, [?PH_S_USERNAME, ?PH_S_CLIENTID]} +). all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), Config. end_per_suite(_Config) -> {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => <<"allow">>, - <<"cache">> => #{<<"enable">> => <<"true">>}, - <<"sources">> => []}), + [authorization], + #{ + <<"no_match">> => <<"allow">>, + <<"cache">> => #{<<"enable">> => <<"true">>}, + <<"sources">> => [] + } + ), emqx_common_test_helpers:stop_apps([emqx_authz, emqx_conf]), ok. @@ -61,115 +69,242 @@ set_special_configs(_App) -> t_compile(_) -> ?assertEqual({deny, all, all, [['#']]}, emqx_authz_rule:compile(?SOURCE1)), - ?assertEqual({allow, {ipaddr, {{127,0,0,1}, {127,0,0,1}, 32}}, - all, [{eq, ['#']}, {eq, ['+']}]}, emqx_authz_rule:compile(?SOURCE2)), + ?assertEqual( + {allow, {ipaddr, {{127, 0, 0, 1}, {127, 0, 0, 1}, 32}}, all, [{eq, ['#']}, {eq, ['+']}]}, + emqx_authz_rule:compile(?SOURCE2) + ), - ?assertEqual({allow, - {ipaddrs,[{{127,0,0,1},{127,0,0,1},32}, - {{192,168,1,0},{192,168,1,255},24}]}, - subscribe, - [{pattern,[?PH_CLIENTID]}] - }, emqx_authz_rule:compile(?SOURCE3)), + ?assertEqual( + {allow, + {ipaddrs, [ + {{127, 0, 0, 1}, {127, 0, 0, 1}, 32}, + {{192, 168, 1, 0}, {192, 168, 1, 255}, 24} + ]}, + subscribe, [{pattern, [?PH_CLIENTID]}]}, + emqx_authz_rule:compile(?SOURCE3) + ), - ?assertMatch({allow, - {'and', [{clientid, {eq, <<"test">>}}, {username, {eq, <<"test">>}}]}, - publish, - [[<<"topic">>, <<"test">>]] - }, emqx_authz_rule:compile(?SOURCE4)), + ?assertMatch( + {allow, {'and', [{clientid, {eq, <<"test">>}}, {username, {eq, <<"test">>}}]}, publish, [ + [<<"topic">>, <<"test">>] + ]}, + emqx_authz_rule:compile(?SOURCE4) + ), - ?assertMatch({allow, - {'or', [{username, {re_pattern, _, _, _, _}}, - {clientid, {re_pattern, _, _, _, _}}]}, - publish, [{pattern, [?PH_USERNAME]}, {pattern, [?PH_CLIENTID]}] - }, emqx_authz_rule:compile(?SOURCE5)), + ?assertMatch( + {allow, + {'or', [ + {username, {re_pattern, _, _, _, _}}, + {clientid, {re_pattern, _, _, _, _}} + ]}, + publish, [{pattern, [?PH_USERNAME]}, {pattern, [?PH_CLIENTID]}]}, + emqx_authz_rule:compile(?SOURCE5) + ), ok. - t_match(_) -> - ClientInfo1 = #{clientid => <<"test">>, - username => <<"test">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, - ClientInfo2 = #{clientid => <<"test">>, - username => <<"test">>, - peerhost => {192,168,1,10}, - zone => default, - listener => {tcp, default} - }, - ClientInfo3 = #{clientid => <<"test">>, - username => <<"fake">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, - ClientInfo4 = #{clientid => <<"fake">>, - username => <<"test">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo1 = #{ + clientid => <<"test">>, + username => <<"test">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, + ClientInfo2 = #{ + clientid => <<"test">>, + username => <<"test">>, + peerhost => {192, 168, 1, 10}, + zone => default, + listener => {tcp, default} + }, + ClientInfo3 = #{ + clientid => <<"test">>, + username => <<"fake">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, + ClientInfo4 = #{ + clientid => <<"fake">>, + username => <<"test">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, - ?assertEqual({matched, deny}, - emqx_authz_rule:match(ClientInfo1, subscribe, <<"#">>, - emqx_authz_rule:compile(?SOURCE1))), - ?assertEqual({matched, deny}, - emqx_authz_rule:match(ClientInfo2, subscribe, <<"+">>, - emqx_authz_rule:compile(?SOURCE1))), - ?assertEqual({matched, deny}, - emqx_authz_rule:match(ClientInfo3, subscribe, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE1))), + ?assertEqual( + {matched, deny}, + emqx_authz_rule:match( + ClientInfo1, + subscribe, + <<"#">>, + emqx_authz_rule:compile(?SOURCE1) + ) + ), + ?assertEqual( + {matched, deny}, + emqx_authz_rule:match( + ClientInfo2, + subscribe, + <<"+">>, + emqx_authz_rule:compile(?SOURCE1) + ) + ), + ?assertEqual( + {matched, deny}, + emqx_authz_rule:match( + ClientInfo3, + subscribe, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE1) + ) + ), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo1, subscribe, <<"#">>, - emqx_authz_rule:compile(?SOURCE2))), - ?assertEqual(nomatch, - emqx_authz_rule:match(ClientInfo1, subscribe, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE2))), - ?assertEqual(nomatch, - emqx_authz_rule:match(ClientInfo2, subscribe, <<"#">>, - emqx_authz_rule:compile(?SOURCE2))), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo1, + subscribe, + <<"#">>, + emqx_authz_rule:compile(?SOURCE2) + ) + ), + ?assertEqual( + nomatch, + emqx_authz_rule:match( + ClientInfo1, + subscribe, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE2) + ) + ), + ?assertEqual( + nomatch, + emqx_authz_rule:match( + ClientInfo2, + subscribe, + <<"#">>, + emqx_authz_rule:compile(?SOURCE2) + ) + ), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo1, subscribe, <<"test">>, - emqx_authz_rule:compile(?SOURCE3))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo2, subscribe, <<"test">>, - emqx_authz_rule:compile(?SOURCE3))), - ?assertEqual(nomatch, - emqx_authz_rule:match(ClientInfo2, subscribe, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE3))), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo1, + subscribe, + <<"test">>, + emqx_authz_rule:compile(?SOURCE3) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo2, + subscribe, + <<"test">>, + emqx_authz_rule:compile(?SOURCE3) + ) + ), + ?assertEqual( + nomatch, + emqx_authz_rule:match( + ClientInfo2, + subscribe, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE3) + ) + ), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo1, publish, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE4))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo2, publish, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE4))), - ?assertEqual(nomatch, - emqx_authz_rule:match(ClientInfo3, publish, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE4))), - ?assertEqual(nomatch, - emqx_authz_rule:match(ClientInfo4, publish, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE4))), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo1, + publish, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE4) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo2, + publish, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE4) + ) + ), + ?assertEqual( + nomatch, + emqx_authz_rule:match( + ClientInfo3, + publish, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE4) + ) + ), + ?assertEqual( + nomatch, + emqx_authz_rule:match( + ClientInfo4, + publish, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE4) + ) + ), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo1, publish, <<"test">>, - emqx_authz_rule:compile(?SOURCE5))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo2, publish, <<"test">>, - emqx_authz_rule:compile(?SOURCE5))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo3, publish, <<"test">>, - emqx_authz_rule:compile(?SOURCE5))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo3, publish, <<"fake">>, - emqx_authz_rule:compile(?SOURCE5))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo4, publish, <<"test">>, - emqx_authz_rule:compile(?SOURCE5))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo4, publish, <<"fake">>, - emqx_authz_rule:compile(?SOURCE5))), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo1, + publish, + <<"test">>, + emqx_authz_rule:compile(?SOURCE5) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo2, + publish, + <<"test">>, + emqx_authz_rule:compile(?SOURCE5) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo3, + publish, + <<"test">>, + emqx_authz_rule:compile(?SOURCE5) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo3, + publish, + <<"fake">>, + emqx_authz_rule:compile(?SOURCE5) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo4, + publish, + <<"test">>, + emqx_authz_rule:compile(?SOURCE5) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo4, + publish, + <<"fake">>, + emqx_authz_rule:compile(?SOURCE5) + ) + ), ok. diff --git a/apps/emqx_authz/test/emqx_authz_test_lib.erl b/apps/emqx_authz/test/emqx_authz_test_lib.erl index 6225d3a83..a2cc39b13 100644 --- a/apps/emqx_authz/test/emqx_authz_test_lib.erl +++ b/apps/emqx_authz/test/emqx_authz_test_lib.erl @@ -32,33 +32,40 @@ restore_authorizers() -> reset_authorizers(Nomatch, ChacheEnabled) -> {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => atom_to_binary(Nomatch), - <<"cache">> => #{<<"enable">> => atom_to_binary(ChacheEnabled)}, - <<"sources">> => []}), + [authorization], + #{ + <<"no_match">> => atom_to_binary(Nomatch), + <<"cache">> => #{<<"enable">> => atom_to_binary(ChacheEnabled)}, + <<"sources">> => [] + } + ), ok. setup_config(BaseConfig, SpecialParams) -> Config = maps:merge(BaseConfig, SpecialParams), case emqx_authz:update(?CMD_REPLACE, [Config]) of - {ok, _} -> ok; - {error, Reason} -> {error, Reason} + {ok, _} -> ok; + {error, Reason} -> {error, Reason} end. test_samples(ClientInfo, Samples) -> lists:foreach( - fun({Expected, Action, Topic}) -> - ct:pal( + fun({Expected, Action, Topic}) -> + ct:pal( "client_info: ~p, action: ~p, topic: ~p, expected: ~p", - [ClientInfo, Action, Topic, Expected]), - ?assertEqual( - Expected, - emqx_access_control:authorize( - ClientInfo, - Action, - Topic)) - end, - Samples). + [ClientInfo, Action, Topic, Expected] + ), + ?assertEqual( + Expected, + emqx_access_control:authorize( + ClientInfo, + Action, + Topic + ) + ) + end, + Samples + ). test_no_topic_rules(ClientInfo, SetupSamples) -> %% No rules @@ -67,43 +74,52 @@ test_no_topic_rules(ClientInfo, SetupSamples) -> ok = SetupSamples(ClientInfo, []), ok = test_samples( - ClientInfo, - [{deny, subscribe, <<"#">>}, + ClientInfo, + [ + {deny, subscribe, <<"#">>}, {deny, subscribe, <<"subs">>}, - {deny, publish, <<"pub">>}]). + {deny, publish, <<"pub">>} + ] + ). test_allow_topic_rules(ClientInfo, SetupSamples) -> - Samples = [#{ - topics => [<<"eq testpub1/${username}">>, - <<"testpub2/${clientid}">>, - <<"testpub3/#">>], - permission => <<"allow">>, - action => <<"publish">> - }, - #{ - topics => [<<"eq testsub1/${username}">>, - <<"testsub2/${clientid}">>, - <<"testsub3/#">>], - permission => <<"allow">>, - action => <<"subscribe">> - }, + Samples = [ + #{ + topics => [ + <<"eq testpub1/${username}">>, + <<"testpub2/${clientid}">>, + <<"testpub3/#">> + ], + permission => <<"allow">>, + action => <<"publish">> + }, + #{ + topics => [ + <<"eq testsub1/${username}">>, + <<"testsub2/${clientid}">>, + <<"testsub3/#">> + ], + permission => <<"allow">>, + action => <<"subscribe">> + }, - #{ - topics => [<<"eq testall1/${username}">>, - <<"testall2/${clientid}">>, - <<"testall3/#">>], - permission => <<"allow">>, - action => <<"all">> - } - ], + #{ + topics => [ + <<"eq testall1/${username}">>, + <<"testall2/${clientid}">>, + <<"testall3/#">> + ], + permission => <<"allow">>, + action => <<"all">> + } + ], ok = reset_authorizers(deny, false), ok = SetupSamples(ClientInfo, Samples), ok = test_samples( - ClientInfo, - [ - + ClientInfo, + [ %% Publish rules {deny, publish, <<"testpub1/username">>}, @@ -114,7 +130,6 @@ test_allow_topic_rules(ClientInfo, SetupSamples) -> {deny, publish, <<"testpub2/username">>}, {deny, publish, <<"testpub1/clientid">>}, - {deny, subscribe, <<"testpub1/username">>}, {deny, subscribe, <<"testpub2/clientid">>}, {deny, subscribe, <<"testpub3/foobar">>}, @@ -154,41 +169,47 @@ test_allow_topic_rules(ClientInfo, SetupSamples) -> {deny, publish, <<"testall2/username">>}, {deny, publish, <<"testall1/clientid">>}, {deny, publish, <<"testall4/foobar">>} - ]). + ] + ). test_deny_topic_rules(ClientInfo, SetupSamples) -> Samples = [ - #{ - topics => [<<"eq testpub1/${username}">>, - <<"testpub2/${clientid}">>, - <<"testpub3/#">>], - permission => <<"deny">>, - action => <<"publish">> - }, - #{ - topics => [<<"eq testsub1/${username}">>, - <<"testsub2/${clientid}">>, - <<"testsub3/#">>], - permission => <<"deny">>, - action => <<"subscribe">> - }, + #{ + topics => [ + <<"eq testpub1/${username}">>, + <<"testpub2/${clientid}">>, + <<"testpub3/#">> + ], + permission => <<"deny">>, + action => <<"publish">> + }, + #{ + topics => [ + <<"eq testsub1/${username}">>, + <<"testsub2/${clientid}">>, + <<"testsub3/#">> + ], + permission => <<"deny">>, + action => <<"subscribe">> + }, - #{ - topics => [<<"eq testall1/${username}">>, - <<"testall2/${clientid}">>, - <<"testall3/#">>], - permission => <<"deny">>, - action => <<"all">> - } - ], + #{ + topics => [ + <<"eq testall1/${username}">>, + <<"testall2/${clientid}">>, + <<"testall3/#">> + ], + permission => <<"deny">>, + action => <<"all">> + } + ], ok = reset_authorizers(allow, false), ok = SetupSamples(ClientInfo, Samples), ok = test_samples( - ClientInfo, - [ - + ClientInfo, + [ %% Publish rules {allow, publish, <<"testpub1/username">>}, @@ -199,7 +220,6 @@ test_deny_topic_rules(ClientInfo, SetupSamples) -> {allow, publish, <<"testpub2/username">>}, {allow, publish, <<"testpub1/clientid">>}, - {allow, subscribe, <<"testpub1/username">>}, {allow, subscribe, <<"testpub2/clientid">>}, {allow, subscribe, <<"testpub3/foobar">>}, @@ -239,4 +259,5 @@ test_deny_topic_rules(ClientInfo, SetupSamples) -> {allow, publish, <<"testall2/username">>}, {allow, publish, <<"testall1/clientid">>}, {allow, publish, <<"testall4/foobar">>} - ]). + ] + ). From c192bb51e3b16aabc4815a1b163debc688d188f3 Mon Sep 17 00:00:00 2001 From: JimMoen Date: Fri, 1 Apr 2022 02:14:33 +0800 Subject: [PATCH 9/9] style: add `authn` & `authz` erlfmt and git blame ignore --- git-blame-ignore-revs | 4 ++++ scripts/check-format.sh | 1 + 2 files changed, 5 insertions(+) diff --git a/git-blame-ignore-revs b/git-blame-ignore-revs index 224b99f12..23efe0642 100644 --- a/git-blame-ignore-revs +++ b/git-blame-ignore-revs @@ -2,6 +2,10 @@ 91bcf02970bd1fe0dfe06744a24aa8a149ac0052 # reformat apps/emqx_modules 4d3157743e0d3ac7e6c09f108f6e79c820fd9c00 +# reformat apps/emqx_authn +aae2d01582b980302f30913522eedf1d9d3d217c +# reformat apps/emqx_authz +82559b9b089c6cfadf54b7ce5143156dd403876f # reformat lib-ee/emqx_license 4f396cceb84d79d5ef540e91c1a8420e8de74a56 # reformat lib-ee/emqx_enterprise_conf diff --git a/scripts/check-format.sh b/scripts/check-format.sh index 582b24b3a..5bf8601e6 100755 --- a/scripts/check-format.sh +++ b/scripts/check-format.sh @@ -9,6 +9,7 @@ cd -P -- "$(dirname -- "$0")/.." APPS=() APPS+=( 'apps/emqx' 'apps/emqx_modules' ) +APPS+=( 'apps/emqx_authn' 'apps/emqx_authz' ) APPS+=( 'lib-ee/emqx_enterprise_conf' 'lib-ee/emqx_license' ) for app in "${APPS[@]}"; do