emqx_authentication.erl 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2021-2023 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. %% @doc Authenticator management API module.
  17. %% Authentication is a core functionality of MQTT,
  18. %% the 'emqx' APP provides APIs for other APPs to implement
  19. %% the authentication callbacks.
  20. -module(emqx_authentication).
  21. -behaviour(gen_server).
  22. -include("emqx.hrl").
  23. -include("logger.hrl").
  24. -include("emqx_authentication.hrl").
  25. -include_lib("emqx/include/emqx_hooks.hrl").
  26. -include_lib("stdlib/include/ms_transform.hrl").
  27. -define(CONF_ROOT, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM).
  28. -define(IS_UNDEFINED(X), (X =:= undefined orelse X =:= <<>>)).
  29. %% The authentication entrypoint.
  30. -export([
  31. pre_hook_authenticate/1,
  32. authenticate/2
  33. ]).
  34. %% Authenticator manager process start/stop
  35. -export([
  36. start_link/0,
  37. stop/0,
  38. get_providers/0
  39. ]).
  40. %% Authenticator management APIs
  41. -export([
  42. initialize_authentication/2,
  43. register_provider/2,
  44. register_providers/1,
  45. deregister_provider/1,
  46. deregister_providers/1,
  47. delete_chain/1,
  48. lookup_chain/1,
  49. list_chains/0,
  50. list_chain_names/0,
  51. create_authenticator/2,
  52. delete_authenticator/2,
  53. update_authenticator/3,
  54. lookup_authenticator/2,
  55. list_authenticators/1,
  56. move_authenticator/3,
  57. reorder_authenticator/2
  58. ]).
  59. %% APIs for observer built_in_database
  60. -export([
  61. import_users/3,
  62. add_user/3,
  63. delete_user/3,
  64. update_user/4,
  65. lookup_user/3,
  66. list_users/3
  67. ]).
  68. %% gen_server callbacks
  69. -export([
  70. init/1,
  71. handle_call/3,
  72. handle_cast/2,
  73. handle_info/2,
  74. terminate/2,
  75. code_change/3
  76. ]).
  77. %% utility functions
  78. -export([authenticator_id/1, metrics_id/2]).
  79. -export_type([
  80. authenticator_id/0,
  81. position/0,
  82. chain_name/0
  83. ]).
  84. -ifdef(TEST).
  85. -compile(export_all).
  86. -compile(nowarn_export_all).
  87. -endif.
  88. -define(CHAINS_TAB, emqx_authn_chains).
  89. -define(TRACE_RESULT(Label, Result, Reason), begin
  90. ?TRACE_AUTHN(Label, #{
  91. result => (Result),
  92. reason => (Reason)
  93. }),
  94. Result
  95. end).
  96. -type chain_name() :: atom().
  97. -type authenticator_id() :: binary().
  98. -type position() :: front | rear | {before, authenticator_id()} | {'after', authenticator_id()}.
  99. -type authn_type() :: atom() | {atom(), atom()}.
  100. -type provider() :: module().
  101. -type chain() :: #{
  102. name := chain_name(),
  103. authenticators := [authenticator()]
  104. }.
  105. -type authenticator() :: #{
  106. id := authenticator_id(),
  107. provider := provider(),
  108. enable := boolean(),
  109. state := map()
  110. }.
  111. -type config() :: emqx_authentication_config:config().
  112. -type state() :: #{atom() => term()}.
  113. -type extra() :: #{
  114. is_superuser := boolean(),
  115. atom() => term()
  116. }.
  117. -type user_info() :: #{
  118. user_id := binary(),
  119. atom() => term()
  120. }.
  121. %% @doc check_config takes raw config from config file,
  122. %% parse and validate it, and return parsed result.
  123. -callback check_config(config()) -> config().
  124. -callback create(AuthenticatorID, Config) ->
  125. {ok, State}
  126. | {error, term()}
  127. when
  128. AuthenticatorID :: authenticator_id(), Config :: config(), State :: state().
  129. -callback update(Config, State) ->
  130. {ok, NewState}
  131. | {error, term()}
  132. when
  133. Config :: config(), State :: state(), NewState :: state().
  134. -callback authenticate(Credential, State) ->
  135. ignore
  136. | {ok, Extra}
  137. | {ok, Extra, AuthData}
  138. | {continue, AuthCache}
  139. | {continue, AuthData, AuthCache}
  140. | {error, term()}
  141. when
  142. Credential :: map(),
  143. State :: state(),
  144. Extra :: extra(),
  145. AuthData :: binary(),
  146. AuthCache :: map().
  147. -callback destroy(State) ->
  148. ok
  149. when
  150. State :: state().
  151. -callback import_users({Filename, FileData}, State) ->
  152. ok
  153. | {error, term()}
  154. when
  155. Filename :: binary(), FileData :: binary(), State :: state().
  156. -callback add_user(UserInfo, State) ->
  157. {ok, User}
  158. | {error, term()}
  159. when
  160. UserInfo :: user_info(), State :: state(), User :: user_info().
  161. -callback delete_user(UserID, State) ->
  162. ok
  163. | {error, term()}
  164. when
  165. UserID :: binary(), State :: state().
  166. -callback update_user(UserID, UserInfo, State) ->
  167. {ok, User}
  168. | {error, term()}
  169. when
  170. UserID :: binary(), UserInfo :: map(), State :: state(), User :: user_info().
  171. -callback lookup_user(UserID, UserInfo, State) ->
  172. {ok, User}
  173. | {error, term()}
  174. when
  175. UserID :: binary(), UserInfo :: map(), State :: state(), User :: user_info().
  176. -callback list_users(State) ->
  177. {ok, Users}
  178. when
  179. State :: state(), Users :: [user_info()].
  180. -optional_callbacks([
  181. import_users/2,
  182. add_user/2,
  183. delete_user/2,
  184. update_user/3,
  185. lookup_user/3,
  186. list_users/1,
  187. check_config/1
  188. ]).
  189. %%------------------------------------------------------------------------------
  190. %% Authenticate
  191. %%------------------------------------------------------------------------------
  192. -spec pre_hook_authenticate(emqx_types:clientinfo()) ->
  193. ok | continue | {error, not_authorized}.
  194. pre_hook_authenticate(#{enable_authn := false}) ->
  195. ?TRACE_RESULT("authentication_result", ok, enable_authn_false);
  196. pre_hook_authenticate(#{enable_authn := quick_deny_anonymous} = Credential) ->
  197. case maps:get(username, Credential, undefined) of
  198. U when ?IS_UNDEFINED(U) ->
  199. ?TRACE_RESULT(
  200. "authentication_result", {error, not_authorized}, enable_authn_false
  201. );
  202. _ ->
  203. continue
  204. end;
  205. pre_hook_authenticate(_) ->
  206. continue.
  207. authenticate(#{listener := Listener, protocol := Protocol} = Credential, AuthResult) ->
  208. case get_authenticators(Listener, global_chain(Protocol)) of
  209. {ok, ChainName, Authenticators} ->
  210. case get_enabled(Authenticators) of
  211. [] ->
  212. ?TRACE_RESULT("authentication_result", AuthResult, empty_chain);
  213. NAuthenticators ->
  214. Result = do_authenticate(ChainName, NAuthenticators, Credential),
  215. ?TRACE_RESULT("authentication_result", Result, chain_result)
  216. end;
  217. none ->
  218. ?TRACE_RESULT("authentication_result", AuthResult, no_chain)
  219. end.
  220. get_authenticators(Listener, Global) ->
  221. case ets:lookup(?CHAINS_TAB, Listener) of
  222. [#chain{name = Name, authenticators = Authenticators}] ->
  223. {ok, Name, Authenticators};
  224. _ ->
  225. case ets:lookup(?CHAINS_TAB, Global) of
  226. [#chain{name = Name, authenticators = Authenticators}] ->
  227. {ok, Name, Authenticators};
  228. _ ->
  229. none
  230. end
  231. end.
  232. get_enabled(Authenticators) ->
  233. [Authenticator || Authenticator <- Authenticators, Authenticator#authenticator.enable =:= true].
  234. %%------------------------------------------------------------------------------
  235. %% APIs
  236. %%------------------------------------------------------------------------------
  237. %% @doc Get all registered authentication providers.
  238. get_providers() ->
  239. call(get_providers).
  240. %% @doc Get authenticator identifier from its config.
  241. %% The authenticator config must contain a 'mechanism' key
  242. %% and maybe a 'backend' key.
  243. %% This function works with both parsed (atom keys) and raw (binary keys)
  244. %% configurations.
  245. authenticator_id(Config) ->
  246. emqx_authentication_config:authenticator_id(Config).
  247. %% @doc Call this API to initialize authenticators implemented in another APP.
  248. -spec initialize_authentication(chain_name(), [config()]) -> ok.
  249. initialize_authentication(_, []) ->
  250. ok;
  251. initialize_authentication(ChainName, AuthenticatorsConfig) ->
  252. CheckedConfig = to_list(AuthenticatorsConfig),
  253. lists:foreach(
  254. fun(AuthenticatorConfig) ->
  255. case create_authenticator(ChainName, AuthenticatorConfig) of
  256. {ok, _} ->
  257. ok;
  258. {error, Reason} ->
  259. ?SLOG(error, #{
  260. msg => "failed_to_create_authenticator",
  261. authenticator => authenticator_id(AuthenticatorConfig),
  262. reason => Reason
  263. })
  264. end
  265. end,
  266. CheckedConfig
  267. ).
  268. -spec start_link() -> {ok, pid()} | ignore | {error, term()}.
  269. start_link() ->
  270. %% Create chains ETS table here so that it belongs to the supervisor
  271. %% and survives `emqx_authentication` crashes.
  272. ok = create_chain_table(),
  273. gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
  274. -spec stop() -> ok.
  275. stop() ->
  276. gen_server:stop(?MODULE).
  277. %% @doc Register authentication providers.
  278. %% A provider is a tuple of `AuthNType' the module which implements
  279. %% the authenticator callbacks.
  280. %% For example, ``[{{'password_based', redis}, emqx_authn_redis}]''
  281. %% NOTE: Later registered provider may override earlier registered if they
  282. %% happen to clash the same `AuthNType'.
  283. -spec register_providers([{authn_type(), module()}]) -> ok.
  284. register_providers(Providers) ->
  285. call({register_providers, Providers}).
  286. -spec register_provider(authn_type(), module()) -> ok.
  287. register_provider(AuthNType, Provider) ->
  288. register_providers([{AuthNType, Provider}]).
  289. -spec deregister_providers([authn_type()]) -> ok.
  290. deregister_providers(AuthNTypes) when is_list(AuthNTypes) ->
  291. call({deregister_providers, AuthNTypes}).
  292. -spec deregister_provider(authn_type()) -> ok.
  293. deregister_provider(AuthNType) ->
  294. deregister_providers([AuthNType]).
  295. -spec delete_chain(chain_name()) -> ok | {error, term()}.
  296. delete_chain(Name) ->
  297. call({delete_chain, Name}).
  298. -spec lookup_chain(chain_name()) -> {ok, chain()} | {error, term()}.
  299. lookup_chain(Name) ->
  300. case ets:lookup(?CHAINS_TAB, Name) of
  301. [] ->
  302. {error, {not_found, {chain, Name}}};
  303. [Chain] ->
  304. {ok, serialize_chain(Chain)}
  305. end.
  306. -spec list_chains() -> {ok, [chain()]}.
  307. list_chains() ->
  308. Chains = ets:tab2list(?CHAINS_TAB),
  309. {ok, [serialize_chain(Chain) || Chain <- Chains]}.
  310. -spec list_chain_names() -> {ok, [atom()]}.
  311. list_chain_names() ->
  312. Select = ets:fun2ms(fun(#chain{name = Name}) -> Name end),
  313. ChainNames = ets:select(?CHAINS_TAB, Select),
  314. {ok, ChainNames}.
  315. -spec create_authenticator(chain_name(), config()) -> {ok, authenticator()} | {error, term()}.
  316. create_authenticator(ChainName, Config) ->
  317. call({create_authenticator, ChainName, Config}).
  318. -spec delete_authenticator(chain_name(), authenticator_id()) -> ok | {error, term()}.
  319. delete_authenticator(ChainName, AuthenticatorID) ->
  320. call({delete_authenticator, ChainName, AuthenticatorID}).
  321. -spec update_authenticator(chain_name(), authenticator_id(), config()) ->
  322. {ok, authenticator()} | {error, term()}.
  323. update_authenticator(ChainName, AuthenticatorID, Config) ->
  324. call({update_authenticator, ChainName, AuthenticatorID, Config}).
  325. -spec lookup_authenticator(chain_name(), authenticator_id()) ->
  326. {ok, authenticator()} | {error, term()}.
  327. lookup_authenticator(ChainName, AuthenticatorID) ->
  328. case ets:lookup(?CHAINS_TAB, ChainName) of
  329. [] ->
  330. {error, {not_found, {chain, ChainName}}};
  331. [#chain{authenticators = Authenticators}] ->
  332. case lists:keyfind(AuthenticatorID, #authenticator.id, Authenticators) of
  333. false ->
  334. {error, {not_found, {authenticator, AuthenticatorID}}};
  335. Authenticator ->
  336. {ok, serialize_authenticator(Authenticator)}
  337. end
  338. end.
  339. -spec list_authenticators(chain_name()) -> {ok, [authenticator()]} | {error, term()}.
  340. list_authenticators(ChainName) ->
  341. case ets:lookup(?CHAINS_TAB, ChainName) of
  342. [] ->
  343. {error, {not_found, {chain, ChainName}}};
  344. [#chain{authenticators = Authenticators}] ->
  345. {ok, serialize_authenticators(Authenticators)}
  346. end.
  347. -spec move_authenticator(chain_name(), authenticator_id(), position()) -> ok | {error, term()}.
  348. move_authenticator(ChainName, AuthenticatorID, Position) ->
  349. call({move_authenticator, ChainName, AuthenticatorID, Position}).
  350. -spec reorder_authenticator(chain_name(), [authenticator_id()]) -> ok.
  351. reorder_authenticator(_ChainName, []) ->
  352. ok;
  353. reorder_authenticator(ChainName, AuthenticatorIDs) ->
  354. call({reorder_authenticator, ChainName, AuthenticatorIDs}).
  355. -spec import_users(chain_name(), authenticator_id(), {binary(), binary()}) ->
  356. ok | {error, term()}.
  357. import_users(ChainName, AuthenticatorID, Filename) ->
  358. call({import_users, ChainName, AuthenticatorID, Filename}).
  359. -spec add_user(chain_name(), authenticator_id(), user_info()) ->
  360. {ok, user_info()} | {error, term()}.
  361. add_user(ChainName, AuthenticatorID, UserInfo) ->
  362. call({add_user, ChainName, AuthenticatorID, UserInfo}).
  363. -spec delete_user(chain_name(), authenticator_id(), binary()) -> ok | {error, term()}.
  364. delete_user(ChainName, AuthenticatorID, UserID) ->
  365. call({delete_user, ChainName, AuthenticatorID, UserID}).
  366. -spec update_user(chain_name(), authenticator_id(), binary(), map()) ->
  367. {ok, user_info()} | {error, term()}.
  368. update_user(ChainName, AuthenticatorID, UserID, NewUserInfo) ->
  369. call({update_user, ChainName, AuthenticatorID, UserID, NewUserInfo}).
  370. -spec lookup_user(chain_name(), authenticator_id(), binary()) ->
  371. {ok, user_info()} | {error, term()}.
  372. lookup_user(ChainName, AuthenticatorID, UserID) ->
  373. call({lookup_user, ChainName, AuthenticatorID, UserID}).
  374. -spec list_users(chain_name(), authenticator_id(), map()) -> {ok, [user_info()]} | {error, term()}.
  375. list_users(ChainName, AuthenticatorID, FuzzyParams) ->
  376. call({list_users, ChainName, AuthenticatorID, FuzzyParams}).
  377. %%--------------------------------------------------------------------
  378. %% gen_server callbacks
  379. %%--------------------------------------------------------------------
  380. init(_Opts) ->
  381. process_flag(trap_exit, true),
  382. Module = emqx_authentication_config,
  383. ok = emqx_config_handler:add_handler([?CONF_ROOT], Module),
  384. ok = emqx_config_handler:add_handler([listeners, '?', '?', ?CONF_ROOT], Module),
  385. {ok, #{hooked => false, providers => #{}}}.
  386. handle_call(get_providers, _From, #{providers := Providers} = State) ->
  387. reply(Providers, State);
  388. handle_call(
  389. {register_providers, Providers},
  390. _From,
  391. #{providers := Reg0} = State
  392. ) ->
  393. case lists:filter(fun({T, _}) -> maps:is_key(T, Reg0) end, Providers) of
  394. [] ->
  395. Reg = lists:foldl(
  396. fun({AuthNType, Module}, Pin) ->
  397. Pin#{AuthNType => Module}
  398. end,
  399. Reg0,
  400. Providers
  401. ),
  402. reply(ok, State#{providers := Reg});
  403. Clashes ->
  404. reply({error, {authentication_type_clash, Clashes}}, State)
  405. end;
  406. handle_call({deregister_providers, AuthNTypes}, _From, #{providers := Providers} = State) ->
  407. reply(ok, State#{providers := maps:without(AuthNTypes, Providers)});
  408. handle_call({delete_chain, ChainName}, _From, State) ->
  409. UpdateFun = fun(Chain) ->
  410. {_MatchedIDs, NewChain} = do_delete_authenticators(fun(_) -> true end, Chain),
  411. {ok, ok, NewChain}
  412. end,
  413. Reply = with_chain(ChainName, UpdateFun),
  414. reply(Reply, maybe_unhook(State));
  415. handle_call({create_authenticator, ChainName, Config}, _From, #{providers := Providers} = State) ->
  416. UpdateFun = fun(Chain) ->
  417. handle_create_authenticator(Chain, Config, Providers)
  418. end,
  419. Reply = with_new_chain(ChainName, UpdateFun),
  420. reply(Reply, maybe_hook(State));
  421. handle_call({delete_authenticator, ChainName, AuthenticatorID}, _From, State) ->
  422. UpdateFun = fun(Chain) ->
  423. handle_delete_authenticator(Chain, AuthenticatorID)
  424. end,
  425. Reply = with_chain(ChainName, UpdateFun),
  426. reply(Reply, maybe_unhook(State));
  427. handle_call({update_authenticator, ChainName, AuthenticatorID, Config}, _From, State) ->
  428. UpdateFun = fun(Chain) ->
  429. handle_update_authenticator(Chain, AuthenticatorID, Config)
  430. end,
  431. Reply = with_chain(ChainName, UpdateFun),
  432. reply(Reply, State);
  433. handle_call({move_authenticator, ChainName, AuthenticatorID, Position}, _From, State) ->
  434. UpdateFun = fun(Chain) ->
  435. handle_move_authenticator(Chain, AuthenticatorID, Position)
  436. end,
  437. Reply = with_chain(ChainName, UpdateFun),
  438. reply(Reply, State);
  439. handle_call({reorder_authenticator, ChainName, AuthenticatorIDs}, _From, State) ->
  440. UpdateFun = fun(Chain) ->
  441. handle_reorder_authenticator(Chain, AuthenticatorIDs)
  442. end,
  443. Reply = with_chain(ChainName, UpdateFun),
  444. reply(Reply, State);
  445. handle_call({import_users, ChainName, AuthenticatorID, Filename}, _From, State) ->
  446. Reply = call_authenticator(ChainName, AuthenticatorID, import_users, [Filename]),
  447. reply(Reply, State);
  448. handle_call({add_user, ChainName, AuthenticatorID, UserInfo}, _From, State) ->
  449. Reply = call_authenticator(ChainName, AuthenticatorID, add_user, [UserInfo]),
  450. reply(Reply, State);
  451. handle_call({delete_user, ChainName, AuthenticatorID, UserID}, _From, State) ->
  452. Reply = call_authenticator(ChainName, AuthenticatorID, delete_user, [UserID]),
  453. reply(Reply, State);
  454. handle_call({update_user, ChainName, AuthenticatorID, UserID, NewUserInfo}, _From, State) ->
  455. Reply = call_authenticator(ChainName, AuthenticatorID, update_user, [UserID, NewUserInfo]),
  456. reply(Reply, State);
  457. handle_call({lookup_user, ChainName, AuthenticatorID, UserID}, _From, State) ->
  458. Reply = call_authenticator(ChainName, AuthenticatorID, lookup_user, [UserID]),
  459. reply(Reply, State);
  460. handle_call({list_users, ChainName, AuthenticatorID, FuzzyParams}, _From, State) ->
  461. Reply = call_authenticator(ChainName, AuthenticatorID, list_users, [FuzzyParams]),
  462. reply(Reply, State);
  463. handle_call(Req, _From, State) ->
  464. ?SLOG(error, #{msg => "unexpected_call", call => Req}),
  465. {reply, ignored, State}.
  466. handle_cast(Req, State) ->
  467. ?SLOG(error, #{msg => "unexpected_cast", cast => Req}),
  468. {noreply, State}.
  469. handle_info(Info, State) ->
  470. ?SLOG(error, #{msg => "unexpected_info", info => Info}),
  471. {noreply, State}.
  472. terminate(Reason, _State) ->
  473. case Reason of
  474. {shutdown, _} ->
  475. ok;
  476. Reason when Reason == normal; Reason == shutdown ->
  477. ok;
  478. Other ->
  479. ?SLOG(error, #{
  480. msg => "emqx_authentication_terminating",
  481. reason => Other
  482. })
  483. end,
  484. emqx_config_handler:remove_handler([?CONF_ROOT]),
  485. emqx_config_handler:remove_handler([listeners, '?', '?', ?CONF_ROOT]),
  486. ok.
  487. code_change(_OldVsn, State, _Extra) ->
  488. {ok, State}.
  489. %%------------------------------------------------------------------------------
  490. %% Private functions
  491. %%------------------------------------------------------------------------------
  492. handle_update_authenticator(Chain, AuthenticatorID, Config) ->
  493. #chain{authenticators = Authenticators} = Chain,
  494. case lists:keyfind(AuthenticatorID, #authenticator.id, Authenticators) of
  495. false ->
  496. {error, {not_found, {authenticator, AuthenticatorID}}};
  497. #authenticator{provider = Provider, state = ST} = Authenticator ->
  498. case AuthenticatorID =:= authenticator_id(Config) of
  499. true ->
  500. NConfig = insert_user_group(Chain, Config),
  501. case Provider:update(NConfig, ST) of
  502. {ok, NewST} ->
  503. NewAuthenticator = Authenticator#authenticator{
  504. state = NewST,
  505. enable = maps:get(enable, NConfig)
  506. },
  507. NewAuthenticators = replace_authenticator(
  508. AuthenticatorID,
  509. NewAuthenticator,
  510. Authenticators
  511. ),
  512. NewChain = Chain#chain{authenticators = NewAuthenticators},
  513. Result = {ok, serialize_authenticator(NewAuthenticator)},
  514. {ok, Result, NewChain};
  515. {error, Reason} ->
  516. {error, Reason}
  517. end;
  518. false ->
  519. {error, change_of_authentication_type_is_not_allowed}
  520. end
  521. end.
  522. handle_delete_authenticator(Chain, AuthenticatorID) ->
  523. MatchFun = fun(#authenticator{id = ID}) ->
  524. ID =:= AuthenticatorID
  525. end,
  526. case do_delete_authenticators(MatchFun, Chain) of
  527. {[], _NewChain} ->
  528. {error, {not_found, {authenticator, AuthenticatorID}}};
  529. {[AuthenticatorID], NewChain} ->
  530. {ok, ok, NewChain}
  531. end.
  532. handle_move_authenticator(Chain, AuthenticatorID, Position) ->
  533. #chain{authenticators = Authenticators} = Chain,
  534. case do_move_authenticator(AuthenticatorID, Authenticators, Position) of
  535. {ok, NAuthenticators} ->
  536. NewChain = Chain#chain{authenticators = NAuthenticators},
  537. {ok, ok, NewChain};
  538. {error, Reason} ->
  539. {error, Reason}
  540. end.
  541. handle_reorder_authenticator(Chain, AuthenticatorIDs) ->
  542. #chain{authenticators = Authenticators} = Chain,
  543. NAuthenticators =
  544. lists:filtermap(
  545. fun(ID) ->
  546. case lists:keyfind(ID, #authenticator.id, Authenticators) of
  547. false ->
  548. ?SLOG(error, #{msg => "authenticator_not_found", id => ID}),
  549. false;
  550. Authenticator ->
  551. {true, Authenticator}
  552. end
  553. end,
  554. AuthenticatorIDs
  555. ),
  556. NewChain = Chain#chain{authenticators = NAuthenticators},
  557. {ok, ok, NewChain}.
  558. handle_create_authenticator(Chain, Config, Providers) ->
  559. #chain{name = Name, authenticators = Authenticators} = Chain,
  560. AuthenticatorID = authenticator_id(Config),
  561. case lists:keymember(AuthenticatorID, #authenticator.id, Authenticators) of
  562. true ->
  563. {error, {already_exists, {authenticator, AuthenticatorID}}};
  564. false ->
  565. NConfig = insert_user_group(Chain, Config),
  566. case do_create_authenticator(AuthenticatorID, NConfig, Providers) of
  567. {ok, Authenticator} ->
  568. NAuthenticators =
  569. Authenticators ++
  570. [Authenticator#authenticator{enable = maps:get(enable, Config)}],
  571. ok = emqx_metrics_worker:create_metrics(
  572. authn_metrics,
  573. metrics_id(Name, AuthenticatorID),
  574. [total, success, failed, nomatch],
  575. [total]
  576. ),
  577. NewChain = Chain#chain{authenticators = NAuthenticators},
  578. Result = {ok, serialize_authenticator(Authenticator)},
  579. {ok, Result, NewChain};
  580. {error, Reason} ->
  581. {error, Reason}
  582. end
  583. end.
  584. do_authenticate(_ChainName, [], _) ->
  585. {ok, {error, not_authorized}};
  586. do_authenticate(
  587. ChainName, [#authenticator{id = ID} = Authenticator | More], Credential
  588. ) ->
  589. MetricsID = metrics_id(ChainName, ID),
  590. emqx_metrics_worker:inc(authn_metrics, MetricsID, total),
  591. try authenticate_with_provider(Authenticator, Credential) of
  592. ignore ->
  593. ok = emqx_metrics_worker:inc(authn_metrics, MetricsID, nomatch),
  594. do_authenticate(ChainName, More, Credential);
  595. Result ->
  596. %% {ok, Extra}
  597. %% {ok, Extra, AuthData}
  598. %% {continue, AuthCache}
  599. %% {continue, AuthData, AuthCache}
  600. %% {error, Reason}
  601. case Result of
  602. {ok, _} ->
  603. emqx_metrics_worker:inc(authn_metrics, MetricsID, success);
  604. {error, _} ->
  605. emqx_metrics_worker:inc(authn_metrics, MetricsID, failed);
  606. _ ->
  607. ok
  608. end,
  609. {stop, Result}
  610. catch
  611. Class:Reason:Stacktrace ->
  612. ?TRACE_AUTHN(warning, "authenticator_error", #{
  613. exception => Class,
  614. reason => Reason,
  615. stacktrace => Stacktrace,
  616. authenticator => ID
  617. }),
  618. emqx_metrics_worker:inc(authn_metrics, MetricsID, nomatch),
  619. do_authenticate(ChainName, More, Credential)
  620. end.
  621. authenticate_with_provider(#authenticator{id = ID, provider = Provider, state = State}, Credential) ->
  622. AuthnResult = Provider:authenticate(Credential, State),
  623. ?TRACE_AUTHN("authenticator_result", #{
  624. authenticator => ID,
  625. result => AuthnResult
  626. }),
  627. AuthnResult.
  628. reply(Reply, State) ->
  629. {reply, Reply, State}.
  630. save_chain(#chain{
  631. name = Name,
  632. authenticators = []
  633. }) ->
  634. ets:delete(?CHAINS_TAB, Name);
  635. save_chain(#chain{} = Chain) ->
  636. ets:insert(?CHAINS_TAB, Chain).
  637. create_chain_table() ->
  638. try
  639. _ = ets:new(?CHAINS_TAB, [
  640. named_table,
  641. set,
  642. public,
  643. {keypos, #chain.name},
  644. {read_concurrency, true}
  645. ]),
  646. ok
  647. catch
  648. error:badarg -> ok
  649. end.
  650. global_chain(mqtt) ->
  651. 'mqtt:global';
  652. global_chain('mqtt-sn') ->
  653. 'mqtt-sn:global';
  654. global_chain(coap) ->
  655. 'coap:global';
  656. global_chain(lwm2m) ->
  657. 'lwm2m:global';
  658. global_chain(stomp) ->
  659. 'stomp:global';
  660. global_chain(_) ->
  661. 'unknown:global'.
  662. maybe_hook(#{hooked := false} = State) ->
  663. case
  664. lists:any(
  665. fun
  666. (#chain{authenticators = []}) -> false;
  667. (_) -> true
  668. end,
  669. ets:tab2list(?CHAINS_TAB)
  670. )
  671. of
  672. true ->
  673. ok = emqx_hooks:put('client.authenticate', {?MODULE, authenticate, []}, ?HP_AUTHN),
  674. State#{hooked => true};
  675. false ->
  676. State
  677. end;
  678. maybe_hook(State) ->
  679. State.
  680. maybe_unhook(#{hooked := true} = State) ->
  681. case
  682. lists:all(
  683. fun
  684. (#chain{authenticators = []}) -> true;
  685. (_) -> false
  686. end,
  687. ets:tab2list(?CHAINS_TAB)
  688. )
  689. of
  690. true ->
  691. ok = emqx_hooks:del('client.authenticate', {?MODULE, authenticate, []}),
  692. State#{hooked => false};
  693. false ->
  694. State
  695. end;
  696. maybe_unhook(State) ->
  697. State.
  698. do_create_authenticator(AuthenticatorID, #{enable := Enable} = Config, Providers) ->
  699. Type = authn_type(Config),
  700. case maps:get(Type, Providers, undefined) of
  701. undefined ->
  702. {error, {no_available_provider_for, Type}};
  703. Provider ->
  704. case Provider:create(AuthenticatorID, Config) of
  705. {ok, State} ->
  706. Authenticator = #authenticator{
  707. id = AuthenticatorID,
  708. provider = Provider,
  709. enable = Enable,
  710. state = State
  711. },
  712. {ok, Authenticator};
  713. {error, Reason} ->
  714. {error, Reason}
  715. end
  716. end.
  717. do_delete_authenticators(MatchFun, #chain{name = Name, authenticators = Authenticators} = Chain) ->
  718. {Matching, Others} = lists:partition(MatchFun, Authenticators),
  719. MatchingIDs = lists:map(
  720. fun(#authenticator{id = ID}) -> ID end,
  721. Matching
  722. ),
  723. ok = lists:foreach(
  724. fun(#authenticator{id = ID} = Authenticator) ->
  725. do_destroy_authenticator(Authenticator),
  726. emqx_metrics_worker:clear_metrics(authn_metrics, metrics_id(Name, ID))
  727. end,
  728. Matching
  729. ),
  730. {MatchingIDs, Chain#chain{authenticators = Others}}.
  731. do_destroy_authenticator(#authenticator{provider = Provider, state = State}) ->
  732. _ = Provider:destroy(State),
  733. ok.
  734. replace_authenticator(ID, Authenticator, Authenticators) ->
  735. lists:keyreplace(ID, #authenticator.id, Authenticators, Authenticator).
  736. do_move_authenticator(ID, Authenticators, Position) ->
  737. case lists:keytake(ID, #authenticator.id, Authenticators) of
  738. false ->
  739. {error, {not_found, {authenticator, ID}}};
  740. {value, Authenticator, NAuthenticators} ->
  741. case Position of
  742. ?CMD_MOVE_FRONT ->
  743. {ok, [Authenticator | NAuthenticators]};
  744. ?CMD_MOVE_REAR ->
  745. {ok, NAuthenticators ++ [Authenticator]};
  746. ?CMD_MOVE_BEFORE(RelatedID) ->
  747. insert(Authenticator, NAuthenticators, ?CMD_MOVE_BEFORE(RelatedID), []);
  748. ?CMD_MOVE_AFTER(RelatedID) ->
  749. insert(Authenticator, NAuthenticators, ?CMD_MOVE_AFTER(RelatedID), [])
  750. end
  751. end.
  752. insert(_, [], {_, RelatedID}, _) ->
  753. {error, {not_found, {authenticator, RelatedID}}};
  754. insert(
  755. Authenticator,
  756. [#authenticator{id = RelatedID} = Related | Rest],
  757. {Relative, RelatedID},
  758. Acc
  759. ) ->
  760. case Relative of
  761. before ->
  762. {ok, lists:reverse(Acc) ++ [Authenticator, Related | Rest]};
  763. 'after' ->
  764. {ok, lists:reverse(Acc) ++ [Related, Authenticator | Rest]}
  765. end;
  766. insert(Authenticator, [Authenticator0 | More], {Relative, RelatedID}, Acc) ->
  767. insert(Authenticator, More, {Relative, RelatedID}, [Authenticator0 | Acc]).
  768. with_new_chain(ChainName, Fun) ->
  769. case ets:lookup(?CHAINS_TAB, ChainName) of
  770. [] ->
  771. Chain = #chain{name = ChainName, authenticators = []},
  772. do_with_chain(Fun, Chain);
  773. [Chain] ->
  774. do_with_chain(Fun, Chain)
  775. end.
  776. with_chain(ChainName, Fun) ->
  777. case ets:lookup(?CHAINS_TAB, ChainName) of
  778. [] ->
  779. {error, {not_found, {chain, ChainName}}};
  780. [Chain] ->
  781. do_with_chain(Fun, Chain)
  782. end.
  783. do_with_chain(Fun, Chain) ->
  784. try
  785. case Fun(Chain) of
  786. {ok, Result} ->
  787. Result;
  788. {ok, Result, NewChain} ->
  789. save_chain(NewChain),
  790. Result;
  791. {error, _} = Error ->
  792. Error
  793. end
  794. catch
  795. Class:Reason:Stk ->
  796. {error, {exception, {Class, Reason, Stk}}}
  797. end.
  798. call_authenticator(ChainName, AuthenticatorID, Func, Args) ->
  799. Fun =
  800. fun(#chain{authenticators = Authenticators}) ->
  801. case lists:keyfind(AuthenticatorID, #authenticator.id, Authenticators) of
  802. false ->
  803. {error, {not_found, {authenticator, AuthenticatorID}}};
  804. #authenticator{provider = Provider, state = State} ->
  805. case erlang:function_exported(Provider, Func, length(Args) + 1) of
  806. true ->
  807. {ok, erlang:apply(Provider, Func, Args ++ [State])};
  808. false ->
  809. {error, unsupported_operation}
  810. end
  811. end
  812. end,
  813. with_chain(ChainName, Fun).
  814. serialize_chain(#chain{
  815. name = Name,
  816. authenticators = Authenticators
  817. }) ->
  818. #{
  819. name => Name,
  820. authenticators => serialize_authenticators(Authenticators)
  821. }.
  822. serialize_authenticators(Authenticators) ->
  823. [serialize_authenticator(Authenticator) || Authenticator <- Authenticators].
  824. serialize_authenticator(#authenticator{
  825. id = ID,
  826. provider = Provider,
  827. enable = Enable,
  828. state = State
  829. }) ->
  830. #{
  831. id => ID,
  832. provider => Provider,
  833. enable => Enable,
  834. state => State
  835. }.
  836. authn_type(#{mechanism := Mechanism, backend := Backend}) ->
  837. {Mechanism, Backend};
  838. authn_type(#{mechanism := Mechanism}) ->
  839. Mechanism.
  840. insert_user_group(
  841. Chain,
  842. Config = #{
  843. mechanism := password_based,
  844. backend := built_in_database
  845. }
  846. ) ->
  847. Config#{user_group => Chain#chain.name};
  848. insert_user_group(_Chain, Config) ->
  849. Config.
  850. metrics_id(ChainName, AuthenticatorId) ->
  851. iolist_to_binary([atom_to_binary(ChainName), <<"-">>, AuthenticatorId]).
  852. to_list(undefined) -> [];
  853. to_list(M) when M =:= #{} -> [];
  854. to_list(M) when is_map(M) -> [M];
  855. to_list(L) when is_list(L) -> L.
  856. call(Call) -> gen_server:call(?MODULE, Call, infinity).