diff --git a/apps/emqx/test/emqx_authentication_SUITE.erl b/apps/emqx/test/emqx_authentication_SUITE.erl new file mode 100644 index 000000000..a940adc88 --- /dev/null +++ b/apps/emqx/test/emqx_authentication_SUITE.erl @@ -0,0 +1,238 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 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_authentication_SUITE). + +-behaviour(hocon_schema). +-behaviour(emqx_authentication). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("typerefl/include/types.hrl"). + +-export([ fields/1 ]). + +-export([ refs/0 + , create/1 + , update/2 + , authenticate/2 + , destroy/1 + ]). + +-define(AUTHN, emqx_authentication). + +%%------------------------------------------------------------------------------ +%% Hocon Schema +%%------------------------------------------------------------------------------ + +fields(type1) -> + [ {mechanism, {enum, ['password-based']}} + , {backend, {enum, ['built-in-database']}} + , {enable, fun enable/1} + ]; + +fields(type2) -> + [ {mechanism, {enum, ['password-based']}} + , {backend, {enum, ['mysql']}} + , {enable, fun enable/1} + ]. + +enable(type) -> boolean(); +enable(default) -> true; +enable(_) -> undefined. + +%%------------------------------------------------------------------------------ +%% Callbacks +%%------------------------------------------------------------------------------ + +refs() -> + [ hoconsc:ref(?MODULE, type1) + , hoconsc:ref(?MODULE, type2) + ]. + +create(_Config) -> + {ok, #{mark => 1}}. + +update(_Config, _State) -> + {ok, #{mark => 2}}. + +authenticate(#{username := <<"good">>}, _State) -> + {ok, #{superuser => true}}; +authenticate(#{username := _}, _State) -> + {error, bad_username_or_password}. + +destroy(_State) -> + ok. + +all() -> + emqx_ct:all(?MODULE). + +init_per_suite(Config) -> + application:set_env(ekka, strict_mode, true), + emqx_ct_helpers:start_apps([]), + Config. + +end_per_suite(_) -> + emqx_ct_helpers:stop_apps([]), + ok. + +t_chain(_) -> + % CRUD of authentication chain + ChainName = <<"test">>, + ?assertMatch({ok, []}, ?AUTHN:list_chains()), + ?assertMatch({ok, #{name := ChainName, authenticators := []}}, ?AUTHN:create_chain(ChainName)), + ?assertEqual({error, {already_exists, {chain, ChainName}}}, ?AUTHN:create_chain(ChainName)), + ?assertMatch({ok, #{name := ChainName, authenticators := []}}, ?AUTHN:lookup_chain(ChainName)), + ?assertMatch({ok, [#{name := ChainName}]}, ?AUTHN:list_chains()), + ?assertEqual(ok, ?AUTHN:delete_chain(ChainName)), + ?assertMatch({error, {not_found, {chain, ChainName}}}, ?AUTHN:lookup_chain(ChainName)), + ok. + +t_authenticator(_) -> + ChainName = <<"test">>, + AuthenticatorConfig1 = #{mechanism => 'password-based', + backend => 'built-in-database', + enable => true}, + + % Create an authenticator when the authentication chain does not exist + ?assertEqual({error, {not_found, {chain, ChainName}}}, ?AUTHN:create_authenticator(ChainName, AuthenticatorConfig1)), + ?AUTHN:create_chain(ChainName), + % Create an authenticator when the provider does not exist + ?assertEqual({error, no_available_provider}, ?AUTHN:create_authenticator(ChainName, AuthenticatorConfig1)), + + AuthNType1 = {'password-based', 'built-in-database'}, + ?AUTHN:add_provider(AuthNType1, ?MODULE), + ID1 = <<"password-based:built-in-database">>, + + % CRUD of authencaticator + ?assertMatch({ok, #{id := ID1, state := #{mark := 1}}}, ?AUTHN:create_authenticator(ChainName, AuthenticatorConfig1)), + ?assertMatch({ok, #{id := ID1}}, ?AUTHN:lookup_authenticator(ChainName, ID1)), + ?assertMatch({ok, [#{id := ID1}]}, ?AUTHN:list_authenticators(ChainName)), + ?assertEqual({error, {already_exists, {authenticator, ID1}}}, ?AUTHN:create_authenticator(ChainName, AuthenticatorConfig1)), + ?assertMatch({ok, #{id := ID1, state := #{mark := 2}}}, ?AUTHN:update_authenticator(ChainName, ID1, AuthenticatorConfig1)), + ?assertEqual(ok, ?AUTHN:delete_authenticator(ChainName, ID1)), + ?assertEqual({error, {not_found, {authenticator, ID1}}}, ?AUTHN:update_authenticator(ChainName, ID1, AuthenticatorConfig1)), + ?assertMatch({ok, []}, ?AUTHN:list_authenticators(ChainName)), + + % Multiple authenticators exist at the same time + AuthNType2 = {'password-based', mysql}, + ?AUTHN:add_provider(AuthNType2, ?MODULE), + ID2 = <<"password-based:mysql">>, + AuthenticatorConfig2 = #{mechanism => 'password-based', + backend => mysql, + enable => true}, + ?assertMatch({ok, #{id := ID1}}, ?AUTHN:create_authenticator(ChainName, AuthenticatorConfig1)), + ?assertMatch({ok, #{id := ID2}}, ?AUTHN:create_authenticator(ChainName, AuthenticatorConfig2)), + + % Move authenticator + ?assertMatch({ok, [#{id := ID1}, #{id := ID2}]}, ?AUTHN:list_authenticators(ChainName)), + ?assertEqual(ok, ?AUTHN:move_authenticator(ChainName, ID2, top)), + ?assertMatch({ok, [#{id := ID2}, #{id := ID1}]}, ?AUTHN:list_authenticators(ChainName)), + ?assertEqual(ok, ?AUTHN:move_authenticator(ChainName, ID2, bottom)), + ?assertMatch({ok, [#{id := ID1}, #{id := ID2}]}, ?AUTHN:list_authenticators(ChainName)), + ?assertEqual(ok, ?AUTHN:move_authenticator(ChainName, ID2, {before, ID1})), + ?assertMatch({ok, [#{id := ID2}, #{id := ID1}]}, ?AUTHN:list_authenticators(ChainName)), + + ?AUTHN:delete_chain(ChainName), + ?AUTHN:remove_provider(AuthNType1), + ?AUTHN:remove_provider(AuthNType2), + ok. + +t_authenticate(_) -> + ListenerID = <<"tcp:default">>, + ClientInfo = #{zone => default, + listener => ListenerID, + protocol => mqtt, + username => <<"good">>, + password => <<"any">>}, + ?assertEqual({ok, #{superuser => false}}, emqx_access_control:authenticate(ClientInfo)), + + AuthNType = {'password-based', 'built-in-database'}, + ?AUTHN:add_provider(AuthNType, ?MODULE), + + AuthenticatorConfig = #{mechanism => 'password-based', + backend => 'built-in-database', + enable => true}, + ?AUTHN:create_chain(ListenerID), + ?assertMatch({ok, _}, ?AUTHN:create_authenticator(ListenerID, AuthenticatorConfig)), + ?assertEqual({ok, #{superuser => true}}, emqx_access_control:authenticate(ClientInfo)), + ?assertEqual({error, bad_username_or_password}, emqx_access_control:authenticate(ClientInfo#{username => <<"bad">>})), + + ?AUTHN:delete_chain(ListenerID), + ?AUTHN:remove_provider(AuthNType), + ok. + +t_update_config(_) -> + emqx_config_handler:add_handler([authentication], emqx_authentication), + + AuthNType1 = {'password-based', 'built-in-database'}, + AuthNType2 = {'password-based', mysql}, + ?AUTHN:add_provider(AuthNType1, ?MODULE), + ?AUTHN:add_provider(AuthNType2, ?MODULE), + + Global = <<"mqtt:global">>, + AuthenticatorConfig1 = #{mechanism => 'password-based', + backend => 'built-in-database', + enable => true}, + AuthenticatorConfig2 = #{mechanism => 'password-based', + backend => mysql, + enable => true}, + ID1 = <<"password-based:built-in-database">>, + ID2 = <<"password-based:mysql">>, + + ?assertMatch({ok, []}, ?AUTHN:list_chains()), + ?assertMatch({ok, _}, update_config([authentication], {create_authenticator, Global, AuthenticatorConfig1})), + ?assertMatch({ok, #{id := ID1, state := #{mark := 1}}}, ?AUTHN:lookup_authenticator(Global, ID1)), + + ?assertMatch({ok, _}, update_config([authentication], {create_authenticator, Global, AuthenticatorConfig2})), + ?assertMatch({ok, #{id := ID2, state := #{mark := 1}}}, ?AUTHN:lookup_authenticator(Global, ID2)), + + ?assertMatch({ok, _}, update_config([authentication], {update_authenticator, Global, ID1, #{}})), + ?assertMatch({ok, #{id := ID1, state := #{mark := 2}}}, ?AUTHN:lookup_authenticator(Global, ID1)), + + ?assertMatch({ok, _}, update_config([authentication], {move_authenticator, Global, ID2, <<"top">>})), + ?assertMatch({ok, [#{id := ID2}, #{id := ID1}]}, ?AUTHN:list_authenticators(Global)), + + ?assertMatch({ok, _}, update_config([authentication], {delete_authenticator, Global, ID1})), + ?assertEqual({error, {not_found, {authenticator, ID1}}}, ?AUTHN:lookup_authenticator(Global, ID1)), + + ListenerID = <<"tcp:default">>, + ConfKeyPath = [listeners, tcp, default, authentication], + ?assertMatch({ok, _}, update_config(ConfKeyPath, {create_authenticator, ListenerID, AuthenticatorConfig1})), + ?assertMatch({ok, #{id := ID1, state := #{mark := 1}}}, ?AUTHN:lookup_authenticator(ListenerID, ID1)), + + ?assertMatch({ok, _}, update_config(ConfKeyPath, {create_authenticator, ListenerID, AuthenticatorConfig2})), + ?assertMatch({ok, #{id := ID2, state := #{mark := 1}}}, ?AUTHN:lookup_authenticator(ListenerID, ID2)), + + ?assertMatch({ok, _}, update_config(ConfKeyPath, {update_authenticator, ListenerID, ID1, #{}})), + ?assertMatch({ok, #{id := ID1, state := #{mark := 2}}}, ?AUTHN:lookup_authenticator(ListenerID, ID1)), + + ?assertMatch({ok, _}, update_config(ConfKeyPath, {move_authenticator, ListenerID, ID2, <<"top">>})), + ?assertMatch({ok, [#{id := ID2}, #{id := ID1}]}, ?AUTHN:list_authenticators(ListenerID)), + + ?assertMatch({ok, _}, update_config(ConfKeyPath, {delete_authenticator, ListenerID, ID1})), + ?assertEqual({error, {not_found, {authenticator, ID1}}}, ?AUTHN:lookup_authenticator(ListenerID, ID1)), + + ?AUTHN:delete_chain(Global), + ?AUTHN:remove_provider(AuthNType1), + ?AUTHN:remove_provider(AuthNType2), + ok. + +update_config(Path, ConfigRequest) -> + emqx:update_config(Path, ConfigRequest, #{rawconf_with_defaults => true}). diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl index 1bec0d903..19417218d 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl @@ -251,8 +251,8 @@ generate_request(Credential, #{method := Method, post -> NPath = append_query(Path, BaseQuery), ContentType = proplists:get_value(<<"content-type">>, Headers), - Body = serialize_body(ContentType, Body), - {NPath, Headers, Body} + NBody = serialize_body(ContentType, Body), + {NPath, Headers, NBody} end. replace_placeholders(KVs, Credential) -> diff --git a/apps/emqx_authn/test/emqx_authn_SUITE.erl b/apps/emqx_authn/test/emqx_authn_SUITE.erl index 31bac76a3..74ec397cc 100644 --- a/apps/emqx_authn/test/emqx_authn_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_SUITE.erl @@ -15,3 +15,8 @@ %%-------------------------------------------------------------------- -module(emqx_authn_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +all() -> emqx_ct:all(?MODULE). \ No newline at end of file diff --git a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl index 5e06211a7..9d8b1d9fc 100644 --- a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl @@ -16,8 +16,8 @@ -module(emqx_authn_jwt_SUITE). -% -compile(export_all). -% -compile(nowarn_export_all). +-compile(export_all). +-compile(nowarn_export_all). % -include_lib("common_test/include/ct.hrl"). % -include_lib("eunit/include/eunit.hrl"). @@ -26,8 +26,8 @@ % -define(AUTH, emqx_authn). -% all() -> -% emqx_ct:all(?MODULE). +all() -> + emqx_ct:all(?MODULE). % init_per_suite(Config) -> % emqx_ct_helpers:start_apps([emqx_authn]), diff --git a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl index acfe71809..4bc6961dd 100644 --- a/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mnesia_SUITE.erl @@ -16,8 +16,8 @@ -module(emqx_authn_mnesia_SUITE). -% -compile(export_all). -% -compile(nowarn_export_all). +-compile(export_all). +-compile(nowarn_export_all). % -include_lib("common_test/include/ct.hrl"). % -include_lib("eunit/include/eunit.hrl"). @@ -26,8 +26,8 @@ % -define(AUTH, emqx_authn). -% all() -> -% emqx_ct:all(?MODULE). +all() -> + emqx_ct:all(?MODULE). % init_per_suite(Config) -> % emqx_ct_helpers:start_apps([emqx_authn]), diff --git a/apps/emqx_connector/src/emqx_connector_schema_lib.erl b/apps/emqx_connector/src/emqx_connector_schema_lib.erl index 5f9472cca..a6d33ffca 100644 --- a/apps/emqx_connector/src/emqx_connector_schema_lib.erl +++ b/apps/emqx_connector/src/emqx_connector_schema_lib.erl @@ -142,7 +142,9 @@ to_ip_port(Str) -> _ -> {error, Str} end. -ip_port_to_string({Ip, Port}) -> +ip_port_to_string({Ip, Port}) when is_list(Ip) -> + iolist_to_binary([Ip, ":", integer_to_list(Port)]); +ip_port_to_string({Ip, Port}) when is_tuple(Ip) -> iolist_to_binary([inet:ntoa(Ip), ":", integer_to_list(Port)]). to_servers(Str) -> diff --git a/apps/emqx_gateway/src/emqx_gateway.app.src b/apps/emqx_gateway/src/emqx_gateway.app.src index e25b767cc..2fc329711 100644 --- a/apps/emqx_gateway/src/emqx_gateway.app.src +++ b/apps/emqx_gateway/src/emqx_gateway.app.src @@ -3,7 +3,7 @@ {vsn, "0.1.0"}, {registered, []}, {mod, {emqx_gateway_app, []}}, - {applications, [kernel, stdlib, grpc, lwm2m_coap, emqx, emqx_authn]}, + {applications, [kernel, stdlib, grpc, lwm2m_coap, emqx]}, {env, []}, {modules, []}, {licenses, ["Apache 2.0"]}, diff --git a/apps/emqx_gateway/test/emqx_exproto_SUITE.erl b/apps/emqx_gateway/test/emqx_exproto_SUITE.erl index 4902aacf5..4ab91da5d 100644 --- a/apps/emqx_gateway/test/emqx_exproto_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_exproto_SUITE.erl @@ -55,20 +55,19 @@ metrics() -> init_per_group(GrpName, Cfg) -> put(grpname, GrpName), Svrs = emqx_exproto_echo_svr:start(), - emqx_ct_helpers:start_apps([emqx_authn, emqx_gateway], fun set_special_cfg/1), + emqx_ct_helpers:start_apps([emqx_gateway], fun set_special_cfg/1), emqx_logger:set_log_level(debug), [{servers, Svrs}, {listener_type, GrpName} | Cfg]. end_per_group(_, Cfg) -> - emqx_ct_helpers:stop_apps([emqx_gateway, emqx_authn]), + emqx_ct_helpers:stop_apps([emqx_gateway]), emqx_exproto_echo_svr:stop(proplists:get_value(servers, Cfg)). set_special_cfg(emqx_gateway) -> LisType = get(grpname), emqx_config:put( [gateway, exproto], - #{authentication => #{enable => false}, - server => #{bind => 9100}, + #{server => #{bind => 9100}, handler => #{address => "http://127.0.0.1:9001"}, listeners => listener_confs(LisType) }); diff --git a/apps/emqx_gateway/test/emqx_gateway_registry_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_registry_SUITE.erl index da03b17c5..56776957f 100644 --- a/apps/emqx_gateway/test/emqx_gateway_registry_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_gateway_registry_SUITE.erl @@ -35,11 +35,11 @@ all() -> emqx_ct:all(?MODULE). init_per_suite(Cfg) -> ok = emqx_config:init_load(emqx_gateway_schema, ?CONF_DEFAULT), - emqx_ct_helpers:start_apps([emqx_authn, emqx_gateway]), + emqx_ct_helpers:start_apps([emqx_gateway]), Cfg. end_per_suite(_Cfg) -> - emqx_ct_helpers:stop_apps([emqx_authn, emqx_gateway]), + emqx_ct_helpers:stop_apps([emqx_gateway]), ok. %%-------------------------------------------------------------------- diff --git a/apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl b/apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl index e355e05cf..5df13f8d6 100644 --- a/apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl @@ -148,12 +148,12 @@ groups() -> ]. init_per_suite(Config) -> - emqx_ct_helpers:start_apps([emqx_authn]), + emqx_ct_helpers:start_apps([]), Config. end_per_suite(Config) -> timer:sleep(300), - emqx_ct_helpers:stop_apps([emqx_authn]), + emqx_ct_helpers:stop_apps([]), Config. init_per_testcase(_AllTestCase, Config) -> diff --git a/apps/emqx_gateway/test/emqx_sn_protocol_SUITE.erl b/apps/emqx_gateway/test/emqx_sn_protocol_SUITE.erl index 2fbd031ff..e4b3d0095 100644 --- a/apps/emqx_gateway/test/emqx_sn_protocol_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_sn_protocol_SUITE.erl @@ -83,11 +83,11 @@ all() -> init_per_suite(Config) -> ok = emqx_config:init_load(emqx_gateway_schema, ?CONF_DEFAULT), - emqx_ct_helpers:start_apps([emqx_authn, emqx_gateway]), + emqx_ct_helpers:start_apps([emqx_gateway]), Config. end_per_suite(_) -> - emqx_ct_helpers:stop_apps([emqx_gateway, emqx_authn]). + emqx_ct_helpers:stop_apps([emqx_gateway]). %%-------------------------------------------------------------------- %% Test cases diff --git a/apps/emqx_gateway/test/emqx_stomp_SUITE.erl b/apps/emqx_gateway/test/emqx_stomp_SUITE.erl index 75f6dadc3..9c3f1090f 100644 --- a/apps/emqx_gateway/test/emqx_stomp_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_stomp_SUITE.erl @@ -43,11 +43,11 @@ all() -> emqx_ct:all(?MODULE). init_per_suite(Cfg) -> ok = emqx_config:init_load(emqx_gateway_schema, ?CONF_DEFAULT), - emqx_ct_helpers:start_apps([emqx_authn, emqx_gateway]), + emqx_ct_helpers:start_apps([emqx_gateway]), Cfg. end_per_suite(_Cfg) -> - emqx_ct_helpers:stop_apps([emqx_gateway, emqx_authn]), + emqx_ct_helpers:stop_apps([emqx_gateway]), ok. %%--------------------------------------------------------------------