feat(authn http api): provide http api for authn and improve update mechanism

This commit is contained in:
zhouzb 2021-07-26 14:37:06 +08:00
parent 7d2aac7e24
commit 327ff8636f
12 changed files with 1065 additions and 371 deletions

View File

@ -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 }
# }
# }
] ]
} }

View File

@ -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()
}). }).

View File

@ -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, []).

View File

@ -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">>}}.

View File

@ -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} ->

View File

@ -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.

View File

@ -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,

View File

@ -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 := _}, _) ->

View File

@ -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,

View File

@ -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;

View File

@ -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 := _}, _) ->

View File

@ -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 := _}, _) ->