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: {
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
}
]
}

View File

@ -17,21 +17,21 @@
-define(APP, emqx_authn).
-define(CHAIN, <<"mqtt">>).
-type chain_id() :: binary().
-type authenticator_name() :: binary().
-type mechanism() :: 'password-based' | jwt | scram.
-define(VER_1, <<"1">>).
-define(VER_2, <<"2">>).
-record(authenticator,
{ name :: authenticator_name()
, mechanism :: mechanism()
{ id :: binary()
, name :: binary()
, provider :: module()
, config :: map()
, state :: map()
, version :: binary()
}).
-record(chain,
{ id :: chain_id()
, authenticators :: [{authenticator_name(), #authenticator{}}]
{ id :: binary()
, authenticators :: [{binary(), binary(), #authenticator{}}]
, created_at :: integer()
}).

View File

@ -31,10 +31,9 @@
, create_authenticator/2
, delete_authenticator/2
, update_authenticator/3
, update_or_create_authenticator/3
, lookup_authenticator/2
, list_authenticators/1
, move_authenticator_to_the_front/2
, move_authenticator_to_the_end/2
, move_authenticator_to_the_nth/3
]).
@ -95,7 +94,7 @@ authenticate(Credential, _AuthResult) ->
do_authenticate([], _) ->
{stop, {error, not_authorized}};
do_authenticate([{_, #authenticator{provider = Provider, state = State}} | More], Credential) ->
do_authenticate([{_, _, #authenticator{provider = Provider, state = State}} | More], Credential) ->
case Provider:authenticate(Credential, State) of
ignore ->
do_authenticate(More, Credential);
@ -130,7 +129,7 @@ delete_chain(ID) ->
[] ->
{error, {not_found, {chain, ID}}};
[#chain{authenticators = Authenticators}] ->
_ = [do_delete_authenticator(Authenticator) || {_, Authenticator} <- Authenticators],
_ = [do_delete_authenticator(Authenticator) || {_, _, Authenticator} <- Authenticators],
mnesia:delete(?CHAIN_TAB, ID, write)
end
end).
@ -147,25 +146,21 @@ list_chains() ->
Chains = ets:tab2list(?CHAIN_TAB),
{ok, [serialize_chain(Chain) || Chain <- Chains]}.
create_authenticator(ChainID, #{name := Name,
mechanism := Mechanism,
config := Config}) ->
create_authenticator(ChainID, #{name := Name} = Config) ->
UpdateFun =
fun(Chain = #chain{authenticators = Authenticators}) ->
case lists:keymember(Name, 1, Authenticators) of
case lists:keymember(Name, 2, Authenticators) of
true ->
{error, {already_exists, {authenticator, Name}}};
{error, name_has_be_used};
false ->
Provider = authenticator_provider(Mechanism, Config),
case Provider:create(ChainID, Name, Config) of
{ok, State} ->
Authenticator = #authenticator{name = Name,
mechanism = Mechanism,
provider = Provider,
config = Config,
state = State},
NChain = Chain#chain{authenticators = Authenticators ++ [{Name, Authenticator}]},
ok = mnesia:write(?CHAIN_TAB, NChain, write),
AlreadyExist = fun(ID) ->
lists:keymember(ID, 1, Authenticators)
end,
AuthenticatorID = gen_id(AlreadyExist),
case do_create_authenticator(ChainID, AuthenticatorID, Config) of
{ok, Authenticator} ->
NAuthenticators = Authenticators ++ [{AuthenticatorID, Name, Authenticator}],
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write),
{ok, serialize_authenticator(Authenticator)};
{error, Reason} ->
{error, Reason}
@ -174,12 +169,12 @@ create_authenticator(ChainID, #{name := Name,
end,
update_chain(ChainID, UpdateFun).
delete_authenticator(ChainID, AuthenticatorName) ->
delete_authenticator(ChainID, AuthenticatorID) ->
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
case lists:keytake(AuthenticatorName, 1, Authenticators) of
case lists:keytake(AuthenticatorID, 1, Authenticators) of
false ->
{error, {not_found, {authenticator, AuthenticatorName}}};
{value, {_, Authenticator}, NAuthenticators} ->
{error, {not_found, {authenticator, AuthenticatorID}}};
{value, {_, _, Authenticator}, NAuthenticators} ->
_ = do_delete_authenticator(Authenticator),
NChain = Chain#chain{authenticators = NAuthenticators},
mnesia:write(?CHAIN_TAB, NChain, write)
@ -187,38 +182,80 @@ delete_authenticator(ChainID, AuthenticatorName) ->
end,
update_chain(ChainID, UpdateFun).
update_authenticator(ChainID, AuthenticatorName, Config) ->
update_authenticator(ChainID, AuthenticatorID, Config) ->
do_update_authenticator(ChainID, AuthenticatorID, Config, false).
update_or_create_authenticator(ChainID, AuthenticatorID, Config) ->
do_update_authenticator(ChainID, AuthenticatorID, Config, true).
do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, CreateWhenNotFound) ->
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
case proplists:get_value(AuthenticatorName, Authenticators, undefined) of
undefined ->
{error, {not_found, {authenticator, AuthenticatorName}}};
#authenticator{provider = Provider,
config = OriginalConfig,
state = State} = Authenticator ->
NewConfig = maps:merge(OriginalConfig, Config),
case Provider:update(ChainID, AuthenticatorName, NewConfig, State) of
{ok, NState} ->
NAuthenticator = Authenticator#authenticator{config = NewConfig,
state = NState},
NAuthenticators = update_value(AuthenticatorName, NAuthenticator, Authenticators),
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write),
{ok, serialize_authenticator(NAuthenticator)};
{error, Reason} ->
{error, Reason}
case lists:keytake(AuthenticatorID, 1, Authenticators) of
false ->
case CreateWhenNotFound of
true ->
case do_create_authenticator(ChainID, AuthenticatorID, Config) of
{ok, Authenticator} ->
NAuthenticators = Authenticators ++ [{AuthenticatorID, NewName, Authenticator}],
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NAuthenticators}, write),
{ok, serialize_authenticator(Authenticator)};
{error, Reason} ->
{error, Reason}
end;
false ->
{error, {not_found, {authenticator, AuthenticatorID}}}
end;
{value,
{_, _, #authenticator{provider = Provider,
state = #{version := Version} = State}},
Others} ->
case lists:keymember(NewName, 2, Others) of
true ->
{error, name_has_be_used};
false ->
case (NewProvider = authenticator_provider(Config)) =:= Provider of
true ->
Unique = {ChainID, AuthenticatorID, Version},
case Provider:update(Config#{'_unique' => Unique}, State) of
{ok, NewState} ->
NewAuthenticator = #authenticator{name = NewName,
config = Config,
state = switch_version(NewState)},
NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}, write),
{ok, serialize_authenticator(NewAuthenticator)};
{error, Reason} ->
{error, Reason}
end;
false ->
case NewProvider:create(Config#{'_unique' => {ChainID, AuthenticatorID, Version}}) of
{ok, NewState} ->
NewAuthenticator = #authenticator{name = NewName,
provider = NewProvider,
config = Config,
state = switch_version(NewState)},
NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
ok = mnesia:write(?CHAIN_TAB, Chain#chain{authenticators = NewAuthenticators}, write),
_ = Provider:destroy(State),
{ok, serialize_authenticator(NewAuthenticator)};
{error, Reason} ->
{error, Reason}
end
end
end
end
end,
end,
update_chain(ChainID, UpdateFun).
lookup_authenticator(ChainID, AuthenticatorName) ->
lookup_authenticator(ChainID, AuthenticatorID) ->
case mnesia:dirty_read(?CHAIN_TAB, ChainID) of
[] ->
{error, {not_found, {chain, ChainID}}};
[#chain{authenticators = Authenticators}] ->
case proplists:get_value(AuthenticatorName, Authenticators, undefined) of
undefined ->
{error, {not_found, {authenticator, AuthenticatorName}}};
Authenticator ->
case lists:keyfind(AuthenticatorID, 1, Authenticators) of
false ->
{error, {not_found, {authenticator, AuthenticatorID}}};
{_, _, Authenticator} ->
{ok, serialize_authenticator(Authenticator)}
end
end.
@ -231,9 +268,9 @@ list_authenticators(ChainID) ->
{ok, serialize_authenticators(Authenticators)}
end.
move_authenticator_to_the_front(ChainID, AuthenticatorName) ->
move_authenticator_to_the_nth(ChainID, AuthenticatorID, N) ->
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
case move_authenticator_to_the_front_(AuthenticatorName, Authenticators) of
case move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N) of
{ok, NAuthenticators} ->
NChain = Chain#chain{authenticators = NAuthenticators},
mnesia:write(?CHAIN_TAB, NChain, write);
@ -243,108 +280,94 @@ move_authenticator_to_the_front(ChainID, AuthenticatorName) ->
end,
update_chain(ChainID, UpdateFun).
move_authenticator_to_the_end(ChainID, AuthenticatorName) ->
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
case move_authenticator_to_the_end_(AuthenticatorName, Authenticators) of
{ok, NAuthenticators} ->
NChain = Chain#chain{authenticators = NAuthenticators},
mnesia:write(?CHAIN_TAB, NChain, write);
{error, Reason} ->
{error, Reason}
end
end,
update_chain(ChainID, UpdateFun).
import_users(ChainID, AuthenticatorID, Filename) ->
call_authenticator(ChainID, AuthenticatorID, import_users, [Filename]).
move_authenticator_to_the_nth(ChainID, AuthenticatorName, N) ->
UpdateFun = fun(Chain = #chain{authenticators = Authenticators}) ->
case move_authenticator_to_the_nth_(AuthenticatorName, Authenticators, N) of
{ok, NAuthenticators} ->
NChain = Chain#chain{authenticators = NAuthenticators},
mnesia:write(?CHAIN_TAB, NChain, write);
{error, Reason} ->
{error, Reason}
end
end,
update_chain(ChainID, UpdateFun).
add_user(ChainID, AuthenticatorID, UserInfo) ->
call_authenticator(ChainID, AuthenticatorID, add_user, [UserInfo]).
import_users(ChainID, AuthenticatorName, Filename) ->
call_authenticator(ChainID, AuthenticatorName, import_users, [Filename]).
delete_user(ChainID, AuthenticatorID, UserID) ->
call_authenticator(ChainID, AuthenticatorID, delete_user, [UserID]).
add_user(ChainID, AuthenticatorName, UserInfo) ->
call_authenticator(ChainID, AuthenticatorName, add_user, [UserInfo]).
update_user(ChainID, AuthenticatorID, UserID, NewUserInfo) ->
call_authenticator(ChainID, AuthenticatorID, update_user, [UserID, NewUserInfo]).
delete_user(ChainID, AuthenticatorName, UserID) ->
call_authenticator(ChainID, AuthenticatorName, delete_user, [UserID]).
lookup_user(ChainID, AuthenticatorID, UserID) ->
call_authenticator(ChainID, AuthenticatorID, lookup_user, [UserID]).
update_user(ChainID, AuthenticatorName, UserID, NewUserInfo) ->
call_authenticator(ChainID, AuthenticatorName, update_user, [UserID, NewUserInfo]).
lookup_user(ChainID, AuthenticatorName, UserID) ->
call_authenticator(ChainID, AuthenticatorName, lookup_user, [UserID]).
list_users(ChainID, AuthenticatorName) ->
call_authenticator(ChainID, AuthenticatorName, list_users, []).
list_users(ChainID, AuthenticatorID) ->
call_authenticator(ChainID, AuthenticatorID, list_users, []).
%%------------------------------------------------------------------------------
%% Internal functions
%%------------------------------------------------------------------------------
authenticator_provider('password-based', #{server_type := 'built-in-database'}) ->
authenticator_provider(#{mechanism := 'password-based', server_type := 'built-in-database'}) ->
emqx_authn_mnesia;
authenticator_provider('password-based', #{server_type := 'mysql'}) ->
authenticator_provider(#{mechanism := 'password-based', server_type := 'mysql'}) ->
emqx_authn_mysql;
authenticator_provider('password-based', #{server_type := 'pgsql'}) ->
authenticator_provider(#{mechanism := 'password-based', server_type := 'pgsql'}) ->
emqx_authn_pgsql;
authenticator_provider('password-based', #{server_type := 'http-server'}) ->
authenticator_provider(#{mechanism := 'password-based', server_type := 'http-server'}) ->
emqx_authn_http;
authenticator_provider(jwt, _) ->
authenticator_provider(#{mechanism := jwt}) ->
emqx_authn_jwt;
authenticator_provider(scram, #{server_type := 'built-in-database'}) ->
authenticator_provider(#{mechanism := scram, server_type := 'built-in-database'}) ->
emqx_enhanced_authn_scram_mnesia.
gen_id(AlreadyExist) ->
ID = list_to_binary(emqx_rule_id:gen()),
case AlreadyExist(ID) of
true -> gen_id(AlreadyExist);
false -> ID
end.
switch_version(State = #{version := ?VER_1}) ->
State#{version := ?VER_2};
switch_version(State = #{version := ?VER_2}) ->
State#{version := ?VER_1};
switch_version(State) ->
State#{version => ?VER_1}.
do_create_authenticator(ChainID, AuthenticatorID, #{name := Name} = Config) ->
Provider = authenticator_provider(Config),
Unique = <<ChainID/binary, "/", AuthenticatorID/binary, ":", ?VER_1/binary>>,
case Provider:create(Config#{'_unique' => Unique}) of
{ok, State} ->
Authenticator = #authenticator{id = AuthenticatorID,
name = Name,
provider = Provider,
config = Config,
state = switch_version(State)},
{ok, Authenticator};
{error, Reason} ->
{error, Reason}
end.
do_delete_authenticator(#authenticator{provider = Provider, state = State}) ->
Provider:destroy(State).
_ = Provider:destroy(State),
ok.
update_value(Key, Value, List) ->
lists:keyreplace(Key, 1, List, {Key, Value}).
replace_authenticator(ID, #authenticator{name = Name} = Authenticator, Authenticators) ->
lists:keyreplace(ID, 1, Authenticators, {ID, Name, Authenticator}).
move_authenticator_to_the_front_(AuthenticatorName, Authenticators) ->
move_authenticator_to_the_front_(AuthenticatorName, Authenticators, []).
move_authenticator_to_the_front_(AuthenticatorName, [], _) ->
{error, {not_found, {authenticator, AuthenticatorName}}};
move_authenticator_to_the_front_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], Passed) ->
{ok, [Authenticator | (lists:reverse(Passed) ++ More)]};
move_authenticator_to_the_front_(AuthenticatorName, [Authenticator | More], Passed) ->
move_authenticator_to_the_front_(AuthenticatorName, More, [Authenticator | Passed]).
move_authenticator_to_the_end_(AuthenticatorName, Authenticators) ->
move_authenticator_to_the_end_(AuthenticatorName, Authenticators, []).
move_authenticator_to_the_end_(AuthenticatorName, [], _) ->
{error, {not_found, {authenticator, AuthenticatorName}}};
move_authenticator_to_the_end_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], Passed) ->
{ok, lists:reverse(Passed) ++ More ++ [Authenticator]};
move_authenticator_to_the_end_(AuthenticatorName, [Authenticator | More], Passed) ->
move_authenticator_to_the_end_(AuthenticatorName, More, [Authenticator | Passed]).
move_authenticator_to_the_nth_(AuthenticatorName, Authenticators, N)
move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N)
when N =< length(Authenticators) andalso N > 0 ->
move_authenticator_to_the_nth_(AuthenticatorName, Authenticators, N, []);
move_authenticator_to_the_nth_(AuthenticatorID, Authenticators, N, []);
move_authenticator_to_the_nth_(_, _, _) ->
{error, out_of_range}.
move_authenticator_to_the_nth_(AuthenticatorName, [], _, _) ->
{error, {not_found, {authenticator, AuthenticatorName}}};
move_authenticator_to_the_nth_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], N, Passed)
move_authenticator_to_the_nth_(AuthenticatorID, [], _, _) ->
{error, {not_found, {authenticator, AuthenticatorID}}};
move_authenticator_to_the_nth_(AuthenticatorID, [{AuthenticatorID, _, _} = Authenticator | More], N, Passed)
when N =< length(Passed) ->
{L1, L2} = lists:split(N - 1, lists:reverse(Passed)),
{ok, L1 ++ [Authenticator] ++ L2 ++ More};
move_authenticator_to_the_nth_(AuthenticatorName, [{AuthenticatorName, _} = Authenticator | More], N, Passed) ->
move_authenticator_to_the_nth_(AuthenticatorID, [{AuthenticatorID, _, _} = Authenticator | More], N, Passed) ->
{L1, L2} = lists:split(N - length(Passed) - 1, More),
{ok, lists:reverse(Passed) ++ L1 ++ [Authenticator] ++ L2};
move_authenticator_to_the_nth_(AuthenticatorName, [Authenticator | More], N, Passed) ->
move_authenticator_to_the_nth_(AuthenticatorName, More, N, [Authenticator | Passed]).
move_authenticator_to_the_nth_(AuthenticatorID, [Authenticator | More], N, Passed) ->
move_authenticator_to_the_nth_(AuthenticatorID, More, N, [Authenticator | Passed]).
update_chain(ChainID, UpdateFun) ->
trans(
@ -357,24 +380,15 @@ update_chain(ChainID, UpdateFun) ->
end
end).
% lookup_chain_by_listener(ListenerID, AuthNType) ->
% case mnesia:dirty_read(?BINDING_TAB, {ListenerID, AuthNType}) of
% [] ->
% {error, not_found};
% [#binding{chain_id = ChainID}] ->
% {ok, ChainID}
% end.
call_authenticator(ChainID, AuthenticatorName, Func, Args) ->
call_authenticator(ChainID, AuthenticatorID, Func, Args) ->
case mnesia:dirty_read(?CHAIN_TAB, ChainID) of
[] ->
{error, {not_found, {chain, ChainID}}};
[#chain{authenticators = Authenticators}] ->
case proplists:get_value(AuthenticatorName, Authenticators, undefined) of
undefined ->
{error, {not_found, {authenticator, AuthenticatorName}}};
#authenticator{provider = Provider, state = State} ->
case lists:keyfind(AuthenticatorID, 1, Authenticators) of
false ->
{error, {not_found, {authenticator, AuthenticatorID}}};
{_, _, #authenticator{provider = Provider, state = State}} ->
case erlang:function_exported(Provider, Func, length(Args) + 1) of
true ->
erlang:apply(Provider, Func, Args ++ [State]);
@ -391,20 +405,12 @@ serialize_chain(#chain{id = ID,
authenticators => serialize_authenticators(Authenticators),
created_at => CreatedAt}.
% serialize_binding(#binding{bound = {ListenerID, _},
% chain_id = ChainID}) ->
% #{listener_id => ListenerID,
% chain_id => ChainID}.
serialize_authenticators(Authenticators) ->
[serialize_authenticator(Authenticator) || {_, Authenticator} <- Authenticators].
[serialize_authenticator(Authenticator) || {_, _, Authenticator} <- Authenticators].
serialize_authenticator(#authenticator{name = Name,
mechanism = Mechanism,
serialize_authenticator(#authenticator{id = ID,
config = Config}) ->
#{name => Name,
mechanism => Mechanism,
config => Config}.
Config#{id => ID}.
trans(Fun) ->
trans(Fun, []).

View File

@ -20,12 +20,25 @@
-include("emqx_authn.hrl").
-export([ api_spec/0 ]).
-export([ api_spec/0
, authenticators/2
, authenticators2/2
, position/2
, import_users/2
, users/2
, users2/2
]).
api_spec() ->
{[authenticator_api()], definitions()}.
{[ authenticators_api()
, authenticators_api2()
, position_api()
, import_users_api()
, users_api()
, users2_api()
], definitions()}.
authenticator_api() ->
authenticators_api() ->
Example1 = #{name => <<"example">>,
mechanism => <<"password-based">>,
config => #{
@ -86,24 +99,506 @@ authenticator_api() ->
},
responses => #{
<<"201">> => #{
description => <<"Created successfully">>,
content => #{}
description => <<"Created">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"returned_authenticator">>)
}
}
}
}
},
get => #{
description => "List authenticators",
responses => #{
<<"200">> => #{
description => <<"OK">>,
content => #{
'application/json' => #{
schema => #{
type => array,
items => minirest:ref(<<"returned_authenticator">>)
}
}
}
}
}
}
},
{"/authentication/authenticators", Metadata, authenticators}.
authenticators_api2() ->
Metadata = #{
get => #{
description => "Get authenicator by id",
parameters => [
#{
name => id,
in => path,
schema => #{
type => string
},
required => true
}
],
responses => #{
<<"200">> => #{
description => <<"OK">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"returned_authenticator">>)
}
}
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
}
},
put => #{
description => "Update authenticator",
parameters => [
#{
name => id,
in => path,
schema => #{
type => string
},
required => true
}
],
requestBody => #{
content => #{
'application/json' => #{
schema => #{
oneOf => [ minirest:ref(<<"password_based">>)
, minirest:ref(<<"jwt">>)
, minirest:ref(<<"scram">>)
]
}
}
}
},
responses => #{
<<"200">> => #{
description => <<"OK">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"returned_authenticator">>)
}
}
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
}
},
delete => #{
description => "Delete authenticator",
parameters => [
#{
name => id,
in => path,
schema => #{
type => string
},
required => true
}
],
responses => #{
<<"204">> => #{
description => <<"No Content">>
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
}
}
},
{"/authentication/authenticators/:id", Metadata, authenticators2}.
position_api() ->
Metadata = #{
post => #{
description => "Change the order of authenticators",
parameters => [
#{
name => id,
in => path,
schema => #{
type => string
},
required => true
}
],
requestBody => #{
content => #{
'application/json' => #{
schema => #{
type => object,
required => [position],
properties => #{
position => #{
type => integer,
example => 1
}
}
}
}
}
},
responses => #{
<<"204">> => #{
description => <<"No Content">>
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
}
}
},
{"/authentication/authenticators/:id/position", Metadata, position}.
import_users_api() ->
Metadata = #{
post => #{
description => "Import users from json/csv file",
parameters => [
#{
name => id,
in => path,
schema => #{
type => string
},
required => true
}
],
requestBody => #{
content => #{
'application/json' => #{
schema => #{
type => object,
required => [filename],
properties => #{
filename => #{
type => string
}
}
}
}
}
},
responses => #{
<<"204">> => #{
description => <<"No Content">>
},
<<"400">> => #{
description => <<"Bad Request">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
}
}
},
{"/authentication/authenticators/:id/import-users", Metadata, import_users}.
users_api() ->
Metadata = #{
post => #{
description => "Add user",
parameters => [
#{
name => id,
in => path,
schema => #{
type => string
},
required => true
}
],
requestBody => #{
content => #{
'application/json' => #{
schema => #{
type => object,
required => [user_id, password],
properties => #{
user_id => #{
type => string
},
password => #{
type => string
}
}
}
}
}
},
responses => #{
<<"201">> => #{
description => <<"Created">>,
content => #{
'application/json' => #{
schema => #{
type => object,
required => [user_id],
properties => #{
user_id => #{
type => string
}
}
}
}
}
},
<<"400">> => #{
description => <<"Bad Request">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
}
},
get => #{
description => "List users",
parameters => [
#{
name => id,
in => path,
schema => #{
type => string
},
required => true
}
],
responses => #{
<<"200">> => #{
description => <<"OK">>,
content => #{
'application/json' => #{
schema => #{
type => array,
items => #{
type => object,
required => [user_id],
properties => #{
user_id => #{
type => string
}
}
}
}
}
}
}
}
}
},
{"/authentication/authenticators/:id/users", Metadata, users}.
users2_api() ->
Metadata = #{
patch => #{
description => "Update user",
parameters => [
#{
name => id,
in => path,
schema => #{
type => string
},
required => true
},
#{
name => user_id,
in => path,
schema => #{
type => string
},
required => true
}
],
requestBody => #{
content => #{
'application/json' => #{
schema => #{
type => object,
required => [password],
properties => #{
password => #{
type => string
}
}
}
}
}
},
responses => #{
<<"200">> => #{
description => <<"OK">>,
content => #{
'application/json' => #{
schema => #{
type => array,
items => #{
type => object,
required => [user_id],
properties => #{
user_id => #{
type => string
}
}
}
}
}
}
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
}
},
get => #{
description => "Get user info",
parameters => [
#{
name => id,
in => path,
schema => #{
type => string
},
required => true
},
#{
name => user_id,
in => path,
schema => #{
type => string
},
required => true
}
],
responses => #{
<<"200">> => #{
description => <<"OK">>,
content => #{
'application/json' => #{
schema => #{
type => array,
items => #{
type => object,
required => [user_id],
properties => #{
user_id => #{
type => string
}
}
}
}
}
}
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
}
},
delete => #{
description => "Delete user",
parameters => [
#{
name => id,
in => path,
schema => #{
type => string
},
required => true
},
#{
name => user_id,
in => path,
schema => #{
type => string
},
required => true
}
],
responses => #{
<<"204">> => #{
description => <<"No Content">>
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
}
}
},
{"/authentication/authenticators/:id/users/:user_id", Metadata, users2}.
definitions() ->
AuthenticatorDef = #{
oneOf => [ minirest:ref(<<"password_based">>)
, minirest:ref(<<"jwt">>)
, minirest:ref(<<"scram">>)
]
},
ReturnedAuthenticatorDef = #{
allOf => [
#{
type => object,
required => [name],
properties => #{
name => #{
type => string,
example => "exmaple"
id => #{
type => string
}
}
},
@ -111,99 +606,108 @@ definitions() ->
oneOf => [ minirest:ref(<<"password_based">>)
, minirest:ref(<<"jwt">>)
, minirest:ref(<<"scram">>)
]
]
}
]
},
PasswordBasedDef = #{
type => object,
properties => #{
mechanism => #{
type => string,
enum => [<<"password-based">>],
example => <<"password-based">>
allOf => [
#{
type => object,
required => [name, mechanism],
properties => #{
name => #{
type => string,
example => "exmaple"
},
mechanism => #{
type => string,
enum => [<<"password-based">>],
example => <<"password-based">>
}
}
},
config => #{
#{
oneOf => [ minirest:ref(<<"password_based_built_in_database">>)
, minirest:ref(<<"password_based_mysql">>)
, minirest:ref(<<"password_based_pgsql">>)
, minirest:ref(<<"password_based_http_server">>)
]
]
}
}
]
},
JWTDef = #{
type => object,
required => [name, mechanism],
properties => #{
name => #{
type => string,
example => "exmaple"
},
mechanism => #{
type => string,
enum => [<<"jwt">>],
example => <<"jwt">>
},
config => #{
use_jwks => #{
type => boolean,
default => false,
example => false
},
algorithm => #{
type => string,
enum => [<<"hmac-based">>, <<"public-key">>],
default => <<"hmac-based">>,
example => <<"hmac-based">>
},
secret => #{
type => string
},
secret_base64_encoded => #{
type => boolean,
default => false
},
certificate => #{
type => string
},
verify_claims => #{
type => object,
properties => #{
use_jwks => #{
type => boolean,
default => false,
example => false
},
algorithm => #{
type => string,
enum => [<<"hmac-based">>, <<"public-key">>],
default => <<"hmac-based">>,
example => <<"hmac-based">>
},
secret => #{
type => string
},
secret_base64_encoded => #{
type => boolean,
default => false
},
certificate => #{
type => string
},
verify_claims => #{
type => object,
additionalProperties => #{
type => string
}
},
ssl => minirest:ref(<<"ssl">>)
}
}
additionalProperties => #{
type => string
}
},
ssl => minirest:ref(<<"ssl">>)
}
},
SCRAMDef = #{
type => object,
required => [name, mechanism],
properties => #{
name => #{
type => string,
example => "exmaple"
},
mechanism => #{
type => string,
enum => [<<"scram">>],
example => <<"scram">>
},
config => #{
type => object,
properties => #{
server_type => #{
type => string,
enum => [<<"built-in-database">>],
default => <<"built-in-database">>
},
algorithm => #{
type => string,
enum => [<<"sha256">>, <<"sha512">>],
default => <<"sha256">>
},
iteration_count => #{
type => integer,
default => 4096
}
}
server_type => #{
type => string,
enum => [<<"built-in-database">>],
default => <<"built-in-database">>
},
algorithm => #{
type => string,
enum => [<<"sha256">>, <<"sha512">>],
default => <<"sha256">>
},
iteration_count => #{
type => integer,
default => 4096
}
}
},
@ -410,7 +914,7 @@ definitions() ->
properties => #{
enable => #{
type => boolean,
default => false
default => false
},
hostname => #{
type => string
@ -420,7 +924,22 @@ definitions() ->
}
},
ErrorDef = #{
type => object,
properties => #{
code => #{
type => string,
enum => [<<"NOT_FOUND">>],
example => <<"NOT_FOUND">>
},
message => #{
type => string
}
}
},
[ #{<<"authenticator">> => AuthenticatorDef}
, #{<<"returned_authenticator">> => ReturnedAuthenticatorDef}
, #{<<"password_based">> => PasswordBasedDef}
, #{<<"jwt">> => JWTDef}
, #{<<"scram">> => SCRAMDef}
@ -430,4 +949,163 @@ definitions() ->
, #{<<"password_based_http_server">> => PasswordBasedHTTPServerDef}
, #{<<"password_hash_algorithm">> => PasswordHashAlgorithmDef}
, #{<<"ssl">> => SSLDef}
, #{<<"error">> => ErrorDef}
].
authenticators(post, Request) ->
{ok, Body, _} = cowboy_req:read_body(Request),
AuthenticatorConfig = emqx_json:decode(Body, [return_maps]),
Config = #{<<"emqx_authn">> => #{
<<"authenticators">> => [AuthenticatorConfig]
}},
NConfig = hocon_schema:check_plain(emqx_authn_schema, Config,
#{nullable => true}),
#{emqx_authn := #{authenticators := [NAuthenticatorConfig]}} = emqx_map_lib:unsafe_atom_key_map(NConfig),
case emqx_authn:create_authenticator(?CHAIN, NAuthenticatorConfig) of
{ok, Authenticator2} ->
{201, Authenticator2};
{error, Reason} ->
serialize_error(Reason)
end;
authenticators(get, _Request) ->
{ok, Authenticators} = emqx_authn:list_authenticators(?CHAIN),
{200, Authenticators}.
authenticators2(get, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request),
case emqx_authn:lookup_authenticator(?CHAIN, AuthenticatorID) of
{ok, Authenticator} ->
{200, Authenticator};
{error, Reason} ->
serialize_error(Reason)
end;
authenticators2(put, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request),
{ok, Body, _} = cowboy_req:read_body(Request),
AuthenticatorConfig = emqx_json:decode(Body, [return_maps]),
Config = #{<<"emqx_authn">> => #{
<<"authenticators">> => [AuthenticatorConfig]
}},
NConfig = hocon_schema:check_plain(emqx_authn_schema, Config,
#{nullable => true}),
#{emqx_authn := #{authenticators := [NAuthenticatorConfig]}} = emqx_map_lib:unsafe_atom_key_map(NConfig),
case emqx_authn:update_or_create_authenticator(?CHAIN, AuthenticatorID, NAuthenticatorConfig) of
{ok, Authenticator} ->
{200, Authenticator};
{error, Reason} ->
serialize_error(Reason)
end;
authenticators2(delete, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request),
case emqx_authn:delete_authenticator(?CHAIN, AuthenticatorID) of
ok ->
{204};
{error, Reason} ->
serialize_error(Reason)
end.
position(post, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request),
{ok, Body, _} = cowboy_req:read_body(Request),
case emqx_json:decode(Body, [return_maps]) of
#{<<"position">> := Position} when is_integer(Position) ->
case emqx_authn:move_authenticator_to_the_nth(?CHAIN, AuthenticatorID, Position) of
ok ->
{204};
{error, Reason} ->
serialize_error(Reason)
end;
_ ->
serialize_error({missing_parameter, position})
end.
import_users(post, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request),
{ok, Body, _} = cowboy_req:read_body(Request),
case emqx_json:decode(Body, [return_maps]) of
#{<<"filename">> := Filename} when is_binary(Filename) ->
case emqx_authn:import_users(?CHAIN, AuthenticatorID, Filename) of
ok ->
{204};
{error, Reason} ->
serialize_error(Reason)
end;
_ ->
serialize_error({missing_parameter, filename})
end.
users(post, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request),
{ok, Body, _} = cowboy_req:read_body(Request),
case emqx_json:decode(Body, [return_maps]) of
#{<<"user_id">> := _,
<<"password">> := _} = UserInfo ->
case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserInfo) of
{ok, User} ->
{201, User};
{error, Reason} ->
serialize_error(Reason)
end;
_ ->
serialize_error({missing_parameter, user_id})
end;
users(get, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request),
case emqx_authn:list_users(?CHAIN, AuthenticatorID) of
{ok, Users} ->
{200, Users};
{error, Reason} ->
serialize_error(Reason)
end.
users2(patch, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request),
UserID = cowboy_req:binding(user_id, Request),
{ok, Body, _} = cowboy_req:read_body(Request),
case emqx_json:decode(Body, [return_maps]) of
#{<<"password">> := _} = UserInfo ->
case emqx_authn:add_user(?CHAIN, AuthenticatorID, UserID, UserInfo) of
{ok, User} ->
{200, User};
{error, Reason} ->
serialize_error(Reason)
end;
_ ->
serialize_error({missing_parameter, password})
end;
users2(get, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request),
UserID = cowboy_req:binding(user_id, Request),
case emqx_authn:lookup_user(?CHAIN, AuthenticatorID, UserID) of
{ok, User} ->
{200, User};
{error, Reason} ->
serialize_error(Reason)
end;
users2(delete, Request) ->
AuthenticatorID = cowboy_req:binding(id, Request),
UserID = cowboy_req:binding(user_id, Request),
case emqx_authn:delete_user(?CHAIN, AuthenticatorID, UserID) of
ok ->
{204};
{error, Reason} ->
serialize_error(Reason)
end.
serialize_error({not_found, {authenticator, ID}}) ->
{404, #{code => <<"NOT_FOUND">>,
message => list_to_binary(io_lib:format("Authenticator '~s' does not exist", [ID]))}};
serialize_error(name_has_be_used) ->
{409, #{code => <<"ALREADY_EXISTS">>,
message => <<"Name has be used">>}};
serialize_error(out_of_range) ->
{400, #{code => <<"OUT_OF_RANGE">>,
message => <<"Out of range">>}};
serialize_error({missing_parameter, Name}) ->
{400, #{code => <<"MISSING_PARAMETER">>,
message => list_to_binary(
io_lib:format("The input parameter '~p' that is mandatory for processing this request is not supplied", [Name])
)}};
serialize_error(_) ->
{400, #{code => <<"BAD_REQUEST">>,
message => <<"Todo">>}}.

