emqx_authz_api_mnesia_SUITE.erl 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020-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_authz_api_mnesia_SUITE).
  16. -compile(nowarn_export_all).
  17. -compile(export_all).
  18. -include_lib("emqx_auth/include/emqx_authz.hrl").
  19. -include_lib("eunit/include/eunit.hrl").
  20. -include_lib("common_test/include/ct.hrl").
  21. -import(emqx_mgmt_api_test_util, [request/3, uri/1]).
  22. all() ->
  23. emqx_common_test_helpers:all(?MODULE).
  24. groups() ->
  25. [].
  26. init_per_suite(Config) ->
  27. Apps = emqx_cth_suite:start(
  28. [
  29. {emqx_conf,
  30. "authorization.cache { enable = false },"
  31. "authorization.no_match = deny,"
  32. "authorization.sources = [{type = built_in_database}]"},
  33. emqx,
  34. emqx_auth,
  35. emqx_auth_mnesia,
  36. emqx_management,
  37. {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"}
  38. ],
  39. #{
  40. work_dir => filename:join(?config(priv_dir, Config), ?MODULE)
  41. }
  42. ),
  43. _ = emqx_common_test_http:create_default_app(),
  44. [{suite_apps, Apps} | Config].
  45. end_per_suite(_Config) ->
  46. ok = emqx_cth_suite:stop(?config(suite_apps, _Config)),
  47. _ = emqx_common_test_http:delete_default_app(),
  48. ok.
  49. %%------------------------------------------------------------------------------
  50. %% Testcases
  51. %%------------------------------------------------------------------------------
  52. t_api(_) ->
  53. {ok, 204, _} =
  54. request(
  55. post,
  56. uri(["authorization", "sources", "built_in_database", "rules", "users"]),
  57. [?USERNAME_RULES_EXAMPLE]
  58. ),
  59. {ok, 409, _} =
  60. request(
  61. post,
  62. uri(["authorization", "sources", "built_in_database", "rules", "users"]),
  63. [?USERNAME_RULES_EXAMPLE]
  64. ),
  65. {ok, 200, Request1} =
  66. request(
  67. get,
  68. uri(["authorization", "sources", "built_in_database", "rules", "users"]),
  69. []
  70. ),
  71. #{
  72. <<"data">> := [#{<<"username">> := <<"user1">>, <<"rules">> := Rules1}],
  73. <<"meta">> := #{
  74. <<"count">> := 1,
  75. <<"limit">> := 100,
  76. <<"page">> := 1,
  77. <<"hasnext">> := false
  78. }
  79. } = emqx_utils_json:decode(Request1),
  80. ?assertEqual(?USERNAME_RULES_EXAMPLE_COUNT, length(Rules1)),
  81. {ok, 200, Request1_1} =
  82. request(
  83. get,
  84. uri([
  85. "authorization",
  86. "sources",
  87. "built_in_database",
  88. "rules",
  89. "users?page=1&limit=20&like_username=noexist"
  90. ]),
  91. []
  92. ),
  93. ?assertEqual(
  94. #{
  95. <<"data">> => [],
  96. <<"meta">> => #{
  97. <<"limit">> => 20,
  98. <<"page">> => 1,
  99. <<"hasnext">> => false
  100. }
  101. },
  102. emqx_utils_json:decode(Request1_1)
  103. ),
  104. {ok, 200, Request2} =
  105. request(
  106. get,
  107. uri(["authorization", "sources", "built_in_database", "rules", "users", "user1"]),
  108. []
  109. ),
  110. #{<<"username">> := <<"user1">>, <<"rules">> := Rules1} = emqx_utils_json:decode(Request2),
  111. {ok, 204, _} =
  112. request(
  113. put,
  114. uri(["authorization", "sources", "built_in_database", "rules", "users", "user1"]),
  115. ?USERNAME_RULES_EXAMPLE#{rules => []}
  116. ),
  117. {ok, 200, Request3} =
  118. request(
  119. get,
  120. uri(["authorization", "sources", "built_in_database", "rules", "users", "user1"]),
  121. []
  122. ),
  123. #{<<"username">> := <<"user1">>, <<"rules">> := Rules2} = emqx_utils_json:decode(Request3),
  124. ?assertEqual(0, length(Rules2)),
  125. {ok, 204, _} =
  126. request(
  127. delete,
  128. uri(["authorization", "sources", "built_in_database", "rules", "users", "user1"]),
  129. []
  130. ),
  131. {ok, 404, _} =
  132. request(
  133. get,
  134. uri(["authorization", "sources", "built_in_database", "rules", "users", "user1"]),
  135. []
  136. ),
  137. {ok, 404, _} =
  138. request(
  139. delete,
  140. uri(["authorization", "sources", "built_in_database", "rules", "users", "user1"]),
  141. []
  142. ),
  143. % ensure that db contain a mix of records
  144. {ok, 204, _} =
  145. request(
  146. post,
  147. uri(["authorization", "sources", "built_in_database", "rules", "users"]),
  148. [?USERNAME_RULES_EXAMPLE]
  149. ),
  150. {ok, 204, _} =
  151. request(
  152. post,
  153. uri(["authorization", "sources", "built_in_database", "rules", "clients"]),
  154. [?CLIENTID_RULES_EXAMPLE]
  155. ),
  156. {ok, 409, _} =
  157. request(
  158. post,
  159. uri(["authorization", "sources", "built_in_database", "rules", "clients"]),
  160. [?CLIENTID_RULES_EXAMPLE]
  161. ),
  162. {ok, 200, Request4} =
  163. request(
  164. get,
  165. uri(["authorization", "sources", "built_in_database", "rules", "clients"]),
  166. []
  167. ),
  168. {ok, 200, Request5} =
  169. request(
  170. get,
  171. uri(["authorization", "sources", "built_in_database", "rules", "clients", "client1"]),
  172. []
  173. ),
  174. #{
  175. <<"data">> := [#{<<"clientid">> := <<"client1">>, <<"rules">> := Rules3}],
  176. <<"meta">> := #{<<"count">> := 1, <<"limit">> := 100, <<"page">> := 1}
  177. } =
  178. emqx_utils_json:decode(Request4),
  179. #{<<"clientid">> := <<"client1">>, <<"rules">> := Rules3} = emqx_utils_json:decode(Request5),
  180. ?assertEqual(?CLIENTID_RULES_EXAMPLE_COUNT, length(Rules3)),
  181. {ok, 204, _} =
  182. request(
  183. put,
  184. uri(["authorization", "sources", "built_in_database", "rules", "clients", "client1"]),
  185. ?CLIENTID_RULES_EXAMPLE#{rules => []}
  186. ),
  187. {ok, 200, Request6} =
  188. request(
  189. get,
  190. uri(["authorization", "sources", "built_in_database", "rules", "clients", "client1"]),
  191. []
  192. ),
  193. #{<<"clientid">> := <<"client1">>, <<"rules">> := Rules4} = emqx_utils_json:decode(Request6),
  194. ?assertEqual(0, length(Rules4)),
  195. {ok, 204, _} =
  196. request(
  197. delete,
  198. uri(["authorization", "sources", "built_in_database", "rules", "clients", "client1"]),
  199. []
  200. ),
  201. {ok, 404, _} =
  202. request(
  203. get,
  204. uri(["authorization", "sources", "built_in_database", "rules", "clients", "client1"]),
  205. []
  206. ),
  207. {ok, 404, _} =
  208. request(
  209. delete,
  210. uri(["authorization", "sources", "built_in_database", "rules", "clients", "client1"]),
  211. []
  212. ),
  213. {ok, 204, _} =
  214. request(
  215. post,
  216. uri(["authorization", "sources", "built_in_database", "rules", "all"]),
  217. ?ALL_RULES_EXAMPLE
  218. ),
  219. {ok, 200, Request7} =
  220. request(
  221. get,
  222. uri(["authorization", "sources", "built_in_database", "rules", "all"]),
  223. []
  224. ),
  225. #{<<"rules">> := Rules5} = emqx_utils_json:decode(Request7),
  226. ?assertEqual(?ALL_RULES_EXAMPLE_COUNT, length(Rules5)),
  227. {ok, 204, _} =
  228. request(
  229. delete,
  230. uri(["authorization", "sources", "built_in_database", "rules", "all"]),
  231. []
  232. ),
  233. {ok, 200, Request8} =
  234. request(
  235. get,
  236. uri(["authorization", "sources", "built_in_database", "rules", "all"]),
  237. []
  238. ),
  239. #{<<"rules">> := Rules6} = emqx_utils_json:decode(Request8),
  240. ?assertEqual(0, length(Rules6)),
  241. {ok, 204, _} =
  242. request(
  243. post,
  244. uri(["authorization", "sources", "built_in_database", "rules", "users"]),
  245. [
  246. #{username => erlang:integer_to_binary(N), rules => []}
  247. || N <- lists:seq(1, 20)
  248. ]
  249. ),
  250. {ok, 200, Request9} =
  251. request(
  252. get,
  253. uri(["authorization", "sources", "built_in_database", "rules", "users?page=2&limit=5"]),
  254. []
  255. ),
  256. #{<<"data">> := Data1} = emqx_utils_json:decode(Request9),
  257. ?assertEqual(5, length(Data1)),
  258. {ok, 204, _} =
  259. request(
  260. post,
  261. uri(["authorization", "sources", "built_in_database", "rules", "clients"]),
  262. [
  263. #{clientid => erlang:integer_to_binary(N), rules => []}
  264. || N <- lists:seq(1, 20)
  265. ]
  266. ),
  267. {ok, 200, Request10} =
  268. request(
  269. get,
  270. uri(["authorization", "sources", "built_in_database", "rules", "clients?limit=5"]),
  271. []
  272. ),
  273. #{<<"data">> := Data2} = emqx_utils_json:decode(Request10),
  274. ?assertEqual(5, length(Data2)),
  275. {ok, 400, Msg1} =
  276. request(
  277. delete,
  278. uri(["authorization", "sources", "built_in_database", "rules"]),
  279. []
  280. ),
  281. ?assertMatch({match, _}, re:run(Msg1, "must\sbe\sdisabled\sbefore")),
  282. {ok, 204, _} =
  283. request(
  284. put,
  285. uri(["authorization", "sources", "built_in_database"]),
  286. #{<<"enable">> => true, <<"type">> => <<"built_in_database">>}
  287. ),
  288. %% test idempotence
  289. {ok, 204, _} =
  290. request(
  291. put,
  292. uri(["authorization", "sources", "built_in_database"]),
  293. #{<<"enable">> => true, <<"type">> => <<"built_in_database">>}
  294. ),
  295. {ok, 204, _} =
  296. request(
  297. put,
  298. uri(["authorization", "sources", "built_in_database"]),
  299. #{<<"enable">> => false, <<"type">> => <<"built_in_database">>}
  300. ),
  301. {ok, 204, _} =
  302. request(
  303. delete,
  304. uri(["authorization", "sources", "built_in_database", "rules"]),
  305. []
  306. ),
  307. ?assertEqual(0, emqx_authz_mnesia:record_count()),
  308. Examples = make_examples(emqx_authz_api_mnesia),
  309. ?assertEqual(
  310. 14,
  311. length(Examples)
  312. ),
  313. Fixtures1 = fun() ->
  314. {ok, _, _} =
  315. request(
  316. delete,
  317. uri(["authorization", "sources", "built_in_database", "rules", "all"]),
  318. []
  319. ),
  320. {ok, _, _} =
  321. request(
  322. delete,
  323. uri(["authorization", "sources", "built_in_database", "rules", "users"]),
  324. []
  325. ),
  326. {ok, _, _} =
  327. request(
  328. delete,
  329. uri(["authorization", "sources", "built_in_database", "rules", "clients"]),
  330. []
  331. )
  332. end,
  333. run_examples(Examples, Fixtures1),
  334. Fixtures2 = fun() ->
  335. %% disable/remove built_in_database
  336. {ok, 204, _} =
  337. request(
  338. delete,
  339. uri(["authorization", "sources", "built_in_database"]),
  340. []
  341. )
  342. end,
  343. run_examples(404, Examples, Fixtures2),
  344. ok.
  345. %% test helpers
  346. -define(REPLACEMENTS, #{
  347. ":clientid" => <<"client1">>,
  348. ":username" => <<"user1">>
  349. }).
  350. run_examples(Examples) ->
  351. %% assume all ok
  352. run_examples(
  353. fun
  354. ({ok, Code, _}) when
  355. Code >= 200,
  356. Code =< 299
  357. ->
  358. true;
  359. (_Res) ->
  360. ct:pal("check failed: ~p", [_Res]),
  361. false
  362. end,
  363. Examples
  364. ).
  365. run_examples(Examples, Fixtures) when is_function(Fixtures) ->
  366. Fixtures(),
  367. run_examples(Examples);
  368. run_examples(Check, Examples) when is_function(Check) ->
  369. lists:foreach(
  370. fun({Path, Op, Body} = _Req) ->
  371. ct:pal("req: ~p", [_Req]),
  372. ?assert(
  373. Check(
  374. request(Op, uri(Path), Body)
  375. )
  376. )
  377. end,
  378. Examples
  379. );
  380. run_examples(Code, Examples) when is_number(Code) ->
  381. run_examples(
  382. fun
  383. ({ok, ResCode, _}) when Code =:= ResCode -> true;
  384. (_Res) ->
  385. ct:pal("check failed: ~p", [_Res]),
  386. false
  387. end,
  388. Examples
  389. ).
  390. run_examples(CodeOrCheck, Examples, Fixtures) when is_function(Fixtures) ->
  391. Fixtures(),
  392. run_examples(CodeOrCheck, Examples).
  393. make_examples(ApiMod) ->
  394. make_examples(ApiMod, ?REPLACEMENTS).
  395. -spec make_examples(Mod :: atom()) -> [{Path :: list(), [{Op :: atom(), Body :: term()}]}].
  396. make_examples(ApiMod, Replacements) ->
  397. Paths = ApiMod:paths(),
  398. lists:flatten(
  399. lists:map(
  400. fun(Path) ->
  401. Schema = ApiMod:schema(Path),
  402. lists:map(
  403. fun({Op, OpSchema}) ->
  404. Body =
  405. case maps:get('requestBody', OpSchema, undefined) of
  406. undefined ->
  407. [];
  408. HoconWithExamples ->
  409. maps:get(
  410. value,
  411. hd(
  412. maps:values(
  413. maps:get(
  414. <<"examples">>,
  415. maps:get(examples, HoconWithExamples)
  416. )
  417. )
  418. )
  419. )
  420. end,
  421. {replace_parts(to_parts(Path), Replacements), Op, Body}
  422. end,
  423. lists:sort(
  424. fun op_sort/2, maps:to_list(maps:with([get, put, post, delete], Schema))
  425. )
  426. )
  427. end,
  428. Paths
  429. )
  430. ).
  431. op_sort({post, _}, {_, _}) ->
  432. true;
  433. op_sort({put, _}, {_, _}) ->
  434. true;
  435. op_sort({get, _}, {delete, _}) ->
  436. true;
  437. op_sort(_, _) ->
  438. false.
  439. to_parts(Path) ->
  440. string:tokens(Path, "/").
  441. replace_parts(Parts, Replacements) ->
  442. lists:map(
  443. fun(Part) ->
  444. %% that's the fun part
  445. case maps:is_key(Part, Replacements) of
  446. true ->
  447. maps:get(Part, Replacements);
  448. false ->
  449. Part
  450. end
  451. end,
  452. Parts
  453. ).