emqx_authn_api_mnesia_SUITE.erl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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. %% http://www.apache.org/licenses/LICENSE-2.0
  8. %%
  9. %% Unless required by applicable law or agreed to in writing, software
  10. %% distributed under the License is distributed on an "AS IS" BASIS,
  11. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. %% See the License for the specific language governing permissions and
  13. %% limitations under the License.
  14. %%--------------------------------------------------------------------
  15. -module(emqx_authn_api_mnesia_SUITE).
  16. -compile(nowarn_export_all).
  17. -compile(export_all).
  18. -import(emqx_dashboard_api_test_helpers, [multipart_formdata_request/3]).
  19. -import(emqx_mgmt_api_test_util, [request/3, uri/1]).
  20. -include_lib("emqx_auth/include/emqx_authn.hrl").
  21. -include_lib("eunit/include/eunit.hrl").
  22. -include_lib("common_test/include/ct.hrl").
  23. -define(TCP_DEFAULT, 'tcp:default').
  24. all() ->
  25. emqx_common_test_helpers:all(?MODULE).
  26. groups() ->
  27. [].
  28. init_per_testcase(_Case, Config) ->
  29. emqx_authn_test_lib:delete_authenticators(
  30. [?CONF_NS_ATOM],
  31. ?GLOBAL
  32. ),
  33. emqx_authn_test_lib:delete_authenticators(
  34. [listeners, tcp, default, ?CONF_NS_ATOM],
  35. ?TCP_DEFAULT
  36. ),
  37. Config.
  38. end_per_testcase(_, Config) ->
  39. Config.
  40. init_per_suite(Config) ->
  41. Apps = emqx_cth_suite:start(
  42. [
  43. emqx,
  44. emqx_conf,
  45. emqx_auth,
  46. emqx_auth_mnesia,
  47. emqx_management,
  48. {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"}
  49. ],
  50. #{
  51. work_dir => ?config(priv_dir, Config)
  52. }
  53. ),
  54. _ = emqx_common_test_http:create_default_app(),
  55. ?AUTHN:delete_chain(?GLOBAL),
  56. {ok, Chains} = ?AUTHN:list_chains(),
  57. ?assertEqual(length(Chains), 0),
  58. [{apps, Apps} | Config].
  59. end_per_suite(Config) ->
  60. _ = emqx_common_test_http:delete_default_app(),
  61. ok = emqx_cth_suite:stop(?config(apps, Config)),
  62. ok.
  63. %%------------------------------------------------------------------------------
  64. %% Tests
  65. %%------------------------------------------------------------------------------
  66. t_authenticator_users(_) ->
  67. test_authenticator_users([]).
  68. t_authenticator_user(_) ->
  69. test_authenticator_user([]).
  70. t_authenticator_import_users(_) ->
  71. test_authenticator_import_users([]).
  72. % t_listener_authenticator_users(_) ->
  73. % test_authenticator_users(["listeners", ?TCP_DEFAULT]).
  74. % t_listener_authenticator_user(_) ->
  75. % test_authenticator_user(["listeners", ?TCP_DEFAULT]).
  76. % t_listener_authenticator_import_users(_) ->
  77. % test_authenticator_import_users(["listeners", ?TCP_DEFAULT]).
  78. test_authenticator_users(PathPrefix) ->
  79. UsersUri = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "users"]),
  80. {ok, 200, _} = request(
  81. post,
  82. uri(PathPrefix ++ [?CONF_NS]),
  83. emqx_authn_test_lib:built_in_database_example()
  84. ),
  85. {ok, Client} = emqtt:start_link(
  86. [
  87. {username, <<"u_event">>},
  88. {clientid, <<"c_event">>},
  89. {proto_ver, v5},
  90. {properties, #{'Session-Expiry-Interval' => 60}}
  91. ]
  92. ),
  93. process_flag(trap_exit, true),
  94. ?assertMatch({error, _}, emqtt:connect(Client)),
  95. timer:sleep(300),
  96. UsersUri0 = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "status"]),
  97. {ok, 200, PageData0} = request(get, UsersUri0),
  98. case PathPrefix of
  99. [] ->
  100. #{
  101. <<"metrics">> := #{
  102. <<"total">> := 1,
  103. <<"success">> := 0,
  104. <<"failed">> := 1
  105. }
  106. } = emqx_utils_json:decode(PageData0, [return_maps]);
  107. ["listeners", 'tcp:default'] ->
  108. #{
  109. <<"metrics">> := #{
  110. <<"total">> := 1,
  111. <<"success">> := 0,
  112. <<"nomatch">> := 1
  113. }
  114. } = emqx_utils_json:decode(PageData0, [return_maps])
  115. end,
  116. InvalidUsers = [
  117. #{clientid => <<"u1">>, password => <<"p1">>},
  118. #{user_id => <<"u2">>},
  119. #{user_id => <<"u3">>, password => <<"p3">>, foobar => <<"foobar">>}
  120. ],
  121. lists:foreach(
  122. fun(User) -> {ok, 400, _} = request(post, UsersUri, User) end,
  123. InvalidUsers
  124. ),
  125. ValidUsers = [
  126. #{user_id => <<"u1">>, password => <<"p1">>},
  127. #{user_id => <<"u2">>, password => <<"p2">>, is_superuser => true},
  128. #{user_id => <<"u3">>, password => <<"p3">>}
  129. ],
  130. lists:foreach(
  131. fun(User) ->
  132. {ok, 201, UserData} = request(post, UsersUri, User),
  133. CreatedUser = emqx_utils_json:decode(UserData, [return_maps]),
  134. ?assertMatch(#{<<"user_id">> := _}, CreatedUser)
  135. end,
  136. ValidUsers
  137. ),
  138. {ok, Client1} = emqtt:start_link(
  139. [
  140. {username, <<"u1">>},
  141. {password, <<"p1">>},
  142. {clientid, <<"c_event">>},
  143. {proto_ver, v5},
  144. {properties, #{'Session-Expiry-Interval' => 60}}
  145. ]
  146. ),
  147. {ok, _} = emqtt:connect(Client1),
  148. timer:sleep(300),
  149. UsersUri01 = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "status"]),
  150. {ok, 200, PageData01} = request(get, UsersUri01),
  151. case PathPrefix of
  152. [] ->
  153. #{
  154. <<"metrics">> := #{
  155. <<"total">> := 2,
  156. <<"success">> := 1,
  157. <<"failed">> := 1
  158. }
  159. } = emqx_utils_json:decode(PageData01, [return_maps]);
  160. ["listeners", 'tcp:default'] ->
  161. #{
  162. <<"metrics">> := #{
  163. <<"total">> := 2,
  164. <<"success">> := 1,
  165. <<"nomatch">> := 1
  166. }
  167. } = emqx_utils_json:decode(PageData01, [return_maps])
  168. end,
  169. {ok, 200, Page1Data} = request(get, UsersUri ++ "?page=1&limit=2"),
  170. #{
  171. <<"data">> := Page1Users,
  172. <<"meta">> :=
  173. #{
  174. <<"page">> := 1,
  175. <<"limit">> := 2,
  176. <<"count">> := 3
  177. }
  178. } =
  179. emqx_utils_json:decode(Page1Data, [return_maps]),
  180. {ok, 200, Page2Data} = request(get, UsersUri ++ "?page=2&limit=2"),
  181. #{
  182. <<"data">> := Page2Users,
  183. <<"meta">> :=
  184. #{
  185. <<"page">> := 2,
  186. <<"limit">> := 2,
  187. <<"count">> := 3
  188. }
  189. } = emqx_utils_json:decode(Page2Data, [return_maps]),
  190. ?assertEqual(2, length(Page1Users)),
  191. ?assertEqual(1, length(Page2Users)),
  192. ?assertEqual(
  193. [<<"u1">>, <<"u2">>, <<"u3">>],
  194. lists:usort([UserId || #{<<"user_id">> := UserId} <- Page1Users ++ Page2Users])
  195. ),
  196. {ok, 200, Super1Data} = request(get, UsersUri ++ "?page=1&limit=3&is_superuser=true"),
  197. #{
  198. <<"data">> := Super1Users,
  199. <<"meta">> :=
  200. #{
  201. <<"page">> := 1,
  202. <<"limit">> := 3,
  203. <<"count">> := 1
  204. }
  205. } = emqx_utils_json:decode(Super1Data, [return_maps]),
  206. ?assertEqual(
  207. [<<"u2">>],
  208. lists:usort([UserId || #{<<"user_id">> := UserId} <- Super1Users])
  209. ),
  210. {ok, 200, Super2Data} = request(get, UsersUri ++ "?page=1&limit=3&is_superuser=false"),
  211. #{
  212. <<"data">> := Super2Users,
  213. <<"meta">> :=
  214. #{
  215. <<"page">> := 1,
  216. <<"limit">> := 3,
  217. <<"count">> := 2
  218. }
  219. } = emqx_utils_json:decode(Super2Data, [return_maps]),
  220. ?assertEqual(
  221. [<<"u1">>, <<"u3">>],
  222. lists:usort([UserId || #{<<"user_id">> := UserId} <- Super2Users])
  223. ),
  224. ok.
  225. test_authenticator_user(PathPrefix) ->
  226. UsersUri = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "users"]),
  227. {ok, 200, _} = request(
  228. post,
  229. uri(PathPrefix ++ [?CONF_NS]),
  230. emqx_authn_test_lib:built_in_database_example()
  231. ),
  232. User = #{user_id => <<"u1">>, password => <<"p1">>},
  233. {ok, 201, _} = request(post, UsersUri, User),
  234. {ok, 404, _} = request(get, UsersUri ++ "/u123"),
  235. {ok, 409, _} = request(post, UsersUri, User),
  236. {ok, 200, UserData} = request(get, UsersUri ++ "/u1"),
  237. FetchedUser = emqx_utils_json:decode(UserData, [return_maps]),
  238. ?assertMatch(#{<<"user_id">> := <<"u1">>}, FetchedUser),
  239. ?assertNotMatch(#{<<"password">> := _}, FetchedUser),
  240. ValidUserUpdates = [
  241. #{password => <<"p1">>},
  242. #{password => <<"p1">>, is_superuser => true}
  243. ],
  244. lists:foreach(
  245. fun(UserUpdate) -> {ok, 200, _} = request(put, UsersUri ++ "/u1", UserUpdate) end,
  246. ValidUserUpdates
  247. ),
  248. InvalidUserUpdates = [#{user_id => <<"u1">>, password => <<"p1">>}],
  249. lists:foreach(
  250. fun(UserUpdate) -> {ok, 400, _} = request(put, UsersUri ++ "/u1", UserUpdate) end,
  251. InvalidUserUpdates
  252. ),
  253. {ok, 404, _} = request(delete, UsersUri ++ "/u123"),
  254. {ok, 204, _} = request(delete, UsersUri ++ "/u1").
  255. test_authenticator_import_users(PathPrefix) ->
  256. ImportUri = uri(
  257. PathPrefix ++
  258. [?CONF_NS, "password_based:built_in_database", "import_users"]
  259. ),
  260. {ok, 200, _} = request(
  261. post,
  262. uri(PathPrefix ++ [?CONF_NS]),
  263. emqx_authn_test_lib:built_in_database_example()
  264. ),
  265. {ok, 400, _} = multipart_formdata_request(ImportUri, [], []),
  266. {ok, 400, _} = multipart_formdata_request(ImportUri, [], [
  267. {filenam, "user-credentials.json", <<>>}
  268. ]),
  269. Dir = code:lib_dir(emqx_auth, test),
  270. JSONFileName = filename:join([Dir, <<"data/user-credentials.json">>]),
  271. CSVFileName = filename:join([Dir, <<"data/user-credentials.csv">>]),
  272. {ok, JSONData} = file:read_file(JSONFileName),
  273. {ok, 204, _} = multipart_formdata_request(ImportUri, [], [
  274. {filename, "user-credentials.json", JSONData}
  275. ]),
  276. {ok, CSVData} = file:read_file(CSVFileName),
  277. {ok, 204, _} = multipart_formdata_request(ImportUri, [], [
  278. {filename, "user-credentials.csv", CSVData}
  279. ]),
  280. %% test application/json
  281. {ok, 204, _} = request(post, ImportUri ++ "?type=hash", emqx_utils_json:decode(JSONData)),
  282. {ok, JSONData1} = file:read_file(filename:join([Dir, <<"data/user-credentials-plain.json">>])),
  283. {ok, 204, _} = request(post, ImportUri ++ "?type=plain", emqx_utils_json:decode(JSONData1)),
  284. %% test application/json; charset=utf-8
  285. {ok, 204, _} = request_with_charset(post, ImportUri ++ "?type=plain", JSONData1),
  286. ok.
  287. %%------------------------------------------------------------------------------
  288. %% Helpers
  289. %%------------------------------------------------------------------------------
  290. request(Method, Url) ->
  291. request(Method, Url, []).
  292. request_with_charset(Method, Url, Body) ->
  293. Headers = [emqx_mgmt_api_test_util:auth_header_()],
  294. Opts = #{compatible_mode => true, httpc_req_opts => [{body_format, binary}]},
  295. Request = {Url, Headers, "application/json; charset=utf-8", Body},
  296. emqx_mgmt_api_test_util:do_request_api(Method, Request, Opts).