View File

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

View File

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

View File

@ -26,8 +26,8 @@
, fields/1
]).
-export([ create/3
, update/4
-export([ create/1
, update/2
, authenticate/2
, destroy/1
]).
@ -71,7 +71,9 @@ mnesia(copy) ->
structs() -> [config].
fields(config) ->
[ {server_type, fun server_type/1}
[ {name, fun emqx_authn_schema:authenticator_name/1}
, {mechanism, {enum, [scram]}}
, {server_type, fun server_type/1}
, {algorithm, fun algorithm/1}
, {iteration_count, fun iteration_count/1}
].
@ -92,16 +94,18 @@ iteration_count(_) -> undefined.
%% APIs
%%------------------------------------------------------------------------------
create(ChainID, Authenticator, #{algorithm := Algorithm,
iteration_count := IterationCount}) ->
State = #{user_group => {ChainID, Authenticator},
create(#{ algorithm := Algorithm
, iteration_count := IterationCount
, '_unique' := Unique
}) ->
State = #{user_group => Unique,
algorithm => Algorithm,
iteration_count => IterationCount},
{ok, State}.
update(_ChainID, _Authenticator, _Config, _State) ->
{error, update_not_suppored}.
update(Config, #{user_group := Unique}) ->
create(Config#{'_unique' => Unique}).
authenticate(#{auth_method := AuthMethod,
auth_data := AuthData,
auth_cache := AuthCache}, State) ->

View File

@ -26,8 +26,8 @@
, validations/0
]).
-export([ create/3
, update/4
-export([ create/1
, update/2
, authenticate/2
, destroy/1
]).
@ -57,7 +57,9 @@ fields(post) ->
] ++ common_fields().
common_fields() ->
[ {server_type, {enum, ['http-server']}}
[ {name, fun emqx_authn_schema:authenticator_name/1}
, {mechanism, {enum, ['password-based']}}
, {server_type, {enum, ['http-server']}}
, {url, fun url/1}
, {form_data, fun form_data/1}
, {request_timeout, fun request_timeout/1}
@ -105,37 +107,41 @@ request_timeout(_) -> undefined.
%% APIs
%%------------------------------------------------------------------------------
create(ChainID, AuthenticatorName,
#{method := Method,
url := URL,
headers := Headers,
form_data := FormData,
request_timeout := RequestTimeout} = Config) ->
create(#{ method := Method
, url := URL
, headers := Headers
, form_data := FormData
, request_timeout := RequestTimeout
, '_unique' := Unique
} = Config) ->
#{path := Path,
query := Query} = URIMap = parse_url(URL),
State = #{method => Method,
path => Path,
base_query => cow_qs:parse_qs(list_to_binary(Query)),
headers => normalize_headers(Headers),
form_data => maps:to_list(FormData),
request_timeout => RequestTimeout},
ResourceID = <<ChainID/binary, "/", AuthenticatorName/binary>>,
case emqx_resource:create_local(ResourceID,
State = #{ method => Method
, path => Path
, base_query => cow_qs:parse_qs(list_to_binary(Query))
, headers => normalize_headers(Headers)
, form_data => maps:to_list(FormData)
, request_timeout => RequestTimeout
},
case emqx_resource:create_local(Unique,
emqx_connector_http,
Config#{base_url => maps:remove(query, URIMap),
pool_type => random}) of
{ok, _} ->
{ok, State#{resource_id => ResourceID}};
{ok, State#{resource_id => Unique}};
{error, already_created} ->
{ok, State#{resource_id => ResourceID}};
{ok, State#{resource_id => Unique}};
{error, Reason} ->
{error, Reason}
end.
update(_ChainID, _AuthenticatorName, Config, #{resource_id := ResourceID} = State) ->
case emqx_resource:update_local(ResourceID, emqx_connector_http, Config, []) of
{ok, _} -> {ok, State};
{error, Reason} -> {error, Reason}
update(Config, State) ->
case create(Config) of
{ok, NewState} ->
ok = destroy(State),
{ok, NewState};
{error, Reason} ->
{error, Reason}
end.
authenticate(#{auth_method := _}, _) ->

View File

@ -24,8 +24,8 @@
, fields/1
]).
-export([ create/3
, update/4
-export([ create/1
, update/2
, authenticate/2
, destroy/1
]).
@ -48,27 +48,24 @@ fields('hmac-based') ->
, {algorithm, {enum, ['hmac-based']}}
, {secret, fun secret/1}
, {secret_base64_encoded, fun secret_base64_encoded/1}
, {verify_claims, fun verify_claims/1}
];
] ++ common_fields();
fields('public-key') ->
[ {use_jwks, {enum, [false]}}
, {algorithm, {enum, ['public-key']}}
, {certificate, fun certificate/1}
, {verify_claims, fun verify_claims/1}
];
] ++ common_fields();
fields('jwks') ->
[ {use_jwks, {enum, [true]}}
, {endpoint, fun endpoint/1}
, {refresh_interval, fun refresh_interval/1}
, {verify_claims, fun verify_claims/1}
, {ssl, #{type => hoconsc:union(
[ hoconsc:ref(?MODULE, ssl_enable)
, hoconsc:ref(?MODULE, ssl_disable)
]),
default => #{<<"enable">> => false}}}
];
] ++ common_fields();
fields(ssl_enable) ->
[ {enable, #{type => true}}
@ -82,6 +79,12 @@ fields(ssl_enable) ->
fields(ssl_disable) ->
[ {enable, #{type => false}} ].
common_fields() ->
[ {name, fun emqx_authn_schema:authenticator_name/1}
, {mechanism, {enum, [jwt]}}
, {verify_claims, fun verify_claims/1}
].
secret(type) -> string();
secret(_) -> undefined.
@ -129,18 +132,18 @@ verify_claims(_) -> undefined.
%% APIs
%%------------------------------------------------------------------------------
create(_ChainID, _AuthenticatorName, Config) ->
create(Config).
create(#{verify_claims := VerifyClaims} = Config) ->
create2(Config#{verify_claims => handle_verify_claims(VerifyClaims)}).
update(_ChainID, _AuthenticatorName, #{use_jwks := false} = Config, #{jwk := Connector})
update(#{use_jwks := false} = Config, #{jwk := Connector})
when is_pid(Connector) ->
_ = emqx_authn_jwks_connector:stop(Connector),
create(Config);
update(_ChainID, _AuthenticatorName, #{use_jwks := false} = Config, _) ->
update(#{use_jwks := false} = Config, _) ->
create(Config);
update(_ChainID, _AuthenticatorName, #{use_jwks := true} = Config, #{jwk := Connector} = State)
update(#{use_jwks := true} = Config, #{jwk := Connector} = State)
when is_pid(Connector) ->
ok = emqx_authn_jwks_connector:update(Connector, Config),
case maps:get(verify_cliams, Config, undefined) of
@ -150,7 +153,7 @@ update(_ChainID, _AuthenticatorName, #{use_jwks := true} = Config, #{jwk := Conn
{ok, State#{verify_claims => handle_verify_claims(VerifyClaims)}}
end;
update(_ChainID, _AuthenticatorName, #{use_jwks := true} = Config, _) ->
update(#{use_jwks := true} = Config, _) ->
create(Config).
authenticate(#{auth_method := _}, _) ->
@ -181,9 +184,6 @@ destroy(_) ->
%% Internal functions
%%--------------------------------------------------------------------
create(#{verify_claims := VerifyClaims} = Config) ->
create2(Config#{verify_claims => handle_verify_claims(VerifyClaims)}).
create2(#{use_jwks := false,
algorithm := 'hmac-based',
secret := Secret0,

View File

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

View File

@ -21,10 +21,12 @@
-behaviour(hocon_schema).
-export([ structs/0, fields/1 ]).
-export([ structs/0
, fields/1
]).
-export([ create/3
, update/4
-export([ create/1
, update/2
, authenticate/2
, destroy/1
]).
@ -36,7 +38,9 @@
structs() -> [config].
fields(config) ->
[ {server_type, {enum, [mysql]}}
[ {name, fun emqx_authn_schema:authenticator_name/1}
, {mechanism, {enum, ['password-based']}}
, {server_type, {enum, [mysql]}}
, {password_hash_algorithm, fun password_hash_algorithm/1}
, {salt_position, fun salt_position/1}
, {query, fun query/1}
@ -70,34 +74,41 @@ query(nullable) -> false;
query(_) -> undefined.
query_timeout(type) -> integer();
query_timeout(defualt) -> 5000;
query_timeout(default) -> 5000;
query_timeout(_) -> undefined.
%%------------------------------------------------------------------------------
%% APIs
%%------------------------------------------------------------------------------
create(ChainID, AuthenticatorName,
#{query := Query0,
password_hash_algorithm := Algorithm} = Config) ->
create(#{ password_hash_algorithm := Algorithm
, salt_position := SaltPosition
, query := Query0
, query_timeout := QueryTimeout
, '_unique' := Unique
} = Config) ->
{Query, PlaceHolders} = parse_query(Query0),
ResourceID = iolist_to_binary(io_lib:format("~s/~s",[ChainID, AuthenticatorName])),
State = #{query => Query,
State = #{password_hash_algorithm => Algorithm,
salt_position => SaltPosition,
query => Query,
placeholders => PlaceHolders,
password_hash_algorithm => Algorithm},
case emqx_resource:create_local(ResourceID, emqx_connector_mysql, Config) of
query_timeout => QueryTimeout},
case emqx_resource:create_local(Unique, emqx_connector_mysql, Config) of
{ok, _} ->
{ok, State#{resource_id => ResourceID}};
{ok, State#{resource_id => Unique}};
{error, already_created} ->
{ok, State#{resource_id => ResourceID}};
{ok, State#{resource_id => Unique}};
{error, Reason} ->
{error, Reason}
end.
update(_ChainID, _AuthenticatorName, Config, #{resource_id := ResourceID} = State) ->
case emqx_resource:update_local(ResourceID, emqx_connector_mysql, Config, []) of
{ok, _} -> {ok, State};
{error, Reason} -> {error, Reason}
update(Config, State) ->
case create(Config) of
{ok, NewState} ->
ok = destroy(State),
{ok, NewState};
{error, Reason} ->
{error, Reason}
end.
authenticate(#{auth_method := _}, _) ->

View File

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