emqx_gateway_api_SUITE.erl 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2021-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_gateway_api_SUITE).
  17. -compile(export_all).
  18. -compile(nowarn_export_all).
  19. -import(
  20. emqx_gateway_test_utils,
  21. [
  22. assert_confs/2,
  23. assert_fields_exist/2,
  24. request/2,
  25. request/3,
  26. ssl_server_opts/0,
  27. ssl_client_opts/0
  28. ]
  29. ).
  30. -include_lib("eunit/include/eunit.hrl").
  31. -include_lib("snabbkaffe/include/snabbkaffe.hrl").
  32. %% this parses to #{}, will not cause config cleanup
  33. %% so we will need call emqx_config:erase
  34. -define(CONF_DEFAULT, <<"gateway {}">>).
  35. %%--------------------------------------------------------------------
  36. %% Setup
  37. %%--------------------------------------------------------------------
  38. all() -> emqx_common_test_helpers:all(?MODULE).
  39. init_per_suite(Conf) ->
  40. Apps = emqx_cth_suite:start(
  41. [
  42. emqx_conf,
  43. emqx_auth,
  44. emqx_auth_mnesia,
  45. emqx_management,
  46. {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"},
  47. {emqx_gateway, ?CONF_DEFAULT}
  48. | emqx_gateway_test_utils:all_gateway_apps()
  49. ],
  50. #{work_dir => emqx_cth_suite:work_dir(Conf)}
  51. ),
  52. _ = emqx_common_test_http:create_default_app(),
  53. [{suite_apps, Apps} | Conf].
  54. end_per_suite(Conf) ->
  55. _ = emqx_common_test_http:delete_default_app(),
  56. ok = emqx_cth_suite:stop(proplists:get_value(suite_apps, Conf)).
  57. init_per_testcase(t_gateway_fail, Config) ->
  58. meck:expect(
  59. emqx_gateway_conf,
  60. update_gateway,
  61. fun
  62. (stomp, V) -> {error, {badconf, #{key => gw, value => V, reason => test_error}}};
  63. (coap, V) -> error({badconf, #{key => gw, value => V, reason => test_crash}})
  64. end
  65. ),
  66. Config;
  67. init_per_testcase(_, Config) ->
  68. Config.
  69. end_per_testcase(TestCase, Config) ->
  70. case TestCase of
  71. t_gateway_fail -> meck:unload(emqx_gateway_conf);
  72. _ -> ok
  73. end,
  74. [emqx_gateway_conf:unload_gateway(GwName) || GwName <- [stomp, mqttsn, coap, lwm2m, exproto]],
  75. Config.
  76. %%--------------------------------------------------------------------
  77. %% Cases
  78. %%--------------------------------------------------------------------
  79. t_gateways(_) ->
  80. {200, Gateways} = request(get, "/gateways"),
  81. lists:foreach(fun assert_gw_unloaded/1, Gateways),
  82. {200, UnloadedGateways} = request(get, "/gateways?status=unloaded"),
  83. lists:foreach(fun assert_gw_unloaded/1, UnloadedGateways),
  84. {200, NoRunningGateways} = request(get, "/gateways?status=running"),
  85. ?assertEqual([], NoRunningGateways),
  86. {400, BadReqInvalidStatus} = request(get, "/gateways?status=invalid_status"),
  87. assert_bad_request(BadReqInvalidStatus),
  88. {400, BadReqUCStatus} = request(get, "/gateways?status=UNLOADED"),
  89. assert_bad_request(BadReqUCStatus),
  90. ok.
  91. t_gateway(_) ->
  92. ?assertMatch({400, #{code := <<"BAD_REQUEST">>}}, request(get, "/gateways/not_a_known_atom")),
  93. ?assertMatch({400, #{code := <<"BAD_REQUEST">>}}, request(get, "/gateways/undefined")),
  94. {204, _} = request(put, "/gateways/stomp", #{}),
  95. {200, StompGw} = request(get, "/gateways/stomp"),
  96. assert_fields_exist(
  97. [name, status, enable, created_at, started_at],
  98. StompGw
  99. ),
  100. {204, _} = request(put, "/gateways/stomp", #{enable => true}),
  101. {200, #{enable := true}} = request(get, "/gateways/stomp"),
  102. {204, _} = request(put, "/gateways/stomp", #{enable => false}),
  103. {200, #{enable := false}} = request(get, "/gateways/stomp"),
  104. ?assertMatch({400, #{code := <<"BAD_REQUEST">>}}, request(put, "/gateways/undefined", #{})),
  105. {400, _} = request(put, "/gateways/stomp", #{bad_key => "foo"}),
  106. ok.
  107. t_gateway_fail(_) ->
  108. {204, _} = request(put, "/gateways/stomp", #{}),
  109. {400, _} = request(put, "/gateways/stomp", #{}),
  110. {204, _} = request(put, "/gateways/coap", #{}),
  111. {400, _} = request(put, "/gateways/coap", #{}),
  112. ok.
  113. t_gateway_enable(_) ->
  114. {204, _} = request(put, "/gateways/stomp", #{}),
  115. {200, #{enable := Enable}} = request(get, "/gateways/stomp"),
  116. NotEnable = not Enable,
  117. {204, _} = request(put, "/gateways/stomp/enable/" ++ atom_to_list(NotEnable), undefined),
  118. {200, #{enable := NotEnable}} = request(get, "/gateways/stomp"),
  119. {204, _} = request(put, "/gateways/stomp/enable/" ++ atom_to_list(Enable), undefined),
  120. {200, #{enable := Enable}} = request(get, "/gateways/stomp"),
  121. ?assertMatch(
  122. {400, #{code := <<"BAD_REQUEST">>}},
  123. request(put, "/gateways/undefined/enable/true", undefined)
  124. ),
  125. ?assertMatch(
  126. {400, #{code := <<"BAD_REQUEST">>}},
  127. request(put, "/gateways/not_a_known_atom/enable/true", undefined)
  128. ),
  129. {404, _} = request(put, "/gateways/coap/enable/true", undefined),
  130. ok.
  131. t_gateway_stomp(_) ->
  132. {200, Gw} = request(get, "/gateways/stomp"),
  133. assert_gw_unloaded(Gw),
  134. GwConf = #{
  135. name => <<"stomp">>,
  136. frame => #{
  137. max_headers => 5,
  138. max_headers_length => 100,
  139. max_body_length => 100
  140. },
  141. listeners => [
  142. #{name => <<"def">>, type => <<"tcp">>, bind => <<"61613">>}
  143. ]
  144. },
  145. {204, _} = request(put, "/gateways/stomp", GwConf),
  146. {200, ConfResp} = request(get, "/gateways/stomp"),
  147. assert_confs(GwConf, ConfResp),
  148. GwConf2 = emqx_utils_maps:deep_merge(GwConf, #{frame => #{max_headers => 10}}),
  149. {204, _} = request(put, "/gateways/stomp", maps:without([name, listeners], GwConf2)),
  150. {200, ConfResp2} = request(get, "/gateways/stomp"),
  151. assert_confs(GwConf2, ConfResp2),
  152. ok.
  153. t_gateway_mqttsn(_) ->
  154. {200, Gw} = request(get, "/gateways/mqttsn"),
  155. assert_gw_unloaded(Gw),
  156. GwConf = #{
  157. name => <<"mqttsn">>,
  158. gateway_id => 1,
  159. broadcast => true,
  160. predefined => [#{id => 1, topic => <<"t/a">>}],
  161. enable_qos3 => true,
  162. listeners => [
  163. #{name => <<"def">>, type => <<"udp">>, bind => <<"1884">>}
  164. ]
  165. },
  166. {204, _} = request(put, "/gateways/mqttsn", GwConf),
  167. {200, ConfResp} = request(get, "/gateways/mqttsn"),
  168. assert_confs(GwConf, ConfResp),
  169. GwConf2 = emqx_utils_maps:deep_merge(GwConf, #{predefined => []}),
  170. {204, _} = request(put, "/gateways/mqttsn", maps:without([name, listeners], GwConf2)),
  171. {200, ConfResp2} = request(get, "/gateways/mqttsn"),
  172. assert_confs(GwConf2, ConfResp2),
  173. ok.
  174. t_gateway_coap(_) ->
  175. {200, Gw} = request(get, "/gateways/coap"),
  176. assert_gw_unloaded(Gw),
  177. GwConf = #{
  178. name => <<"coap">>,
  179. heartbeat => <<"60s">>,
  180. connection_required => true,
  181. listeners => [
  182. #{name => <<"def">>, type => <<"udp">>, bind => <<"5683">>}
  183. ]
  184. },
  185. {204, _} = request(put, "/gateways/coap", GwConf),
  186. {200, ConfResp} = request(get, "/gateways/coap"),
  187. assert_confs(GwConf, ConfResp),
  188. GwConf2 = emqx_utils_maps:deep_merge(GwConf, #{heartbeat => <<"10s">>}),
  189. {204, _} = request(put, "/gateways/coap", maps:without([name, listeners], GwConf2)),
  190. {200, ConfResp2} = request(get, "/gateways/coap"),
  191. assert_confs(GwConf2, ConfResp2),
  192. ok.
  193. t_gateway_lwm2m(_) ->
  194. {200, Gw} = request(get, "/gateways/lwm2m"),
  195. assert_gw_unloaded(Gw),
  196. XmlDir = filename:join(
  197. [
  198. emqx_common_test_helpers:proj_root(),
  199. "apps",
  200. "emqx_gateway_lwm2m",
  201. "lwm2m_xml"
  202. ]
  203. ),
  204. GwConf = #{
  205. name => <<"lwm2m">>,
  206. xml_dir => list_to_binary(XmlDir),
  207. lifetime_min => <<"1s">>,
  208. lifetime_max => <<"1000s">>,
  209. qmode_time_window => <<"30s">>,
  210. auto_observe => true,
  211. translators => #{
  212. command => #{topic => <<"dn/#">>},
  213. response => #{topic => <<"up/resp">>},
  214. notify => #{topic => <<"up/resp">>},
  215. register => #{topic => <<"up/resp">>},
  216. update => #{topic => <<"up/resp">>}
  217. },
  218. listeners => [
  219. #{name => <<"def">>, type => <<"udp">>, bind => <<"5783">>}
  220. ]
  221. },
  222. {204, _} = request(put, "/gateways/lwm2m", GwConf),
  223. {200, ConfResp} = request(get, "/gateways/lwm2m"),
  224. assert_confs(GwConf, ConfResp),
  225. GwConf2 = emqx_utils_maps:deep_merge(GwConf, #{qmode_time_window => <<"10s">>}),
  226. {204, _} = request(put, "/gateways/lwm2m", maps:without([name, listeners], GwConf2)),
  227. {200, ConfResp2} = request(get, "/gateways/lwm2m"),
  228. assert_confs(GwConf2, ConfResp2),
  229. ok.
  230. t_gateway_exproto(_) ->
  231. {200, Gw} = request(get, "/gateways/exproto"),
  232. assert_gw_unloaded(Gw),
  233. GwConf = #{
  234. name => <<"exproto">>,
  235. server => #{bind => <<"9100">>},
  236. handler => #{address => <<"http://127.0.0.1:9001">>},
  237. listeners => [
  238. #{name => <<"def">>, type => <<"tcp">>, bind => <<"7993">>}
  239. ]
  240. },
  241. {204, _} = request(put, "/gateways/exproto", GwConf),
  242. {200, ConfResp} = request(get, "/gateways/exproto"),
  243. assert_confs(GwConf, ConfResp),
  244. GwConf2 = emqx_utils_maps:deep_merge(GwConf, #{server => #{bind => <<"9200">>}}),
  245. {204, _} = request(put, "/gateways/exproto", maps:without([name, listeners], GwConf2)),
  246. {200, ConfResp2} = request(get, "/gateways/exproto"),
  247. assert_confs(GwConf2, ConfResp2),
  248. ok.
  249. t_gateway_exproto_with_ssl(_) ->
  250. {200, Gw} = request(get, "/gateways/exproto"),
  251. assert_gw_unloaded(Gw),
  252. SslSvrOpts = ssl_server_opts(),
  253. SslCliOpts = ssl_client_opts(),
  254. GwConf = #{
  255. name => <<"exproto">>,
  256. server => #{
  257. bind => <<"9100">>,
  258. ssl_options => SslSvrOpts
  259. },
  260. handler => #{
  261. address => <<"http://127.0.0.1:9001">>,
  262. ssl_options => SslCliOpts#{enable => true}
  263. },
  264. listeners => [
  265. #{name => <<"def">>, type => <<"tcp">>, bind => <<"7993">>}
  266. ]
  267. },
  268. {204, _} = request(put, "/gateways/exproto", GwConf),
  269. {200, ConfResp} = request(get, "/gateways/exproto"),
  270. assert_confs(GwConf, ConfResp),
  271. GwConf2 = emqx_utils_maps:deep_merge(GwConf, #{
  272. server => #{
  273. bind => <<"9200">>,
  274. ssl_options => SslCliOpts
  275. }
  276. }),
  277. {204, _} = request(put, "/gateways/exproto", maps:without([name, listeners], GwConf2)),
  278. {200, ConfResp2} = request(get, "/gateways/exproto"),
  279. assert_confs(GwConf2, ConfResp2),
  280. ok.
  281. t_authn(_) ->
  282. init_gw("stomp"),
  283. AuthConf = #{
  284. mechanism => <<"password_based">>,
  285. backend => <<"built_in_database">>,
  286. user_id_type => <<"clientid">>
  287. },
  288. {201, _} = request(post, "/gateways/stomp/authentication", AuthConf),
  289. {200, ConfResp} = request(get, "/gateways/stomp/authentication"),
  290. assert_confs(AuthConf, ConfResp),
  291. AuthConf2 = maps:merge(AuthConf, #{user_id_type => <<"username">>}),
  292. {200, _} = request(put, "/gateways/stomp/authentication", AuthConf2),
  293. {200, ConfResp2} = request(get, "/gateways/stomp/authentication"),
  294. assert_confs(AuthConf2, ConfResp2),
  295. {204, _} = request(delete, "/gateways/stomp/authentication"),
  296. {204, _} = request(get, "/gateways/stomp/authentication"),
  297. ok.
  298. t_authn_data_mgmt(_) ->
  299. init_gw("stomp"),
  300. AuthConf = #{
  301. mechanism => <<"password_based">>,
  302. backend => <<"built_in_database">>,
  303. user_id_type => <<"clientid">>
  304. },
  305. {201, _} = request(post, "/gateways/stomp/authentication", AuthConf),
  306. {200, ConfResp} =
  307. ?retry(10, 10, {200, _} = request(get, "/gateways/stomp/authentication")),
  308. assert_confs(AuthConf, ConfResp),
  309. User1 = #{
  310. user_id => <<"test">>,
  311. password => <<"123456">>,
  312. is_superuser => false
  313. },
  314. {201, _} = request(post, "/gateways/stomp/authentication/users", User1),
  315. {200, #{data := [UserRespd1]}} = request(get, "/gateways/stomp/authentication/users"),
  316. assert_confs(UserRespd1, User1),
  317. {200, UserRespd2} = request(
  318. get,
  319. "/gateways/stomp/authentication/users/test"
  320. ),
  321. assert_confs(UserRespd2, User1),
  322. {200, UserRespd3} = request(
  323. put,
  324. "/gateways/stomp/authentication/users/test",
  325. #{
  326. password => <<"654321">>,
  327. is_superuser => true
  328. }
  329. ),
  330. assert_confs(UserRespd3, User1#{is_superuser => true}),
  331. {200, UserRespd4} = request(
  332. get,
  333. "/gateways/stomp/authentication/users/test"
  334. ),
  335. assert_confs(UserRespd4, User1#{is_superuser => true}),
  336. {204, _} = request(delete, "/gateways/stomp/authentication/users/test"),
  337. {200, #{data := []}} = request(
  338. get,
  339. "/gateways/stomp/authentication/users"
  340. ),
  341. ImportUri = emqx_dashboard_api_test_helpers:uri(
  342. ["gateways", "stomp", "authentication", "import_users"]
  343. ),
  344. Dir = code:lib_dir(emqx_auth, test),
  345. JSONFileName = filename:join([Dir, <<"data/user-credentials.json">>]),
  346. {ok, JSONData} = file:read_file(JSONFileName),
  347. {ok, 204, _} = emqx_dashboard_api_test_helpers:multipart_formdata_request(ImportUri, [], [
  348. {filename, "user-credentials.json", JSONData}
  349. ]),
  350. CSVFileName = filename:join([Dir, <<"data/user-credentials.csv">>]),
  351. {ok, CSVData} = file:read_file(CSVFileName),
  352. {ok, 204, _} = emqx_dashboard_api_test_helpers:multipart_formdata_request(ImportUri, [], [
  353. {filename, "user-credentials.csv", CSVData}
  354. ]),
  355. {204, _} = request(delete, "/gateways/stomp/authentication"),
  356. {204, _} = request(get, "/gateways/stomp/authentication"),
  357. ok.
  358. t_listeners_tcp(_) ->
  359. {204, _} = request(put, "/gateways/stomp", #{}),
  360. {404, _} = request(get, "/gateways/stomp/listeners"),
  361. LisConf = #{
  362. name => <<"def">>,
  363. type => <<"tcp">>,
  364. bind => <<"127.0.0.1:61613">>
  365. },
  366. {201, _} = request(post, "/gateways/stomp/listeners", LisConf),
  367. {200, ConfResp} = request(get, "/gateways/stomp/listeners"),
  368. assert_confs([LisConf], ConfResp),
  369. {200, ConfResp1} = request(get, "/gateways/stomp/listeners/stomp:tcp:def"),
  370. assert_confs(LisConf, ConfResp1),
  371. LisConf2 = maps:merge(LisConf, #{bind => <<"127.0.0.1:61614">>}),
  372. {200, _} = request(
  373. put,
  374. "/gateways/stomp/listeners/stomp:tcp:def",
  375. LisConf2
  376. ),
  377. {200, ConfResp2} = request(get, "/gateways/stomp/listeners/stomp:tcp:def"),
  378. assert_confs(LisConf2, ConfResp2),
  379. {204, _} = request(delete, "/gateways/stomp/listeners/stomp:tcp:def"),
  380. {404, _} = request(get, "/gateways/stomp/listeners/stomp:tcp:def"),
  381. {404, _} = request(delete, "/gateways/stomp/listeners/stomp:tcp:def"),
  382. ok.
  383. t_listeners_max_conns(_) ->
  384. {204, _} = request(put, "/gateways/stomp", #{}),
  385. {404, _} = request(get, "/gateways/stomp/listeners"),
  386. LisConf = #{
  387. name => <<"def">>,
  388. type => <<"tcp">>,
  389. bind => <<"127.0.0.1:61613">>,
  390. max_connections => 1024
  391. },
  392. {201, _} = request(post, "/gateways/stomp/listeners", LisConf),
  393. {200, ConfResp} = request(get, "/gateways/stomp/listeners"),
  394. assert_confs([LisConf], ConfResp),
  395. {200, ConfResp1} = request(get, "/gateways/stomp/listeners/stomp:tcp:def"),
  396. assert_confs(LisConf, ConfResp1),
  397. LisConf2 = maps:merge(LisConf, #{max_connections => <<"infinity">>}),
  398. {200, _} = request(
  399. put,
  400. "/gateways/stomp/listeners/stomp:tcp:def",
  401. LisConf2
  402. ),
  403. {200, ConfResp2} = request(get, "/gateways/stomp/listeners/stomp:tcp:def"),
  404. assert_confs(LisConf2, ConfResp2),
  405. {200, [Listeners]} = request(get, "/gateways/stomp/listeners"),
  406. ?assertMatch(#{max_connections := <<"infinity">>}, Listeners),
  407. {200, Gateways} = request(get, "/gateways"),
  408. [StompGwOverview] = lists:filter(
  409. fun(Gw) -> maps:get(name, Gw) =:= <<"stomp">> end,
  410. Gateways
  411. ),
  412. ?assertMatch(#{max_connections := <<"infinity">>}, StompGwOverview),
  413. {204, _} = request(delete, "/gateways/stomp/listeners/stomp:tcp:def"),
  414. {404, _} = request(get, "/gateways/stomp/listeners/stomp:tcp:def"),
  415. ok.
  416. t_listeners_authn(_) ->
  417. GwConf = #{
  418. name => <<"stomp">>,
  419. listeners => [
  420. #{
  421. name => <<"def">>,
  422. type => <<"tcp">>,
  423. bind => <<"127.0.0.1:61613">>
  424. }
  425. ]
  426. },
  427. ConfResp = init_gw("stomp", GwConf),
  428. assert_confs(GwConf, ConfResp),
  429. AuthConf = #{
  430. mechanism => <<"password_based">>,
  431. backend => <<"built_in_database">>,
  432. user_id_type => <<"clientid">>
  433. },
  434. Path = "/gateways/stomp/listeners/stomp:tcp:def/authentication",
  435. {201, _} = request(post, Path, AuthConf),
  436. {200, ConfResp2} = request(get, Path),
  437. assert_confs(AuthConf, ConfResp2),
  438. AuthConf2 = maps:merge(AuthConf, #{user_id_type => <<"username">>}),
  439. {200, _} = request(put, Path, AuthConf2),
  440. {200, ConfResp3} = request(get, Path),
  441. assert_confs(AuthConf2, ConfResp3),
  442. {404, _} = request(get, Path ++ "/users/not_exists"),
  443. {404, _} = request(delete, Path ++ "/users/not_exists"),
  444. {204, _} = request(delete, Path),
  445. %% FIXME: 204?
  446. {204, _} = request(get, Path),
  447. BadPath = "/gateways/stomp/listeners/stomp:tcp:not_exists/authentication/users/foo",
  448. {404, _} = request(get, BadPath),
  449. {404, _} = request(delete, BadPath),
  450. {404, _} = request(get, "/gateways/stomp/listeners/not_exists"),
  451. {404, _} = request(delete, "/gateways/stomp/listeners/not_exists"),
  452. ok.
  453. t_listeners_authn_data_mgmt(_) ->
  454. GwConf = #{
  455. name => <<"stomp">>,
  456. listeners => [
  457. #{
  458. name => <<"def">>,
  459. type => <<"tcp">>,
  460. bind => <<"127.0.0.1:61613">>
  461. }
  462. ]
  463. },
  464. {204, _} = request(put, "/gateways/stomp", GwConf),
  465. {200, ConfResp} = request(get, "/gateways/stomp"),
  466. assert_confs(GwConf, ConfResp),
  467. AuthConf = #{
  468. mechanism => <<"password_based">>,
  469. backend => <<"built_in_database">>,
  470. user_id_type => <<"clientid">>
  471. },
  472. Path = "/gateways/stomp/listeners/stomp:tcp:def/authentication",
  473. {201, _} = request(post, Path, AuthConf),
  474. {200, ConfResp2} = request(get, Path),
  475. assert_confs(AuthConf, ConfResp2),
  476. User1 = #{
  477. user_id => <<"test">>,
  478. password => <<"123456">>,
  479. is_superuser => false
  480. },
  481. {201, _} = request(
  482. post,
  483. "/gateways/stomp/listeners/stomp:tcp:def/authentication/users",
  484. User1
  485. ),
  486. {200, #{data := [UserRespd1]}} = request(
  487. get,
  488. Path ++ "/users"
  489. ),
  490. assert_confs(UserRespd1, User1),
  491. {200, UserRespd2} = request(
  492. get,
  493. Path ++ "/users/test"
  494. ),
  495. assert_confs(UserRespd2, User1),
  496. {200, UserRespd3} = request(
  497. put,
  498. Path ++ "/users/test",
  499. #{password => <<"654321">>, is_superuser => true}
  500. ),
  501. assert_confs(UserRespd3, User1#{is_superuser => true}),
  502. {200, UserRespd4} = request(
  503. get,
  504. Path ++ "/users/test"
  505. ),
  506. assert_confs(UserRespd4, User1#{is_superuser => true}),
  507. {204, _} = request(
  508. delete,
  509. Path ++ "/users/test"
  510. ),
  511. {200, #{data := []}} = request(
  512. get,
  513. Path ++ "/users"
  514. ),
  515. ImportUri = emqx_dashboard_api_test_helpers:uri(
  516. ["gateways", "stomp", "listeners", "stomp:tcp:def", "authentication", "import_users"]
  517. ),
  518. Dir = code:lib_dir(emqx_auth, test),
  519. JSONFileName = filename:join([Dir, <<"data/user-credentials.json">>]),
  520. {ok, JSONData} = file:read_file(JSONFileName),
  521. {ok, 204, _} = emqx_dashboard_api_test_helpers:multipart_formdata_request(ImportUri, [], [
  522. {filename, "user-credentials.json", JSONData}
  523. ]),
  524. CSVFileName = filename:join([Dir, <<"data/user-credentials.csv">>]),
  525. {ok, CSVData} = file:read_file(CSVFileName),
  526. {ok, 204, _} = emqx_dashboard_api_test_helpers:multipart_formdata_request(ImportUri, [], [
  527. {filename, "user-credentials.csv", CSVData}
  528. ]),
  529. ok.
  530. t_clients(_) ->
  531. GwConf = #{
  532. name => <<"mqttsn">>,
  533. gateway_id => 1,
  534. broadcast => true,
  535. predefined => [#{id => 1, topic => <<"t/a">>}],
  536. enable_qos3 => true,
  537. listeners => [
  538. #{name => <<"def">>, type => <<"udp">>, bind => <<"1884">>}
  539. ]
  540. },
  541. init_gw("mqttsn", GwConf),
  542. Path = "/gateways/mqttsn/clients",
  543. MyClient = Path ++ "/my_client",
  544. MyClientSubscriptions = MyClient ++ "/subscriptions",
  545. {200, NoClients} = request(get, Path),
  546. ?assertMatch(#{data := []}, NoClients),
  547. ClientSocket = emqx_gateway_test_utils:sn_client_connect(<<"my_client">>),
  548. {200, _} = request(get, MyClient),
  549. {200, Clients} = request(get, Path),
  550. ?assertMatch(#{data := [#{clientid := <<"my_client">>}]}, Clients),
  551. {201, _} = request(post, MyClientSubscriptions, #{topic => <<"test/topic">>}),
  552. {200, Subscriptions} = request(get, MyClientSubscriptions),
  553. ?assertMatch([#{topic := <<"test/topic">>}], Subscriptions),
  554. {204, _} = request(delete, MyClientSubscriptions ++ "/test%2Ftopic"),
  555. {200, []} = request(get, MyClientSubscriptions),
  556. {404, _} = request(delete, MyClientSubscriptions ++ "/test%2Ftopic"),
  557. {204, _} = request(delete, MyClient),
  558. {404, _} = request(delete, MyClient),
  559. {404, _} = request(get, MyClient),
  560. {404, _} = request(get, MyClientSubscriptions),
  561. {404, _} = request(post, MyClientSubscriptions, #{topic => <<"foo">>}),
  562. {404, _} = request(delete, MyClientSubscriptions ++ "/topic"),
  563. {200, NoClients2} = request(get, Path),
  564. ?assertMatch(#{data := []}, NoClients2),
  565. emqx_gateway_test_utils:sn_client_disconnect(ClientSocket),
  566. ok.
  567. t_authn_fuzzy_search(_) ->
  568. init_gw("stomp"),
  569. AuthConf = #{
  570. mechanism => <<"password_based">>,
  571. backend => <<"built_in_database">>,
  572. user_id_type => <<"clientid">>
  573. },
  574. {201, _} = request(post, "/gateways/stomp/authentication", AuthConf),
  575. {200, ConfResp} = request(get, "/gateways/stomp/authentication"),
  576. assert_confs(AuthConf, ConfResp),
  577. Checker = fun({User, Fuzzy}) ->
  578. {200, #{data := [UserRespd]}} = request(
  579. get, "/gateways/stomp/authentication/users", Fuzzy
  580. ),
  581. assert_confs(UserRespd, User)
  582. end,
  583. Create = fun(User) ->
  584. {201, _} = request(post, "/gateways/stomp/authentication/users", User)
  585. end,
  586. UserDatas = [
  587. #{
  588. user_id => <<"test">>,
  589. password => <<"123456">>,
  590. is_superuser => false
  591. },
  592. #{
  593. user_id => <<"foo">>,
  594. password => <<"123456">>,
  595. is_superuser => true
  596. }
  597. ],
  598. FuzzyDatas = [[{<<"like_user_id">>, <<"test">>}], [{<<"is_superuser">>, <<"true">>}]],
  599. lists:foreach(Create, UserDatas),
  600. lists:foreach(Checker, lists:zip(UserDatas, FuzzyDatas)),
  601. {204, _} = request(delete, "/gateways/stomp/authentication"),
  602. {204, _} = request(get, "/gateways/stomp/authentication"),
  603. ok.
  604. %%--------------------------------------------------------------------
  605. %% Helpers
  606. init_gw(GwName) ->
  607. init_gw(GwName, #{}).
  608. init_gw(GwName, GwConf) ->
  609. {204, _} = request(put, "/gateways/" ++ GwName, GwConf),
  610. ?retry(
  611. 10,
  612. 10,
  613. begin
  614. {200, #{status := Status} = RespConf} = request(get, "/gateways/" ++ GwName),
  615. false = (Status == <<"unloaded">>),
  616. RespConf
  617. end
  618. ).
  619. %%--------------------------------------------------------------------
  620. %% Asserts
  621. assert_gw_unloaded(Gateway) ->
  622. ?assertEqual(<<"unloaded">>, maps:get(status, Gateway)).
  623. assert_bad_request(BadReq) ->
  624. ?assertEqual(<<"BAD_REQUEST">>, maps:get(code, BadReq)).
  625. assert_not_found(NotFoundReq) ->
  626. ?assertEqual(<<"RESOURCE_NOT_FOUND">>, maps:get(code, NotFoundReq)).