emqx_authz_api_mnesia.erl 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020-2022 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_authz_api_mnesia).
  17. -behaviour(minirest_api).
  18. -include("emqx_authz.hrl").
  19. -include_lib("emqx/include/logger.hrl").
  20. -include_lib("hocon/include/hoconsc.hrl").
  21. -import(hoconsc, [mk/1, mk/2, ref/1, ref/2, array/1, enum/1]).
  22. -define(QUERY_USERNAME_FUN, {?MODULE, query_username}).
  23. -define(QUERY_CLIENTID_FUN, {?MODULE, query_clientid}).
  24. -define(ACL_USERNAME_QSCHEMA, [{<<"like_username">>, binary}]).
  25. -define(ACL_CLIENTID_QSCHEMA, [{<<"like_clientid">>, binary}]).
  26. -export([
  27. api_spec/0,
  28. paths/0,
  29. schema/1,
  30. fields/1
  31. ]).
  32. %% operation funs
  33. -export([
  34. users/2,
  35. clients/2,
  36. user/2,
  37. client/2,
  38. all/2,
  39. purge/2
  40. ]).
  41. %% query funs
  42. -export([
  43. query_username/4,
  44. query_clientid/4
  45. ]).
  46. -export([format_result/1]).
  47. -define(BAD_REQUEST, 'BAD_REQUEST').
  48. -define(NOT_FOUND, 'NOT_FOUND').
  49. -define(ALREADY_EXISTS, 'ALREADY_EXISTS').
  50. -define(TYPE_REF, ref).
  51. -define(TYPE_ARRAY, array).
  52. -define(PAGE_QUERY_EXAMPLE, example_in_data).
  53. -define(PUT_MAP_EXAMPLE, in_put_requestBody).
  54. -define(POST_ARRAY_EXAMPLE, in_post_requestBody).
  55. api_spec() ->
  56. emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
  57. paths() ->
  58. [
  59. "/authorization/sources/built_in_database/username",
  60. "/authorization/sources/built_in_database/clientid",
  61. "/authorization/sources/built_in_database/username/:username",
  62. "/authorization/sources/built_in_database/clientid/:clientid",
  63. "/authorization/sources/built_in_database/all",
  64. "/authorization/sources/built_in_database/purge-all"
  65. ].
  66. %%--------------------------------------------------------------------
  67. %% Schema for each URI
  68. %%--------------------------------------------------------------------
  69. schema("/authorization/sources/built_in_database/username") ->
  70. #{
  71. 'operationId' => users,
  72. get =>
  73. #{
  74. tags => [<<"authorization">>],
  75. description => ?DESC(users_username_get),
  76. parameters =>
  77. [
  78. ref(emqx_dashboard_swagger, page),
  79. ref(emqx_dashboard_swagger, limit),
  80. {like_username,
  81. mk(binary(), #{
  82. in => query,
  83. required => false,
  84. desc => ?DESC(fuzzy_username)
  85. })}
  86. ],
  87. responses =>
  88. #{
  89. 200 => swagger_with_example(
  90. {username_response_data, ?TYPE_REF},
  91. {username, ?PAGE_QUERY_EXAMPLE}
  92. )
  93. }
  94. },
  95. post =>
  96. #{
  97. tags => [<<"authorization">>],
  98. description => ?DESC(users_username_post),
  99. 'requestBody' => swagger_with_example(
  100. {rules_for_username, ?TYPE_ARRAY},
  101. {username, ?POST_ARRAY_EXAMPLE}
  102. ),
  103. responses =>
  104. #{
  105. 204 => <<"Created">>,
  106. 400 => emqx_dashboard_swagger:error_codes(
  107. [?BAD_REQUEST], <<"Bad username or bad rule schema">>
  108. ),
  109. 409 => emqx_dashboard_swagger:error_codes(
  110. [?ALREADY_EXISTS], <<"ALREADY_EXISTS">>
  111. )
  112. }
  113. }
  114. };
  115. schema("/authorization/sources/built_in_database/clientid") ->
  116. #{
  117. 'operationId' => clients,
  118. get =>
  119. #{
  120. tags => [<<"authorization">>],
  121. description => ?DESC(users_clientid_get),
  122. parameters =>
  123. [
  124. ref(emqx_dashboard_swagger, page),
  125. ref(emqx_dashboard_swagger, limit),
  126. {like_clientid,
  127. mk(
  128. binary(),
  129. #{
  130. in => query,
  131. required => false,
  132. desc => ?DESC(fuzzy_clientid)
  133. }
  134. )}
  135. ],
  136. responses =>
  137. #{
  138. 200 => swagger_with_example(
  139. {clientid_response_data, ?TYPE_REF},
  140. {clientid, ?PAGE_QUERY_EXAMPLE}
  141. )
  142. }
  143. },
  144. post =>
  145. #{
  146. tags => [<<"authorization">>],
  147. description => ?DESC(users_clientid_post),
  148. 'requestBody' => swagger_with_example(
  149. {rules_for_clientid, ?TYPE_ARRAY},
  150. {clientid, ?POST_ARRAY_EXAMPLE}
  151. ),
  152. responses =>
  153. #{
  154. 204 => <<"Created">>,
  155. 400 => emqx_dashboard_swagger:error_codes(
  156. [?BAD_REQUEST], <<"Bad clientid or bad rule schema">>
  157. )
  158. }
  159. }
  160. };
  161. schema("/authorization/sources/built_in_database/username/:username") ->
  162. #{
  163. 'operationId' => user,
  164. get =>
  165. #{
  166. tags => [<<"authorization">>],
  167. description => ?DESC(user_username_get),
  168. parameters => [ref(username)],
  169. responses =>
  170. #{
  171. 200 => swagger_with_example(
  172. {rules_for_username, ?TYPE_REF},
  173. {username, ?PUT_MAP_EXAMPLE}
  174. ),
  175. 404 => emqx_dashboard_swagger:error_codes(
  176. [?NOT_FOUND], <<"Not Found">>
  177. )
  178. }
  179. },
  180. put =>
  181. #{
  182. tags => [<<"authorization">>],
  183. description => ?DESC(user_username_put),
  184. parameters => [ref(username)],
  185. 'requestBody' => swagger_with_example(
  186. {rules_for_username, ?TYPE_REF},
  187. {username, ?PUT_MAP_EXAMPLE}
  188. ),
  189. responses =>
  190. #{
  191. 204 => <<"Updated">>,
  192. 400 => emqx_dashboard_swagger:error_codes(
  193. [?BAD_REQUEST], <<"Bad username or bad rule schema">>
  194. )
  195. }
  196. },
  197. delete =>
  198. #{
  199. tags => [<<"authorization">>],
  200. description => ?DESC(user_username_delete),
  201. parameters => [ref(username)],
  202. responses =>
  203. #{
  204. 204 => <<"Deleted">>,
  205. 400 => emqx_dashboard_swagger:error_codes(
  206. [?BAD_REQUEST], <<"Bad username">>
  207. ),
  208. 404 => emqx_dashboard_swagger:error_codes(
  209. [?NOT_FOUND], <<"Username Not Found">>
  210. )
  211. }
  212. }
  213. };
  214. schema("/authorization/sources/built_in_database/clientid/:clientid") ->
  215. #{
  216. 'operationId' => client,
  217. get =>
  218. #{
  219. tags => [<<"authorization">>],
  220. description => ?DESC(user_clientid_get),
  221. parameters => [ref(clientid)],
  222. responses =>
  223. #{
  224. 200 => swagger_with_example(
  225. {rules_for_clientid, ?TYPE_REF},
  226. {clientid, ?PUT_MAP_EXAMPLE}
  227. ),
  228. 404 => emqx_dashboard_swagger:error_codes(
  229. [?NOT_FOUND], <<"Not Found">>
  230. )
  231. }
  232. },
  233. put =>
  234. #{
  235. tags => [<<"authorization">>],
  236. description => ?DESC(user_clientid_put),
  237. parameters => [ref(clientid)],
  238. 'requestBody' => swagger_with_example(
  239. {rules_for_clientid, ?TYPE_REF},
  240. {clientid, ?PUT_MAP_EXAMPLE}
  241. ),
  242. responses =>
  243. #{
  244. 204 => <<"Updated">>,
  245. 400 => emqx_dashboard_swagger:error_codes(
  246. [?BAD_REQUEST], <<"Bad clientid or bad rule schema">>
  247. )
  248. }
  249. },
  250. delete =>
  251. #{
  252. tags => [<<"authorization">>],
  253. description => ?DESC(user_clientid_delete),
  254. parameters => [ref(clientid)],
  255. responses =>
  256. #{
  257. 204 => <<"Deleted">>,
  258. 400 => emqx_dashboard_swagger:error_codes(
  259. [?BAD_REQUEST], <<"Bad clientid">>
  260. ),
  261. 404 => emqx_dashboard_swagger:error_codes(
  262. [?NOT_FOUND], <<"ClientID Not Found">>
  263. )
  264. }
  265. }
  266. };
  267. schema("/authorization/sources/built_in_database/all") ->
  268. #{
  269. 'operationId' => all,
  270. get =>
  271. #{
  272. tags => [<<"authorization">>],
  273. description => ?DESC(rules_for_all_get),
  274. responses =>
  275. #{200 => swagger_with_example({rules, ?TYPE_REF}, {all, ?PUT_MAP_EXAMPLE})}
  276. },
  277. post =>
  278. #{
  279. tags => [<<"authorization">>],
  280. description => ?DESC(rules_for_all_post),
  281. 'requestBody' =>
  282. swagger_with_example({rules, ?TYPE_REF}, {all, ?PUT_MAP_EXAMPLE}),
  283. responses =>
  284. #{
  285. 204 => <<"Updated">>,
  286. 400 => emqx_dashboard_swagger:error_codes(
  287. [?BAD_REQUEST], <<"Bad rule schema">>
  288. )
  289. }
  290. }
  291. };
  292. schema("/authorization/sources/built_in_database/purge-all") ->
  293. #{
  294. 'operationId' => purge,
  295. delete =>
  296. #{
  297. tags => [<<"authorization">>],
  298. description => ?DESC(purge_all_delete),
  299. responses =>
  300. #{
  301. 204 => <<"Deleted">>,
  302. 400 => emqx_dashboard_swagger:error_codes(
  303. [?BAD_REQUEST], <<"Bad Request">>
  304. )
  305. }
  306. }
  307. }.
  308. fields(rule_item) ->
  309. [
  310. {topic,
  311. mk(
  312. string(),
  313. #{
  314. required => true,
  315. desc => ?DESC(topic),
  316. example => <<"test/topic/1">>
  317. }
  318. )},
  319. {permission,
  320. mk(
  321. enum([allow, deny]),
  322. #{
  323. desc => ?DESC(permission),
  324. required => true,
  325. example => allow
  326. }
  327. )},
  328. {action,
  329. mk(
  330. enum([publish, subscribe, all]),
  331. #{
  332. desc => ?DESC(action),
  333. required => true,
  334. example => publish
  335. }
  336. )}
  337. ];
  338. fields(clientid) ->
  339. [
  340. {clientid,
  341. mk(
  342. binary(),
  343. #{
  344. in => path,
  345. required => true,
  346. desc => ?DESC(clientid),
  347. example => <<"client1">>
  348. }
  349. )}
  350. ];
  351. fields(username) ->
  352. [
  353. {username,
  354. mk(
  355. binary(),
  356. #{
  357. in => path,
  358. required => true,
  359. desc => ?DESC(username),
  360. example => <<"user1">>
  361. }
  362. )}
  363. ];
  364. fields(rules_for_username) ->
  365. fields(rules) ++
  366. fields(username);
  367. fields(username_response_data) ->
  368. [
  369. {data, mk(array(ref(rules_for_username)), #{})},
  370. {meta, ref(emqx_dashboard_swagger, meta)}
  371. ];
  372. fields(rules_for_clientid) ->
  373. fields(rules) ++
  374. fields(clientid);
  375. fields(clientid_response_data) ->
  376. [
  377. {data, mk(array(ref(rules_for_clientid)), #{})},
  378. {meta, ref(emqx_dashboard_swagger, meta)}
  379. ];
  380. fields(rules) ->
  381. [{rules, mk(array(ref(rule_item)))}].
  382. %%--------------------------------------------------------------------
  383. %% HTTP API
  384. %%--------------------------------------------------------------------
  385. users(get, #{query_string := QueryString}) ->
  386. case
  387. emqx_mgmt_api:node_query(
  388. node(),
  389. QueryString,
  390. ?ACL_TABLE,
  391. ?ACL_USERNAME_QSCHEMA,
  392. ?QUERY_USERNAME_FUN
  393. )
  394. of
  395. {error, page_limit_invalid} ->
  396. {400, #{code => <<"INVALID_PARAMETER">>, message => <<"page_limit_invalid">>}};
  397. {error, Node, {badrpc, R}} ->
  398. Message = list_to_binary(io_lib:format("bad rpc call ~p, Reason ~p", [Node, R])),
  399. {500, #{code => <<"NODE_DOWN">>, message => Message}};
  400. Result ->
  401. {200, Result}
  402. end;
  403. users(post, #{body := Body}) when is_list(Body) ->
  404. case ensure_all_not_exists(<<"username">>, username, Body) of
  405. [] ->
  406. lists:foreach(
  407. fun(#{<<"username">> := Username, <<"rules">> := Rules}) ->
  408. emqx_authz_mnesia:store_rules({username, Username}, format_rules(Rules))
  409. end,
  410. Body
  411. ),
  412. {204};
  413. Exists ->
  414. {409, #{
  415. code => <<"ALREADY_EXISTS">>,
  416. message => binfmt("Users '~ts' already exist", [binjoin(Exists)])
  417. }}
  418. end.
  419. clients(get, #{query_string := QueryString}) ->
  420. case
  421. emqx_mgmt_api:node_query(
  422. node(),
  423. QueryString,
  424. ?ACL_TABLE,
  425. ?ACL_CLIENTID_QSCHEMA,
  426. ?QUERY_CLIENTID_FUN
  427. )
  428. of
  429. {error, page_limit_invalid} ->
  430. {400, #{code => <<"INVALID_PARAMETER">>, message => <<"page_limit_invalid">>}};
  431. {error, Node, {badrpc, R}} ->
  432. Message = list_to_binary(io_lib:format("bad rpc call ~p, Reason ~p", [Node, R])),
  433. {500, #{code => <<"NODE_DOWN">>, message => Message}};
  434. Result ->
  435. {200, Result}
  436. end;
  437. clients(post, #{body := Body}) when is_list(Body) ->
  438. case ensure_all_not_exists(<<"clientid">>, clientid, Body) of
  439. [] ->
  440. lists:foreach(
  441. fun(#{<<"clientid">> := ClientID, <<"rules">> := Rules}) ->
  442. emqx_authz_mnesia:store_rules({clientid, ClientID}, format_rules(Rules))
  443. end,
  444. Body
  445. ),
  446. {204};
  447. Exists ->
  448. {409, #{
  449. code => <<"ALREADY_EXISTS">>,
  450. message => binfmt("Clients '~ts' already exist", [binjoin(Exists)])
  451. }}
  452. end.
  453. user(get, #{bindings := #{username := Username}}) ->
  454. case emqx_authz_mnesia:get_rules({username, Username}) of
  455. not_found ->
  456. {404, #{code => <<"NOT_FOUND">>, message => <<"Not Found">>}};
  457. {ok, Rules} ->
  458. {200, #{
  459. username => Username,
  460. rules => [
  461. #{
  462. topic => Topic,
  463. action => Action,
  464. permission => Permission
  465. }
  466. || {Permission, Action, Topic} <- Rules
  467. ]
  468. }}
  469. end;
  470. user(put, #{
  471. bindings := #{username := Username},
  472. body := #{<<"username">> := Username, <<"rules">> := Rules}
  473. }) ->
  474. emqx_authz_mnesia:store_rules({username, Username}, format_rules(Rules)),
  475. {204};
  476. user(delete, #{bindings := #{username := Username}}) ->
  477. case emqx_authz_mnesia:get_rules({username, Username}) of
  478. not_found ->
  479. {404, #{code => <<"NOT_FOUND">>, message => <<"Username Not Found">>}};
  480. {ok, _Rules} ->
  481. emqx_authz_mnesia:delete_rules({username, Username}),
  482. {204}
  483. end.
  484. client(get, #{bindings := #{clientid := ClientID}}) ->
  485. case emqx_authz_mnesia:get_rules({clientid, ClientID}) of
  486. not_found ->
  487. {404, #{code => <<"NOT_FOUND">>, message => <<"Not Found">>}};
  488. {ok, Rules} ->
  489. {200, #{
  490. clientid => ClientID,
  491. rules => [
  492. #{
  493. topic => Topic,
  494. action => Action,
  495. permission => Permission
  496. }
  497. || {Permission, Action, Topic} <- Rules
  498. ]
  499. }}
  500. end;
  501. client(put, #{
  502. bindings := #{clientid := ClientID},
  503. body := #{<<"clientid">> := ClientID, <<"rules">> := Rules}
  504. }) ->
  505. emqx_authz_mnesia:store_rules({clientid, ClientID}, format_rules(Rules)),
  506. {204};
  507. client(delete, #{bindings := #{clientid := ClientID}}) ->
  508. case emqx_authz_mnesia:get_rules({clientid, ClientID}) of
  509. not_found ->
  510. {404, #{code => <<"NOT_FOUND">>, message => <<"ClientID Not Found">>}};
  511. {ok, _Rules} ->
  512. emqx_authz_mnesia:delete_rules({clientid, ClientID}),
  513. {204}
  514. end.
  515. all(get, _) ->
  516. case emqx_authz_mnesia:get_rules(all) of
  517. not_found ->
  518. {200, #{rules => []}};
  519. {ok, Rules} ->
  520. {200, #{
  521. rules => [
  522. #{
  523. topic => Topic,
  524. action => Action,
  525. permission => Permission
  526. }
  527. || {Permission, Action, Topic} <- Rules
  528. ]
  529. }}
  530. end;
  531. all(post, #{body := #{<<"rules">> := Rules}}) ->
  532. emqx_authz_mnesia:store_rules(all, format_rules(Rules)),
  533. {204}.
  534. purge(delete, _) ->
  535. case emqx_authz_api_sources:get_raw_source(<<"built_in_database">>) of
  536. [#{<<"enable">> := false}] ->
  537. ok = emqx_authz_mnesia:purge_rules(),
  538. {204};
  539. [#{<<"enable">> := true}] ->
  540. {400, #{
  541. code => <<"BAD_REQUEST">>,
  542. message =>
  543. <<"'built_in_database' type source must be disabled before purge.">>
  544. }};
  545. [] ->
  546. {404, #{
  547. code => <<"BAD_REQUEST">>,
  548. message => <<"'built_in_database' type source is not found.">>
  549. }}
  550. end.
  551. %%--------------------------------------------------------------------
  552. %% Query Functions
  553. query_username(Tab, {_QString, []}, Continuation, Limit) ->
  554. Ms = emqx_authz_mnesia:list_username_rules(),
  555. emqx_mgmt_api:select_table_with_count(
  556. Tab,
  557. Ms,
  558. Continuation,
  559. Limit,
  560. fun format_result/1
  561. );
  562. query_username(Tab, {_QString, FuzzyQString}, Continuation, Limit) ->
  563. Ms = emqx_authz_mnesia:list_username_rules(),
  564. FuzzyFilterFun = fuzzy_filter_fun(FuzzyQString),
  565. emqx_mgmt_api:select_table_with_count(
  566. Tab,
  567. {Ms, FuzzyFilterFun},
  568. Continuation,
  569. Limit,
  570. fun format_result/1
  571. ).
  572. query_clientid(Tab, {_QString, []}, Continuation, Limit) ->
  573. Ms = emqx_authz_mnesia:list_clientid_rules(),
  574. emqx_mgmt_api:select_table_with_count(
  575. Tab,
  576. Ms,
  577. Continuation,
  578. Limit,
  579. fun format_result/1
  580. );
  581. query_clientid(Tab, {_QString, FuzzyQString}, Continuation, Limit) ->
  582. Ms = emqx_authz_mnesia:list_clientid_rules(),
  583. FuzzyFilterFun = fuzzy_filter_fun(FuzzyQString),
  584. emqx_mgmt_api:select_table_with_count(
  585. Tab,
  586. {Ms, FuzzyFilterFun},
  587. Continuation,
  588. Limit,
  589. fun format_result/1
  590. ).
  591. %%--------------------------------------------------------------------
  592. %% Match funcs
  593. %% Fuzzy username funcs
  594. fuzzy_filter_fun(Fuzzy) ->
  595. fun(MsRaws) when is_list(MsRaws) ->
  596. lists:filter(
  597. fun(E) -> run_fuzzy_filter(E, Fuzzy) end,
  598. MsRaws
  599. )
  600. end.
  601. run_fuzzy_filter(_, []) ->
  602. true;
  603. run_fuzzy_filter(
  604. E = [{username, Username}, _Rule],
  605. [{username, like, UsernameSubStr} | Fuzzy]
  606. ) ->
  607. binary:match(Username, UsernameSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy);
  608. run_fuzzy_filter(
  609. E = [{clientid, ClientId}, _Rule],
  610. [{clientid, like, ClientIdSubStr} | Fuzzy]
  611. ) ->
  612. binary:match(ClientId, ClientIdSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy).
  613. %%--------------------------------------------------------------------
  614. %% format funcs
  615. %% format rule from api
  616. format_rules(Rules) when is_list(Rules) ->
  617. lists:foldl(
  618. fun(
  619. #{
  620. <<"topic">> := Topic,
  621. <<"action">> := Action,
  622. <<"permission">> := Permission
  623. },
  624. AccIn
  625. ) when
  626. ?PUBSUB(Action) andalso
  627. ?ALLOW_DENY(Permission)
  628. ->
  629. AccIn ++ [{atom(Permission), atom(Action), Topic}]
  630. end,
  631. [],
  632. Rules
  633. ).
  634. %% format result from mnesia tab
  635. format_result([{username, Username}, {rules, Rules}]) ->
  636. #{
  637. username => Username,
  638. rules => [
  639. #{
  640. topic => Topic,
  641. action => Action,
  642. permission => Permission
  643. }
  644. || {Permission, Action, Topic} <- Rules
  645. ]
  646. };
  647. format_result([{clientid, ClientID}, {rules, Rules}]) ->
  648. #{
  649. clientid => ClientID,
  650. rules => [
  651. #{
  652. topic => Topic,
  653. action => Action,
  654. permission => Permission
  655. }
  656. || {Permission, Action, Topic} <- Rules
  657. ]
  658. }.
  659. atom(B) when is_binary(B) ->
  660. try
  661. binary_to_existing_atom(B, utf8)
  662. catch
  663. _Error:_Expection -> binary_to_atom(B)
  664. end;
  665. atom(A) when is_atom(A) -> A.
  666. %%--------------------------------------------------------------------
  667. %% Internal functions
  668. %%--------------------------------------------------------------------
  669. swagger_with_example({Ref, TypeP}, {_Name, _Type} = Example) ->
  670. emqx_dashboard_swagger:schema_with_examples(
  671. case TypeP of
  672. ?TYPE_REF -> ref(?MODULE, Ref);
  673. ?TYPE_ARRAY -> array(ref(?MODULE, Ref))
  674. end,
  675. rules_example(Example)
  676. ).
  677. rules_example({ExampleName, ExampleType}) ->
  678. {Summary, Example} =
  679. case ExampleName of
  680. username -> {<<"Username">>, ?USERNAME_RULES_EXAMPLE};
  681. clientid -> {<<"ClientID">>, ?CLIENTID_RULES_EXAMPLE};
  682. all -> {<<"All">>, ?ALL_RULES_EXAMPLE}
  683. end,
  684. Value =
  685. case ExampleType of
  686. ?PAGE_QUERY_EXAMPLE ->
  687. #{
  688. data => [Example],
  689. meta => ?META_EXAMPLE
  690. };
  691. ?PUT_MAP_EXAMPLE ->
  692. Example;
  693. ?POST_ARRAY_EXAMPLE ->
  694. [Example]
  695. end,
  696. #{
  697. 'password_based:built_in_database' => #{
  698. summary => Summary,
  699. value => Value
  700. }
  701. }.
  702. ensure_all_not_exists(Key, Type, Cfgs) ->
  703. lists:foldl(
  704. fun(#{Key := Id}, Acc) ->
  705. case emqx_authz_mnesia:get_rules({Type, Id}) of
  706. not_found ->
  707. Acc;
  708. _ ->
  709. [Id | Acc]
  710. end
  711. end,
  712. [],
  713. Cfgs
  714. ).
  715. binjoin([Bin]) ->
  716. Bin;
  717. binjoin(Bins) ->
  718. binjoin(Bins, <<>>).
  719. binjoin([H | T], Acc) ->
  720. binjoin(T, <<H/binary, $,, Acc/binary>>);
  721. binjoin([], Acc) ->
  722. Acc.
  723. binfmt(Fmt, Args) -> iolist_to_binary(io_lib:format(Fmt, Args)).