feat(authn http api): provide http api for authn and improve update mechanism
This commit is contained in:
parent
7d2aac7e24
commit
327ff8636f
|
@ -1,13 +1,11 @@
|
|||
emqx_authn: {
|
||||
enable: false
|
||||
authenticators: [
|
||||
# {
|
||||
# name: "authenticator1"
|
||||
# mechanism: password-based
|
||||
# config: {
|
||||
# server_type: built-in-database
|
||||
# user_id_type: clientid
|
||||
# }
|
||||
# }
|
||||
{
|
||||
name: "authenticator1"
|
||||
mechanism: password-based
|
||||
server_type: built-in-database
|
||||
user_id_type: clientid
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -17,21 +17,21 @@
|
|||
-define(APP, emqx_authn).
|
||||
-define(CHAIN, <<"mqtt">>).
|
||||
|
||||
-type chain_id() :: binary().
|
||||
-type authenticator_name() :: binary().
|
||||
-type mechanism() :: 'password-based' | jwt | scram.
|
||||
-define(VER_1, <<"1">>).
|
||||
-define(VER_2, <<"2">>).
|
||||
|
||||
-record(authenticator,
|
||||
{ name :: authenticator_name()
|
||||
, mechanism :: mechanism()
|
||||
{ id :: binary()
|
||||
, name :: binary()
|
||||
, provider :: module()
|
||||
, config :: map()
|
||||
, state :: map()
|
||||
, version :: binary()
|
||||
}).
|
||||
|
||||
-record(chain,
|
||||
{ id :: chain_id()
|
||||
, authenticators :: [{authenticator_name(), #authenticator{}}]
|
||||
{ id :: binary()
|
||||
, authenticators :: [{binary(), binary(), #authenticator{}}]
|
||||
, created_at :: integer()
|
||||
}).
|
||||
|
||||
|
|
|
@ -31,10 +31,9 @@
|
|||
, create_authenticator/2
|
||||
, delete_authenticator/2
|
||||
, update_authenticator/3
|
||||
, update_or_create_authenticator/3
|
||||
, lookup_authenticator/2
|
||||
, list_authenticators/1
|
||||
, move_authenticator_to_the_front/2
|
||||
, move_authenticator_to_the_end/2
|
||||
, move_authenticator_to_the_nth/3
|
||||
]).
|
||||
|
||||
|
@ -95,7 +94,7 @@ authenticate(Credential, _AuthResult) ->
|
|||
|
||||
do_authenticate([], _) ->
|
||||
{stop, {error, not_authorized}};
|
||||
do_authenticate([{_, #authenticator{provider = Provider, state = State}} | More], Credential) ->
|
||||
do_authenticate([{_, _, #authenticator{provider = Provider, state = State}} | More], Credential) ->
|
||||
case Provider:authenticate(Credential, State) of
|
||||
ignore ->
|
||||
do_authenticate(More, Credential);
|
||||
|
@ -130,7 +129,7 @@ delete_chain(ID) ->
|
|||
[] ->
|
||||
{error, {not_found, {chain, ID}}};
|
||||
[#chain{authenticators = Authenticators}] ->
|
||||
_ = [do_delete_authenticator(Authenticator) || {_, Authenticator} <- Authenticators],
|
||||
_ = [do_delete_authenticator(Authenticator) || {_, _, Authenticator} <- Authenticators],
|
||||
mnesia:delete(?CHAIN_TAB, ID, write)
|
||||
end
|
||||
end).
|
||||
|
@ -147,25 +146,21 @@ list_chains() ->
|
|||
Chains = ets:tab2list(?CHAIN_TAB),
|
||||
{ok, [serialize_chain(Chain) || Chain <- Chains]}.
|
||||
|
||||
create_authenticator(ChainID, #{name := Name,
|
||||
mechanism := Mechanism,
|
||||
config := Config}) ->
|
||||
create_authenticator(ChainID, #{name := Name} = Config) ->
|
||||
UpdateFun =
|
||||
fun(Chain = #chain{authenticators = Authenticators}) ->
|
||||
case lists:keymember(Name, 1, Authenticators) of
|
||||
case lists:keymember(Name, 2, Authenticators) of
|
||||
true ->
|
||||
{error, {already_exists, {authenticator, Name}}};
|
||||
{error, name_has_be_used};
|
||||
false ->
|
||||
Provider = authenticator_provider(Mechanism, Config),
|
||||
case Provider:create(ChainID, Name, Config) of
|
||||
{ok, State} ->
|
||||
Authenticator = #authenticator{name = Name,
|
||||
mechanism = Mechanism,
|
||||
provider = Provider,
|
||||
config = Config,
|
||||
state = State},
|
||||
NChain = Chain#chain{authenticators = Authenticators ++ [{Name, Authenticator}]},
|
||||
ok = mnesia:write(?CHAIN_TAB, NChain, write),
|
||||
AlreadyExist = fun(ID) ->
|
||||
lists:keymember(ID, 1, Authenticators)
|
||||
end,
|
||||
AuthenticatorID = gen_id(AlreadyExist),
|
||||
case do_create_authenticator(ChainID, AuthenticatorID, Config) of
|
||||
{ok, Authenticator} ->
|
||||
NAuthenticators = Authenticators ++ [{AuthenticatorID, Name, Authenticator}],
|
||||
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write),
|
||||
{ok, serialize_authenticator(Authenticator)};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
|
@ -174,12 +169,12 @@ create_authenticator(ChainID, #{name := Name,
|
|||
end,
|
||||
update_chain(ChainID, UpdateFun).
|
||||
|
||||
delete_authenticator(ChainID, AuthenticatorName) ->
|
||||
delete_authenticator(ChainID, AuthenticatorID) ->
|
||||
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
|
||||
case lists:keytake(AuthenticatorName, 1, Authenticators) of
|
||||
case lists:keytake(AuthenticatorID, 1, Authenticators) of
|
||||
false ->
|
||||
{error, {not_found, {authenticator, AuthenticatorName}}};
|
||||
{value, {_, Authenticator}, NAuthenticators} ->
|
||||
{error, {not_found, {authenticator, AuthenticatorID}}};
|
||||
{value, {_, _, Authenticator}, NAuthenticators} ->
|
||||
_ = do_delete_authenticator(Authenticator),
|
||||
NChain = Chain#chain{authenticators = NAuthenticators},
|
||||
mnesia:write(?CHAIN_TAB, NChain, write)
|
||||
|
@ -187,38 +182,80 @@ delete_authenticator(ChainID, AuthenticatorName) ->
|
|||
end,
|
||||
update_chain(ChainID, UpdateFun).
|
||||
|
||||
update_authenticator(ChainID, AuthenticatorName, Config) ->
|
||||
update_authenticator(ChainID, AuthenticatorID, Config) ->
|
||||
do_update_authenticator(ChainID, AuthenticatorID, Config, false).
|
||||
|
||||
update_or_create_authenticator(ChainID, AuthenticatorID, Config) ->
|
||||
do_update_authenticator(ChainID, AuthenticatorID, Config, true).
|
||||
|
||||
do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, CreateWhenNotFound) ->
|
||||
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
|
||||
case proplists:get_value(AuthenticatorName, Authenticators, undefined) of
|
||||
undefined ->
|
||||
{error, {not_found, {authenticator, AuthenticatorName}}};
|
||||
#authenticator{provider = Provider,
|
||||
config = OriginalConfig,
|
||||
state = State} = Authenticator ->
|
||||
NewConfig = maps:merge(OriginalConfig, Config),
|
||||
case Provider:update(ChainID, AuthenticatorName, NewConfig, State) of
|
||||
{ok, NState} ->
|
||||
NAuthenticator = Authenticator#authenticator{config = NewConfig,
|
||||
state = NState},
|
||||
NAuthenticators = update_value(AuthenticatorName, NAuthenticator, Authenticators),
|
||||
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write),
|
||||
{ok, serialize_authenticator(NAuthenticator)};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
case lists:keytake(AuthenticatorID, 1, Authenticators) of
|
||||
false ->
|
||||
case CreateWhenNotFound of
|
||||
true ->
|
||||
case do_create_authenticator(ChainID, AuthenticatorID, Config) of
|
||||
{ok, Authenticator} ->
|
||||
NAuthenticators = Authenticators ++ [{AuthenticatorID, NewName, Authenticator}],
|
||||
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write),
|
||||
{ok, serialize_authenticator(Authenticator)};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end;
|
||||
false ->
|
||||
{error, {not_found, {authenticator, AuthenticatorID}}}
|
||||
end;
|
||||
{value,
|
||||
{_, _, #authenticator{provider = Provider,
|
||||
state = #{version := Version} = State}},
|
||||
Others} ->
|
||||
case lists:keymember(NewName, 2, Others) of
|
||||
true ->
|
||||
{error, name_has_be_used};
|
||||
false ->
|
||||
case (NewProvider = authenticator_provider(Config)) =:= Provider of
|
||||
true ->
|
||||
Unique = {ChainID, AuthenticatorID, Version},
|
||||
case Provider:update(Config#{'_unique' => Unique}, State) of
|
||||
{ok, NewState} ->
|
||||
NewAuthenticator = #authenticator{name = NewName,
|
||||
config = Config,
|
||||
state = switch_version(NewState)},
|
||||
NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
|
||||
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}, write),
|
||||
{ok, serialize_authenticator(NewAuthenticator)};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end;
|
||||
false ->
|
||||
case NewProvider:create(Config#{'_unique' => {ChainID, AuthenticatorID, Version}}) of
|
||||
{ok, NewState} ->
|
||||
NewAuthenticator = #authenticator{name = NewName,
|
||||
provider = NewProvider,
|
||||
config = Config,
|
||||
state = switch_version(NewState)},
|
||||
NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
|
||||
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}, write),
|
||||
_ = Provider:destroy(State),
|
||||
{ok, serialize_authenticator(NewAuthenticator)};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
end,
|
||||
update_chain(ChainID, UpdateFun).
|
||||
|
||||
lookup_authenticator(ChainID, AuthenticatorName) ->
|
||||
lookup_authenticator(ChainID, AuthenticatorID) ->
|
||||
case mnesia:dirty_read(?CHAIN_TAB, ChainID) of
|
||||
[] ->
|
||||
{error, {not_found, {chain, ChainID}}};
|
||||
[#chain{authenticators = Authenticators}] ->
|
||||
case proplists:get_value(AuthenticatorName, Authenticators, undefined) of
|
||||
undefined ->
|
||||
{error, {not_found, {authenticator, AuthenticatorName}}};
|
||||
Authenticator ->
|
||||
case lists:keyfind(AuthenticatorID, 1, Authenticators) of
|
||||
false ->
|
||||
{error, {not_found, {authenticator, AuthenticatorID}}};
|
||||
{_, _, Authenticator} ->
|
||||
{ok, serialize_authenticator(Authenticator)}
|
||||
end
|
||||
end.
|
||||
|
@ -231,9 +268,9 @@ list_authenticators(ChainID) ->
|
|||
{ok, serialize_authenticators(Authenticators)}
|
||||
end.
|
||||
|
||||
move_authenticator_to_the_front(ChainID, AuthenticatorName) ->
|
||||
move_authenticator_to_the_nth(ChainID, AuthenticatorID, N) ->
|
||||
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
|
||||
case move_authenticator_to_the_front_(AuthenticatorName, Authenticators) of
|
||||
case move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N) of
|
||||
{ok, NAuthenticators} ->
|
||||
NChain = Chain#chain{authenticators = NAuthenticators},
|
||||
mnesia:write(?CHAIN_TAB, NChain, write);
|
||||
|
@ -243,108 +280,94 @@ move_authenticator_to_the_front(ChainID, AuthenticatorName) ->
|
|||
end,
|
||||
update_chain(ChainID, UpdateFun).
|
||||
|
||||
move_authenticator_to_the_end(ChainID, AuthenticatorName) ->
|
||||
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
|
||||
case move_authenticator_to_the_end_(AuthenticatorName, Authenticators) of
|
||||
{ok, NAuthenticators} ->
|
||||
NChain = Chain#chain{authenticators = NAuthenticators},
|
||||
mnesia:write(?CHAIN_TAB, NChain, write);
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end
|
||||
end,
|
||||
update_chain(ChainID, UpdateFun).
|
||||
import_users(ChainID, AuthenticatorID, Filename) ->
|
||||
call_authenticator(ChainID, AuthenticatorID, import_users, [Filename]).
|
||||
|
||||
move_authenticator_to_the_nth(ChainID, AuthenticatorName, N) ->
|
||||
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
|
||||
case move_authenticator_to_the_nth_(AuthenticatorName, Authenticators, N) of
|
||||
{ok, NAuthenticators} ->
|
||||
NChain = Chain#chain{authenticators = NAuthenticators},
|
||||
mnesia:write(?CHAIN_TAB, NChain, write);
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end
|
||||
end,
|
||||
update_chain(ChainID, UpdateFun).
|
||||
add_user(ChainID, AuthenticatorID, UserInfo) ->
|
||||
call_authenticator(ChainID, AuthenticatorID, add_user, [UserInfo]).
|
||||
|
||||
import_users(ChainID, AuthenticatorName, Filename) ->
|
||||
call_authenticator(ChainID, AuthenticatorName, import_users, [Filename]).
|
||||
delete_user(ChainID, AuthenticatorID, UserID) ->
|
||||
call_authenticator(ChainID, AuthenticatorID, delete_user, [UserID]).
|
||||
|
||||
add_user(ChainID, AuthenticatorName, UserInfo) ->
|
||||
call_authenticator(ChainID, AuthenticatorName, add_user, [UserInfo]).
|
||||
update_user(ChainID, AuthenticatorID, UserID, NewUserInfo) ->
|
||||
call_authenticator(ChainID, AuthenticatorID, update_user, [UserID, NewUserInfo]).
|
||||
|
||||
delete_user(ChainID, AuthenticatorName, UserID) ->
|
||||
call_authenticator(ChainID, AuthenticatorName, delete_user, [UserID]).
|
||||
lookup_user(ChainID, AuthenticatorID, UserID) ->
|
||||
call_authenticator(ChainID, AuthenticatorID, lookup_user, [UserID]).
|
||||
|
||||
update_user(ChainID, AuthenticatorName, UserID, NewUserInfo) ->
|
||||
call_authenticator(ChainID, AuthenticatorName, update_user, [UserID, NewUserInfo]).
|
||||
|
||||
lookup_user(ChainID, AuthenticatorName, UserID) ->
|
||||
call_authenticator(ChainID, AuthenticatorName, lookup_user, [UserID]).
|
||||
|
||||
list_users(ChainID, AuthenticatorName) ->
|
||||
call_authenticator(ChainID, AuthenticatorName, list_users, []).
|
||||
list_users(ChainID, AuthenticatorID) ->
|
||||
call_authenticator(ChainID, AuthenticatorID, list_users, []).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
authenticator_provider('password-based', #{server_type := 'built-in-database'}) ->
|
||||
authenticator_provider(#{mechanism := 'password-based', server_type := 'built-in-database'}) ->
|
||||
emqx_authn_mnesia;
|
||||
authenticator_provider('password-based', #{server_type := 'mysql'}) ->
|
||||
authenticator_provider(#{mechanism := 'password-based', server_type := 'mysql'}) ->
|
||||
emqx_authn_mysql;
|
||||
authenticator_provider('password-based', #{server_type := 'pgsql'}) ->
|
||||
authenticator_provider(#{mechanism := 'password-based', server_type := 'pgsql'}) ->
|
||||
emqx_authn_pgsql;
|
||||
authenticator_provider('password-based', #{server_type := 'http-server'}) ->
|
||||
authenticator_provider(#{mechanism := 'password-based', server_type := 'http-server'}) ->
|
||||
emqx_authn_http;
|
||||
authenticator_provider(jwt, _) ->
|
||||
authenticator_provider(#{mechanism := jwt}) ->
|
||||
emqx_authn_jwt;
|
||||
authenticator_provider(scram, #{server_type := 'built-in-database'}) ->
|
||||
authenticator_provider(#{mechanism := scram, server_type := 'built-in-database'}) ->
|
||||
emqx_enhanced_authn_scram_mnesia.
|
||||
|
||||
gen_id(AlreadyExist) ->
|
||||
ID = list_to_binary(emqx_rule_id:gen()),
|
||||
case AlreadyExist(ID) of
|
||||
true -> gen_id(AlreadyExist);
|
||||
false -> ID
|
||||
end.
|
||||
|
||||
switch_version(State = #{version := ?VER_1}) ->
|
||||
State#{version := ?VER_2};
|
||||
switch_version(State = #{version := ?VER_2}) ->
|
||||
State#{version := ?VER_1};
|
||||
switch_version(State) ->
|
||||
State#{version => ?VER_1}.
|
||||
|
||||
do_create_authenticator(ChainID, AuthenticatorID, #{name := Name} = Config) ->
|
||||
Provider = authenticator_provider(Config),
|
||||
Unique = <<ChainID/binary, "/", AuthenticatorID/binary, ":", ?VER_1/binary>>,
|
||||
case Provider:create(Config#{'_unique' => Unique}) of
|
||||
{ok, State} ->
|
||||
Authenticator = #authenticator{id = AuthenticatorID,
|
||||
name = Name,
|
||||
provider = Provider,
|
||||
config = Config,
|
||||
state = switch_version(State)},
|
||||
{ok, Authenticator};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
do_delete_authenticator(#authenticator{provider = Provider, state = State}) ->
|
||||
Provider:destroy(State).
|
||||
_ = Provider:destroy(State),
|
||||
ok.
|
||||
|
||||
update_value(Key, Value, List) ->
|
||||
lists:keyreplace(Key, 1, List, {Key, Value}).
|
||||
replace_authenticator(ID, #authenticator{name = Name} = Authenticator, Authenticators) ->
|
||||
lists:keyreplace(ID, 1, Authenticators, {ID, Name, Authenticator}).
|
||||
|
||||
move_authenticator_to_the_front_(AuthenticatorName, Authenticators) ->
|
||||
move_authenticator_to_the_front_(AuthenticatorName, Authenticators, []).
|
||||
|
||||
move_authenticator_to_the_front_(AuthenticatorName, [], _) ->
|
||||
{error, {not_found, {authenticator, AuthenticatorName}}};
|
||||
move_authenticator_to_the_front_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], Passed) ->
|
||||
{ok, [Authenticator | (lists:reverse(Passed) ++ More)]};
|
||||
move_authenticator_to_the_front_(AuthenticatorName, [Authenticator | More], Passed) ->
|
||||
move_authenticator_to_the_front_(AuthenticatorName, More, [Authenticator | Passed]).
|
||||
|
||||
move_authenticator_to_the_end_(AuthenticatorName, Authenticators) ->
|
||||
move_authenticator_to_the_end_(AuthenticatorName, Authenticators, []).
|
||||
|
||||
move_authenticator_to_the_end_(AuthenticatorName, [], _) ->
|
||||
{error, {not_found, {authenticator, AuthenticatorName}}};
|
||||
move_authenticator_to_the_end_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], Passed) ->
|
||||
{ok, lists:reverse(Passed) ++ More ++ [Authenticator]};
|
||||
move_authenticator_to_the_end_(AuthenticatorName, [Authenticator | More], Passed) ->
|
||||
move_authenticator_to_the_end_(AuthenticatorName, More, [Authenticator | Passed]).
|
||||
|
||||
move_authenticator_to_the_nth_(AuthenticatorName, Authenticators, N)
|
||||
move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N)
|
||||
when N =< length(Authenticators) andalso N > 0 ->
|
||||
move_authenticator_to_the_nth_(AuthenticatorName, Authenticators, N, []);
|
||||
move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N, []);
|
||||
move_authenticator_to_the_nth_(_, _, _) ->
|
||||
{error, out_of_range}.
|
||||
|
||||
move_authenticator_to_the_nth_(AuthenticatorName, [], _, _) ->
|
||||
{error, {not_found, {authenticator, AuthenticatorName}}};
|
||||
move_authenticator_to_the_nth_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], N, Passed)
|
||||
move_authenticator_to_the_nth_(AuthenticatorID, [], _, _) ->
|
||||
{error, {not_found, {authenticator, AuthenticatorID}}};
|
||||
move_authenticator_to_the_nth_(AuthenticatorID, [{AuthenticatorID, _, _} = Authenticator | More], N, Passed)
|
||||
when N =< length(Passed) ->
|
||||
{L1, L2} = lists:split(N - 1, lists:reverse(Passed)),
|
||||
{ok, L1 ++ [Authenticator] ++ L2 ++ More};
|
||||
move_authenticator_to_the_nth_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], N, Passed) ->
|
||||
move_authenticator_to_the_nth_(AuthenticatorID, [{AuthenticatorID, _, _} = Authenticator | More], N, Passed) ->
|
||||
{L1, L2} = lists:split(N - length(Passed) - 1, More),
|
||||
{ok, lists:reverse(Passed) ++ L1 ++ [Authenticator] ++ L2};
|
||||
move_authenticator_to_the_nth_(AuthenticatorName, [Authenticator | More], N, Passed) ->
|
||||
move_authenticator_to_the_nth_(AuthenticatorName, More, N, [Authenticator | Passed]).
|
||||
move_authenticator_to_the_nth_(AuthenticatorID, [Authenticator | More], N, Passed) ->
|
||||
move_authenticator_to_the_nth_(AuthenticatorID, More, N, [Authenticator | Passed]).
|
||||
|
||||
update_chain(ChainID, UpdateFun) ->
|
||||
trans(
|
||||
|
@ -357,24 +380,15 @@ update_chain(ChainID, UpdateFun) ->
|
|||
end
|
||||
end).
|
||||
|
||||
% lookup_chain_by_listener(ListenerID, AuthNType) ->
|
||||
% case mnesia:dirty_read(?BINDING_TAB, {ListenerID, AuthNType}) of
|
||||
% [] ->
|
||||
% {error, not_found};
|
||||
% [#binding{chain_id = ChainID}] ->
|
||||
% {ok, ChainID}
|
||||
% end.
|
||||
|
||||
|
||||
call_authenticator(ChainID, AuthenticatorName, Func, Args) ->
|
||||
call_authenticator(ChainID, AuthenticatorID, Func, Args) ->
|
||||
case mnesia:dirty_read(?CHAIN_TAB, ChainID) of
|
||||
[] ->
|
||||
{error, {not_found, {chain, ChainID}}};
|
||||
[#chain{authenticators = Authenticators}] ->
|
||||
case proplists:get_value(AuthenticatorName, Authenticators, undefined) of
|
||||
undefined ->
|
||||
{error, {not_found, {authenticator, AuthenticatorName}}};
|
||||
#authenticator{provider = Provider, state = State} ->
|
||||
case lists:keyfind(AuthenticatorID, 1, Authenticators) of
|
||||
false ->
|
||||
{error, {not_found, {authenticator, AuthenticatorID}}};
|
||||
{_, _, #authenticator{provider = Provider, state = State}} ->
|
||||
case erlang:function_exported(Provider, Func, length(Args) + 1) of
|
||||
true ->
|
||||
erlang:apply(Provider, Func, Args ++ [State]);
|
||||
|
@ -391,20 +405,12 @@ serialize_chain(#chain{id = ID,
|
|||
authenticators => serialize_authenticators(Authenticators),
|
||||
created_at => CreatedAt}.
|
||||
|
||||
% serialize_binding(#binding{bound = {ListenerID, _},
|
||||
% chain_id = ChainID}) ->
|
||||
% #{listener_id => ListenerID,
|
||||
% chain_id => ChainID}.
|
||||
|
||||
serialize_authenticators(Authenticators) ->
|
||||
[serialize_authenticator(Authenticator) || {_, Authenticator} <- Authenticators].
|
||||
[serialize_authenticator(Authenticator) || {_, _, Authenticator} <- Authenticators].
|
||||
|
||||
serialize_authenticator(#authenticator{name = Name,
|
||||
mechanism = Mechanism,
|
||||
serialize_authenticator(#authenticator{id = ID,
|
||||
config = Config}) ->
|
||||
#{name => Name,
|
||||
mechanism => Mechanism,
|
||||
config => Config}.
|
||||
Config#{id => ID}.
|
||||
|
||||
trans(Fun) ->
|
||||
trans(Fun, []).
|
||||
|
|
|
@ -20,12 +20,25 @@
|
|||
|
||||
-include("emqx_authn.hrl").
|
||||
|
||||
-export([ api_spec/0 ]).
|
||||
-export([ api_spec/0
|
||||
, authenticators/2
|
||||
, authenticators2/2
|
||||
, position/2
|
||||
, import_users/2
|
||||
, users/2
|
||||
, users2/2
|
||||
]).
|
||||
|
||||
api_spec() ->
|
||||
{[authenticator_api()], definitions()}.
|
||||
{[ authenticators_api()
|
||||
, authenticators_api2()
|
||||
, position_api()
|
||||
, import_users_api()
|
||||
, users_api()
|
||||
, users2_api()
|
||||
], definitions()}.
|
||||
|
||||
authenticator_api() ->
|
||||
authenticators_api() ->
|
||||
Example1 = #{name => <<"example">>,
|
||||
mechanism => <<"password-based">>,
|
||||
config => #{
|
||||
|
@ -86,24 +99,506 @@ authenticator_api() ->
|
|||
},
|
||||
responses => #{
|
||||
<<"201">> => #{
|
||||
description => <<"Created successfully">>,
|
||||
content => #{}
|
||||
description => <<"Created">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"returned_authenticator">>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
get => #{
|
||||
description => "List authenticators",
|
||||
responses => #{
|
||||
<<"200">> => #{
|
||||
description => <<"OK">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => #{
|
||||
type => array,
|
||||
items => minirest:ref(<<"returned_authenticator">>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{"/authentication/authenticators", Metadata, authenticators}.
|
||||
|
||||
authenticators_api2() ->
|
||||
Metadata = #{
|
||||
get => #{
|
||||
description => "Get authenicator by id",
|
||||
parameters => [
|
||||
#{
|
||||
name => id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
}
|
||||
],
|
||||
responses => #{
|
||||
<<"200">> => #{
|
||||
description => <<"OK">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"returned_authenticator">>)
|
||||
}
|
||||
}
|
||||
},
|
||||
<<"404">> => #{
|
||||
description => <<"Not Found">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"error">>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
put => #{
|
||||
description => "Update authenticator",
|
||||
parameters => [
|
||||
#{
|
||||
name => id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
}
|
||||
],
|
||||
requestBody => #{
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => #{
|
||||
oneOf => [ minirest:ref(<<"password_based">>)
|
||||
, minirest:ref(<<"jwt">>)
|
||||
, minirest:ref(<<"scram">>)
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{
|
||||
<<"200">> => #{
|
||||
description => <<"OK">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"returned_authenticator">>)
|
||||
}
|
||||
}
|
||||
},
|
||||
<<"404">> => #{
|
||||
description => <<"Not Found">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"error">>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
delete => #{
|
||||
description => "Delete authenticator",
|
||||
parameters => [
|
||||
#{
|
||||
name => id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
}
|
||||
],
|
||||
responses => #{
|
||||
<<"204">> => #{
|
||||
description => <<"No Content">>
|
||||
},
|
||||
<<"404">> => #{
|
||||
description => <<"Not Found">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"error">>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{"/authentication/authenticators/:id", Metadata, authenticators2}.
|
||||
|
||||
position_api() ->
|
||||
Metadata = #{
|
||||
post => #{
|
||||
description => "Change the order of authenticators",
|
||||
parameters => [
|
||||
#{
|
||||
name => id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
}
|
||||
],
|
||||
requestBody => #{
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => #{
|
||||
type => object,
|
||||
required => [position],
|
||||
properties => #{
|
||||
position => #{
|
||||
type => integer,
|
||||
example => 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{
|
||||
<<"204">> => #{
|
||||
description => <<"No Content">>
|
||||
},
|
||||
<<"404">> => #{
|
||||
description => <<"Not Found">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"error">>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{"/authentication/authenticators/:id/position", Metadata, position}.
|
||||
|
||||
import_users_api() ->
|
||||
Metadata = #{
|
||||
post => #{
|
||||
description => "Import users from json/csv file",
|
||||
parameters => [
|
||||
#{
|
||||
name => id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
}
|
||||
],
|
||||
requestBody => #{
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => #{
|
||||
type => object,
|
||||
required => [filename],
|
||||
properties => #{
|
||||
filename => #{
|
||||
type => string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{
|
||||
<<"204">> => #{
|
||||
description => <<"No Content">>
|
||||
},
|
||||
<<"400">> => #{
|
||||
description => <<"Bad Request">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"error">>)
|
||||
}
|
||||
}
|
||||
},
|
||||
<<"404">> => #{
|
||||
description => <<"Not Found">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"error">>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{"/authentication/authenticators/:id/import-users", Metadata, import_users}.
|
||||
|
||||
users_api() ->
|
||||
Metadata = #{
|
||||
post => #{
|
||||
description => "Add user",
|
||||
parameters => [
|
||||
#{
|
||||
name => id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
}
|
||||
],
|
||||
requestBody => #{
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => #{
|
||||
type => object,
|
||||
required => [user_id, password],
|
||||
properties => #{
|
||||
user_id => #{
|
||||
type => string
|
||||
},
|
||||
password => #{
|
||||
type => string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{
|
||||
<<"201">> => #{
|
||||
description => <<"Created">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => #{
|
||||
type => object,
|
||||
required => [user_id],
|
||||
properties => #{
|
||||
user_id => #{
|
||||
type => string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
<<"400">> => #{
|
||||
description => <<"Bad Request">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"error">>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
get => #{
|
||||
description => "List users",
|
||||
parameters => [
|
||||
#{
|
||||
name => id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
}
|
||||
],
|
||||
responses => #{
|
||||
<<"200">> => #{
|
||||
description => <<"OK">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => #{
|
||||
type => array,
|
||||
items => #{
|
||||
type => object,
|
||||
required => [user_id],
|
||||
properties => #{
|
||||
user_id => #{
|
||||
type => string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{"/authentication/authenticators/:id/users", Metadata, users}.
|
||||
|
||||
users2_api() ->
|
||||
Metadata = #{
|
||||
patch => #{
|
||||
description => "Update user",
|
||||
parameters => [
|
||||
#{
|
||||
name => id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
},
|
||||
#{
|
||||
name => user_id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
}
|
||||
],
|
||||
requestBody => #{
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => #{
|
||||
type => object,
|
||||
required => [password],
|
||||
properties => #{
|
||||
password => #{
|
||||
type => string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => #{
|
||||
<<"200">> => #{
|
||||
description => <<"OK">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => #{
|
||||
type => array,
|
||||
items => #{
|
||||
type => object,
|
||||
required => [user_id],
|
||||
properties => #{
|
||||
user_id => #{
|
||||
type => string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
<<"404">> => #{
|
||||
description => <<"Not Found">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"error">>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
get => #{
|
||||
description => "Get user info",
|
||||
parameters => [
|
||||
#{
|
||||
name => id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
},
|
||||
#{
|
||||
name => user_id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
}
|
||||
],
|
||||
responses => #{
|
||||
<<"200">> => #{
|
||||
description => <<"OK">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => #{
|
||||
type => array,
|
||||
items => #{
|
||||
type => object,
|
||||
required => [user_id],
|
||||
properties => #{
|
||||
user_id => #{
|
||||
type => string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
<<"404">> => #{
|
||||
description => <<"Not Found">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"error">>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
delete => #{
|
||||
description => "Delete user",
|
||||
parameters => [
|
||||
#{
|
||||
name => id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
},
|
||||
#{
|
||||
name => user_id,
|
||||
in => path,
|
||||
schema => #{
|
||||
type => string
|
||||
},
|
||||
required => true
|
||||
}
|
||||
],
|
||||
responses => #{
|
||||
<<"204">> => #{
|
||||
description => <<"No Content">>
|
||||
},
|
||||
<<"404">> => #{
|
||||
description => <<"Not Found">>,
|
||||
content => #{
|
||||
'application/json' => #{
|
||||
schema => minirest:ref(<<"error">>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{"/authentication/authenticators/:id/users/:user_id", Metadata, users2}.
|
||||
|
||||
|
||||
definitions() ->
|
||||
AuthenticatorDef = #{
|
||||
oneOf => [ minirest:ref(<<"password_based">>)
|
||||
, minirest:ref(<<"jwt">>)
|
||||
, minirest:ref(<<"scram">>)
|
||||
]
|
||||
},
|
||||
|
||||
ReturnedAuthenticatorDef = #{
|
||||
allOf => [
|
||||
#{
|
||||
type => object,
|
||||
required => [name],
|
||||
properties => #{
|
||||
name => #{
|
||||
type => string,
|
||||
example => "exmaple"
|
||||
id => #{
|
||||
type => string
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -111,99 +606,108 @@ definitions() ->
|
|||
oneOf => [ minirest:ref(<<"password_based">>)
|
||||
, minirest:ref(<<"jwt">>)
|
||||
, minirest:ref(<<"scram">>)
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
PasswordBasedDef = #{
|
||||
type => object,
|
||||
properties => #{
|
||||
mechanism => #{
|
||||
type => string,
|
||||
enum => [<<"password-based">>],
|
||||
example => <<"password-based">>
|
||||
allOf => [
|
||||
#{
|
||||
type => object,
|
||||
required => [name, mechanism],
|
||||
properties => #{
|
||||
name => #{
|
||||
type => string,
|
||||
example => "exmaple"
|
||||
},
|
||||
mechanism => #{
|
||||
type => string,
|
||||
enum => [<<"password-based">>],
|
||||
example => <<"password-based">>
|
||||
}
|
||||
}
|
||||
},
|
||||
config => #{
|
||||
#{
|
||||
oneOf => [ minirest:ref(<<"password_based_built_in_database">>)
|
||||
, minirest:ref(<<"password_based_mysql">>)
|
||||
, minirest:ref(<<"password_based_pgsql">>)
|
||||
, minirest:ref(<<"password_based_http_server">>)
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
JWTDef = #{
|
||||
type => object,
|
||||
required => [name, mechanism],
|
||||
properties => #{
|
||||
name => #{
|
||||
type => string,
|
||||
example => "exmaple"
|
||||
},
|
||||
mechanism => #{
|
||||
type => string,
|
||||
enum => [<<"jwt">>],
|
||||
example => <<"jwt">>
|
||||
},
|
||||
config => #{
|
||||
use_jwks => #{
|
||||
type => boolean,
|
||||
default => false,
|
||||
example => false
|
||||
},
|
||||
algorithm => #{
|
||||
type => string,
|
||||
enum => [<<"hmac-based">>, <<"public-key">>],
|
||||
default => <<"hmac-based">>,
|
||||
example => <<"hmac-based">>
|
||||
},
|
||||
secret => #{
|
||||
type => string
|
||||
},
|
||||
secret_base64_encoded => #{
|
||||
type => boolean,
|
||||
default => false
|
||||
},
|
||||
certificate => #{
|
||||
type => string
|
||||
},
|
||||
verify_claims => #{
|
||||
type => object,
|
||||
properties => #{
|
||||
use_jwks => #{
|
||||
type => boolean,
|
||||
default => false,
|
||||
example => false
|
||||
},
|
||||
algorithm => #{
|
||||
type => string,
|
||||
enum => [<<"hmac-based">>, <<"public-key">>],
|
||||
default => <<"hmac-based">>,
|
||||
example => <<"hmac-based">>
|
||||
},
|
||||
secret => #{
|
||||
type => string
|
||||
},
|
||||
secret_base64_encoded => #{
|
||||
type => boolean,
|
||||
default => false
|
||||
},
|
||||
certificate => #{
|
||||
type => string
|
||||
},
|
||||
verify_claims => #{
|
||||
type => object,
|
||||
additionalProperties => #{
|
||||
type => string
|
||||
}
|
||||
},
|
||||
ssl => minirest:ref(<<"ssl">>)
|
||||
}
|
||||
}
|
||||
additionalProperties => #{
|
||||
type => string
|
||||
}
|
||||
},
|
||||
ssl => minirest:ref(<<"ssl">>)
|
||||
}
|
||||
},
|
||||
|
||||
SCRAMDef = #{
|
||||
type => object,
|
||||
required => [name, mechanism],
|
||||
properties => #{
|
||||
name => #{
|
||||
type => string,
|
||||
example => "exmaple"
|
||||
},
|
||||
mechanism => #{
|
||||
type => string,
|
||||
enum => [<<"scram">>],
|
||||
example => <<"scram">>
|
||||
},
|
||||
config => #{
|
||||
type => object,
|
||||
properties => #{
|
||||
server_type => #{
|
||||
type => string,
|
||||
enum => [<<"built-in-database">>],
|
||||
default => <<"built-in-database">>
|
||||
},
|
||||
algorithm => #{
|
||||
type => string,
|
||||
enum => [<<"sha256">>, <<"sha512">>],
|
||||
default => <<"sha256">>
|
||||
},
|
||||
iteration_count => #{
|
||||
type => integer,
|
||||
default => 4096
|
||||
}
|
||||
}
|
||||
server_type => #{
|
||||
type => string,
|
||||
enum => [<<"built-in-database">>],
|
||||
default => <<"built-in-database">>
|
||||
},
|
||||
algorithm => #{
|
||||
type => string,
|
||||
enum => [<<"sha256">>, <<"sha512">>],
|
||||
default => <<"sha256">>
|
||||
},
|
||||
iteration_count => #{
|
||||
type => integer,
|
||||
default => 4096
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -410,7 +914,7 @@ definitions() ->
|
|||
properties => #{
|
||||
enable => #{
|
||||
type => boolean,
|
||||
default => false
|
||||
default => false
|
||||
},
|
||||
hostname => #{
|
||||
type => string
|
||||
|
@ -420,7 +924,22 @@ definitions() ->
|
|||
}
|
||||
},
|
||||
|
||||
ErrorDef = #{
|
||||
type => object,
|
||||
properties => #{
|
||||
code => #{
|
||||
type => string,
|
||||
enum => [<<"NOT_FOUND">>],
|
||||
example => <<"NOT_FOUND">>
|
||||
},
|
||||
message => #{
|
||||
type => string
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
[ #{<<"authenticator">> => AuthenticatorDef}
|
||||
, #{<<"returned_authenticator">> => ReturnedAuthenticatorDef}
|
||||
, #{<<"password_based">> => PasswordBasedDef}
|
||||
, #{<<"jwt">> => JWTDef}
|
||||
, #{<<"scram">> => SCRAMDef}
|
||||
|
@ -430,4 +949,163 @@ definitions() ->
|
|||
, #{<<"password_based_http_server">> => PasswordBasedHTTPServerDef}
|
||||
, #{<<"password_hash_algorithm">> => PasswordHashAlgorithmDef}
|
||||
, #{<<"ssl">> => SSLDef}
|
||||
, #{<<"error">> => ErrorDef}
|
||||
].
|
||||
|
||||
authenticators(post, Request) ->
|
||||
{ok, Body, _} = cowboy_req:read_body(Request),
|
||||
AuthenticatorConfig = emqx_json:decode(Body, [return_maps]),
|
||||
Config = #{<<"emqx_authn">> => #{
|
||||
<<"authenticators">> => [AuthenticatorConfig]
|
||||
}},
|
||||
NConfig = hocon_schema:check_plain(emqx_authn_schema, Config,
|
||||
#{nullable => true}),
|
||||
#{emqx_authn := #{authenticators := [NAuthenticatorConfig]}} = emqx_map_lib:unsafe_atom_key_map(NConfig),
|
||||
case emqx_authn:create_authenticator(?CHAIN, NAuthenticatorConfig) of
|
||||
{ok, Authenticator2} ->
|
||||
{201, Authenticator2};
|
||||
{error, Reason} ->
|
||||
serialize_error(Reason)
|
||||
end;
|
||||
authenticators(get, _Request) ->
|
||||
{ok, Authenticators} = emqx_authn:list_authenticators(?CHAIN),
|
||||
{200, Authenticators}.
|
||||
|
||||
authenticators2(get, Request) ->
|
||||
AuthenticatorID = cowboy_req:binding(id, Request),
|
||||
case emqx_authn:lookup_authenticator(?CHAIN, AuthenticatorID) of
|
||||
{ok, Authenticator} ->
|
||||
{200, Authenticator};
|
||||
{error, Reason} ->
|
||||
serialize_error(Reason)
|
||||
end;
|
||||
authenticators2(put, Request) ->
|
||||
AuthenticatorID = cowboy_req:binding(id, Request),
|
||||
{ok, Body, _} = cowboy_req:read_body(Request),
|
||||
AuthenticatorConfig = emqx_json:decode(Body, [return_maps]),
|
||||
Config = #{<<"emqx_authn">> => #{
|
||||
<<"authenticators">> => [AuthenticatorConfig]
|
||||
}},
|
||||
NConfig = hocon_schema:check_plain(emqx_authn_schema, Config,
|
||||
#{nullable => true}),
|
||||
#{emqx_authn := #{authenticators := [NAuthenticatorConfig]}} = emqx_map_lib:unsafe_atom_key_map(NConfig),
|
||||
case emqx_authn:update_or_create_authenticator(?CHAIN, AuthenticatorID, NAuthenticatorConfig) of
|
||||
{ok, Authenticator} ->
|
||||
{200, Authenticator};
|
||||
{error, Reason} ->
|
||||
serialize_error(Reason)
|
||||
end;
|
||||
authenticators2(delete, Request) ->
|
||||
AuthenticatorID = cowboy_req:binding(id, Request),
|
||||
case emqx_authn:delete_authenticator(?CHAIN, AuthenticatorID) of
|
||||
ok ->
|
||||
{204};
|
||||
{error, Reason} ->
|
||||
serialize_error(Reason)
|
||||
end.
|
||||
|
||||
position(post, Request) ->
|
||||
AuthenticatorID = cowboy_req:binding(id, Request),
|
||||
{ok, Body, _} = cowboy_req:read_body(Request),
|
||||
case emqx_json:decode(Body, [return_maps]) of
|
||||
#{<<"position">> := Position} when is_integer(Position) ->
|
||||
case emqx_authn:move_authenticator_to_the_nth(?CHAIN, AuthenticatorID, Position) of
|
||||
ok ->
|
||||
{204};
|
||||
{error, Reason} ->
|
||||
serialize_error(Reason)
|
||||
end;
|
||||
_ ->
|
||||
serialize_error({missing_parameter, position})
|
||||
end.
|
||||
|
||||
import_users(post, Request) ->
|
||||
AuthenticatorID = cowboy_req:binding(id, Request),
|
||||
{ok, Body, _} = cowboy_req:read_body(Request),
|
||||
case emqx_json:decode(Body, [return_maps]) of
|
||||
#{<<"filename">> := Filename} when is_binary(Filename) ->
|
||||
case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of
|
||||
ok ->
|
||||
{204};
|
||||
{error, Reason} ->
|
||||
serialize_error(Reason)
|
||||
end;
|
||||
_ ->
|
||||
serialize_error({missing_parameter, filename})
|
||||
end.
|
||||
|
||||
users(post, Request) ->
|
||||
AuthenticatorID = cowboy_req:binding(id, Request),
|
||||
{ok, Body, _} = cowboy_req:read_body(Request),
|
||||
case emqx_json:decode(Body, [return_maps]) of
|
||||
#{<<"user_id">> := _,
|
||||
<<"password">> := _} = UserInfo ->
|
||||
case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserInfo) of
|
||||
{ok, User} ->
|
||||
{201, User};
|
||||
{error, Reason} ->
|
||||
serialize_error(Reason)
|
||||
end;
|
||||
_ ->
|
||||
serialize_error({missing_parameter, user_id})
|
||||
end;
|
||||
users(get, Request) ->
|
||||
AuthenticatorID = cowboy_req:binding(id, Request),
|
||||
case emqx_authn:list_users(?CHAIN, AuthenticatorID) of
|
||||
{ok, Users} ->
|
||||
{200, Users};
|
||||
{error, Reason} ->
|
||||
serialize_error(Reason)
|
||||
end.
|
||||
|
||||
users2(patch, Request) ->
|
||||
AuthenticatorID = cowboy_req:binding(id, Request),
|
||||
UserID = cowboy_req:binding(user_id, Request),
|
||||
{ok, Body, _} = cowboy_req:read_body(Request),
|
||||
case emqx_json:decode(Body, [return_maps]) of
|
||||
#{<<"password">> := _} = UserInfo ->
|
||||
case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserID, UserInfo) of
|
||||
{ok, User} ->
|
||||
{200, User};
|
||||
{error, Reason} ->
|
||||
serialize_error(Reason)
|
||||
end;
|
||||
_ ->
|
||||
serialize_error({missing_parameter, password})
|
||||
end;
|
||||
users2(get, Request) ->
|
||||
AuthenticatorID = cowboy_req:binding(id, Request),
|
||||
UserID = cowboy_req:binding(user_id, Request),
|
||||
case emqx_authn:lookup_user(?CHAIN, AuthenticatorID, UserID) of
|
||||
{ok, User} ->
|
||||
{200, User};
|
||||
{error, Reason} ->
|
||||
serialize_error(Reason)
|
||||
end;
|
||||
users2(delete, Request) ->
|
||||
AuthenticatorID = cowboy_req:binding(id, Request),
|
||||
UserID = cowboy_req:binding(user_id, Request),
|
||||
case emqx_authn:delete_user(?CHAIN, AuthenticatorID, UserID) of
|
||||
ok ->
|
||||
{204};
|
||||
{error, Reason} ->
|
||||
serialize_error(Reason)
|
||||
end.
|
||||
|
||||
serialize_error({not_found, {authenticator, ID}}) ->
|
||||
{404, #{code => <<"NOT_FOUND">>,
|
||||
message => list_to_binary(io_lib:format("Authenticator '~s' does not exist", [ID]))}};
|
||||
serialize_error(name_has_be_used) ->
|
||||
{409, #{code => <<"ALREADY_EXISTS">>,
|
||||
message => <<"Name has be used">>}};
|
||||
serialize_error(out_of_range) ->
|
||||
{400, #{code => <<"OUT_OF_RANGE">>,
|
||||
message => <<"Out of range">>}};
|
||||
serialize_error({missing_parameter, Name}) ->
|
||||
{400, #{code => <<"MISSING_PARAMETER">>,
|
||||
message => list_to_binary(
|
||||
io_lib:format("The input parameter '~p' that is mandatory for processing this request is not supplied", [Name])
|
||||
)}};
|
||||
serialize_error(_) ->
|
||||
{400, #{code => <<"BAD_REQUEST">>,
|
||||
message => <<"Todo">>}}.
|
|
@ -42,16 +42,16 @@ initialize() ->
|
|||
authenticators => []}),
|
||||
initialize(AuthNConfig).
|
||||
|
||||
initialize(#{enable := Enable, authenticators := Authenticators}) ->
|
||||
initialize(#{enable := Enable, authenticators := AuthenticatorsConfig}) ->
|
||||
{ok, _} = emqx_authn:create_chain(#{id => ?CHAIN}),
|
||||
initialize_authenticators(Authenticators),
|
||||
initialize_authenticators(AuthenticatorsConfig),
|
||||
Enable =:= true andalso emqx_authn:enable(),
|
||||
ok.
|
||||
|
||||
initialize_authenticators([]) ->
|
||||
ok;
|
||||
initialize_authenticators([#{name := Name} = Authenticator | More]) ->
|
||||
case emqx_authn:create_authenticator(?CHAIN, Authenticator) of
|
||||
initialize_authenticators([#{name := Name} = AuthenticatorConfig | More]) ->
|
||||
case emqx_authn:create_authenticator(?CHAIN, AuthenticatorConfig) of
|
||||
{ok, _} ->
|
||||
initialize_authenticators(More);
|
||||
{error, Reason} ->
|
||||
|
|
|
@ -25,57 +25,34 @@
|
|||
, fields/1
|
||||
]).
|
||||
|
||||
-reflect_type([ authenticator_name/0
|
||||
]).
|
||||
-export([ authenticator_name/1
|
||||
]).
|
||||
|
||||
structs() -> ["emqx_authn"].
|
||||
structs() -> [ "emqx_authn" ].
|
||||
|
||||
fields("emqx_authn") ->
|
||||
[ {enable, fun enable/1}
|
||||
, {authenticators, fun authenticators/1}
|
||||
];
|
||||
|
||||
fields('password-based') ->
|
||||
[ {name, fun authenticator_name/1}
|
||||
, {mechanism, {enum, ['password-based']}}
|
||||
, {config, hoconsc:t(hoconsc:union(
|
||||
[ hoconsc:ref(emqx_authn_mnesia, config)
|
||||
, hoconsc:ref(emqx_authn_mysql, config)
|
||||
, hoconsc:ref(emqx_authn_pgsql, config)
|
||||
, hoconsc:ref(emqx_authn_http, get)
|
||||
, hoconsc:ref(emqx_authn_http, post)
|
||||
]))}
|
||||
];
|
||||
|
||||
fields(jwt) ->
|
||||
[ {name, fun authenticator_name/1}
|
||||
, {mechanism, {enum, [jwt]}}
|
||||
, {config, hoconsc:t(hoconsc:union(
|
||||
[ hoconsc:ref(emqx_authn_jwt, 'hmac-based')
|
||||
, hoconsc:ref(emqx_authn_jwt, 'public-key')
|
||||
, hoconsc:ref(emqx_authn_jwt, 'jwks')
|
||||
]))}
|
||||
];
|
||||
|
||||
fields(scram) ->
|
||||
[ {name, fun authenticator_name/1}
|
||||
, {mechanism, {enum, [scram]}}
|
||||
, {config, hoconsc:t(hoconsc:union(
|
||||
[ hoconsc:ref(emqx_enhanced_authn_scram_mnesia, config)
|
||||
]))}
|
||||
].
|
||||
|
||||
authenticator_name(type) -> binary();
|
||||
authenticator_name(nullable) -> false;
|
||||
authenticator_name(_) -> undefined.
|
||||
|
||||
enable(type) -> boolean();
|
||||
enable(defualt) -> false;
|
||||
enable(default) -> false;
|
||||
enable(_) -> undefined.
|
||||
|
||||
authenticators(type) ->
|
||||
hoconsc:array({union, [ hoconsc:ref(?MODULE, 'password-based')
|
||||
, hoconsc:ref(?MODULE, jwt)
|
||||
, hoconsc:ref(?MODULE, scram)]});
|
||||
hoconsc:array({union, [ hoconsc:ref(emqx_authn_mnesia, config)
|
||||
, hoconsc:ref(emqx_authn_mysql, config)
|
||||
, hoconsc:ref(emqx_authn_pgsql, config)
|
||||
, hoconsc:ref(emqx_authn_http, get)
|
||||
, hoconsc:ref(emqx_authn_http, post)
|
||||
, hoconsc:ref(emqx_authn_jwt, 'hmac-based')
|
||||
, hoconsc:ref(emqx_authn_jwt, 'public-key')
|
||||
, hoconsc:ref(emqx_authn_jwt, 'jwks')
|
||||
, hoconsc:ref(emqx_enhanced_authn_scram_mnesia, config)
|
||||
]});
|
||||
authenticators(default) -> [];
|
||||
authenticators(_) -> undefined.
|
||||
|
||||
authenticator_name(type) -> authenticator_name();
|
||||
authenticator_name(nullable) -> false;
|
||||
authenticator_name(_) -> undefined.
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
, fields/1
|
||||
]).
|
||||
|
||||
-export([ create/3
|
||||
, update/4
|
||||
-export([ create/1
|
||||
, update/2
|
||||
, authenticate/2
|
||||
, destroy/1
|
||||
]).
|
||||
|
@ -71,7 +71,9 @@ mnesia(copy) ->
|
|||
structs() -> [config].
|
||||
|
||||
fields(config) ->
|
||||
[ {server_type, fun server_type/1}
|
||||
[ {name, fun emqx_authn_schema:authenticator_name/1}
|
||||
, {mechanism, {enum, [scram]}}
|
||||
, {server_type, fun server_type/1}
|
||||
, {algorithm, fun algorithm/1}
|
||||
, {iteration_count, fun iteration_count/1}
|
||||
].
|
||||
|
@ -92,16 +94,18 @@ iteration_count(_) -> undefined.
|
|||
%% APIs
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
create(ChainID, Authenticator, #{algorithm := Algorithm,
|
||||
iteration_count := IterationCount}) ->
|
||||
State = #{user_group => {ChainID, Authenticator},
|
||||
create(#{ algorithm := Algorithm
|
||||
, iteration_count := IterationCount
|
||||
, '_unique' := Unique
|
||||
}) ->
|
||||
State = #{user_group => Unique,
|
||||
algorithm => Algorithm,
|
||||
iteration_count => IterationCount},
|
||||
{ok, State}.
|
||||
|
||||
update(_ChainID, _Authenticator, _Config, _State) ->
|
||||
{error, update_not_suppored}.
|
||||
|
||||
update(Config, #{user_group := Unique}) ->
|
||||
create(Config#{'_unique' => Unique}).
|
||||
|
||||
authenticate(#{auth_method := AuthMethod,
|
||||
auth_data := AuthData,
|
||||
auth_cache := AuthCache}, State) ->
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
, validations/0
|
||||
]).
|
||||
|
||||
-export([ create/3
|
||||
, update/4
|
||||
-export([ create/1
|
||||
, update/2
|
||||
, authenticate/2
|
||||
, destroy/1
|
||||
]).
|
||||
|
@ -57,7 +57,9 @@ fields(post) ->
|
|||
] ++ common_fields().
|
||||
|
||||
common_fields() ->
|
||||
[ {server_type, {enum, ['http-server']}}
|
||||
[ {name, fun emqx_authn_schema:authenticator_name/1}
|
||||
, {mechanism, {enum, ['password-based']}}
|
||||
, {server_type, {enum, ['http-server']}}
|
||||
, {url, fun url/1}
|
||||
, {form_data, fun form_data/1}
|
||||
, {request_timeout, fun request_timeout/1}
|
||||
|
@ -105,37 +107,41 @@ request_timeout(_) -> undefined.
|
|||
%% APIs
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
create(ChainID, AuthenticatorName,
|
||||
#{method := Method,
|
||||
url := URL,
|
||||
headers := Headers,
|
||||
form_data := FormData,
|
||||
request_timeout := RequestTimeout} = Config) ->
|
||||
create(#{ method := Method
|
||||
, url := URL
|
||||
, headers := Headers
|
||||
, form_data := FormData
|
||||
, request_timeout := RequestTimeout
|
||||
, '_unique' := Unique
|
||||
} = Config) ->
|
||||
#{path := Path,
|
||||
query := Query} = URIMap = parse_url(URL),
|
||||
State = #{method => Method,
|
||||
path => Path,
|
||||
base_query => cow_qs:parse_qs(list_to_binary(Query)),
|
||||
headers => normalize_headers(Headers),
|
||||
form_data => maps:to_list(FormData),
|
||||
request_timeout => RequestTimeout},
|
||||
ResourceID = <<ChainID/binary, "/", AuthenticatorName/binary>>,
|
||||
case emqx_resource:create_local(ResourceID,
|
||||
State = #{ method => Method
|
||||
, path => Path
|
||||
, base_query => cow_qs:parse_qs(list_to_binary(Query))
|
||||
, headers => normalize_headers(Headers)
|
||||
, form_data => maps:to_list(FormData)
|
||||
, request_timeout => RequestTimeout
|
||||
},
|
||||
case emqx_resource:create_local(Unique,
|
||||
emqx_connector_http,
|
||||
Config#{base_url => maps:remove(query, URIMap),
|
||||
pool_type => random}) of
|
||||
{ok, _} ->
|
||||
{ok, State#{resource_id => ResourceID}};
|
||||
{ok, State#{resource_id => Unique}};
|
||||
{error, already_created} ->
|
||||
{ok, State#{resource_id => ResourceID}};
|
||||
{ok, State#{resource_id => Unique}};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
update(_ChainID, _AuthenticatorName, Config, #{resource_id := ResourceID} = State) ->
|
||||
case emqx_resource:update_local(ResourceID, emqx_connector_http, Config, []) of
|
||||
{ok, _} -> {ok, State};
|
||||
{error, Reason} -> {error, Reason}
|
||||
update(Config, State) ->
|
||||
case create(Config) of
|
||||
{ok, NewState} ->
|
||||
ok = destroy(State),
|
||||
{ok, NewState};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
authenticate(#{auth_method := _}, _) ->
|
||||
|
|
|
@ -24,8 +24,8 @@
|
|||
, fields/1
|
||||
]).
|
||||
|
||||
-export([ create/3
|
||||
, update/4
|
||||
-export([ create/1
|
||||
, update/2
|
||||
, authenticate/2
|
||||
, destroy/1
|
||||
]).
|
||||
|
@ -48,27 +48,24 @@ fields('hmac-based') ->
|
|||
, {algorithm, {enum, ['hmac-based']}}
|
||||
, {secret, fun secret/1}
|
||||
, {secret_base64_encoded, fun secret_base64_encoded/1}
|
||||
, {verify_claims, fun verify_claims/1}
|
||||
];
|
||||
] ++ common_fields();
|
||||
|
||||
fields('public-key') ->
|
||||
[ {use_jwks, {enum, [false]}}
|
||||
, {algorithm, {enum, ['public-key']}}
|
||||
, {certificate, fun certificate/1}
|
||||
, {verify_claims, fun verify_claims/1}
|
||||
];
|
||||
] ++ common_fields();
|
||||
|
||||
fields('jwks') ->
|
||||
[ {use_jwks, {enum, [true]}}
|
||||
, {endpoint, fun endpoint/1}
|
||||
, {refresh_interval, fun refresh_interval/1}
|
||||
, {verify_claims, fun verify_claims/1}
|
||||
, {ssl, #{type => hoconsc:union(
|
||||
[ hoconsc:ref(?MODULE, ssl_enable)
|
||||
, hoconsc:ref(?MODULE, ssl_disable)
|
||||
]),
|
||||
default => #{<<"enable">> => false}}}
|
||||
];
|
||||
] ++ common_fields();
|
||||
|
||||
fields(ssl_enable) ->
|
||||
[ {enable, #{type => true}}
|
||||
|
@ -82,6 +79,12 @@ fields(ssl_enable) ->
|
|||
fields(ssl_disable) ->
|
||||
[ {enable, #{type => false}} ].
|
||||
|
||||
common_fields() ->
|
||||
[ {name, fun emqx_authn_schema:authenticator_name/1}
|
||||
, {mechanism, {enum, [jwt]}}
|
||||
, {verify_claims, fun verify_claims/1}
|
||||
].
|
||||
|
||||
secret(type) -> string();
|
||||
secret(_) -> undefined.
|
||||
|
||||
|
@ -129,18 +132,18 @@ verify_claims(_) -> undefined.
|
|||
%% APIs
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
create(_ChainID, _AuthenticatorName, Config) ->
|
||||
create(Config).
|
||||
create(#{verify_claims := VerifyClaims} = Config) ->
|
||||
create2(Config#{verify_claims => handle_verify_claims(VerifyClaims)}).
|
||||
|
||||
update(_ChainID, _AuthenticatorName, #{use_jwks := false} = Config, #{jwk := Connector})
|
||||
update(#{use_jwks := false} = Config, #{jwk := Connector})
|
||||
when is_pid(Connector) ->
|
||||
_ = emqx_authn_jwks_connector:stop(Connector),
|
||||
create(Config);
|
||||
|
||||
update(_ChainID, _AuthenticatorName, #{use_jwks := false} = Config, _) ->
|
||||
update(#{use_jwks := false} = Config, _) ->
|
||||
create(Config);
|
||||
|
||||
update(_ChainID, _AuthenticatorName, #{use_jwks := true} = Config, #{jwk := Connector} = State)
|
||||
update(#{use_jwks := true} = Config, #{jwk := Connector} = State)
|
||||
when is_pid(Connector) ->
|
||||
ok = emqx_authn_jwks_connector:update(Connector, Config),
|
||||
case maps:get(verify_cliams, Config, undefined) of
|
||||
|
@ -150,7 +153,7 @@ update(_ChainID, _AuthenticatorName, #{use_jwks := true} = Config, #{jwk := Conn
|
|||
{ok, State#{verify_claims => handle_verify_claims(VerifyClaims)}}
|
||||
end;
|
||||
|
||||
update(_ChainID, _AuthenticatorName, #{use_jwks := true} = Config, _) ->
|
||||
update(#{use_jwks := true} = Config, _) ->
|
||||
create(Config).
|
||||
|
||||
authenticate(#{auth_method := _}, _) ->
|
||||
|
@ -181,9 +184,6 @@ destroy(_) ->
|
|||
%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
create(#{verify_claims := VerifyClaims} = Config) ->
|
||||
create2(Config#{verify_claims => handle_verify_claims(VerifyClaims)}).
|
||||
|
||||
create2(#{use_jwks := false,
|
||||
algorithm := 'hmac-based',
|
||||
secret := Secret0,
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
|
||||
-export([ structs/0, fields/1 ]).
|
||||
|
||||
-export([ create/3
|
||||
, update/4
|
||||
-export([ create/1
|
||||
, update/2
|
||||
, authenticate/2
|
||||
, destroy/1
|
||||
]).
|
||||
|
@ -39,7 +39,7 @@
|
|||
|
||||
-type user_id_type() :: clientid | username.
|
||||
|
||||
-type user_group() :: {chain_id(), authenticator_name()}.
|
||||
-type user_group() :: {binary(), binary()}.
|
||||
-type user_id() :: binary().
|
||||
|
||||
-record(user_info,
|
||||
|
@ -81,8 +81,10 @@ mnesia(copy) ->
|
|||
structs() -> [config].
|
||||
|
||||
fields(config) ->
|
||||
[ {server_type, {enum, ['built-in-database']}}
|
||||
, {user_id_type, fun user_id_type/1}
|
||||
[ {name, fun emqx_authn_schema:authenticator_name/1}
|
||||
, {mechanism, {enum, ['password-based']}}
|
||||
, {server_type, {enum, ['built-in-database']}}
|
||||
, {user_id_type, fun user_id_type/1}
|
||||
, {password_hash_algorithm, fun password_hash_algorithm/1}
|
||||
];
|
||||
|
||||
|
@ -111,25 +113,29 @@ salt_rounds(_) -> undefined.
|
|||
%% APIs
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
create(ChainID, AuthenticatorName, #{user_id_type := Type,
|
||||
password_hash_algorithm := #{name := bcrypt,
|
||||
salt_rounds := SaltRounds}}) ->
|
||||
create(#{ user_id_type := Type
|
||||
, password_hash_algorithm := #{name := bcrypt,
|
||||
salt_rounds := SaltRounds}
|
||||
, '_unique' := Unique
|
||||
}) ->
|
||||
{ok, _} = application:ensure_all_started(bcrypt),
|
||||
State = #{user_group => {ChainID, AuthenticatorName},
|
||||
State = #{user_group => Unique,
|
||||
user_id_type => Type,
|
||||
password_hash_algorithm => bcrypt,
|
||||
salt_rounds => SaltRounds},
|
||||
{ok, State};
|
||||
|
||||
create(ChainID, AuthenticatorName, #{user_id_type := Type,
|
||||
password_hash_algorithm := #{name := Name}}) ->
|
||||
State = #{user_group => {ChainID, AuthenticatorName},
|
||||
create(#{ user_id_type := Type
|
||||
, password_hash_algorithm := #{name := Name}
|
||||
, '_unique' := Unique
|
||||
}) ->
|
||||
State = #{user_group => Unique,
|
||||
user_id_type => Type,
|
||||
password_hash_algorithm => Name},
|
||||
{ok, State}.
|
||||
|
||||
update(ChainID, AuthenticatorName, Config, _State) ->
|
||||
create(ChainID, AuthenticatorName, Config).
|
||||
update(Config, #{user_group := Unique}) ->
|
||||
create(Config#{'_unique' => Unique}).
|
||||
|
||||
authenticate(#{auth_method := _}, _) ->
|
||||
ignore;
|
||||
|
|
|
@ -21,10 +21,12 @@
|
|||
|
||||
-behaviour(hocon_schema).
|
||||
|
||||
-export([ structs/0, fields/1 ]).
|
||||
-export([ structs/0
|
||||
, fields/1
|
||||
]).
|
||||
|
||||
-export([ create/3
|
||||
, update/4
|
||||
-export([ create/1
|
||||
, update/2
|
||||
, authenticate/2
|
||||
, destroy/1
|
||||
]).
|
||||
|
@ -36,7 +38,9 @@
|
|||
structs() -> [config].
|
||||
|
||||
fields(config) ->
|
||||
[ {server_type, {enum, [mysql]}}
|
||||
[ {name, fun emqx_authn_schema:authenticator_name/1}
|
||||
, {mechanism, {enum, ['password-based']}}
|
||||
, {server_type, {enum, [mysql]}}
|
||||
, {password_hash_algorithm, fun password_hash_algorithm/1}
|
||||
, {salt_position, fun salt_position/1}
|
||||
, {query, fun query/1}
|
||||
|
@ -70,34 +74,41 @@ query(nullable) -> false;
|
|||
query(_) -> undefined.
|
||||
|
||||
query_timeout(type) -> integer();
|
||||
query_timeout(defualt) -> 5000;
|
||||
query_timeout(default) -> 5000;
|
||||
query_timeout(_) -> undefined.
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% APIs
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
create(ChainID, AuthenticatorName,
|
||||
#{query := Query0,
|
||||
password_hash_algorithm := Algorithm} = Config) ->
|
||||
create(#{ password_hash_algorithm := Algorithm
|
||||
, salt_position := SaltPosition
|
||||
, query := Query0
|
||||
, query_timeout := QueryTimeout
|
||||
, '_unique' := Unique
|
||||
} = Config) ->
|
||||
{Query, PlaceHolders} = parse_query(Query0),
|
||||
ResourceID = iolist_to_binary(io_lib:format("~s/~s",[ChainID, AuthenticatorName])),
|
||||
State = #{query => Query,
|
||||
State = #{password_hash_algorithm => Algorithm,
|
||||
salt_position => SaltPosition,
|
||||
query => Query,
|
||||
placeholders => PlaceHolders,
|
||||
password_hash_algorithm => Algorithm},
|
||||
case emqx_resource:create_local(ResourceID, emqx_connector_mysql, Config) of
|
||||
query_timeout => QueryTimeout},
|
||||
case emqx_resource:create_local(Unique, emqx_connector_mysql, Config) of
|
||||
{ok, _} ->
|
||||
{ok, State#{resource_id => ResourceID}};
|
||||
{ok, State#{resource_id => Unique}};
|
||||
{error, already_created} ->
|
||||
{ok, State#{resource_id => ResourceID}};
|
||||
{ok, State#{resource_id => Unique}};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
update(_ChainID, _AuthenticatorName, Config, #{resource_id := ResourceID} = State) ->
|
||||
case emqx_resource:update_local(ResourceID, emqx_connector_mysql, Config, []) of
|
||||
{ok, _} -> {ok, State};
|
||||
{error, Reason} -> {error, Reason}
|
||||
update(Config, State) ->
|
||||
case create(Config) of
|
||||
{ok, NewState} ->
|
||||
ok = destroy(State),
|
||||
{ok, NewState};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
authenticate(#{auth_method := _}, _) ->
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
|
||||
-export([ structs/0, fields/1 ]).
|
||||
|
||||
-export([ create/3
|
||||
, update/4
|
||||
-export([ create/1
|
||||
, update/2
|
||||
, authenticate/2
|
||||
, destroy/1
|
||||
]).
|
||||
|
@ -36,7 +36,9 @@
|
|||
structs() -> [config].
|
||||
|
||||
fields(config) ->
|
||||
[ {server_type, {enum, [pgsql]}}
|
||||
[ {name, fun emqx_authn_schema:authenticator_name/1}
|
||||
, {mechanism, {enum, ['password-based']}}
|
||||
, {server_type, {enum, [pgsql]}}
|
||||
, {password_hash_algorithm, fun password_hash_algorithm/1}
|
||||
, {salt_position, {enum, [prefix, suffix]}}
|
||||
, {query, fun query/1}
|
||||
|
@ -54,26 +56,32 @@ query(_) -> undefined.
|
|||
%% APIs
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
create(ChainID, ServiceName, #{query := Query0,
|
||||
password_hash_algorithm := Algorithm} = Config) ->
|
||||
create(#{ query := Query0
|
||||
, password_hash_algorithm := Algorithm
|
||||
, salt_position := SaltPosition
|
||||
, '_unique' := Unique
|
||||
} = Config) ->
|
||||
{Query, PlaceHolders} = parse_query(Query0),
|
||||
ResourceID = iolist_to_binary(io_lib:format("~s/~s",[ChainID, ServiceName])),
|
||||
State = #{query => Query,
|
||||
placeholders => PlaceHolders,
|
||||
password_hash_algorithm => Algorithm},
|
||||
case emqx_resource:create_local(ResourceID, emqx_connector_pgsql, Config) of
|
||||
password_hash_algorithm => Algorithm,
|
||||
salt_position => SaltPosition},
|
||||
case emqx_resource:create_local(Unique, emqx_connector_pgsql, Config) of
|
||||
{ok, _} ->
|
||||
{ok, State#{resource_id => ResourceID}};
|
||||
{ok, State#{resource_id => Unique}};
|
||||
{error, already_created} ->
|
||||
{ok, State#{resource_id => ResourceID}};
|
||||
{ok, State#{resource_id => Unique}};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
update(_ChainID, _ServiceName, Config, #{resource_id := ResourceID} = State) ->
|
||||
case emqx_resource:update_local(ResourceID, emqx_connector_pgsql, Config, []) of
|
||||
{ok, _} -> {ok, State};
|
||||
{error, Reason} -> {error, Reason}
|
||||
update(Config, State) ->
|
||||
case create(Config) of
|
||||
{ok, NewState} ->
|
||||
ok = destroy(State),
|
||||
{ok, NewState};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
authenticate(#{auth_method := _}, _) ->
|
||||
|
|
Loading…
Reference in New Issue