Merge pull request #5319 from tigercl/feat/authn-http-api
feat(authn): provide http api and improve update mechanism
This commit is contained in:
commit
4dea41f8a2
|
@ -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,20 @@
|
|||
-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()
|
||||
}).
|
||||
|
||||
-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,86 @@ 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 lists:keymember(NewName, 2, Authenticators) of
|
||||
true ->
|
||||
{error, name_has_be_used};
|
||||
false ->
|
||||
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
|
||||
end;
|
||||
false ->
|
||||
{error, {not_found, {authenticator, AuthenticatorID}}}
|
||||
end;
|
||||
{value,
|
||||
{_, _, #authenticator{provider = Provider,
|
||||
state = #{version := Version} = State} = Authenticator},
|
||||
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/binary, "/", AuthenticatorID/binary, ":", Version/binary>>,
|
||||
case Provider:update(Config#{'_unique' => Unique}, State) of
|
||||
{ok, NewState} ->
|
||||
NewAuthenticator = Authenticator#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 ->
|
||||
Unique = <<ChainID/binary, "/", AuthenticatorID/binary, ":", Version/binary>>,
|
||||
case NewProvider:create(Config#{'_unique' => Unique}) of
|
||||
{ok, NewState} ->
|
||||
NewAuthenticator = Authenticator#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 +274,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 +286,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 +386,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 +411,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, []).
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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}
|
||||
].
|
||||
|
@ -80,7 +82,7 @@ server_type(type) -> hoconsc:enum(['built-in-database']);
|
|||
server_type(default) -> 'built-in-database';
|
||||
server_type(_) -> undefined.
|
||||
|
||||
algorithm(type) -> hoconsc:enum([sha256, sha256]);
|
||||
algorithm(type) -> hoconsc:enum([sha256, sha512]);
|
||||
algorithm(default) -> sha256;
|
||||
algorithm(_) -> undefined.
|
||||
|
||||
|
@ -92,15 +94,17 @@ 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,
|
||||
|
@ -129,8 +133,8 @@ destroy(#{user_group := UserGroup}) ->
|
|||
end).
|
||||
|
||||
%% TODO: binary to atom
|
||||
add_user(#{<<"user_id">> := UserID,
|
||||
<<"password">> := Password}, #{user_group := UserGroup} = State) ->
|
||||
add_user(#{user_id := UserID,
|
||||
password := Password}, #{user_group := UserGroup} = State) ->
|
||||
trans(
|
||||
fun() ->
|
||||
case mnesia:read(?TAB, {UserGroup, UserID}, write) of
|
||||
|
@ -153,7 +157,7 @@ delete_user(UserID, #{user_group := UserGroup}) ->
|
|||
end
|
||||
end).
|
||||
|
||||
update_user(UserID, #{<<"password">> := Password},
|
||||
update_user(UserID, #{password := Password},
|
||||
#{user_group := UserGroup} = State) ->
|
||||
trans(
|
||||
fun() ->
|
||||
|
|
|
@ -26,15 +26,8 @@
|
|||
, validations/0
|
||||
]).
|
||||
|
||||
-type accept() :: 'application/json' | 'application/x-www-form-urlencoded'.
|
||||
-type content_type() :: accept().
|
||||
|
||||
-reflect_type([ accept/0
|
||||
, content_type/0
|
||||
]).
|
||||
|
||||
-export([ create/3
|
||||
, update/4
|
||||
-export([ create/1
|
||||
, update/2
|
||||
, authenticate/2
|
||||
, destroy/1
|
||||
]).
|
||||
|
@ -53,45 +46,55 @@ fields("") ->
|
|||
|
||||
fields(get) ->
|
||||
[ {method, #{type => get,
|
||||
default => get}}
|
||||
default => post}}
|
||||
, {headers, fun headers_no_content_type/1}
|
||||
] ++ common_fields();
|
||||
|
||||
fields(post) ->
|
||||
[ {method, #{type => post,
|
||||
default => get}}
|
||||
, {content_type, fun content_type/1}
|
||||
default => post}}
|
||||
, {headers, fun headers/1}
|
||||
] ++ 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}
|
||||
, {accept, fun accept/1}
|
||||
, {headers, fun headers/1}
|
||||
, {form_data, fun form_data/1}
|
||||
, {request_timeout, fun request_timeout/1}
|
||||
] ++ proplists:delete(base_url, emqx_connector_http:fields(config)).
|
||||
] ++ maps:to_list(maps:without([ base_url
|
||||
, pool_type],
|
||||
maps:from_list(emqx_connector_http:fields(config)))).
|
||||
|
||||
validations() ->
|
||||
[ {check_ssl_opts, fun check_ssl_opts/1} ].
|
||||
[ {check_ssl_opts, fun check_ssl_opts/1}
|
||||
, {check_headers, fun check_headers/1}
|
||||
].
|
||||
|
||||
url(type) -> binary();
|
||||
url(nullable) -> false;
|
||||
url(validate) -> [fun check_url/1];
|
||||
url(_) -> undefined.
|
||||
|
||||
accept(type) -> accept();
|
||||
accept(default) -> 'application/json';
|
||||
accept(_) -> undefined.
|
||||
|
||||
content_type(type) -> content_type();
|
||||
content_type(default) -> 'application/json';
|
||||
content_type(_) -> undefined.
|
||||
|
||||
headers(type) -> list();
|
||||
headers(default) -> [];
|
||||
headers(type) -> map();
|
||||
headers(converter) ->
|
||||
fun(Headers) ->
|
||||
maps:merge(default_headers(), transform_header_name(Headers))
|
||||
end;
|
||||
headers(default) -> default_headers();
|
||||
headers(_) -> undefined.
|
||||
|
||||
form_data(type) -> binary();
|
||||
headers_no_content_type(type) -> map();
|
||||
headers_no_content_type(converter) ->
|
||||
fun(Headers) ->
|
||||
maps:merge(default_headers_no_content_type(), transform_header_name(Headers))
|
||||
end;
|
||||
headers_no_content_type(default) -> default_headers_no_content_type();
|
||||
headers_no_content_type(_) -> undefined.
|
||||
|
||||
%% TODO: Using map()
|
||||
form_data(type) -> map();
|
||||
form_data(nullable) -> false;
|
||||
form_data(validate) -> [fun check_form_data/1];
|
||||
form_data(_) -> undefined.
|
||||
|
@ -104,46 +107,41 @@ request_timeout(_) -> undefined.
|
|||
%% APIs
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
create(ChainID, AuthenticatorName,
|
||||
#{method := Method,
|
||||
url := URL,
|
||||
accept := Accept,
|
||||
headers := Headers,
|
||||
form_data := FormData,
|
||||
request_timeout := RequestTimeout} = Config) ->
|
||||
ContentType = maps:get(content_type, Config, undefined),
|
||||
DefaultHeader0 = case ContentType of
|
||||
undefined -> #{};
|
||||
_ -> #{<<"content-type">> => atom_to_binary(ContentType, utf8)}
|
||||
end,
|
||||
DefaultHeader = DefaultHeader0#{<<"accept">> => atom_to_binary(Accept, utf8)},
|
||||
NHeaders = maps:to_list(maps:merge(DefaultHeader, maps:from_list(Headers))),
|
||||
NFormData = preprocess_form_data(FormData),
|
||||
create(#{ method := Method
|
||||
, url := URL
|
||||
, headers := Headers
|
||||
, form_data := FormData
|
||||
, request_timeout := RequestTimeout
|
||||
, '_unique' := Unique
|
||||
} = Config) ->
|
||||
#{path := Path,
|
||||
query := Query} = URIMap = parse_url(URL),
|
||||
BaseURL = generate_base_url(URIMap),
|
||||
State = #{method => Method,
|
||||
path => Path,
|
||||
base_query => cow_qs:parse_qs(list_to_binary(Query)),
|
||||
accept => Accept,
|
||||
content_type => ContentType,
|
||||
headers => NHeaders,
|
||||
form_data => NFormData,
|
||||
request_timeout => RequestTimeout},
|
||||
ResourceID = <<ChainID/binary, "/", AuthenticatorName/binary>>,
|
||||
case emqx_resource:create_local(ResourceID, emqx_connector_http, Config#{base_url => BaseURL}) of
|
||||
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 := _}, _) ->
|
||||
|
@ -182,26 +180,38 @@ check_url(URL) ->
|
|||
end.
|
||||
|
||||
check_form_data(FormData) ->
|
||||
KVs = binary:split(FormData, [<<"&">>], [global]),
|
||||
case false =:= lists:any(fun(T) -> T =:= <<>> end, KVs) of
|
||||
true ->
|
||||
NKVs = [list_to_tuple(binary:split(KV, [<<"=">>], [global])) || KV <- KVs],
|
||||
false =:=
|
||||
lists:any(fun({K, V}) ->
|
||||
K =:= <<>> orelse V =:= <<>>;
|
||||
(_) ->
|
||||
true
|
||||
end, NKVs);
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
lists:any(fun({_, V}) ->
|
||||
not is_binary(V)
|
||||
end, maps:to_list(FormData)).
|
||||
|
||||
default_headers() ->
|
||||
maps:put(<<"content-type">>,
|
||||
<<"application/json">>,
|
||||
default_headers_no_content_type()).
|
||||
|
||||
default_headers_no_content_type() ->
|
||||
#{ <<"accept">> => <<"application/json">>
|
||||
, <<"cache-control">> => <<"no-cache">>
|
||||
, <<"connection">> => <<"keep-alive">>
|
||||
, <<"keep-alive">> => <<"timeout=5">>
|
||||
}.
|
||||
|
||||
transform_header_name(Headers) ->
|
||||
maps:fold(fun(K0, V, Acc) ->
|
||||
K = list_to_binary(string:to_lower(binary_to_list(K0))),
|
||||
maps:put(K, V, Acc)
|
||||
end, #{}, Headers).
|
||||
|
||||
check_ssl_opts(Conf) ->
|
||||
emqx_connector_http:check_ssl_opts("url", Conf).
|
||||
|
||||
preprocess_form_data(FormData) ->
|
||||
KVs = binary:split(FormData, [<<"&">>], [global]),
|
||||
[list_to_tuple(binary:split(KV, [<<"=">>], [global])) || KV <- KVs].
|
||||
check_headers(Conf) ->
|
||||
Method = hocon_schema:get_value("method", Conf),
|
||||
Headers = hocon_schema:get_value("headers", Conf),
|
||||
case Method =:= get andalso maps:get(<<"content-type">>, Headers, undefined) =/= undefined of
|
||||
true -> false;
|
||||
false -> true
|
||||
end.
|
||||
|
||||
parse_url(URL) ->
|
||||
{ok, URIMap} = emqx_http_lib:uri_parse(URL),
|
||||
|
@ -212,15 +222,12 @@ parse_url(URL) ->
|
|||
URIMap
|
||||
end.
|
||||
|
||||
generate_base_url(#{scheme := Scheme,
|
||||
host := Host,
|
||||
port := Port}) ->
|
||||
iolist_to_binary(io_lib:format("~p://~s:~p", [Scheme, Host, Port])).
|
||||
normalize_headers(Headers) ->
|
||||
[{atom_to_binary(K), V} || {K, V} <- maps:to_list(Headers)].
|
||||
|
||||
generate_request(Credential, #{method := Method,
|
||||
path := Path,
|
||||
base_query := BaseQuery,
|
||||
content_type := ContentType,
|
||||
headers := Headers,
|
||||
form_data := FormData0}) ->
|
||||
FormData = replace_placeholders(FormData0, Credential),
|
||||
|
@ -230,6 +237,7 @@ generate_request(Credential, #{method := Method,
|
|||
{NPath, Headers};
|
||||
post ->
|
||||
NPath = append_query(Path, BaseQuery),
|
||||
ContentType = proplists:get_value(<<"content-type">>, Headers),
|
||||
Body = serialize_body(ContentType, FormData),
|
||||
{NPath, Headers, Body}
|
||||
end.
|
||||
|
@ -249,6 +257,8 @@ replace_placeholder(<<"${mqtt-username}">>, Credential) ->
|
|||
maps:get(username, Credential, undefined);
|
||||
replace_placeholder(<<"${mqtt-clientid}">>, Credential) ->
|
||||
maps:get(clientid, Credential, undefined);
|
||||
replace_placeholder(<<"${mqtt-password}">>, Credential) ->
|
||||
maps:get(password, Credential, undefined);
|
||||
replace_placeholder(<<"${ip-address}">>, Credential) ->
|
||||
maps:get(peerhost, Credential, undefined);
|
||||
replace_placeholder(<<"${cert-subject}">>, Credential) ->
|
||||
|
@ -272,9 +282,9 @@ qs([], Acc) ->
|
|||
qs([{K, V} | More], Acc) ->
|
||||
qs(More, [["&", emqx_http_lib:uri_encode(K), "=", emqx_http_lib:uri_encode(V)] | Acc]).
|
||||
|
||||
serialize_body('application/json', FormData) ->
|
||||
serialize_body(<<"application/json">>, FormData) ->
|
||||
emqx_json:encode(FormData);
|
||||
serialize_body('application/x-www-form-urlencoded', FormData) ->
|
||||
serialize_body(<<"application/x-www-form-urlencoded">>, FormData) ->
|
||||
qs(FormData).
|
||||
|
||||
safely_parse_body(ContentType, Body) ->
|
||||
|
|
|
@ -22,11 +22,10 @@
|
|||
|
||||
-export([ structs/0
|
||||
, fields/1
|
||||
, validations/0
|
||||
]).
|
||||
|
||||
-export([ create/3
|
||||
, update/4
|
||||
-export([ create/1
|
||||
, update/2
|
||||
, authenticate/2
|
||||
, destroy/1
|
||||
]).
|
||||
|
@ -49,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}}
|
||||
|
@ -81,19 +77,19 @@ fields(ssl_enable) ->
|
|||
];
|
||||
|
||||
fields(ssl_disable) ->
|
||||
[ {enable, #{type => false}} ];
|
||||
[ {enable, #{type => false}} ].
|
||||
|
||||
fields(claim) ->
|
||||
[ {"$name", fun expected_claim_value/1} ].
|
||||
|
||||
validations() ->
|
||||
[ {check_verify_claims, fun check_verify_claims/1} ].
|
||||
common_fields() ->
|
||||
[ {name, fun emqx_authn_schema:authenticator_name/1}
|
||||
, {mechanism, {enum, [jwt]}}
|
||||
, {verify_claims, fun verify_claims/1}
|
||||
].
|
||||
|
||||
secret(type) -> string();
|
||||
secret(_) -> undefined.
|
||||
|
||||
secret_base64_encoded(type) -> boolean();
|
||||
secret_base64_encoded(defualt) -> false;
|
||||
secret_base64_encoded(default) -> false;
|
||||
secret_base64_encoded(_) -> undefined.
|
||||
|
||||
certificate(type) -> string();
|
||||
|
@ -123,29 +119,31 @@ verify(_) -> undefined.
|
|||
server_name_indication(type) -> string();
|
||||
server_name_indication(_) -> undefined.
|
||||
|
||||
verify_claims(type) -> hoconsc:array(hoconsc:ref(claim));
|
||||
verify_claims(default) -> [];
|
||||
verify_claims(type) -> list();
|
||||
verify_claims(default) -> #{};
|
||||
verify_claims(validate) -> [fun check_verify_claims/1];
|
||||
verify_claims(converter) ->
|
||||
fun(VerifyClaims) ->
|
||||
maps:to_list(VerifyClaims)
|
||||
end;
|
||||
verify_claims(_) -> undefined.
|
||||
|
||||
expected_claim_value(type) -> string();
|
||||
expected_claim_value(_) -> 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
|
||||
|
@ -155,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 := _}, _) ->
|
||||
|
@ -186,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;
|
||||
|
@ -172,8 +178,8 @@ import_users(Filename0, State) ->
|
|||
{error, {unsupported_file_format, Extension}}
|
||||
end.
|
||||
|
||||
add_user(#{<<"user_id">> := UserID,
|
||||
<<"password">> := Password},
|
||||
add_user(#{user_id := UserID,
|
||||
password := Password},
|
||||
#{user_group := UserGroup} = State) ->
|
||||
trans(
|
||||
fun() ->
|
||||
|
@ -197,7 +203,7 @@ delete_user(UserID, #{user_group := UserGroup}) ->
|
|||
end
|
||||
end).
|
||||
|
||||
update_user(UserID, #{<<"password">> := Password},
|
||||
update_user(UserID, #{password := Password},
|
||||
#{user_group := UserGroup} = State) ->
|
||||
trans(
|
||||
fun() ->
|
||||
|
|
|
@ -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,50 +38,77 @@
|
|||
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, {enum, [prefix, suffix]}}
|
||||
, {salt_position, fun salt_position/1}
|
||||
, {query, fun query/1}
|
||||
, {query_timeout, fun query_timeout/1}
|
||||
] ++ emqx_connector_schema_lib:relational_db_fields()
|
||||
++ emqx_connector_schema_lib:ssl_fields().
|
||||
++ emqx_connector_schema_lib:ssl_fields();
|
||||
|
||||
password_hash_algorithm(type) -> string();
|
||||
fields(bcrypt) ->
|
||||
[ {name, {enum, [bcrypt]}}
|
||||
, {salt_rounds, fun salt_rounds/1}
|
||||
];
|
||||
|
||||
fields(other_algorithms) ->
|
||||
[ {name, {enum, [plain, md5, sha, sha256, sha512]}}
|
||||
].
|
||||
|
||||
password_hash_algorithm(type) -> {union, [hoconsc:ref(bcrypt), hoconsc:ref(other_algorithms)]};
|
||||
password_hash_algorithm(default) -> #{<<"name">> => sha256};
|
||||
password_hash_algorithm(_) -> undefined.
|
||||
|
||||
salt_rounds(type) -> integer();
|
||||
salt_rounds(default) -> 10;
|
||||
salt_rounds(_) -> undefined.
|
||||
|
||||
salt_position(type) -> {enum, [prefix, suffix]};
|
||||
salt_position(default) -> prefix;
|
||||
salt_position(_) -> undefined.
|
||||
|
||||
query(type) -> string();
|
||||
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 := _}, _) ->
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%%
|
||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||
%% you may not use this file except in compliance with the License.
|
||||
%% You may obtain a copy of the License at
|
||||
%%
|
||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||
%%
|
||||
%% Unless required by applicable law or agreed to in writing, software
|
||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
%% See the License for the specific language governing permissions and
|
||||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_authn_other_schema).
|
||||
|
||||
-include("emqx_authn.hrl").
|
||||
-include_lib("typerefl/include/types.hrl").
|
||||
|
||||
-behaviour(hocon_schema).
|
||||
|
||||
-export([ structs/0
|
||||
, fields/1
|
||||
]).
|
||||
|
||||
structs() -> [ "filename", "position", "user_info", "new_user_info"].
|
||||
|
||||
fields("filename") ->
|
||||
[ {filename, fun filename/1} ];
|
||||
fields("position") ->
|
||||
[ {position, fun position/1} ];
|
||||
fields("user_info") ->
|
||||
[ {user_id, fun user_id/1}
|
||||
, {password, fun password/1}
|
||||
];
|
||||
fields("new_user_info") ->
|
||||
[ {password, fun password/1}
|
||||
].
|
||||
|
||||
filename(type) -> string();
|
||||
filename(nullable) -> false;
|
||||
filename(_) -> undefined.
|
||||
|
||||
position(type) -> integer();
|
||||
position(validate) -> [fun (Position) -> Position > 0 end];
|
||||
position(nullable) -> false;
|
||||
position(_) -> undefined.
|
||||
|
||||
user_id(type) -> binary();
|
||||
user_id(nullable) -> false;
|
||||
user_id(_) -> undefined.
|
||||
|
||||
password(type) -> binary();
|
||||
password(nullable) -> false;
|
||||
password(_) -> undefined.
|
||||
|
|
@ -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 := _}, _) ->
|
||||
|
|
|
@ -54,34 +54,44 @@ t_authenticator(_) ->
|
|||
AuthenticatorName1 = <<"myauthenticator1">>,
|
||||
AuthenticatorConfig1 = #{name => AuthenticatorName1,
|
||||
mechanism => 'password-based',
|
||||
config => #{
|
||||
server_type => 'built-in-database',
|
||||
user_id_type => username,
|
||||
password_hash_algorithm => #{
|
||||
name => sha256
|
||||
}}},
|
||||
?assertEqual({ok, AuthenticatorConfig1}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)),
|
||||
?assertEqual({ok, AuthenticatorConfig1}, ?AUTH:lookup_authenticator(?CHAIN, AuthenticatorName1)),
|
||||
?assertEqual({ok, [AuthenticatorConfig1]}, ?AUTH:list_authenticators(?CHAIN)),
|
||||
?assertEqual({error, {already_exists, {authenticator, AuthenticatorName1}}}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)),
|
||||
server_type => 'built-in-database',
|
||||
user_id_type => username,
|
||||
password_hash_algorithm => #{
|
||||
name => sha256
|
||||
}},
|
||||
{ok, #{name := AuthenticatorName1, id := ID1}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1),
|
||||
?assertMatch({ok, #{name := AuthenticatorName1}}, ?AUTH:lookup_authenticator(?CHAIN, ID1)),
|
||||
?assertMatch({ok, [#{name := AuthenticatorName1}]}, ?AUTH:list_authenticators(?CHAIN)),
|
||||
?assertEqual({error, name_has_be_used}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)),
|
||||
|
||||
AuthenticatorConfig2 = #{name => AuthenticatorName1,
|
||||
mechanism => jwt,
|
||||
use_jwks => false,
|
||||
algorithm => 'hmac-based',
|
||||
secret => <<"abcdef">>,
|
||||
secret_base64_encoded => false,
|
||||
verify_claims => []},
|
||||
{ok, #{name := AuthenticatorName1, id := ID1, mechanism := jwt}} = ?AUTH:update_authenticator(?CHAIN, ID1, AuthenticatorConfig2),
|
||||
|
||||
ID2 = <<"random">>,
|
||||
?assertEqual({error, {not_found, {authenticator, ID2}}}, ?AUTH:update_authenticator(?CHAIN, ID2, AuthenticatorConfig2)),
|
||||
?assertEqual({error, name_has_be_used}, ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig2)),
|
||||
|
||||
AuthenticatorName2 = <<"myauthenticator2">>,
|
||||
AuthenticatorConfig2 = AuthenticatorConfig1#{name => AuthenticatorName2},
|
||||
?assertEqual({ok, AuthenticatorConfig2}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig2)),
|
||||
?assertMatch({ok, #{id := ?CHAIN, authenticators := [AuthenticatorConfig1, AuthenticatorConfig2]}}, ?AUTH:lookup_chain(?CHAIN)),
|
||||
?assertEqual({ok, AuthenticatorConfig2}, ?AUTH:lookup_authenticator(?CHAIN, AuthenticatorName2)),
|
||||
?assertEqual({ok, [AuthenticatorConfig1, AuthenticatorConfig2]}, ?AUTH:list_authenticators(?CHAIN)),
|
||||
AuthenticatorConfig3 = AuthenticatorConfig2#{name => AuthenticatorName2},
|
||||
{ok, #{name := AuthenticatorName2, id := ID2, secret := <<"abcdef">>}} = ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig3),
|
||||
?assertMatch({ok, #{name := AuthenticatorName2}}, ?AUTH:lookup_authenticator(?CHAIN, ID2)),
|
||||
{ok, #{name := AuthenticatorName2, id := ID2, secret := <<"fedcba">>}} = ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig3#{secret := <<"fedcba">>}),
|
||||
|
||||
?assertEqual(ok, ?AUTH:move_authenticator_to_the_front(?CHAIN, AuthenticatorName2)),
|
||||
?assertEqual({ok, [AuthenticatorConfig2, AuthenticatorConfig1]}, ?AUTH:list_authenticators(?CHAIN)),
|
||||
?assertEqual(ok, ?AUTH:move_authenticator_to_the_end(?CHAIN, AuthenticatorName2)),
|
||||
?assertEqual({ok, [AuthenticatorConfig1, AuthenticatorConfig2]}, ?AUTH:list_authenticators(?CHAIN)),
|
||||
?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, AuthenticatorName2, 1)),
|
||||
?assertEqual({ok, [AuthenticatorConfig2, AuthenticatorConfig1]}, ?AUTH:list_authenticators(?CHAIN)),
|
||||
?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, AuthenticatorName2, 3)),
|
||||
?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, AuthenticatorName2, 0)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName1)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName2)),
|
||||
?assertMatch({ok, #{id := ?CHAIN, authenticators := [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}}, ?AUTH:lookup_chain(?CHAIN)),
|
||||
?assertMatch({ok, [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}, ?AUTH:list_authenticators(?CHAIN)),
|
||||
|
||||
?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 1)),
|
||||
?assertMatch({ok, [#{name := AuthenticatorName2}, #{name := AuthenticatorName1}]}, ?AUTH:list_authenticators(?CHAIN)),
|
||||
?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 3)),
|
||||
?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 0)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID2)),
|
||||
?assertEqual({ok, []}, ?AUTH:list_authenticators(?CHAIN)),
|
||||
ok.
|
||||
|
||||
|
|
|
@ -39,15 +39,14 @@ end_per_suite(_) ->
|
|||
|
||||
t_jwt_authenticator(_) ->
|
||||
AuthenticatorName = <<"myauthenticator">>,
|
||||
Config = #{use_jwks => false,
|
||||
Config = #{name => AuthenticatorName,
|
||||
mechanism => jwt,
|
||||
use_jwks => false,
|
||||
algorithm => 'hmac-based',
|
||||
secret => <<"abcdef">>,
|
||||
secret_base64_encoded => false,
|
||||
verify_claims => []},
|
||||
AuthenticatorConfig = #{name => AuthenticatorName,
|
||||
mechanism => jwt,
|
||||
config => Config},
|
||||
?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
|
||||
{ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, Config),
|
||||
|
||||
Payload = #{<<"username">> => <<"myuser">>},
|
||||
JWS = generate_jws('hmac-based', Payload, <<"abcdef">>),
|
||||
|
@ -62,11 +61,11 @@ t_jwt_authenticator(_) ->
|
|||
%% secret_base64_encoded
|
||||
Config2 = Config#{secret => base64:encode(<<"abcdef">>),
|
||||
secret_base64_encoded => true},
|
||||
?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, AuthenticatorName, Config2)),
|
||||
?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, ID, Config2)),
|
||||
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)),
|
||||
|
||||
Config3 = Config#{verify_claims => [{<<"username">>, <<"${mqtt-username}">>}]},
|
||||
?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, AuthenticatorName, Config3)),
|
||||
?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, ID, Config3)),
|
||||
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)),
|
||||
?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo#{username => <<"otheruser">>}, ok)),
|
||||
|
||||
|
@ -109,7 +108,7 @@ t_jwt_authenticator(_) ->
|
|||
ClientInfo8 = ClientInfo#{password => JWS8},
|
||||
?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo8, ok)),
|
||||
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)),
|
||||
ok.
|
||||
|
||||
t_jwt_authenticator2(_) ->
|
||||
|
@ -117,14 +116,13 @@ t_jwt_authenticator2(_) ->
|
|||
PublicKey = list_to_binary(filename:join([Dir, "data/public_key.pem"])),
|
||||
PrivateKey = list_to_binary(filename:join([Dir, "data/private_key.pem"])),
|
||||
AuthenticatorName = <<"myauthenticator">>,
|
||||
Config = #{use_jwks => false,
|
||||
Config = #{name => AuthenticatorName,
|
||||
mechanism => jwt,
|
||||
use_jwks => false,
|
||||
algorithm => 'public-key',
|
||||
certificate => PublicKey,
|
||||
verify_claims => []},
|
||||
AuthenticatorConfig = #{name => AuthenticatorName,
|
||||
mechanism => jwt,
|
||||
config => Config},
|
||||
?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
|
||||
{ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, Config),
|
||||
|
||||
Payload = #{<<"username">> => <<"myuser">>},
|
||||
JWS = generate_jws('public-key', Payload, PrivateKey),
|
||||
|
@ -133,7 +131,7 @@ t_jwt_authenticator2(_) ->
|
|||
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)),
|
||||
?assertEqual({stop, {error, not_authorized}}, ?AUTH:authenticate(ClientInfo#{password => <<"badpassword">>}, ok)),
|
||||
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)),
|
||||
ok.
|
||||
|
||||
generate_jws('hmac-based', Payload, Secret) ->
|
||||
|
|
|
@ -41,18 +41,17 @@ t_mnesia_authenticator(_) ->
|
|||
AuthenticatorName = <<"myauthenticator">>,
|
||||
AuthenticatorConfig = #{name => AuthenticatorName,
|
||||
mechanism => 'password-based',
|
||||
config => #{
|
||||
server_type => 'built-in-database',
|
||||
user_id_type => username,
|
||||
password_hash_algorithm => #{
|
||||
name => sha256
|
||||
}}},
|
||||
?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
|
||||
server_type => 'built-in-database',
|
||||
user_id_type => username,
|
||||
password_hash_algorithm => #{
|
||||
name => sha256
|
||||
}},
|
||||
{ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig),
|
||||
|
||||
UserInfo = #{<<"user_id">> => <<"myuser">>,
|
||||
<<"password">> => <<"mypass">>},
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, AuthenticatorName, UserInfo)),
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
|
||||
UserInfo = #{user_id => <<"myuser">>,
|
||||
password => <<"mypass">>},
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, ID, UserInfo)),
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)),
|
||||
|
||||
ClientInfo = #{zone => external,
|
||||
username => <<"myuser">>,
|
||||
|
@ -69,40 +68,39 @@ t_mnesia_authenticator(_) ->
|
|||
?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo3, ok)),
|
||||
?assertEqual({error, bad_username_or_password}, emqx_access_control:authenticate(ClientInfo3)),
|
||||
|
||||
UserInfo2 = UserInfo#{<<"password">> => <<"mypass2">>},
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:update_user(?CHAIN, AuthenticatorName, <<"myuser">>, UserInfo2)),
|
||||
UserInfo2 = UserInfo#{password => <<"mypass2">>},
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:update_user(?CHAIN, ID, <<"myuser">>, UserInfo2)),
|
||||
ClientInfo4 = ClientInfo#{password => <<"mypass2">>},
|
||||
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo4, ok)),
|
||||
|
||||
?assertEqual(ok, ?AUTH:delete_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
|
||||
?assertEqual({error, not_found}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
|
||||
?assertEqual(ok, ?AUTH:delete_user(?CHAIN, ID, <<"myuser">>)),
|
||||
?assertEqual({error, not_found}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)),
|
||||
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, AuthenticatorName, UserInfo)),
|
||||
?assertMatch({ok, #{user_id := <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, ID, UserInfo)),
|
||||
?assertMatch({ok, #{user_id := <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)),
|
||||
|
||||
?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
|
||||
?assertMatch({error, not_found}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
|
||||
{ok, #{name := AuthenticatorName, id := ID1}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig),
|
||||
?assertMatch({error, not_found}, ?AUTH:lookup_user(?CHAIN, ID1, <<"myuser">>)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)),
|
||||
ok.
|
||||
|
||||
t_import(_) ->
|
||||
AuthenticatorName = <<"myauthenticator">>,
|
||||
AuthenticatorConfig = #{name => AuthenticatorName,
|
||||
mechanism => 'password-based',
|
||||
config => #{
|
||||
server_type => 'built-in-database',
|
||||
user_id_type => username,
|
||||
password_hash_algorithm => #{
|
||||
name => sha256
|
||||
}}},
|
||||
?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
|
||||
server_type => 'built-in-database',
|
||||
user_id_type => username,
|
||||
password_hash_algorithm => #{
|
||||
name => sha256
|
||||
}},
|
||||
{ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig),
|
||||
|
||||
Dir = code:lib_dir(emqx_authn, test),
|
||||
?assertEqual(ok, ?AUTH:import_users(?CHAIN, AuthenticatorName, filename:join([Dir, "data/user-credentials.json"]))),
|
||||
?assertEqual(ok, ?AUTH:import_users(?CHAIN, AuthenticatorName, filename:join([Dir, "data/user-credentials.csv"]))),
|
||||
?assertMatch({ok, #{user_id := <<"myuser1">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser1">>)),
|
||||
?assertMatch({ok, #{user_id := <<"myuser3">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser3">>)),
|
||||
?assertEqual(ok, ?AUTH:import_users(?CHAIN, ID, filename:join([Dir, "data/user-credentials.json"]))),
|
||||
?assertEqual(ok, ?AUTH:import_users(?CHAIN, ID, filename:join([Dir, "data/user-credentials.csv"]))),
|
||||
?assertMatch({ok, #{user_id := <<"myuser1">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser1">>)),
|
||||
?assertMatch({ok, #{user_id := <<"myuser3">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser3">>)),
|
||||
|
||||
ClientInfo1 = #{username => <<"myuser1">>,
|
||||
password => <<"mypassword1">>},
|
||||
|
@ -110,50 +108,48 @@ t_import(_) ->
|
|||
ClientInfo2 = ClientInfo1#{username => <<"myuser3">>,
|
||||
password => <<"mypassword3">>},
|
||||
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo2, ok)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)),
|
||||
ok.
|
||||
|
||||
t_multi_mnesia_authenticator(_) ->
|
||||
AuthenticatorName1 = <<"myauthenticator1">>,
|
||||
AuthenticatorConfig1 = #{name => AuthenticatorName1,
|
||||
mechanism => 'password-based',
|
||||
config => #{
|
||||
server_type => 'built-in-database',
|
||||
user_id_type => username,
|
||||
password_hash_algorithm => #{
|
||||
name => sha256
|
||||
}}},
|
||||
server_type => 'built-in-database',
|
||||
user_id_type => username,
|
||||
password_hash_algorithm => #{
|
||||
name => sha256
|
||||
}},
|
||||
AuthenticatorName2 = <<"myauthenticator2">>,
|
||||
AuthenticatorConfig2 = #{name => AuthenticatorName2,
|
||||
mechanism => 'password-based',
|
||||
config => #{
|
||||
server_type => 'built-in-database',
|
||||
user_id_type => clientid,
|
||||
password_hash_algorithm => #{
|
||||
name => sha256
|
||||
}}},
|
||||
?assertEqual({ok, AuthenticatorConfig1}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)),
|
||||
?assertEqual({ok, AuthenticatorConfig2}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig2)),
|
||||
server_type => 'built-in-database',
|
||||
user_id_type => clientid,
|
||||
password_hash_algorithm => #{
|
||||
name => sha256
|
||||
}},
|
||||
{ok, #{name := AuthenticatorName1, id := ID1}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1),
|
||||
{ok, #{name := AuthenticatorName2, id := ID2}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig2),
|
||||
|
||||
?assertEqual({ok, #{user_id => <<"myuser">>}},
|
||||
?AUTH:add_user(?CHAIN, AuthenticatorName1,
|
||||
#{<<"user_id">> => <<"myuser">>,
|
||||
<<"password">> => <<"mypass1">>})),
|
||||
?AUTH:add_user(?CHAIN, ID1,
|
||||
#{user_id => <<"myuser">>,
|
||||
password => <<"mypass1">>})),
|
||||
?assertEqual({ok, #{user_id => <<"myclient">>}},
|
||||
?AUTH:add_user(?CHAIN, AuthenticatorName2,
|
||||
#{<<"user_id">> => <<"myclient">>,
|
||||
<<"password">> => <<"mypass2">>})),
|
||||
?AUTH:add_user(?CHAIN, ID2,
|
||||
#{user_id => <<"myclient">>,
|
||||
password => <<"mypass2">>})),
|
||||
|
||||
ClientInfo1 = #{username => <<"myuser">>,
|
||||
clientid => <<"myclient">>,
|
||||
password => <<"mypass1">>},
|
||||
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo1, ok)),
|
||||
?assertEqual(ok, ?AUTH:move_authenticator_to_the_front(?CHAIN, AuthenticatorName2)),
|
||||
?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 1)),
|
||||
|
||||
?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo1, ok)),
|
||||
ClientInfo2 = ClientInfo1#{password => <<"mypass2">>},
|
||||
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo2, ok)),
|
||||
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName1)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName2)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)),
|
||||
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID2)),
|
||||
ok.
|
||||
|
|
|
@ -53,14 +53,15 @@ fields("") ->
|
|||
[{config, #{type => hoconsc:ref(?MODULE, config)}}];
|
||||
|
||||
fields(config) ->
|
||||
[ {base_url, fun base_url/1}
|
||||
, {connect_timeout, fun connect_timeout/1}
|
||||
, {max_retries, fun max_retries/1}
|
||||
, {retry_interval, fun retry_interval/1}
|
||||
, {pool_type, fun pool_type/1}
|
||||
, {pool_size, fun pool_size/1}
|
||||
, {ssl_opts, #{type => hoconsc:ref(?MODULE, ssl_opts),
|
||||
default => #{}}}
|
||||
[ {base_url, fun base_url/1}
|
||||
, {connect_timeout, fun connect_timeout/1}
|
||||
, {max_retries, fun max_retries/1}
|
||||
, {retry_interval, fun retry_interval/1}
|
||||
, {pool_type, fun pool_type/1}
|
||||
, {pool_size, fun pool_size/1}
|
||||
, {enable_pipelining, fun enable_pipelining/1}
|
||||
, {ssl_opts, #{type => hoconsc:ref(?MODULE, ssl_opts),
|
||||
default => #{}}}
|
||||
];
|
||||
|
||||
fields(ssl_opts) ->
|
||||
|
@ -101,6 +102,10 @@ pool_size(type) -> non_neg_integer();
|
|||
pool_size(default) -> 8;
|
||||
pool_size(_) -> undefined.
|
||||
|
||||
enable_pipelining(type) -> boolean();
|
||||
enable_pipelining(default) -> true;
|
||||
enable_pipelining(_) -> undefined.
|
||||
|
||||
cacertfile(type) -> string();
|
||||
cacertfile(nullable) -> true;
|
||||
cacertfile(_) -> undefined.
|
||||
|
@ -147,7 +152,7 @@ on_start(InstId, #{base_url := #{scheme := Scheme,
|
|||
, {pool_type, PoolType}
|
||||
, {pool_size, PoolSize}
|
||||
, {transport, Transport}
|
||||
, {transport, NTransportOpts}],
|
||||
, {transport_opts, NTransportOpts}],
|
||||
PoolName = emqx_plugin_libs_pool:pool_name(InstId),
|
||||
{ok, _} = ehttpc_sup:start_pool(PoolName, PoolOpts),
|
||||
{ok, #{pool_name => PoolName,
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
{deps,
|
||||
[ {gpb, "4.11.2"} %% gpb only used to build, but not for release, pin it here to avoid fetching a wrong version due to rebar plugins scattered in all the deps
|
||||
, {ehttpc, {git, "https://github.com/emqx/ehttpc", {tag, "0.1.7"}}}
|
||||
, {ehttpc, {git, "https://github.com/emqx/ehttpc", {tag, "0.1.8"}}}
|
||||
, {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}}
|
||||
, {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}}
|
||||
, {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}}
|
||||
|
|
Loading…
Reference in New Issue