emqx_authz_schema.erl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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_schema).
  17. -include("emqx_authz.hrl").
  18. -include_lib("hocon/include/hoconsc.hrl").
  19. -include_lib("emqx_connector/include/emqx_connector.hrl").
  20. -reflect_type([
  21. permission/0,
  22. action/0
  23. ]).
  24. -type action() :: publish | subscribe | all.
  25. -type permission() :: allow | deny.
  26. -import(emqx_schema, [mk_duration/2]).
  27. -export([
  28. namespace/0,
  29. roots/0,
  30. fields/1,
  31. validations/0,
  32. desc/1
  33. ]).
  34. -export([
  35. headers_no_content_type/1,
  36. headers/1
  37. ]).
  38. %%--------------------------------------------------------------------
  39. %% Hocon Schema
  40. %%--------------------------------------------------------------------
  41. namespace() -> authz.
  42. %% @doc authorization schema is not exported
  43. %% but directly used by emqx_schema
  44. roots() -> [].
  45. fields("authorization") ->
  46. Types = [
  47. file,
  48. http_get,
  49. http_post,
  50. mnesia,
  51. mongo_single,
  52. mongo_rs,
  53. mongo_sharded,
  54. mysql,
  55. postgresql,
  56. redis_single,
  57. redis_sentinel,
  58. redis_cluster
  59. ],
  60. Unions = [?R_REF(Type) || Type <- Types],
  61. [
  62. {sources,
  63. ?HOCON(
  64. ?ARRAY(?UNION(Unions)),
  65. #{
  66. default => [],
  67. desc => ?DESC(sources)
  68. }
  69. )}
  70. ];
  71. fields(file) ->
  72. authz_common_fields(file) ++
  73. [{path, ?HOCON(string(), #{required => true, desc => ?DESC(path)})}];
  74. fields(http_get) ->
  75. authz_common_fields(http) ++
  76. http_common_fields() ++
  77. [
  78. {method, method(get)},
  79. {headers, fun headers_no_content_type/1}
  80. ];
  81. fields(http_post) ->
  82. authz_common_fields(http) ++
  83. http_common_fields() ++
  84. [
  85. {method, method(post)},
  86. {headers, fun headers/1}
  87. ];
  88. fields(mnesia) ->
  89. authz_common_fields(built_in_database);
  90. fields(mongo_single) ->
  91. authz_common_fields(mongodb) ++
  92. mongo_common_fields() ++
  93. emqx_connector_mongo:fields(single);
  94. fields(mongo_rs) ->
  95. authz_common_fields(mongodb) ++
  96. mongo_common_fields() ++
  97. emqx_connector_mongo:fields(rs);
  98. fields(mongo_sharded) ->
  99. authz_common_fields(mongodb) ++
  100. mongo_common_fields() ++
  101. emqx_connector_mongo:fields(sharded);
  102. fields(mysql) ->
  103. authz_common_fields(mysql) ++
  104. connector_fields(mysql) ++
  105. [{query, query()}];
  106. fields(postgresql) ->
  107. authz_common_fields(postgresql) ++
  108. emqx_connector_pgsql:fields(config) ++
  109. [{query, query()}];
  110. fields(redis_single) ->
  111. authz_common_fields(redis) ++
  112. connector_fields(redis, single) ++
  113. [{cmd, cmd()}];
  114. fields(redis_sentinel) ->
  115. authz_common_fields(redis) ++
  116. connector_fields(redis, sentinel) ++
  117. [{cmd, cmd()}];
  118. fields(redis_cluster) ->
  119. authz_common_fields(redis) ++
  120. connector_fields(redis, cluster) ++
  121. [{cmd, cmd()}];
  122. fields("metrics_status_fields") ->
  123. [
  124. {"resource_metrics", ?HOCON(?R_REF("resource_metrics"), #{desc => ?DESC("metrics")})},
  125. {"node_resource_metrics", array("node_resource_metrics", "node_metrics")},
  126. {"metrics", ?HOCON(?R_REF("metrics"), #{desc => ?DESC("metrics")})},
  127. {"node_metrics", array("node_metrics")},
  128. {"status", ?HOCON(cluster_status(), #{desc => ?DESC("status")})},
  129. {"node_status", array("node_status")},
  130. {"node_error", array("node_error")}
  131. ];
  132. fields("metrics") ->
  133. [
  134. {"total", ?HOCON(integer(), #{desc => ?DESC("metrics_total")})},
  135. {"allow", ?HOCON(integer(), #{desc => ?DESC("allow")})},
  136. {"deny", ?HOCON(integer(), #{desc => ?DESC("deny")})},
  137. {"nomatch", ?HOCON(float(), #{desc => ?DESC("nomatch")})}
  138. ] ++ common_rate_field();
  139. fields("node_metrics") ->
  140. [
  141. node_name(),
  142. {"metrics", ?HOCON(?R_REF("metrics"), #{desc => ?DESC("metrics")})}
  143. ];
  144. fields("resource_metrics") ->
  145. common_field();
  146. fields("node_resource_metrics") ->
  147. [
  148. node_name(),
  149. {"metrics", ?HOCON(?R_REF("resource_metrics"), #{desc => ?DESC("metrics")})}
  150. ];
  151. fields("node_status") ->
  152. [
  153. node_name(),
  154. {"status", ?HOCON(status(), #{desc => ?DESC("node_status")})}
  155. ];
  156. fields("node_error") ->
  157. [
  158. node_name(),
  159. {"error", ?HOCON(string(), #{desc => ?DESC("node_error")})}
  160. ].
  161. common_field() ->
  162. [
  163. {"matched", ?HOCON(integer(), #{desc => ?DESC("matched")})},
  164. {"success", ?HOCON(integer(), #{desc => ?DESC("success")})},
  165. {"failed", ?HOCON(integer(), #{desc => ?DESC("failed")})}
  166. ] ++ common_rate_field().
  167. status() ->
  168. hoconsc:enum([connected, disconnected, connecting]).
  169. cluster_status() ->
  170. hoconsc:enum([connected, disconnected, connecting, inconsistent]).
  171. node_name() ->
  172. {"node", ?HOCON(binary(), #{desc => ?DESC("node"), example => "emqx@127.0.0.1"})}.
  173. desc(?CONF_NS) ->
  174. ?DESC(?CONF_NS);
  175. desc(file) ->
  176. ?DESC(file);
  177. desc(http_get) ->
  178. ?DESC(http_get);
  179. desc(http_post) ->
  180. ?DESC(http_post);
  181. desc(mnesia) ->
  182. ?DESC(mnesia);
  183. desc(mongo_single) ->
  184. ?DESC(mongo_single);
  185. desc(mongo_rs) ->
  186. ?DESC(mongo_rs);
  187. desc(mongo_sharded) ->
  188. ?DESC(mongo_sharded);
  189. desc(mysql) ->
  190. ?DESC(mysql);
  191. desc(postgresql) ->
  192. ?DESC(postgresql);
  193. desc(redis_single) ->
  194. ?DESC(redis_single);
  195. desc(redis_sentinel) ->
  196. ?DESC(redis_sentinel);
  197. desc(redis_cluster) ->
  198. ?DESC(redis_cluster);
  199. desc(_) ->
  200. undefined.
  201. authz_common_fields(Type) ->
  202. [
  203. {type, ?HOCON(Type, #{required => true, desc => ?DESC(type)})},
  204. {enable, ?HOCON(boolean(), #{default => true, desc => ?DESC(enable)})}
  205. ].
  206. http_common_fields() ->
  207. [
  208. {url, fun url/1},
  209. {request_timeout,
  210. mk_duration("Request timeout", #{
  211. required => false, default => "30s", desc => ?DESC(request_timeout)
  212. })},
  213. {body, ?HOCON(map(), #{required => false, desc => ?DESC(body)})}
  214. ] ++
  215. maps:to_list(
  216. maps:without(
  217. [
  218. base_url,
  219. pool_type
  220. ],
  221. maps:from_list(connector_fields(http))
  222. )
  223. ).
  224. mongo_common_fields() ->
  225. [
  226. {collection,
  227. ?HOCON(atom(), #{
  228. required => true,
  229. desc => ?DESC(collection)
  230. })},
  231. {filter,
  232. ?HOCON(map(), #{
  233. required => false,
  234. default => #{},
  235. desc => ?DESC(filter)
  236. })}
  237. ].
  238. validations() ->
  239. [
  240. {check_ssl_opts, fun check_ssl_opts/1}
  241. ].
  242. headers(type) ->
  243. list({binary(), binary()});
  244. headers(desc) ->
  245. ?DESC(?FUNCTION_NAME);
  246. headers(converter) ->
  247. fun(Headers) ->
  248. maps:to_list(maps:merge(default_headers(), transform_header_name(Headers)))
  249. end;
  250. headers(default) ->
  251. default_headers();
  252. headers(_) ->
  253. undefined.
  254. headers_no_content_type(type) ->
  255. list({binary(), binary()});
  256. headers_no_content_type(desc) ->
  257. ?DESC(?FUNCTION_NAME);
  258. headers_no_content_type(converter) ->
  259. fun(Headers) ->
  260. maps:to_list(
  261. maps:without(
  262. [<<"content-type">>],
  263. maps:merge(default_headers_no_content_type(), transform_header_name(Headers))
  264. )
  265. )
  266. end;
  267. headers_no_content_type(default) ->
  268. default_headers_no_content_type();
  269. headers_no_content_type(validator) ->
  270. fun(Headers) ->
  271. case lists:keyfind(<<"content-type">>, 1, Headers) of
  272. false -> ok;
  273. _ -> {error, do_not_include_content_type}
  274. end
  275. end;
  276. headers_no_content_type(_) ->
  277. undefined.
  278. url(type) -> binary();
  279. url(desc) -> ?DESC(?FUNCTION_NAME);
  280. url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")];
  281. url(required) -> true;
  282. url(_) -> undefined.
  283. %%--------------------------------------------------------------------
  284. %% Internal functions
  285. %%--------------------------------------------------------------------
  286. default_headers() ->
  287. maps:put(
  288. <<"content-type">>,
  289. <<"application/json">>,
  290. default_headers_no_content_type()
  291. ).
  292. default_headers_no_content_type() ->
  293. #{
  294. <<"accept">> => <<"application/json">>,
  295. <<"cache-control">> => <<"no-cache">>,
  296. <<"connection">> => <<"keep-alive">>,
  297. <<"keep-alive">> => <<"timeout=30, max=1000">>
  298. }.
  299. transform_header_name(Headers) ->
  300. maps:fold(
  301. fun(K0, V, Acc) ->
  302. K = list_to_binary(string:to_lower(to_list(K0))),
  303. maps:put(K, V, Acc)
  304. end,
  305. #{},
  306. Headers
  307. ).
  308. check_ssl_opts(Conf) ->
  309. Sources = hocon_maps:get("authorization.sources", Conf, []),
  310. lists:foreach(
  311. fun
  312. (#{<<"url">> := Url} = Source) ->
  313. case emqx_authz_http:parse_url(Url) of
  314. {<<"https", _/binary>>, _, _} ->
  315. case emqx_map_lib:deep_find([<<"ssl">>, <<"enable">>], Source) of
  316. {ok, true} -> true;
  317. {ok, false} -> throw({ssl_not_enable, Url});
  318. _ -> throw({ssl_enable_not_found, Url})
  319. end;
  320. {<<"http", _/binary>>, _, _} ->
  321. ok;
  322. Bad ->
  323. throw({bad_scheme, Url, Bad})
  324. end;
  325. (_Source) ->
  326. ok
  327. end,
  328. Sources
  329. ).
  330. query() ->
  331. ?HOCON(binary(), #{
  332. desc => ?DESC(query),
  333. required => true,
  334. validator => fun(S) ->
  335. case size(S) > 0 of
  336. true -> ok;
  337. _ -> {error, "Request query"}
  338. end
  339. end
  340. }).
  341. cmd() ->
  342. ?HOCON(binary(), #{
  343. desc => ?DESC(cmd),
  344. required => true,
  345. validator => fun(S) ->
  346. case size(S) > 0 of
  347. true -> ok;
  348. _ -> {error, "Request query"}
  349. end
  350. end
  351. }).
  352. connector_fields(DB) ->
  353. connector_fields(DB, config).
  354. connector_fields(DB, Fields) ->
  355. Mod0 = io_lib:format("~ts_~ts", [emqx_connector, DB]),
  356. Mod =
  357. try
  358. list_to_existing_atom(Mod0)
  359. catch
  360. error:badarg ->
  361. list_to_atom(Mod0);
  362. error:Reason ->
  363. erlang:error(Reason)
  364. end,
  365. erlang:apply(Mod, fields, [Fields]).
  366. to_list(A) when is_atom(A) ->
  367. atom_to_list(A);
  368. to_list(B) when is_binary(B) ->
  369. binary_to_list(B).
  370. common_rate_field() ->
  371. [
  372. {"rate", ?HOCON(float(), #{desc => ?DESC("rate")})},
  373. {"rate_max", ?HOCON(float(), #{desc => ?DESC("rate_max")})},
  374. {"rate_last5m", ?HOCON(float(), #{desc => ?DESC("rate_last5m")})}
  375. ].
  376. method(Method) ->
  377. ?HOCON(Method, #{default => Method, required => true, desc => ?DESC(method)}).
  378. array(Ref) -> array(Ref, Ref).
  379. array(Ref, DescId) ->
  380. ?HOCON(?ARRAY(?R_REF(Ref)), #{desc => ?DESC(DescId)}).