emqx_mgmt_api_api_keys_SUITE.erl 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  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. %%
  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_mgmt_api_api_keys_SUITE).
  17. -compile(export_all).
  18. -compile(nowarn_export_all).
  19. -include_lib("eunit/include/eunit.hrl").
  20. -include_lib("common_test/include/ct.hrl").
  21. -include_lib("emqx_dashboard/include/emqx_dashboard_rbac.hrl").
  22. -if(?EMQX_RELEASE_EDITION == ee).
  23. -define(EE_CASES, [
  24. t_ee_create,
  25. t_ee_update,
  26. t_ee_authorize_viewer,
  27. t_ee_authorize_admin,
  28. t_ee_authorize_publisher
  29. ]).
  30. -else.
  31. -define(EE_CASES, []).
  32. -endif.
  33. -define(APP, emqx_app).
  34. -record(?APP, {
  35. name = <<>> :: binary() | '_',
  36. api_key = <<>> :: binary() | '_',
  37. api_secret_hash = <<>> :: binary() | '_',
  38. enable = true :: boolean() | '_',
  39. desc = <<>> :: binary() | '_',
  40. expired_at = 0 :: integer() | undefined | infinity | '_',
  41. created_at = 0 :: integer() | '_'
  42. }).
  43. all() -> [{group, parallel}, {group, sequence}].
  44. suite() -> [{timetrap, {minutes, 1}}].
  45. groups() ->
  46. [
  47. {parallel, [parallel], [t_create, t_update, t_delete, t_authorize, t_create_unexpired_app]},
  48. {parallel, [parallel], ?EE_CASES},
  49. {sequence, [], [t_bootstrap_file, t_bootstrap_file_with_role, t_create_failed]}
  50. ].
  51. init_per_suite(Config) ->
  52. emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_management]),
  53. Config.
  54. end_per_suite(_) ->
  55. emqx_mgmt_api_test_util:end_suite([emqx_conf, emqx_management]).
  56. t_bootstrap_file(_) ->
  57. TestPath = <<"/api/v5/status">>,
  58. Bin = <<"test-1:secret-1\ntest-2:secret-2">>,
  59. File = "./bootstrap_api_keys.txt",
  60. ok = file:write_file(File, Bin),
  61. update_file(File),
  62. ?assertEqual(ok, auth_authorize(TestPath, <<"test-1">>, <<"secret-1">>)),
  63. ?assertEqual(ok, auth_authorize(TestPath, <<"test-2">>, <<"secret-2">>)),
  64. ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-2">>, <<"secret-1">>)),
  65. %% relaunch to check if the table is changed.
  66. Bin1 = <<"test-1:new-secret-1\ntest-2:new-secret-2">>,
  67. ok = file:write_file(File, Bin1),
  68. update_file(File),
  69. ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-1">>, <<"secret-1">>)),
  70. ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-2">>, <<"secret-2">>)),
  71. ?assertEqual(ok, auth_authorize(TestPath, <<"test-1">>, <<"new-secret-1">>)),
  72. ?assertEqual(ok, auth_authorize(TestPath, <<"test-2">>, <<"new-secret-2">>)),
  73. %% not error when bootstrap_file is empty
  74. update_file(<<>>),
  75. update_file("./bootstrap_apps_not_exist.txt"),
  76. ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-1">>, <<"secret-1">>)),
  77. ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-2">>, <<"secret-2">>)),
  78. ?assertEqual(ok, auth_authorize(TestPath, <<"test-1">>, <<"new-secret-1">>)),
  79. ?assertEqual(ok, auth_authorize(TestPath, <<"test-2">>, <<"new-secret-2">>)),
  80. %% bad format
  81. BadBin = <<"test-1:secret-11\ntest-2 secret-12">>,
  82. ok = file:write_file(File, BadBin),
  83. update_file(File),
  84. ?assertMatch({error, #{reason := "invalid_format"}}, emqx_mgmt_auth:init_bootstrap_file()),
  85. ?assertEqual(ok, auth_authorize(TestPath, <<"test-1">>, <<"secret-11">>)),
  86. ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-2">>, <<"secret-12">>)),
  87. update_file(<<>>),
  88. %% skip the empty line
  89. Bin2 = <<"test-3:new-secret-1\n\n\n \ntest-4:new-secret-2">>,
  90. ok = file:write_file(File, Bin2),
  91. update_file(File),
  92. ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-3">>, <<"secret-1">>)),
  93. ?assertMatch({error, _}, auth_authorize(TestPath, <<"test-4">>, <<"secret-2">>)),
  94. ?assertEqual(ok, auth_authorize(TestPath, <<"test-3">>, <<"new-secret-1">>)),
  95. ?assertEqual(ok, auth_authorize(TestPath, <<"test-4">>, <<"new-secret-2">>)),
  96. ok.
  97. t_bootstrap_file_override(_) ->
  98. TestPath = <<"/api/v5/status">>,
  99. Bin =
  100. <<"test-1:secret-1\ntest-1:duplicated-secret-1\ntest-2:secret-2\ntest-2:duplicated-secret-2">>,
  101. File = "./bootstrap_api_keys.txt",
  102. ok = file:write_file(File, Bin),
  103. update_file(File),
  104. ?assertEqual(ok, emqx_mgmt_auth:init_bootstrap_file()),
  105. MatchFun = fun(ApiKey) -> mnesia:match_object(#?APP{api_key = ApiKey, _ = '_'}) end,
  106. ?assertMatch(
  107. {ok, [
  108. #?APP{
  109. name = <<"from_bootstrap_file_18926f94712af04e">>,
  110. api_key = <<"test-1">>
  111. }
  112. ]},
  113. emqx_mgmt_auth:trans(MatchFun, [<<"test-1">>])
  114. ),
  115. ?assertEqual(ok, emqx_mgmt_auth:authorize(TestPath, <<"test-1">>, <<"duplicated-secret-1">>)),
  116. ?assertMatch(
  117. {ok, [
  118. #?APP{
  119. name = <<"from_bootstrap_file_de1c28a2e610e734">>,
  120. api_key = <<"test-2">>
  121. }
  122. ]},
  123. emqx_mgmt_auth:trans(MatchFun, [<<"test-2">>])
  124. ),
  125. ?assertEqual(ok, emqx_mgmt_auth:authorize(TestPath, <<"test-2">>, <<"duplicated-secret-2">>)),
  126. ok.
  127. t_bootstrap_file_dup_override(_) ->
  128. TestPath = <<"/api/v5/status">>,
  129. TestApiKey = <<"test-1">>,
  130. Bin = <<"test-1:secret-1">>,
  131. File = "./bootstrap_api_keys.txt",
  132. ok = file:write_file(File, Bin),
  133. update_file(File),
  134. ?assertEqual(ok, emqx_mgmt_auth:init_bootstrap_file()),
  135. SameAppWithDiffName = #?APP{
  136. name = <<"name-1">>,
  137. api_key = <<"test-1">>,
  138. api_secret_hash = emqx_dashboard_admin:hash(<<"duplicated-secret-1">>),
  139. enable = true,
  140. desc = <<"dup api key">>,
  141. created_at = erlang:system_time(second),
  142. expired_at = infinity
  143. },
  144. WriteFun = fun(App) -> mnesia:write(App) end,
  145. MatchFun = fun(ApiKey) -> mnesia:match_object(#?APP{api_key = ApiKey, _ = '_'}) end,
  146. ?assertEqual({ok, ok}, emqx_mgmt_auth:trans(WriteFun, [SameAppWithDiffName])),
  147. %% as erlang term order
  148. ?assertMatch(
  149. {ok, [
  150. #?APP{
  151. name = <<"name-1">>,
  152. api_key = <<"test-1">>
  153. },
  154. #?APP{
  155. name = <<"from_bootstrap_file_18926f94712af04e">>,
  156. api_key = <<"test-1">>
  157. }
  158. ]},
  159. emqx_mgmt_auth:trans(MatchFun, [TestApiKey])
  160. ),
  161. update_file(File),
  162. %% Similar to loading bootstrap file at node startup
  163. %% the duplicated apikey in mnesia will be cleaned up
  164. ?assertEqual(ok, emqx_mgmt_auth:init_bootstrap_file()),
  165. ?assertMatch(
  166. {ok, [
  167. #?APP{
  168. name = <<"from_bootstrap_file_18926f94712af04e">>,
  169. api_key = <<"test-1">>
  170. }
  171. ]},
  172. emqx_mgmt_auth:trans(MatchFun, [<<"test-1">>])
  173. ),
  174. %% the last apikey in bootstrap file will override the all in mnesia and the previous one(s) in bootstrap file
  175. ?assertEqual(ok, emqx_mgmt_auth:authorize(TestPath, <<"test-1">>, <<"secret-1">>)),
  176. ok.
  177. -if(?EMQX_RELEASE_EDITION == ee).
  178. t_bootstrap_file_with_role(_) ->
  179. Search = fun(Name) ->
  180. lists:search(
  181. fun(#{api_key := AppName}) ->
  182. AppName =:= Name
  183. end,
  184. emqx_mgmt_auth:list()
  185. )
  186. end,
  187. Bin = <<"role-1:role-1:viewer\nrole-2:role-2:administrator\nrole-3:role-3">>,
  188. File = "./bootstrap_api_keys.txt",
  189. ok = file:write_file(File, Bin),
  190. update_file(File),
  191. ?assertMatch(
  192. {value, #{api_key := <<"role-1">>, role := <<"viewer">>}},
  193. Search(<<"role-1">>)
  194. ),
  195. ?assertMatch(
  196. {value, #{api_key := <<"role-2">>, role := <<"administrator">>}},
  197. Search(<<"role-2">>)
  198. ),
  199. ?assertMatch(
  200. {value, #{api_key := <<"role-3">>, role := <<"administrator">>}},
  201. Search(<<"role-3">>)
  202. ),
  203. %% bad role
  204. BadBin = <<"role-4:secret-11:bad\n">>,
  205. ok = file:write_file(File, BadBin),
  206. update_file(File),
  207. ?assertEqual(
  208. false,
  209. Search(<<"role-4">>)
  210. ),
  211. ok.
  212. -else.
  213. t_bootstrap_file_with_role(_) ->
  214. Search = fun(Name) ->
  215. lists:search(
  216. fun(#{api_key := AppName}) ->
  217. AppName =:= Name
  218. end,
  219. emqx_mgmt_auth:list()
  220. )
  221. end,
  222. Bin = <<"role-1:role-1:administrator\nrole-2:role-2">>,
  223. File = "./bootstrap_api_keys.txt",
  224. ok = file:write_file(File, Bin),
  225. update_file(File),
  226. ?assertMatch(
  227. {value, #{api_key := <<"role-1">>, role := <<"administrator">>}},
  228. Search(<<"role-1">>)
  229. ),
  230. ?assertMatch(
  231. {value, #{api_key := <<"role-2">>, role := <<"administrator">>}},
  232. Search(<<"role-2">>)
  233. ),
  234. %% only administrator
  235. OtherRoleBin = <<"role-3:role-3:viewer\n">>,
  236. ok = file:write_file(File, OtherRoleBin),
  237. update_file(File),
  238. ?assertEqual(
  239. false,
  240. Search(<<"role-3">>)
  241. ),
  242. %% bad role
  243. BadBin = <<"role-4:secret-11:bad\n">>,
  244. ok = file:write_file(File, BadBin),
  245. update_file(File),
  246. ?assertEqual(
  247. false,
  248. Search(<<"role-4">>)
  249. ),
  250. ok.
  251. -endif.
  252. auth_authorize(Path, Key, Secret) ->
  253. FakePath = erlang:list_to_binary(emqx_dashboard_swagger:relative_uri("/fake")),
  254. FakeReq = #{method => <<"GET">>, path => FakePath},
  255. emqx_mgmt_auth:authorize(Path, FakeReq, Key, Secret).
  256. update_file(File) ->
  257. ?assertMatch({ok, _}, emqx:update_config([<<"api_key">>], #{<<"bootstrap_file">> => File})).
  258. t_create(_Config) ->
  259. Name = <<"EMQX-API-KEY-1">>,
  260. {ok, Create} = create_app(Name),
  261. ?assertMatch(
  262. #{
  263. <<"api_key">> := _,
  264. <<"api_secret">> := _,
  265. <<"created_at">> := _,
  266. <<"desc">> := _,
  267. <<"enable">> := true,
  268. <<"expired_at">> := _,
  269. <<"name">> := Name
  270. },
  271. Create
  272. ),
  273. {ok, List} = list_app(),
  274. [App] = lists:filter(fun(#{<<"name">> := NameA}) -> NameA =:= Name end, List),
  275. ?assertEqual(false, maps:is_key(<<"api_secret">>, App)),
  276. {ok, App1} = read_app(Name),
  277. ?assertEqual(Name, maps:get(<<"name">>, App1)),
  278. ?assertEqual(true, maps:get(<<"enable">>, App1)),
  279. ?assertEqual(false, maps:is_key(<<"api_secret">>, App1)),
  280. ?assertEqual({error, {"HTTP/1.1", 404, "Not Found"}}, read_app(<<"EMQX-API-KEY-NO-EXIST">>)),
  281. ok.
  282. t_create_failed(_Config) ->
  283. BadRequest = {error, {"HTTP/1.1", 400, "Bad Request"}},
  284. ?assertEqual(BadRequest, create_app(<<" error format name">>)),
  285. LongName = iolist_to_binary(lists:duplicate(257, "A")),
  286. ?assertEqual(BadRequest, create_app(<<" error format name">>)),
  287. ?assertEqual(BadRequest, create_app(LongName)),
  288. {ok, List} = list_app(),
  289. CreateNum = 100 - erlang:length(List),
  290. Names = lists:map(
  291. fun(Seq) ->
  292. <<"EMQX-API-FAILED-KEY-", (integer_to_binary(Seq))/binary>>
  293. end,
  294. lists:seq(1, CreateNum)
  295. ),
  296. lists:foreach(fun(N) -> {ok, _} = create_app(N) end, Names),
  297. ?assertEqual(BadRequest, create_app(<<"EMQX-API-KEY-MAXIMUM">>)),
  298. lists:foreach(fun(N) -> {ok, _} = delete_app(N) end, Names),
  299. Name = <<"EMQX-API-FAILED-KEY-1">>,
  300. ?assertMatch({ok, _}, create_app(Name)),
  301. ?assertEqual(BadRequest, create_app(Name)),
  302. {ok, _} = delete_app(Name),
  303. ?assertMatch({ok, #{<<"name">> := Name}}, create_app(Name)),
  304. {ok, _} = delete_app(Name),
  305. ok.
  306. t_update(_Config) ->
  307. Name = <<"EMQX-API-UPDATE-KEY">>,
  308. {ok, _} = create_app(Name),
  309. ExpiredAt = to_rfc3339(erlang:system_time(second) + 10000),
  310. Change = #{
  311. expired_at => ExpiredAt,
  312. desc => <<"NoteVersion1"/utf8>>,
  313. enable => false
  314. },
  315. {ok, Update1} = update_app(Name, Change),
  316. ?assertEqual(Name, maps:get(<<"name">>, Update1)),
  317. ?assertEqual(false, maps:get(<<"enable">>, Update1)),
  318. ?assertEqual(<<"NoteVersion1"/utf8>>, maps:get(<<"desc">>, Update1)),
  319. ?assertEqual(
  320. calendar:rfc3339_to_system_time(binary_to_list(ExpiredAt)),
  321. calendar:rfc3339_to_system_time(binary_to_list(maps:get(<<"expired_at">>, Update1)))
  322. ),
  323. Unexpired1 = maps:without([expired_at], Change),
  324. {ok, Update2} = update_app(Name, Unexpired1),
  325. ?assertEqual(<<"infinity">>, maps:get(<<"expired_at">>, Update2)),
  326. Unexpired2 = Change#{expired_at => <<"infinity">>},
  327. {ok, Update3} = update_app(Name, Unexpired2),
  328. ?assertEqual(<<"infinity">>, maps:get(<<"expired_at">>, Update3)),
  329. ?assertEqual({error, {"HTTP/1.1", 404, "Not Found"}}, update_app(<<"Not-Exist">>, Change)),
  330. ok.
  331. t_delete(_Config) ->
  332. Name = <<"EMQX-API-DELETE-KEY">>,
  333. {ok, _Create} = create_app(Name),
  334. {ok, Delete} = delete_app(Name),
  335. ?assertEqual([], Delete),
  336. ?assertEqual({error, {"HTTP/1.1", 404, "Not Found"}}, delete_app(Name)),
  337. ok.
  338. t_authorize(_Config) ->
  339. Name = <<"EMQX-API-AUTHORIZE-KEY">>,
  340. {ok, #{<<"api_key">> := ApiKey, <<"api_secret">> := ApiSecret}} = create_app(Name),
  341. BasicHeader = emqx_common_test_http:auth_header(
  342. binary_to_list(ApiKey),
  343. binary_to_list(ApiSecret)
  344. ),
  345. SecretError = emqx_common_test_http:auth_header(
  346. binary_to_list(ApiKey),
  347. binary_to_list(ApiKey)
  348. ),
  349. KeyError = emqx_common_test_http:auth_header("not_found_key", binary_to_list(ApiSecret)),
  350. Unauthorized = {error, {"HTTP/1.1", 401, "Unauthorized"}},
  351. BanPath = emqx_mgmt_api_test_util:api_path(["banned"]),
  352. ApiKeyPath = emqx_mgmt_api_test_util:api_path(["api_key"]),
  353. UserPath = emqx_mgmt_api_test_util:api_path(["users"]),
  354. {ok, _Status} = emqx_mgmt_api_test_util:request_api(get, BanPath, BasicHeader),
  355. ?assertEqual(Unauthorized, emqx_mgmt_api_test_util:request_api(get, BanPath, KeyError)),
  356. ?assertEqual(Unauthorized, emqx_mgmt_api_test_util:request_api(get, BanPath, SecretError)),
  357. ?assertEqual(Unauthorized, emqx_mgmt_api_test_util:request_api(get, UserPath, BasicHeader)),
  358. {error, {{"HTTP/1.1", 401, "Unauthorized"}, _Headers, Body}} =
  359. emqx_mgmt_api_test_util:request_api(
  360. get,
  361. ApiKeyPath,
  362. [],
  363. BasicHeader,
  364. [],
  365. #{return_all => true}
  366. ),
  367. ?assertMatch(
  368. #{
  369. <<"code">> := <<"API_KEY_NOT_ALLOW">>,
  370. <<"message">> := _
  371. },
  372. emqx_utils_json:decode(Body, [return_maps])
  373. ),
  374. ?assertMatch(
  375. {ok, #{<<"api_key">> := _, <<"enable">> := false}},
  376. update_app(Name, #{enable => false})
  377. ),
  378. ?assertEqual(Unauthorized, emqx_mgmt_api_test_util:request_api(get, BanPath, BasicHeader)),
  379. Expired = #{
  380. expired_at => to_rfc3339(erlang:system_time(second) - 1),
  381. enable => true
  382. },
  383. ?assertMatch({ok, #{<<"api_key">> := _, <<"enable">> := true}}, update_app(Name, Expired)),
  384. ?assertEqual(Unauthorized, emqx_mgmt_api_test_util:request_api(get, BanPath, BasicHeader)),
  385. UnExpired = #{expired_at => infinity},
  386. ?assertMatch(
  387. {ok, #{<<"api_key">> := _, <<"expired_at">> := <<"infinity">>}},
  388. update_app(Name, UnExpired)
  389. ),
  390. {ok, _Status1} = emqx_mgmt_api_test_util:request_api(get, BanPath, BasicHeader),
  391. ok.
  392. t_create_unexpired_app(_Config) ->
  393. Name1 = <<"EMQX-UNEXPIRED-API-KEY-1">>,
  394. Name2 = <<"EMQX-UNEXPIRED-API-KEY-2">>,
  395. {ok, Create1} = create_unexpired_app(Name1, #{}),
  396. ?assertMatch(#{<<"expired_at">> := <<"infinity">>}, Create1),
  397. {ok, Create2} = create_unexpired_app(Name2, #{expired_at => <<"infinity">>}),
  398. ?assertMatch(#{<<"expired_at">> := <<"infinity">>}, Create2),
  399. ok.
  400. t_ee_create(_Config) ->
  401. Name = <<"EMQX-EE-API-KEY-1">>,
  402. {ok, Create} = create_app(Name, #{role => ?ROLE_API_VIEWER}),
  403. ?assertMatch(
  404. #{
  405. <<"api_key">> := _,
  406. <<"api_secret">> := _,
  407. <<"created_at">> := _,
  408. <<"desc">> := _,
  409. <<"enable">> := true,
  410. <<"expired_at">> := _,
  411. <<"name">> := Name,
  412. <<"role">> := ?ROLE_API_VIEWER
  413. },
  414. Create
  415. ),
  416. {ok, App} = read_app(Name),
  417. ?assertMatch(#{<<"name">> := Name, <<"role">> := ?ROLE_API_VIEWER}, App).
  418. t_ee_update(_Config) ->
  419. Name = <<"EMQX-EE-API-UPDATE-KEY">>,
  420. {ok, _} = create_app(Name, #{role => ?ROLE_API_VIEWER}),
  421. Change = #{
  422. desc => <<"NoteVersion1"/utf8>>,
  423. enable => false,
  424. role => ?ROLE_API_SUPERUSER
  425. },
  426. {ok, Update1} = update_app(Name, Change),
  427. ?assertEqual(?ROLE_API_SUPERUSER, maps:get(<<"role">>, Update1)),
  428. {ok, App} = read_app(Name),
  429. ?assertMatch(#{<<"name">> := Name, <<"role">> := ?ROLE_API_SUPERUSER}, App).
  430. t_ee_authorize_viewer(_Config) ->
  431. Name = <<"EMQX-EE-API-AUTHORIZE-KEY-VIEWER">>,
  432. {ok, #{<<"api_key">> := ApiKey, <<"api_secret">> := ApiSecret}} = create_app(Name, #{
  433. role => ?ROLE_API_VIEWER
  434. }),
  435. BasicHeader = emqx_common_test_http:auth_header(
  436. binary_to_list(ApiKey),
  437. binary_to_list(ApiSecret)
  438. ),
  439. BanPath = emqx_mgmt_api_test_util:api_path(["banned"]),
  440. ?assertMatch({ok, _}, emqx_mgmt_api_test_util:request_api(get, BanPath, BasicHeader)),
  441. ?assertMatch(
  442. {error, {_, 403, _}}, emqx_mgmt_api_test_util:request_api(delete, BanPath, BasicHeader)
  443. ).
  444. t_ee_authorize_admin(_Config) ->
  445. Name = <<"EMQX-EE-API-AUTHORIZE-KEY-ADMIN">>,
  446. {ok, #{<<"api_key">> := ApiKey, <<"api_secret">> := ApiSecret}} = create_app(Name, #{
  447. role => ?ROLE_API_SUPERUSER
  448. }),
  449. BasicHeader = emqx_common_test_http:auth_header(
  450. binary_to_list(ApiKey),
  451. binary_to_list(ApiSecret)
  452. ),
  453. BanPath = emqx_mgmt_api_test_util:api_path(["banned"]),
  454. ?assertMatch({ok, _}, emqx_mgmt_api_test_util:request_api(get, BanPath, BasicHeader)),
  455. ?assertMatch(
  456. {ok, _}, emqx_mgmt_api_test_util:request_api(delete, BanPath, BasicHeader)
  457. ).
  458. t_ee_authorize_publisher(_Config) ->
  459. Name = <<"EMQX-EE-API-AUTHORIZE-KEY-PUBLISHER">>,
  460. {ok, #{<<"api_key">> := ApiKey, <<"api_secret">> := ApiSecret}} = create_app(Name, #{
  461. role => ?ROLE_API_PUBLISHER
  462. }),
  463. BasicHeader = emqx_common_test_http:auth_header(
  464. binary_to_list(ApiKey),
  465. binary_to_list(ApiSecret)
  466. ),
  467. BanPath = emqx_mgmt_api_test_util:api_path(["banned"]),
  468. Publish = emqx_mgmt_api_test_util:api_path(["publish"]),
  469. ?assertMatch(
  470. {error, {_, 403, _}}, emqx_mgmt_api_test_util:request_api(get, BanPath, BasicHeader)
  471. ),
  472. ?assertMatch(
  473. {error, {_, 403, _}}, emqx_mgmt_api_test_util:request_api(delete, BanPath, BasicHeader)
  474. ),
  475. ?_assertMatch(
  476. {ok, _},
  477. emqx_mgmt_api_test_util:request_api(
  478. post,
  479. Publish,
  480. [],
  481. BasicHeader,
  482. #{topic => <<"t/t_ee_authorize_publisher">>, payload => <<"hello">>}
  483. )
  484. ).
  485. list_app() ->
  486. AuthHeader = emqx_dashboard_SUITE:auth_header_(),
  487. Path = emqx_mgmt_api_test_util:api_path(["api_key"]),
  488. case emqx_mgmt_api_test_util:request_api(get, Path, AuthHeader) of
  489. {ok, Apps} -> {ok, emqx_utils_json:decode(Apps, [return_maps])};
  490. Error -> Error
  491. end.
  492. read_app(Name) ->
  493. AuthHeader = emqx_dashboard_SUITE:auth_header_(),
  494. Path = emqx_mgmt_api_test_util:api_path(["api_key", Name]),
  495. case emqx_mgmt_api_test_util:request_api(get, Path, AuthHeader) of
  496. {ok, Res} -> {ok, emqx_utils_json:decode(Res, [return_maps])};
  497. Error -> Error
  498. end.
  499. create_app(Name) ->
  500. create_app(Name, #{}).
  501. create_app(Name, Extra) ->
  502. AuthHeader = emqx_dashboard_SUITE:auth_header_(),
  503. Path = emqx_mgmt_api_test_util:api_path(["api_key"]),
  504. ExpiredAt = to_rfc3339(erlang:system_time(second) + 1000),
  505. App = Extra#{
  506. name => Name,
  507. expired_at => ExpiredAt,
  508. desc => <<"Note"/utf8>>,
  509. enable => true
  510. },
  511. case emqx_mgmt_api_test_util:request_api(post, Path, "", AuthHeader, App) of
  512. {ok, Res} -> {ok, emqx_utils_json:decode(Res, [return_maps])};
  513. Error -> Error
  514. end.
  515. create_unexpired_app(Name, Params) ->
  516. AuthHeader = emqx_dashboard_SUITE:auth_header_(),
  517. Path = emqx_mgmt_api_test_util:api_path(["api_key"]),
  518. App = maps:merge(#{name => Name, desc => <<"Note"/utf8>>, enable => true}, Params),
  519. case emqx_mgmt_api_test_util:request_api(post, Path, "", AuthHeader, App) of
  520. {ok, Res} -> {ok, emqx_utils_json:decode(Res, [return_maps])};
  521. Error -> Error
  522. end.
  523. delete_app(Name) ->
  524. AuthHeader = emqx_dashboard_SUITE:auth_header_(),
  525. DeletePath = emqx_mgmt_api_test_util:api_path(["api_key", Name]),
  526. emqx_mgmt_api_test_util:request_api(delete, DeletePath, AuthHeader).
  527. update_app(Name, Change) ->
  528. AuthHeader = emqx_dashboard_SUITE:auth_header_(),
  529. UpdatePath = emqx_mgmt_api_test_util:api_path(["api_key", Name]),
  530. case emqx_mgmt_api_test_util:request_api(put, UpdatePath, "", AuthHeader, Change) of
  531. {ok, Update} -> {ok, emqx_utils_json:decode(Update, [return_maps])};
  532. Error -> Error
  533. end.
  534. to_rfc3339(Sec) ->
  535. list_to_binary(calendar:system_time_to_rfc3339(Sec)).