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: {
|
emqx_authn: {
|
||||||
enable: false
|
enable: false
|
||||||
authenticators: [
|
authenticators: [
|
||||||
# {
|
{
|
||||||
# name: "authenticator1"
|
name: "authenticator1"
|
||||||
# mechanism: password-based
|
mechanism: password-based
|
||||||
# config: {
|
server_type: built-in-database
|
||||||
# server_type: built-in-database
|
user_id_type: clientid
|
||||||
# user_id_type: clientid
|
}
|
||||||
# }
|
|
||||||
# }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,21 +17,21 @@
|
||||||
-define(APP, emqx_authn).
|
-define(APP, emqx_authn).
|
||||||
-define(CHAIN, <<"mqtt">>).
|
-define(CHAIN, <<"mqtt">>).
|
||||||
|
|
||||||
-type chain_id() :: binary().
|
-define(VER_1, <<"1">>).
|
||||||
-type authenticator_name() :: binary().
|
-define(VER_2, <<"2">>).
|
||||||
-type mechanism() :: 'password-based' | jwt | scram.
|
|
||||||
|
|
||||||
-record(authenticator,
|
-record(authenticator,
|
||||||
{ name :: authenticator_name()
|
{ id :: binary()
|
||||||
, mechanism :: mechanism()
|
, name :: binary()
|
||||||
, provider :: module()
|
, provider :: module()
|
||||||
, config :: map()
|
, config :: map()
|
||||||
, state :: map()
|
, state :: map()
|
||||||
|
, version :: binary()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-record(chain,
|
-record(chain,
|
||||||
{ id :: chain_id()
|
{ id :: binary()
|
||||||
, authenticators :: [{authenticator_name(), #authenticator{}}]
|
, authenticators :: [{binary(), binary(), #authenticator{}}]
|
||||||
, created_at :: integer()
|
, created_at :: integer()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,9 @@
|
||||||
, create_authenticator/2
|
, create_authenticator/2
|
||||||
, delete_authenticator/2
|
, delete_authenticator/2
|
||||||
, update_authenticator/3
|
, update_authenticator/3
|
||||||
|
, update_or_create_authenticator/3
|
||||||
, lookup_authenticator/2
|
, lookup_authenticator/2
|
||||||
, list_authenticators/1
|
, list_authenticators/1
|
||||||
, move_authenticator_to_the_front/2
|
|
||||||
, move_authenticator_to_the_end/2
|
|
||||||
, move_authenticator_to_the_nth/3
|
, move_authenticator_to_the_nth/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
@ -95,7 +94,7 @@ authenticate(Credential, _AuthResult) ->
|
||||||
|
|
||||||
do_authenticate([], _) ->
|
do_authenticate([], _) ->
|
||||||
{stop, {error, not_authorized}};
|
{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
|
case Provider:authenticate(Credential, State) of
|
||||||
ignore ->
|
ignore ->
|
||||||
do_authenticate(More, Credential);
|
do_authenticate(More, Credential);
|
||||||
|
@ -130,7 +129,7 @@ delete_chain(ID) ->
|
||||||
[] ->
|
[] ->
|
||||||
{error, {not_found, {chain, ID}}};
|
{error, {not_found, {chain, ID}}};
|
||||||
[#chain{authenticators = Authenticators}] ->
|
[#chain{authenticators = Authenticators}] ->
|
||||||
_ = [do_delete_authenticator(Authenticator) || {_, Authenticator} <- Authenticators],
|
_ = [do_delete_authenticator(Authenticator) || {_, _, Authenticator} <- Authenticators],
|
||||||
mnesia:delete(?CHAIN_TAB, ID, write)
|
mnesia:delete(?CHAIN_TAB, ID, write)
|
||||||
end
|
end
|
||||||
end).
|
end).
|
||||||
|
@ -147,25 +146,21 @@ list_chains() ->
|
||||||
Chains = ets:tab2list(?CHAIN_TAB),
|
Chains = ets:tab2list(?CHAIN_TAB),
|
||||||
{ok, [serialize_chain(Chain) || Chain <- Chains]}.
|
{ok, [serialize_chain(Chain) || Chain <- Chains]}.
|
||||||
|
|
||||||
create_authenticator(ChainID, #{name := Name,
|
create_authenticator(ChainID, #{name := Name} = Config) ->
|
||||||
mechanism := Mechanism,
|
|
||||||
config := Config}) ->
|
|
||||||
UpdateFun =
|
UpdateFun =
|
||||||
fun(Chain = #chain{authenticators = Authenticators}) ->
|
fun(Chain = #chain{authenticators = Authenticators}) ->
|
||||||
case lists:keymember(Name, 1, Authenticators) of
|
case lists:keymember(Name, 2, Authenticators) of
|
||||||
true ->
|
true ->
|
||||||
{error, {already_exists, {authenticator, Name}}};
|
{error, name_has_be_used};
|
||||||
false ->
|
false ->
|
||||||
Provider = authenticator_provider(Mechanism, Config),
|
AlreadyExist = fun(ID) ->
|
||||||
case Provider:create(ChainID, Name, Config) of
|
lists:keymember(ID, 1, Authenticators)
|
||||||
{ok, State} ->
|
end,
|
||||||
Authenticator = #authenticator{name = Name,
|
AuthenticatorID = gen_id(AlreadyExist),
|
||||||
mechanism = Mechanism,
|
case do_create_authenticator(ChainID, AuthenticatorID, Config) of
|
||||||
provider = Provider,
|
{ok, Authenticator} ->
|
||||||
config = Config,
|
NAuthenticators = Authenticators ++ [{AuthenticatorID, Name, Authenticator}],
|
||||||
state = State},
|
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write),
|
||||||
NChain = Chain#chain{authenticators = Authenticators ++ [{Name, Authenticator}]},
|
|
||||||
ok = mnesia:write(?CHAIN_TAB, NChain, write),
|
|
||||||
{ok, serialize_authenticator(Authenticator)};
|
{ok, serialize_authenticator(Authenticator)};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
|
@ -174,12 +169,12 @@ create_authenticator(ChainID, #{name := Name,
|
||||||
end,
|
end,
|
||||||
update_chain(ChainID, UpdateFun).
|
update_chain(ChainID, UpdateFun).
|
||||||
|
|
||||||
delete_authenticator(ChainID, AuthenticatorName) ->
|
delete_authenticator(ChainID, AuthenticatorID) ->
|
||||||
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
|
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
|
||||||
case lists:keytake(AuthenticatorName, 1, Authenticators) of
|
case lists:keytake(AuthenticatorID, 1, Authenticators) of
|
||||||
false ->
|
false ->
|
||||||
{error, {not_found, {authenticator, AuthenticatorName}}};
|
{error, {not_found, {authenticator, AuthenticatorID}}};
|
||||||
{value, {_, Authenticator}, NAuthenticators} ->
|
{value, {_, _, Authenticator}, NAuthenticators} ->
|
||||||
_ = do_delete_authenticator(Authenticator),
|
_ = do_delete_authenticator(Authenticator),
|
||||||
NChain = Chain#chain{authenticators = NAuthenticators},
|
NChain = Chain#chain{authenticators = NAuthenticators},
|
||||||
mnesia:write(?CHAIN_TAB, NChain, write)
|
mnesia:write(?CHAIN_TAB, NChain, write)
|
||||||
|
@ -187,38 +182,80 @@ delete_authenticator(ChainID, AuthenticatorName) ->
|
||||||
end,
|
end,
|
||||||
update_chain(ChainID, UpdateFun).
|
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}) ->
|
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
|
||||||
case proplists:get_value(AuthenticatorName, Authenticators, undefined) of
|
case lists:keytake(AuthenticatorID, 1, Authenticators) of
|
||||||
undefined ->
|
false ->
|
||||||
{error, {not_found, {authenticator, AuthenticatorName}}};
|
case CreateWhenNotFound of
|
||||||
#authenticator{provider = Provider,
|
true ->
|
||||||
config = OriginalConfig,
|
case do_create_authenticator(ChainID, AuthenticatorID, Config) of
|
||||||
state = State} = Authenticator ->
|
{ok, Authenticator} ->
|
||||||
NewConfig = maps:merge(OriginalConfig, Config),
|
NAuthenticators = Authenticators ++ [{AuthenticatorID, NewName, Authenticator}],
|
||||||
case Provider:update(ChainID, AuthenticatorName, NewConfig, State) of
|
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write),
|
||||||
{ok, NState} ->
|
{ok, serialize_authenticator(Authenticator)};
|
||||||
NAuthenticator = Authenticator#authenticator{config = NewConfig,
|
{error, Reason} ->
|
||||||
state = NState},
|
{error, Reason}
|
||||||
NAuthenticators = update_value(AuthenticatorName, NAuthenticator, Authenticators),
|
end;
|
||||||
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write),
|
false ->
|
||||||
{ok, serialize_authenticator(NAuthenticator)};
|
{error, {not_found, {authenticator, AuthenticatorID}}}
|
||||||
{error, Reason} ->
|
end;
|
||||||
{error, Reason}
|
{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
|
||||||
end,
|
end,
|
||||||
update_chain(ChainID, UpdateFun).
|
update_chain(ChainID, UpdateFun).
|
||||||
|
|
||||||
lookup_authenticator(ChainID, AuthenticatorName) ->
|
lookup_authenticator(ChainID, AuthenticatorID) ->
|
||||||
case mnesia:dirty_read(?CHAIN_TAB, ChainID) of
|
case mnesia:dirty_read(?CHAIN_TAB, ChainID) of
|
||||||
[] ->
|
[] ->
|
||||||
{error, {not_found, {chain, ChainID}}};
|
{error, {not_found, {chain, ChainID}}};
|
||||||
[#chain{authenticators = Authenticators}] ->
|
[#chain{authenticators = Authenticators}] ->
|
||||||
case proplists:get_value(AuthenticatorName, Authenticators, undefined) of
|
case lists:keyfind(AuthenticatorID, 1, Authenticators) of
|
||||||
undefined ->
|
false ->
|
||||||
{error, {not_found, {authenticator, AuthenticatorName}}};
|
{error, {not_found, {authenticator, AuthenticatorID}}};
|
||||||
Authenticator ->
|
{_, _, Authenticator} ->
|
||||||
{ok, serialize_authenticator(Authenticator)}
|
{ok, serialize_authenticator(Authenticator)}
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
@ -231,9 +268,9 @@ list_authenticators(ChainID) ->
|
||||||
{ok, serialize_authenticators(Authenticators)}
|
{ok, serialize_authenticators(Authenticators)}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
move_authenticator_to_the_front(ChainID, AuthenticatorName) ->
|
move_authenticator_to_the_nth(ChainID, AuthenticatorID, N) ->
|
||||||
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
|
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} ->
|
{ok, NAuthenticators} ->
|
||||||
NChain = Chain#chain{authenticators = NAuthenticators},
|
NChain = Chain#chain{authenticators = NAuthenticators},
|
||||||
mnesia:write(?CHAIN_TAB, NChain, write);
|
mnesia:write(?CHAIN_TAB, NChain, write);
|
||||||
|
@ -243,108 +280,94 @@ move_authenticator_to_the_front(ChainID, AuthenticatorName) ->
|
||||||
end,
|
end,
|
||||||
update_chain(ChainID, UpdateFun).
|
update_chain(ChainID, UpdateFun).
|
||||||
|
|
||||||
move_authenticator_to_the_end(ChainID, AuthenticatorName) ->
|
import_users(ChainID, AuthenticatorID, Filename) ->
|
||||||
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
|
call_authenticator(ChainID, AuthenticatorID, import_users, [Filename]).
|
||||||
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).
|
|
||||||
|
|
||||||
move_authenticator_to_the_nth(ChainID, AuthenticatorName, N) ->
|
add_user(ChainID, AuthenticatorID, UserInfo) ->
|
||||||
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
|
call_authenticator(ChainID, AuthenticatorID, add_user, [UserInfo]).
|
||||||
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).
|
|
||||||
|
|
||||||
import_users(ChainID, AuthenticatorName, Filename) ->
|
delete_user(ChainID, AuthenticatorID, UserID) ->
|
||||||
call_authenticator(ChainID, AuthenticatorName, import_users, [Filename]).
|
call_authenticator(ChainID, AuthenticatorID, delete_user, [UserID]).
|
||||||
|
|
||||||
add_user(ChainID, AuthenticatorName, UserInfo) ->
|
update_user(ChainID, AuthenticatorID, UserID, NewUserInfo) ->
|
||||||
call_authenticator(ChainID, AuthenticatorName, add_user, [UserInfo]).
|
call_authenticator(ChainID, AuthenticatorID, update_user, [UserID, NewUserInfo]).
|
||||||
|
|
||||||
delete_user(ChainID, AuthenticatorName, UserID) ->
|
lookup_user(ChainID, AuthenticatorID, UserID) ->
|
||||||
call_authenticator(ChainID, AuthenticatorName, delete_user, [UserID]).
|
call_authenticator(ChainID, AuthenticatorID, lookup_user, [UserID]).
|
||||||
|
|
||||||
update_user(ChainID, AuthenticatorName, UserID, NewUserInfo) ->
|
list_users(ChainID, AuthenticatorID) ->
|
||||||
call_authenticator(ChainID, AuthenticatorName, update_user, [UserID, NewUserInfo]).
|
call_authenticator(ChainID, AuthenticatorID, list_users, []).
|
||||||
|
|
||||||
lookup_user(ChainID, AuthenticatorName, UserID) ->
|
|
||||||
call_authenticator(ChainID, AuthenticatorName, lookup_user, [UserID]).
|
|
||||||
|
|
||||||
list_users(ChainID, AuthenticatorName) ->
|
|
||||||
call_authenticator(ChainID, AuthenticatorName, list_users, []).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
authenticator_provider('password-based', #{server_type := 'built-in-database'}) ->
|
authenticator_provider(#{mechanism := 'password-based', server_type := 'built-in-database'}) ->
|
||||||
emqx_authn_mnesia;
|
emqx_authn_mnesia;
|
||||||
authenticator_provider('password-based', #{server_type := 'mysql'}) ->
|
authenticator_provider(#{mechanism := 'password-based', server_type := 'mysql'}) ->
|
||||||
emqx_authn_mysql;
|
emqx_authn_mysql;
|
||||||
authenticator_provider('password-based', #{server_type := 'pgsql'}) ->
|
authenticator_provider(#{mechanism := 'password-based', server_type := 'pgsql'}) ->
|
||||||
emqx_authn_pgsql;
|
emqx_authn_pgsql;
|
||||||
authenticator_provider('password-based', #{server_type := 'http-server'}) ->
|
authenticator_provider(#{mechanism := 'password-based', server_type := 'http-server'}) ->
|
||||||
emqx_authn_http;
|
emqx_authn_http;
|
||||||
authenticator_provider(jwt, _) ->
|
authenticator_provider(#{mechanism := jwt}) ->
|
||||||
emqx_authn_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.
|
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}) ->
|
do_delete_authenticator(#authenticator{provider = Provider, state = State}) ->
|
||||||
Provider:destroy(State).
|
_ = Provider:destroy(State),
|
||||||
|
ok.
|
||||||
|
|
||||||
update_value(Key, Value, List) ->
|
replace_authenticator(ID, #authenticator{name = Name} = Authenticator, Authenticators) ->
|
||||||
lists:keyreplace(Key, 1, List, {Key, Value}).
|
lists:keyreplace(ID, 1, Authenticators, {ID, Name, Authenticator}).
|
||||||
|
|
||||||
move_authenticator_to_the_front_(AuthenticatorName, Authenticators) ->
|
move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N)
|
||||||
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)
|
|
||||||
when N =< length(Authenticators) andalso N > 0 ->
|
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_(_, _, _) ->
|
move_authenticator_to_the_nth_(_, _, _) ->
|
||||||
{error, out_of_range}.
|
{error, out_of_range}.
|
||||||
|
|
||||||
move_authenticator_to_the_nth_(AuthenticatorName, [], _, _) ->
|
move_authenticator_to_the_nth_(AuthenticatorID, [], _, _) ->
|
||||||
{error, {not_found, {authenticator, AuthenticatorName}}};
|
{error, {not_found, {authenticator, AuthenticatorID}}};
|
||||||
move_authenticator_to_the_nth_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], N, Passed)
|
move_authenticator_to_the_nth_(AuthenticatorID, [{AuthenticatorID, _, _} = Authenticator | More], N, Passed)
|
||||||
when N =< length(Passed) ->
|
when N =< length(Passed) ->
|
||||||
{L1, L2} = lists:split(N - 1, lists:reverse(Passed)),
|
{L1, L2} = lists:split(N - 1, lists:reverse(Passed)),
|
||||||
{ok, L1 ++ [Authenticator] ++ L2 ++ More};
|
{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),
|
{L1, L2} = lists:split(N - length(Passed) - 1, More),
|
||||||
{ok, lists:reverse(Passed) ++ L1 ++ [Authenticator] ++ L2};
|
{ok, lists:reverse(Passed) ++ L1 ++ [Authenticator] ++ L2};
|
||||||
move_authenticator_to_the_nth_(AuthenticatorName, [Authenticator | More], N, Passed) ->
|
move_authenticator_to_the_nth_(AuthenticatorID, [Authenticator | More], N, Passed) ->
|
||||||
move_authenticator_to_the_nth_(AuthenticatorName, More, N, [Authenticator | Passed]).
|
move_authenticator_to_the_nth_(AuthenticatorID, More, N, [Authenticator | Passed]).
|
||||||
|
|
||||||
update_chain(ChainID, UpdateFun) ->
|
update_chain(ChainID, UpdateFun) ->
|
||||||
trans(
|
trans(
|
||||||
|
@ -357,24 +380,15 @@ update_chain(ChainID, UpdateFun) ->
|
||||||
end
|
end
|
||||||
end).
|
end).
|
||||||
|
|
||||||
% lookup_chain_by_listener(ListenerID, AuthNType) ->
|
call_authenticator(ChainID, AuthenticatorID, Func, Args) ->
|
||||||
% 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) ->
|
|
||||||
case mnesia:dirty_read(?CHAIN_TAB, ChainID) of
|
case mnesia:dirty_read(?CHAIN_TAB, ChainID) of
|
||||||
[] ->
|
[] ->
|
||||||
{error, {not_found, {chain, ChainID}}};
|
{error, {not_found, {chain, ChainID}}};
|
||||||
[#chain{authenticators = Authenticators}] ->
|
[#chain{authenticators = Authenticators}] ->
|
||||||
case proplists:get_value(AuthenticatorName, Authenticators, undefined) of
|
case lists:keyfind(AuthenticatorID, 1, Authenticators) of
|
||||||
undefined ->
|
false ->
|
||||||
{error, {not_found, {authenticator, AuthenticatorName}}};
|
{error, {not_found, {authenticator, AuthenticatorID}}};
|
||||||
#authenticator{provider = Provider, state = State} ->
|
{_, _, #authenticator{provider = Provider, state = State}} ->
|
||||||
case erlang:function_exported(Provider, Func, length(Args) + 1) of
|
case erlang:function_exported(Provider, Func, length(Args) + 1) of
|
||||||
true ->
|
true ->
|
||||||
erlang:apply(Provider, Func, Args ++ [State]);
|
erlang:apply(Provider, Func, Args ++ [State]);
|
||||||
|
@ -391,20 +405,12 @@ serialize_chain(#chain{id = ID,
|
||||||
authenticators => serialize_authenticators(Authenticators),
|
authenticators => serialize_authenticators(Authenticators),
|
||||||
created_at => CreatedAt}.
|
created_at => CreatedAt}.
|
||||||
|
|
||||||
% serialize_binding(#binding{bound = {ListenerID, _},
|
|
||||||
% chain_id = ChainID}) ->
|
|
||||||
% #{listener_id => ListenerID,
|
|
||||||
% chain_id => ChainID}.
|
|
||||||
|
|
||||||
serialize_authenticators(Authenticators) ->
|
serialize_authenticators(Authenticators) ->
|
||||||
[serialize_authenticator(Authenticator) || {_, Authenticator} <- Authenticators].
|
[serialize_authenticator(Authenticator) || {_, _, Authenticator} <- Authenticators].
|
||||||
|
|
||||||
serialize_authenticator(#authenticator{name = Name,
|
serialize_authenticator(#authenticator{id = ID,
|
||||||
mechanism = Mechanism,
|
|
||||||
config = Config}) ->
|
config = Config}) ->
|
||||||
#{name => Name,
|
Config#{id => ID}.
|
||||||
mechanism => Mechanism,
|
|
||||||
config => Config}.
|
|
||||||
|
|
||||||
trans(Fun) ->
|
trans(Fun) ->
|
||||||
trans(Fun, []).
|
trans(Fun, []).
|
||||||
|
|
|
@ -20,12 +20,25 @@
|
||||||
|
|
||||||
-include("emqx_authn.hrl").
|
-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() ->
|
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">>,
|
Example1 = #{name => <<"example">>,
|
||||||
mechanism => <<"password-based">>,
|
mechanism => <<"password-based">>,
|
||||||
config => #{
|
config => #{
|
||||||
|
@ -86,24 +99,506 @@ authenticator_api() ->
|
||||||
},
|
},
|
||||||
responses => #{
|
responses => #{
|
||||||
<<"201">> => #{
|
<<"201">> => #{
|
||||||
description => <<"Created successfully">>,
|
description => <<"Created">>,
|
||||||
content => #{}
|
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}.
|
{"/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() ->
|
definitions() ->
|
||||||
AuthenticatorDef = #{
|
AuthenticatorDef = #{
|
||||||
|
oneOf => [ minirest:ref(<<"password_based">>)
|
||||||
|
, minirest:ref(<<"jwt">>)
|
||||||
|
, minirest:ref(<<"scram">>)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
ReturnedAuthenticatorDef = #{
|
||||||
allOf => [
|
allOf => [
|
||||||
#{
|
#{
|
||||||
type => object,
|
type => object,
|
||||||
required => [name],
|
|
||||||
properties => #{
|
properties => #{
|
||||||
name => #{
|
id => #{
|
||||||
type => string,
|
type => string
|
||||||
example => "exmaple"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -111,99 +606,108 @@ definitions() ->
|
||||||
oneOf => [ minirest:ref(<<"password_based">>)
|
oneOf => [ minirest:ref(<<"password_based">>)
|
||||||
, minirest:ref(<<"jwt">>)
|
, minirest:ref(<<"jwt">>)
|
||||||
, minirest:ref(<<"scram">>)
|
, minirest:ref(<<"scram">>)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
PasswordBasedDef = #{
|
PasswordBasedDef = #{
|
||||||
type => object,
|
allOf => [
|
||||||
properties => #{
|
#{
|
||||||
mechanism => #{
|
type => object,
|
||||||
type => string,
|
required => [name, mechanism],
|
||||||
enum => [<<"password-based">>],
|
properties => #{
|
||||||
example => <<"password-based">>
|
name => #{
|
||||||
|
type => string,
|
||||||
|
example => "exmaple"
|
||||||
|
},
|
||||||
|
mechanism => #{
|
||||||
|
type => string,
|
||||||
|
enum => [<<"password-based">>],
|
||||||
|
example => <<"password-based">>
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
config => #{
|
#{
|
||||||
oneOf => [ minirest:ref(<<"password_based_built_in_database">>)
|
oneOf => [ minirest:ref(<<"password_based_built_in_database">>)
|
||||||
, minirest:ref(<<"password_based_mysql">>)
|
, minirest:ref(<<"password_based_mysql">>)
|
||||||
, minirest:ref(<<"password_based_pgsql">>)
|
, minirest:ref(<<"password_based_pgsql">>)
|
||||||
, minirest:ref(<<"password_based_http_server">>)
|
, minirest:ref(<<"password_based_http_server">>)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
JWTDef = #{
|
JWTDef = #{
|
||||||
type => object,
|
type => object,
|
||||||
|
required => [name, mechanism],
|
||||||
properties => #{
|
properties => #{
|
||||||
|
name => #{
|
||||||
|
type => string,
|
||||||
|
example => "exmaple"
|
||||||
|
},
|
||||||
mechanism => #{
|
mechanism => #{
|
||||||
type => string,
|
type => string,
|
||||||
enum => [<<"jwt">>],
|
enum => [<<"jwt">>],
|
||||||
example => <<"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,
|
type => object,
|
||||||
properties => #{
|
additionalProperties => #{
|
||||||
use_jwks => #{
|
type => string
|
||||||
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">>)
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
ssl => minirest:ref(<<"ssl">>)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
SCRAMDef = #{
|
SCRAMDef = #{
|
||||||
type => object,
|
type => object,
|
||||||
|
required => [name, mechanism],
|
||||||
properties => #{
|
properties => #{
|
||||||
|
name => #{
|
||||||
|
type => string,
|
||||||
|
example => "exmaple"
|
||||||
|
},
|
||||||
mechanism => #{
|
mechanism => #{
|
||||||
type => string,
|
type => string,
|
||||||
enum => [<<"scram">>],
|
enum => [<<"scram">>],
|
||||||
example => <<"scram">>
|
example => <<"scram">>
|
||||||
},
|
},
|
||||||
config => #{
|
server_type => #{
|
||||||
type => object,
|
type => string,
|
||||||
properties => #{
|
enum => [<<"built-in-database">>],
|
||||||
server_type => #{
|
default => <<"built-in-database">>
|
||||||
type => string,
|
},
|
||||||
enum => [<<"built-in-database">>],
|
algorithm => #{
|
||||||
default => <<"built-in-database">>
|
type => string,
|
||||||
},
|
enum => [<<"sha256">>, <<"sha512">>],
|
||||||
algorithm => #{
|
default => <<"sha256">>
|
||||||
type => string,
|
},
|
||||||
enum => [<<"sha256">>, <<"sha512">>],
|
iteration_count => #{
|
||||||
default => <<"sha256">>
|
type => integer,
|
||||||
},
|
default => 4096
|
||||||
iteration_count => #{
|
|
||||||
type => integer,
|
|
||||||
default => 4096
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -420,7 +924,22 @@ definitions() ->
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ErrorDef = #{
|
||||||
|
type => object,
|
||||||
|
properties => #{
|
||||||
|
code => #{
|
||||||
|
type => string,
|
||||||
|
enum => [<<"NOT_FOUND">>],
|
||||||
|
example => <<"NOT_FOUND">>
|
||||||
|
},
|
||||||
|
message => #{
|
||||||
|
type => string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
[ #{<<"authenticator">> => AuthenticatorDef}
|
[ #{<<"authenticator">> => AuthenticatorDef}
|
||||||
|
, #{<<"returned_authenticator">> => ReturnedAuthenticatorDef}
|
||||||
, #{<<"password_based">> => PasswordBasedDef}
|
, #{<<"password_based">> => PasswordBasedDef}
|
||||||
, #{<<"jwt">> => JWTDef}
|
, #{<<"jwt">> => JWTDef}
|
||||||
, #{<<"scram">> => SCRAMDef}
|
, #{<<"scram">> => SCRAMDef}
|
||||||
|
@ -430,4 +949,163 @@ definitions() ->
|
||||||
, #{<<"password_based_http_server">> => PasswordBasedHTTPServerDef}
|
, #{<<"password_based_http_server">> => PasswordBasedHTTPServerDef}
|
||||||
, #{<<"password_hash_algorithm">> => PasswordHashAlgorithmDef}
|
, #{<<"password_hash_algorithm">> => PasswordHashAlgorithmDef}
|
||||||
, #{<<"ssl">> => SSLDef}
|
, #{<<"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 => []}),
|
authenticators => []}),
|
||||||
initialize(AuthNConfig).
|
initialize(AuthNConfig).
|
||||||
|
|
||||||
initialize(#{enable := Enable, authenticators := Authenticators}) ->
|
initialize(#{enable := Enable, authenticators := AuthenticatorsConfig}) ->
|
||||||
{ok, _} = emqx_authn:create_chain(#{id => ?CHAIN}),
|
{ok, _} = emqx_authn:create_chain(#{id => ?CHAIN}),
|
||||||
initialize_authenticators(Authenticators),
|
initialize_authenticators(AuthenticatorsConfig),
|
||||||
Enable =:= true andalso emqx_authn:enable(),
|
Enable =:= true andalso emqx_authn:enable(),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
initialize_authenticators([]) ->
|
initialize_authenticators([]) ->
|
||||||
ok;
|
ok;
|
||||||
initialize_authenticators([#{name := Name} = Authenticator | More]) ->
|
initialize_authenticators([#{name := Name} = AuthenticatorConfig | More]) ->
|
||||||
case emqx_authn:create_authenticator(?CHAIN, Authenticator) of
|
case emqx_authn:create_authenticator(?CHAIN, AuthenticatorConfig) of
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
initialize_authenticators(More);
|
initialize_authenticators(More);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
|
|
|
@ -25,57 +25,34 @@
|
||||||
, fields/1
|
, fields/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-reflect_type([ authenticator_name/0
|
-export([ authenticator_name/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
structs() -> ["emqx_authn"].
|
structs() -> [ "emqx_authn" ].
|
||||||
|
|
||||||
fields("emqx_authn") ->
|
fields("emqx_authn") ->
|
||||||
[ {enable, fun enable/1}
|
[ {enable, fun enable/1}
|
||||||
, {authenticators, fun authenticators/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(type) -> boolean();
|
||||||
enable(defualt) -> false;
|
enable(default) -> false;
|
||||||
enable(_) -> undefined.
|
enable(_) -> undefined.
|
||||||
|
|
||||||
authenticators(type) ->
|
authenticators(type) ->
|
||||||
hoconsc:array({union, [ hoconsc:ref(?MODULE, 'password-based')
|
hoconsc:array({union, [ hoconsc:ref(emqx_authn_mnesia, config)
|
||||||
, hoconsc:ref(?MODULE, jwt)
|
, hoconsc:ref(emqx_authn_mysql, config)
|
||||||
, hoconsc:ref(?MODULE, scram)]});
|
, 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(default) -> [];
|
||||||
authenticators(_) -> undefined.
|
authenticators(_) -> undefined.
|
||||||
|
|
||||||
authenticator_name(type) -> authenticator_name();
|
|
||||||
authenticator_name(nullable) -> false;
|
|
||||||
authenticator_name(_) -> undefined.
|
|
||||||
|
|
|
@ -26,8 +26,8 @@
|
||||||
, fields/1
|
, fields/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ create/3
|
-export([ create/1
|
||||||
, update/4
|
, update/2
|
||||||
, authenticate/2
|
, authenticate/2
|
||||||
, destroy/1
|
, destroy/1
|
||||||
]).
|
]).
|
||||||
|
@ -71,7 +71,9 @@ mnesia(copy) ->
|
||||||
structs() -> [config].
|
structs() -> [config].
|
||||||
|
|
||||||
fields(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}
|
, {algorithm, fun algorithm/1}
|
||||||
, {iteration_count, fun iteration_count/1}
|
, {iteration_count, fun iteration_count/1}
|
||||||
].
|
].
|
||||||
|
@ -92,15 +94,17 @@ iteration_count(_) -> undefined.
|
||||||
%% APIs
|
%% APIs
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
create(ChainID, Authenticator, #{algorithm := Algorithm,
|
create(#{ algorithm := Algorithm
|
||||||
iteration_count := IterationCount}) ->
|
, iteration_count := IterationCount
|
||||||
State = #{user_group => {ChainID, Authenticator},
|
, '_unique' := Unique
|
||||||
|
}) ->
|
||||||
|
State = #{user_group => Unique,
|
||||||
algorithm => Algorithm,
|
algorithm => Algorithm,
|
||||||
iteration_count => IterationCount},
|
iteration_count => IterationCount},
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
update(_ChainID, _Authenticator, _Config, _State) ->
|
update(Config, #{user_group := Unique}) ->
|
||||||
{error, update_not_suppored}.
|
create(Config#{'_unique' => Unique}).
|
||||||
|
|
||||||
authenticate(#{auth_method := AuthMethod,
|
authenticate(#{auth_method := AuthMethod,
|
||||||
auth_data := AuthData,
|
auth_data := AuthData,
|
||||||
|
|
|
@ -26,8 +26,8 @@
|
||||||
, validations/0
|
, validations/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ create/3
|
-export([ create/1
|
||||||
, update/4
|
, update/2
|
||||||
, authenticate/2
|
, authenticate/2
|
||||||
, destroy/1
|
, destroy/1
|
||||||
]).
|
]).
|
||||||
|
@ -57,7 +57,9 @@ fields(post) ->
|
||||||
] ++ common_fields().
|
] ++ common_fields().
|
||||||
|
|
||||||
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}
|
, {url, fun url/1}
|
||||||
, {form_data, fun form_data/1}
|
, {form_data, fun form_data/1}
|
||||||
, {request_timeout, fun request_timeout/1}
|
, {request_timeout, fun request_timeout/1}
|
||||||
|
@ -105,37 +107,41 @@ request_timeout(_) -> undefined.
|
||||||
%% APIs
|
%% APIs
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
create(ChainID, AuthenticatorName,
|
create(#{ method := Method
|
||||||
#{method := Method,
|
, url := URL
|
||||||
url := URL,
|
, headers := Headers
|
||||||
headers := Headers,
|
, form_data := FormData
|
||||||
form_data := FormData,
|
, request_timeout := RequestTimeout
|
||||||
request_timeout := RequestTimeout} = Config) ->
|
, '_unique' := Unique
|
||||||
|
} = Config) ->
|
||||||
#{path := Path,
|
#{path := Path,
|
||||||
query := Query} = URIMap = parse_url(URL),
|
query := Query} = URIMap = parse_url(URL),
|
||||||
State = #{method => Method,
|
State = #{ method => Method
|
||||||
path => Path,
|
, path => Path
|
||||||
base_query => cow_qs:parse_qs(list_to_binary(Query)),
|
, base_query => cow_qs:parse_qs(list_to_binary(Query))
|
||||||
headers => normalize_headers(Headers),
|
, headers => normalize_headers(Headers)
|
||||||
form_data => maps:to_list(FormData),
|
, form_data => maps:to_list(FormData)
|
||||||
request_timeout => RequestTimeout},
|
, request_timeout => RequestTimeout
|
||||||
ResourceID = <<ChainID/binary, "/", AuthenticatorName/binary>>,
|
},
|
||||||
case emqx_resource:create_local(ResourceID,
|
case emqx_resource:create_local(Unique,
|
||||||
emqx_connector_http,
|
emqx_connector_http,
|
||||||
Config#{base_url => maps:remove(query, URIMap),
|
Config#{base_url => maps:remove(query, URIMap),
|
||||||
pool_type => random}) of
|
pool_type => random}) of
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
{ok, State#{resource_id => ResourceID}};
|
{ok, State#{resource_id => Unique}};
|
||||||
{error, already_created} ->
|
{error, already_created} ->
|
||||||
{ok, State#{resource_id => ResourceID}};
|
{ok, State#{resource_id => Unique}};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
update(_ChainID, _AuthenticatorName, Config, #{resource_id := ResourceID} = State) ->
|
update(Config, State) ->
|
||||||
case emqx_resource:update_local(ResourceID, emqx_connector_http, Config, []) of
|
case create(Config) of
|
||||||
{ok, _} -> {ok, State};
|
{ok, NewState} ->
|
||||||
{error, Reason} -> {error, Reason}
|
ok = destroy(State),
|
||||||
|
{ok, NewState};
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
authenticate(#{auth_method := _}, _) ->
|
authenticate(#{auth_method := _}, _) ->
|
||||||
|
|
|
@ -24,8 +24,8 @@
|
||||||
, fields/1
|
, fields/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ create/3
|
-export([ create/1
|
||||||
, update/4
|
, update/2
|
||||||
, authenticate/2
|
, authenticate/2
|
||||||
, destroy/1
|
, destroy/1
|
||||||
]).
|
]).
|
||||||
|
@ -48,27 +48,24 @@ fields('hmac-based') ->
|
||||||
, {algorithm, {enum, ['hmac-based']}}
|
, {algorithm, {enum, ['hmac-based']}}
|
||||||
, {secret, fun secret/1}
|
, {secret, fun secret/1}
|
||||||
, {secret_base64_encoded, fun secret_base64_encoded/1}
|
, {secret_base64_encoded, fun secret_base64_encoded/1}
|
||||||
, {verify_claims, fun verify_claims/1}
|
] ++ common_fields();
|
||||||
];
|
|
||||||
|
|
||||||
fields('public-key') ->
|
fields('public-key') ->
|
||||||
[ {use_jwks, {enum, [false]}}
|
[ {use_jwks, {enum, [false]}}
|
||||||
, {algorithm, {enum, ['public-key']}}
|
, {algorithm, {enum, ['public-key']}}
|
||||||
, {certificate, fun certificate/1}
|
, {certificate, fun certificate/1}
|
||||||
, {verify_claims, fun verify_claims/1}
|
] ++ common_fields();
|
||||||
];
|
|
||||||
|
|
||||||
fields('jwks') ->
|
fields('jwks') ->
|
||||||
[ {use_jwks, {enum, [true]}}
|
[ {use_jwks, {enum, [true]}}
|
||||||
, {endpoint, fun endpoint/1}
|
, {endpoint, fun endpoint/1}
|
||||||
, {refresh_interval, fun refresh_interval/1}
|
, {refresh_interval, fun refresh_interval/1}
|
||||||
, {verify_claims, fun verify_claims/1}
|
|
||||||
, {ssl, #{type => hoconsc:union(
|
, {ssl, #{type => hoconsc:union(
|
||||||
[ hoconsc:ref(?MODULE, ssl_enable)
|
[ hoconsc:ref(?MODULE, ssl_enable)
|
||||||
, hoconsc:ref(?MODULE, ssl_disable)
|
, hoconsc:ref(?MODULE, ssl_disable)
|
||||||
]),
|
]),
|
||||||
default => #{<<"enable">> => false}}}
|
default => #{<<"enable">> => false}}}
|
||||||
];
|
] ++ common_fields();
|
||||||
|
|
||||||
fields(ssl_enable) ->
|
fields(ssl_enable) ->
|
||||||
[ {enable, #{type => true}}
|
[ {enable, #{type => true}}
|
||||||
|
@ -82,6 +79,12 @@ fields(ssl_enable) ->
|
||||||
fields(ssl_disable) ->
|
fields(ssl_disable) ->
|
||||||
[ {enable, #{type => false}} ].
|
[ {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(type) -> string();
|
||||||
secret(_) -> undefined.
|
secret(_) -> undefined.
|
||||||
|
|
||||||
|
@ -129,18 +132,18 @@ verify_claims(_) -> undefined.
|
||||||
%% APIs
|
%% APIs
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
create(_ChainID, _AuthenticatorName, Config) ->
|
create(#{verify_claims := VerifyClaims} = Config) ->
|
||||||
create(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) ->
|
when is_pid(Connector) ->
|
||||||
_ = emqx_authn_jwks_connector:stop(Connector),
|
_ = emqx_authn_jwks_connector:stop(Connector),
|
||||||
create(Config);
|
create(Config);
|
||||||
|
|
||||||
update(_ChainID, _AuthenticatorName, #{use_jwks := false} = Config, _) ->
|
update(#{use_jwks := false} = Config, _) ->
|
||||||
create(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) ->
|
when is_pid(Connector) ->
|
||||||
ok = emqx_authn_jwks_connector:update(Connector, Config),
|
ok = emqx_authn_jwks_connector:update(Connector, Config),
|
||||||
case maps:get(verify_cliams, Config, undefined) of
|
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)}}
|
{ok, State#{verify_claims => handle_verify_claims(VerifyClaims)}}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
update(_ChainID, _AuthenticatorName, #{use_jwks := true} = Config, _) ->
|
update(#{use_jwks := true} = Config, _) ->
|
||||||
create(Config).
|
create(Config).
|
||||||
|
|
||||||
authenticate(#{auth_method := _}, _) ->
|
authenticate(#{auth_method := _}, _) ->
|
||||||
|
@ -181,9 +184,6 @@ destroy(_) ->
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
create(#{verify_claims := VerifyClaims} = Config) ->
|
|
||||||
create2(Config#{verify_claims => handle_verify_claims(VerifyClaims)}).
|
|
||||||
|
|
||||||
create2(#{use_jwks := false,
|
create2(#{use_jwks := false,
|
||||||
algorithm := 'hmac-based',
|
algorithm := 'hmac-based',
|
||||||
secret := Secret0,
|
secret := Secret0,
|
||||||
|
|
|
@ -23,8 +23,8 @@
|
||||||
|
|
||||||
-export([ structs/0, fields/1 ]).
|
-export([ structs/0, fields/1 ]).
|
||||||
|
|
||||||
-export([ create/3
|
-export([ create/1
|
||||||
, update/4
|
, update/2
|
||||||
, authenticate/2
|
, authenticate/2
|
||||||
, destroy/1
|
, destroy/1
|
||||||
]).
|
]).
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
-type user_id_type() :: clientid | username.
|
-type user_id_type() :: clientid | username.
|
||||||
|
|
||||||
-type user_group() :: {chain_id(), authenticator_name()}.
|
-type user_group() :: {binary(), binary()}.
|
||||||
-type user_id() :: binary().
|
-type user_id() :: binary().
|
||||||
|
|
||||||
-record(user_info,
|
-record(user_info,
|
||||||
|
@ -81,8 +81,10 @@ mnesia(copy) ->
|
||||||
structs() -> [config].
|
structs() -> [config].
|
||||||
|
|
||||||
fields(config) ->
|
fields(config) ->
|
||||||
[ {server_type, {enum, ['built-in-database']}}
|
[ {name, fun emqx_authn_schema:authenticator_name/1}
|
||||||
, {user_id_type, fun user_id_type/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}
|
, {password_hash_algorithm, fun password_hash_algorithm/1}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -111,25 +113,29 @@ salt_rounds(_) -> undefined.
|
||||||
%% APIs
|
%% APIs
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
create(ChainID, AuthenticatorName, #{user_id_type := Type,
|
create(#{ user_id_type := Type
|
||||||
password_hash_algorithm := #{name := bcrypt,
|
, password_hash_algorithm := #{name := bcrypt,
|
||||||
salt_rounds := SaltRounds}}) ->
|
salt_rounds := SaltRounds}
|
||||||
|
, '_unique' := Unique
|
||||||
|
}) ->
|
||||||
{ok, _} = application:ensure_all_started(bcrypt),
|
{ok, _} = application:ensure_all_started(bcrypt),
|
||||||
State = #{user_group => {ChainID, AuthenticatorName},
|
State = #{user_group => Unique,
|
||||||
user_id_type => Type,
|
user_id_type => Type,
|
||||||
password_hash_algorithm => bcrypt,
|
password_hash_algorithm => bcrypt,
|
||||||
salt_rounds => SaltRounds},
|
salt_rounds => SaltRounds},
|
||||||
{ok, State};
|
{ok, State};
|
||||||
|
|
||||||
create(ChainID, AuthenticatorName, #{user_id_type := Type,
|
create(#{ user_id_type := Type
|
||||||
password_hash_algorithm := #{name := Name}}) ->
|
, password_hash_algorithm := #{name := Name}
|
||||||
State = #{user_group => {ChainID, AuthenticatorName},
|
, '_unique' := Unique
|
||||||
|
}) ->
|
||||||
|
State = #{user_group => Unique,
|
||||||
user_id_type => Type,
|
user_id_type => Type,
|
||||||
password_hash_algorithm => Name},
|
password_hash_algorithm => Name},
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
update(ChainID, AuthenticatorName, Config, _State) ->
|
update(Config, #{user_group := Unique}) ->
|
||||||
create(ChainID, AuthenticatorName, Config).
|
create(Config#{'_unique' => Unique}).
|
||||||
|
|
||||||
authenticate(#{auth_method := _}, _) ->
|
authenticate(#{auth_method := _}, _) ->
|
||||||
ignore;
|
ignore;
|
||||||
|
|
|
@ -21,10 +21,12 @@
|
||||||
|
|
||||||
-behaviour(hocon_schema).
|
-behaviour(hocon_schema).
|
||||||
|
|
||||||
-export([ structs/0, fields/1 ]).
|
-export([ structs/0
|
||||||
|
, fields/1
|
||||||
|
]).
|
||||||
|
|
||||||
-export([ create/3
|
-export([ create/1
|
||||||
, update/4
|
, update/2
|
||||||
, authenticate/2
|
, authenticate/2
|
||||||
, destroy/1
|
, destroy/1
|
||||||
]).
|
]).
|
||||||
|
@ -36,7 +38,9 @@
|
||||||
structs() -> [config].
|
structs() -> [config].
|
||||||
|
|
||||||
fields(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}
|
, {password_hash_algorithm, fun password_hash_algorithm/1}
|
||||||
, {salt_position, fun salt_position/1}
|
, {salt_position, fun salt_position/1}
|
||||||
, {query, fun query/1}
|
, {query, fun query/1}
|
||||||
|
@ -70,34 +74,41 @@ query(nullable) -> false;
|
||||||
query(_) -> undefined.
|
query(_) -> undefined.
|
||||||
|
|
||||||
query_timeout(type) -> integer();
|
query_timeout(type) -> integer();
|
||||||
query_timeout(defualt) -> 5000;
|
query_timeout(default) -> 5000;
|
||||||
query_timeout(_) -> undefined.
|
query_timeout(_) -> undefined.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% APIs
|
%% APIs
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
create(ChainID, AuthenticatorName,
|
create(#{ password_hash_algorithm := Algorithm
|
||||||
#{query := Query0,
|
, salt_position := SaltPosition
|
||||||
password_hash_algorithm := Algorithm} = Config) ->
|
, query := Query0
|
||||||
|
, query_timeout := QueryTimeout
|
||||||
|
, '_unique' := Unique
|
||||||
|
} = Config) ->
|
||||||
{Query, PlaceHolders} = parse_query(Query0),
|
{Query, PlaceHolders} = parse_query(Query0),
|
||||||
ResourceID = iolist_to_binary(io_lib:format("~s/~s",[ChainID, AuthenticatorName])),
|
State = #{password_hash_algorithm => Algorithm,
|
||||||
State = #{query => Query,
|
salt_position => SaltPosition,
|
||||||
|
query => Query,
|
||||||
placeholders => PlaceHolders,
|
placeholders => PlaceHolders,
|
||||||
password_hash_algorithm => Algorithm},
|
query_timeout => QueryTimeout},
|
||||||
case emqx_resource:create_local(ResourceID, emqx_connector_mysql, Config) of
|
case emqx_resource:create_local(Unique, emqx_connector_mysql, Config) of
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
{ok, State#{resource_id => ResourceID}};
|
{ok, State#{resource_id => Unique}};
|
||||||
{error, already_created} ->
|
{error, already_created} ->
|
||||||
{ok, State#{resource_id => ResourceID}};
|
{ok, State#{resource_id => Unique}};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
update(_ChainID, _AuthenticatorName, Config, #{resource_id := ResourceID} = State) ->
|
update(Config, State) ->
|
||||||
case emqx_resource:update_local(ResourceID, emqx_connector_mysql, Config, []) of
|
case create(Config) of
|
||||||
{ok, _} -> {ok, State};
|
{ok, NewState} ->
|
||||||
{error, Reason} -> {error, Reason}
|
ok = destroy(State),
|
||||||
|
{ok, NewState};
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
authenticate(#{auth_method := _}, _) ->
|
authenticate(#{auth_method := _}, _) ->
|
||||||
|
|
|
@ -23,8 +23,8 @@
|
||||||
|
|
||||||
-export([ structs/0, fields/1 ]).
|
-export([ structs/0, fields/1 ]).
|
||||||
|
|
||||||
-export([ create/3
|
-export([ create/1
|
||||||
, update/4
|
, update/2
|
||||||
, authenticate/2
|
, authenticate/2
|
||||||
, destroy/1
|
, destroy/1
|
||||||
]).
|
]).
|
||||||
|
@ -36,7 +36,9 @@
|
||||||
structs() -> [config].
|
structs() -> [config].
|
||||||
|
|
||||||
fields(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}
|
, {password_hash_algorithm, fun password_hash_algorithm/1}
|
||||||
, {salt_position, {enum, [prefix, suffix]}}
|
, {salt_position, {enum, [prefix, suffix]}}
|
||||||
, {query, fun query/1}
|
, {query, fun query/1}
|
||||||
|
@ -54,26 +56,32 @@ query(_) -> undefined.
|
||||||
%% APIs
|
%% APIs
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
create(ChainID, ServiceName, #{query := Query0,
|
create(#{ query := Query0
|
||||||
password_hash_algorithm := Algorithm} = Config) ->
|
, password_hash_algorithm := Algorithm
|
||||||
|
, salt_position := SaltPosition
|
||||||
|
, '_unique' := Unique
|
||||||
|
} = Config) ->
|
||||||
{Query, PlaceHolders} = parse_query(Query0),
|
{Query, PlaceHolders} = parse_query(Query0),
|
||||||
ResourceID = iolist_to_binary(io_lib:format("~s/~s",[ChainID, ServiceName])),
|
|
||||||
State = #{query => Query,
|
State = #{query => Query,
|
||||||
placeholders => PlaceHolders,
|
placeholders => PlaceHolders,
|
||||||
password_hash_algorithm => Algorithm},
|
password_hash_algorithm => Algorithm,
|
||||||
case emqx_resource:create_local(ResourceID, emqx_connector_pgsql, Config) of
|
salt_position => SaltPosition},
|
||||||
|
case emqx_resource:create_local(Unique, emqx_connector_pgsql, Config) of
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
{ok, State#{resource_id => ResourceID}};
|
{ok, State#{resource_id => Unique}};
|
||||||
{error, already_created} ->
|
{error, already_created} ->
|
||||||
{ok, State#{resource_id => ResourceID}};
|
{ok, State#{resource_id => Unique}};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
update(_ChainID, _ServiceName, Config, #{resource_id := ResourceID} = State) ->
|
update(Config, State) ->
|
||||||
case emqx_resource:update_local(ResourceID, emqx_connector_pgsql, Config, []) of
|
case create(Config) of
|
||||||
{ok, _} -> {ok, State};
|
{ok, NewState} ->
|
||||||
{error, Reason} -> {error, Reason}
|
ok = destroy(State),
|
||||||
|
{ok, NewState};
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
authenticate(#{auth_method := _}, _) ->
|
authenticate(#{auth_method := _}, _) ->
|
||||||
|
|
Loading…
Reference in New Issue