From ec243d894611d321c00402b8ebb1d80c68d9db2a Mon Sep 17 00:00:00 2001 From: firest Date: Fri, 1 Apr 2022 17:19:57 +0800 Subject: [PATCH] test(gateway): integration CoAP && LwM2M test with http authn --- .../src/coap/emqx_coap_message.erl | 2 +- apps/emqx_gateway/test/emqx_coap_SUITE.erl | 29 +- .../test/emqx_gateway_authn_http_SUITE.erl | 261 ++++++++++++++++++ apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl | 6 + 4 files changed, 288 insertions(+), 10 deletions(-) create mode 100644 apps/emqx_gateway/test/emqx_gateway_authn_http_SUITE.erl diff --git a/apps/emqx_gateway/src/coap/emqx_coap_message.erl b/apps/emqx_gateway/src/coap/emqx_coap_message.erl index d7a229532..41d42b165 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_message.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_message.erl @@ -43,7 +43,7 @@ set_payload_block/3, set_payload_block/4 ]). --include("src/coap/include/emqx_coap.hrl"). +-include_lib("emqx_gateway/src/coap/include/emqx_coap.hrl"). request(Type, Method) -> request(Type, Method, <<>>, []). diff --git a/apps/emqx_gateway/test/emqx_coap_SUITE.erl b/apps/emqx_gateway/test/emqx_coap_SUITE.erl index d3f4f8014..563fa9fa7 100644 --- a/apps/emqx_gateway/test/emqx_coap_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_coap_SUITE.erl @@ -64,6 +64,12 @@ end_per_suite(_) -> {ok, _} = emqx:remove_config([<<"gateway">>, <<"coap">>]), emqx_mgmt_api_test_util:end_suite([emqx_gateway]). +default_config() -> + ?CONF_DEFAULT. + +mqtt_prefix() -> + ?MQTT_PREFIX. + %%-------------------------------------------------------------------- %% Test Cases %%-------------------------------------------------------------------- @@ -100,19 +106,19 @@ t_connection(_) -> t_publish(_) -> Action = fun(Channel, Token) -> - Topic = <<"/abc">>, - Payload = <<"123">>, + Topic = <<"/abc">>, + Payload = <<"123">>, - TopicStr = binary_to_list(Topic), - URI = ?PS_PREFIX ++ TopicStr ++ "?clientid=client1&token=" ++ Token, + TopicStr = binary_to_list(Topic), + URI = ?PS_PREFIX ++ TopicStr ++ "?clientid=client1&token=" ++ Token, - %% Sub topic first - emqx:subscribe(Topic), + %% Sub topic first + emqx:subscribe(Topic), - Req = make_req(post, Payload), - {ok, changed, _} = do_request(Channel, URI, Req), + Req = make_req(post, Payload), + {ok, changed, _} = do_request(Channel, URI, Req), - receive + receive {deliver, Topic, Msg} -> ?assertEqual(Topic, Msg#message.topic), ?assertEqual(Payload, Msg#message.payload) @@ -425,3 +431,8 @@ receive_deliver(Wait) -> after Wait -> {error, timeout} end. + +get_field(type, #coap_message{type = Type}) -> + Type; +get_field(method, #coap_message{method = Method}) -> + Method. diff --git a/apps/emqx_gateway/test/emqx_gateway_authn_http_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_authn_http_SUITE.erl new file mode 100644 index 000000000..f21e6a353 --- /dev/null +++ b/apps/emqx_gateway/test/emqx_gateway_authn_http_SUITE.erl @@ -0,0 +1,261 @@ +%%-------------------------------------------------------------------- +%% 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_gateway_authn_http_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include("emqx_authn.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). +-include_lib("emqx/include/emqx_placeholder.hrl"). + +-define(PATH, [?CONF_NS_ATOM]). + +-define(HTTP_PORT, 33333). +-define(HTTP_PATH, "/auth"). + +-define(CREDENTIALS, #{username => <<"admin">>, + password => <<"public">>, + listener => 'tcp:default', + protocol => mqtt + }). + +-define(checkMatch(Guard), + (fun (Expr) -> + case (Expr) of + Guard -> ok; + X__V -> + erlang:error({assertMatch, + [{module, ?MODULE}, + {line, ?LINE}, + {expression, (??Expr)}, + {pattern, (??Guard)}, + {value, X__V}]}) + end + end)). +-define(FUNCTOR(Expr), fun() -> Expr end). +-define(FUNCTOR(Arg, Expr), fun(Arg) -> Expr end). + +-define(PROTOCOLS, [coap, lwm2m] ). +-define(CONFS, [emqx_coap_SUITE, emqx_lwm2m_SUITE]). +-define(CASES, [fun case_coap/0, fun case_lwm2m/0, fun case_emqx_sn/0, fun case_stomp/0]). +-define(AUTHNS, [fun set_http_authn/1]). + +-type auth_controller() :: fun((start | stop) -> ok). + +all() -> + emqx_common_test_helpers:all(?MODULE). + +init_per_suite(Config) -> + _ = application:load(emqx_conf), + ok = emqx_common_test_helpers:load_config(emqx_gateway_schema, init_conf()), + emqx_common_test_helpers:start_apps([emqx_authn, emqx_gateway]), + application:ensure_all_started(cowboy), + Config. + +end_per_suite(_) -> + clear_authn(), + ok = emqx_common_test_helpers:load_config(emqx_gateway_schema, <<>>), + emqx_common_test_helpers:stop_apps([emqx_authn, emqx_gateway]), + application:stop(cowboy), + ok. + +init_per_testcase(_Case, Config) -> + {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), + Config. + +end_per_testcase(_Case, Config) -> + Config. + +%%------------------------------------------------------------------------------ +%% Tests +%%------------------------------------------------------------------------------ +t_authn(_) -> + test_gateway_with_auths(?CASES, ?AUTHNS). + +%%------------------------------------------------------------------------------ +%% Tests +%%------------------------------------------------------------------------------ + +case_coap() -> + Login = fun(URI, Checker) -> + Action = fun(Channel) -> + Req = emqx_coap_SUITE:make_req(post), + Checker(emqx_coap_SUITE:do_request(Channel, URI, Req)) + end, + emqx_coap_SUITE:do(Action) + end, + Prefix = emqx_coap_SUITE:mqtt_prefix(), + RightUrl = Prefix ++ + "/connection?clientid=client1&username=admin&password=public", + Login(RightUrl, ?checkMatch({ok, created, _Data})), + + LeftUrl = Prefix ++ + "/connection?clientid=client1&username=bad&password=bad", + Login(LeftUrl, ?checkMatch({error, bad_request, _Data})), + ok. + +-record(coap_content, {content_format, payload = <<>>}). + +case_lwm2m() -> + MsgId = 12, + Mod = emqx_lwm2m_SUITE, + Epn = "urn:oma:lwm2m:oma:3", + Port = emqx_lwm2m_SUITE:default_port(), + Login = fun(URI, Checker) -> + with_resource(?FUNCTOR(gen_udp:open(0, [binary, {active, false}])), + ?FUNCTOR(Socket, gen_udp:close(Socket)), + fun(Socket) -> + Mod:test_send_coap_request( + Socket, + post, + Mod:sprintf(URI, [Port, Epn]), + #coap_content{content_format = <<"text/plain">>, + payload = <<", , , , ">>}, + [], + MsgId), + + Checker(Mod:test_recv_coap_response(Socket)) + end) + end, + + MakeCheker = fun(Type, Method) -> + fun(Msg) -> + ?assertEqual(Type, emqx_coap_SUITE:get_field(type, Msg)), + ?assertEqual(Method, emqx_coap_SUITE:get_field(method, Msg)) + end + end, + + RightUrl = "coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1&imei=admin&password=public", + Login(RightUrl, MakeCheker(ack, {ok, created})), + + LeftUrl = "coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1&imei=bad&password=bad", + Login(LeftUrl, MakeCheker(ack, {error, bad_request})), + + NoInfoUrl = "coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", + Login(NoInfoUrl, MakeCheker(ack, {error, bad_request})), + ok. + +case_emqx_sn() -> + ok. + +case_stomp() -> + ok. + +%%------------------------------------------------------------------------------ +%% Authenticators +%%------------------------------------------------------------------------------ + +raw_http_auth_config() -> + #{ + mechanism => <<"password_based">>, + enable => <<"true">>, + + backend => <<"http">>, + method => <<"get">>, + url => <<"http://127.0.0.1:33333/auth">>, + body => #{<<"username">> => ?PH_USERNAME, <<"password">> => ?PH_PASSWORD}, + headers => #{<<"X-Test-Header">> => <<"Test Value">>} + }. + +set_http_authn(start) -> + {ok, _} = emqx_authn_http_test_server:start_link(?HTTP_PORT, ?HTTP_PATH), + + AuthConfig = raw_http_auth_config(), + + Set = fun(Protocol) -> + Chain = emqx_authentication:global_chain(Protocol), + emqx_authn_test_lib:delete_authenticators([authentication], Chain), + + {ok, _} = emqx:update_config( + ?PATH, + {create_authenticator, Chain, AuthConfig}), + + {ok, [#{provider := emqx_authn_http}]} = emqx_authentication:list_authenticators(Chain) + end, + lists:foreach(Set, ?PROTOCOLS), + + Handler = fun(Req0, State) -> + ct:pal("Req:~p State:~p~n", [Req0, State]), + case cowboy_req:match_qs([username, password], Req0) of + #{username := <<"admin">>, + password := <<"public">>} -> + Req = cowboy_req:reply(200, Req0); + _ -> + Req = cowboy_req:reply(400, Req0) + end, + {ok, Req, State} + end, + emqx_authn_http_test_server:set_handler(Handler); +set_http_authn(stop) -> + ok = emqx_authn_http_test_server:stop(). + +clear_authn() -> + Clear = fun(Protocol) -> + Chain = emqx_authentication:global_chain(Protocol), + emqx_authn_test_lib:delete_authenticators([authentication], Chain) + end, + lists:foreach(Clear, ?PROTOCOLS). + +%%------------------------------------------------------------------------------ +%% Helpers +%%------------------------------------------------------------------------------ +-spec test_gateway_with_auths(_, list(auth_controller())) -> ok. +test_gateway_with_auths(Gateways, Authenticators) -> + Cases = [{Auth, Gateways} || Auth <- Authenticators], + test_gateway_with_auths(Cases). + +test_gateway_with_auths([{Auth, Gateways} | T]) -> + {name, Name} = erlang:fun_info(Auth, name), + ct:pal("start auth:~p~n", [Name]), + Auth(start), + lists:foreach(fun(Gateway) -> + {name, GwName} = erlang:fun_info(Gateway, name), + ct:pal("start gateway case:~p~n", [GwName]), + Gateway() + end, Gateways), + ct:pal("stop auth:~p~n", [Name]), + Auth(stop), + test_gateway_with_auths(T); +test_gateway_with_auths([]) -> + ok. + +init_conf() -> + merge_conf([X:default_config() || X <- ?CONFS], []). + +merge_conf([Conf | T], Acc) -> + case re:run(Conf, "\s*gateway\\.(.*)", [global, {capture, all_but_first, list}, dotall]) of + {match, [[Content]]} -> + merge_conf(T, [Content | Acc]); + _ -> + merge_conf(T, Acc) + end; +merge_conf([ ], Acc) -> + erlang:list_to_binary("gateway{" ++ string:join(Acc, ",") ++ "}"). + +with_resource(Init, Close, Fun) -> + Res = case Init() of + {ok, X} -> X; + Other -> Other + end, + try + Fun(Res) + catch C:R:S -> + Close(Res), + erlang:raise(C, R, S) + end. diff --git a/apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl b/apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl index c32bf7b70..39a4ac7aa 100644 --- a/apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl @@ -186,6 +186,12 @@ end_per_testcase(_AllTestCase, Config) -> emqtt:disconnect(?config(emqx_c, Config)), ok = application:stop(emqx_gateway). +default_config() -> + ?CONF_DEFAULT. + +default_port() -> + ?PORT. + %%-------------------------------------------------------------------- %% Cases %%--------------------------------------------------------------------