feat(authn http api): add test case and improve http api spec

This commit is contained in:
zhouzb 2021-07-27 09:54:08 +08:00
parent 327ff8636f
commit 7febcb852a
10 changed files with 361 additions and 284 deletions

View File

@ -1,11 +1,11 @@
emqx_authn: {
enable: false
authenticators: [
{
name: "authenticator1"
mechanism: password-based
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

@ -26,7 +26,6 @@
, provider :: module()
, config :: map()
, state :: map()
, version :: binary()
}).
-record(chain,

View File

@ -194,6 +194,10 @@ do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, C
false ->
case CreateWhenNotFound of
true ->
case lists:keymember(NewName, 2, Authenticators) of
true ->
{error, name_has_be_used};
false ->
case do_create_authenticator(ChainID, AuthenticatorID, Config) of
{ok, Authenticator} ->
NAuthenticators = Authenticators ++ [{AuthenticatorID, NewName, Authenticator}],
@ -201,13 +205,14 @@ do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, C
{ok, serialize_authenticator(Authenticator)};
{error, Reason} ->
{error, Reason}
end
end;
false ->
{error, {not_found, {authenticator, AuthenticatorID}}}
end;
{value,
{_, _, #authenticator{provider = Provider,
state = #{version := Version} = State}},
state = #{version := Version} = State} = Authenticator},
Others} ->
case lists:keymember(NewName, 2, Others) of
true ->
@ -215,10 +220,10 @@ do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, C
false ->
case (NewProvider = authenticator_provider(Config)) =:= Provider of
true ->
Unique = {ChainID, AuthenticatorID, Version},
Unique = <<ChainID/binary, "/", AuthenticatorID/binary, ":", Version/binary>>,
case Provider:update(Config#{'_unique' => Unique}, State) of
{ok, NewState} ->
NewAuthenticator = #authenticator{name = NewName,
NewAuthenticator = Authenticator#authenticator{name = NewName,
config = Config,
state = switch_version(NewState)},
NewAuthenticators = replace_authenticator(AuthenticatorID, NewAuthenticator, Authenticators),
@ -228,9 +233,10 @@ do_update_authenticator(ChainID, AuthenticatorID, #{name := NewName} = Config, C
{error, Reason}
end;
false ->
case NewProvider:create(Config#{'_unique' => {ChainID, AuthenticatorID, Version}}) of
Unique = <<ChainID/binary, "/", AuthenticatorID/binary, ":", Version/binary>>,
case NewProvider:create(Config#{'_unique' => Unique}) of
{ok, NewState} ->
NewAuthenticator = #authenticator{name = NewName,
NewAuthenticator = Authenticator#authenticator{name = NewName,
provider = NewProvider,
config = Config,
state = switch_version(NewState)},

View File

@ -29,6 +29,56 @@
, users2/2
]).
-define(EXAMPLE_1, #{name => <<"example 1">>,
mechanism => <<"password-based">>,
server_type => <<"built-in-example">>,
user_id_type => <<"username">>,
password_hash_algorithm => #{
name => <<"sha256">>
}}).
-define(EXAMPLE_2, #{name => <<"example 2">>,
mechanism => <<"password-based">>,
server_type => <<"http-server">>,
method => <<"post">>,
url => <<"http://localhost:80/login">>,
headers => #{
<<"content-type">> => <<"application/json">>
},
form_data => #{
<<"username">> => <<"${mqtt-username}">>,
<<"password">> => <<"${mqtt-password}">>
}}).
-define(EXAMPLE_3, #{name => <<"example 3">>,
mechanism => <<"jwt">>,
use_jwks => false,
algorithm => <<"hmac-based">>,
secret => <<"mysecret">>,
secret_base64_encoded => false,
verify_claims => #{
<<"username">> => <<"${mqtt-username}">>
}}).
-define(ERR_RESPONSE(Desc), #{description => Desc,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>),
examples => #{
example1 => #{
summary => <<"Not Found">>,
value => #{code => <<"NOT_FOUND">>, message => <<"Authenticator '67e4c9d3' does not exist">>}
},
example2 => #{
summary => <<"Conflict">>,
value => #{code => <<"ALREADY_EXISTS">>, message => <<"Name has be used">>}
},
example3 => #{
summary => <<"Bad Request 1">>,
value => #{code => <<"OUT_OF_RANGE">>, message => <<"Out of range">>}
}
}}}}).
api_spec() ->
{[ authenticators_api()
, authenticators_api2()
@ -39,40 +89,6 @@ api_spec() ->
], definitions()}.
authenticators_api() ->
Example1 = #{name => <<"example">>,
mechanism => <<"password-based">>,
config => #{
server_type => <<"built-in-example">>,
user_id_type => <<"username">>,
password_hash_algorithm => #{
name => <<"sha256">>
}
}},
Example2 = #{name => <<"example">>,
mechanism => <<"password-based">>,
config => #{
server_type => <<"http-server">>,
method => <<"post">>,
url => <<"http://localhost:80/login">>,
headers => #{
<<"content-type">> => <<"application/json">>
},
form_data => #{
<<"username">> => <<"${mqtt-username}">>,
<<"password">> => <<"${mqtt-password}">>
}
}},
Example3 = #{name => <<"example">>,
mechanism => <<"jwt">>,
config => #{
use_jwks => false,
algorithm => <<"hmac-based">>,
secret => <<"mysecret">>,
secret_base64_encoded => false,
verify_claims => #{
<<"username">> => <<"${mqtt-username}">>
}
}},
Metadata = #{
post => #{
description => "Create authenticator",
@ -83,15 +99,15 @@ authenticators_api() ->
examples => #{
default => #{
summary => <<"Default">>,
value => emqx_json:encode(Example1)
value => emqx_json:encode(?EXAMPLE_1)
},
http => #{
summary => <<"Authentication provided by HTTP Server">>,
value => emqx_json:encode(Example2)
value => emqx_json:encode(?EXAMPLE_2)
},
jwt => #{
summary => <<"JWT Authentication">>,
value => emqx_json:encode(Example3)
value => emqx_json:encode(?EXAMPLE_3)
}
}
}
@ -102,12 +118,28 @@ authenticators_api() ->
description => <<"Created">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"returned_authenticator">>)
schema => minirest:ref(<<"returned_authenticator">>),
examples => #{
example1 => #{
summary => <<"Example 1">>,
value => emqx_json:encode(maps:put(id, <<"example 1">>, ?EXAMPLE_1))
},
example2 => #{
summary => <<"Example 2">>,
value => emqx_json:encode(maps:put(id, <<"example 2">>, ?EXAMPLE_2))
},
example3 => #{
summary => <<"Example 3">>,
value => emqx_json:encode(maps:put(id, <<"example 3">>, ?EXAMPLE_3))
}
}
}
}
},
<<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
<<"409">> => ?ERR_RESPONSE(<<"Conflict">>)
}
},
get => #{
description => "List authenticators",
responses => #{
@ -118,6 +150,15 @@ authenticators_api() ->
schema => #{
type => array,
items => minirest:ref(<<"returned_authenticator">>)
},
examples => #{
example1 => #{
summary => <<"Example 1">>,
value => emqx_json:encode([ maps:put(id, <<"example 1">>, ?EXAMPLE_1)
, maps:put(id, <<"example 2">>, ?EXAMPLE_2)
, maps:put(id, <<"example 3">>, ?EXAMPLE_3)
])
}
}
}
}
@ -146,18 +187,25 @@ authenticators_api2() ->
description => <<"OK">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"returned_authenticator">>)
schema => minirest:ref(<<"returned_authenticator">>),
examples => #{
example1 => #{
summary => <<"Example 1">>,
value => emqx_json:encode(maps:put(id, <<"example 1">>, ?EXAMPLE_1))
},
example2 => #{
summary => <<"Example 2">>,
value => emqx_json:encode(maps:put(id, <<"example 2">>, ?EXAMPLE_2))
},
example3 => #{
summary => <<"Example 3">>,
value => emqx_json:encode(maps:put(id, <<"example 3">>, ?EXAMPLE_3))
}
}
}
}
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
<<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
}
},
put => #{
@ -180,6 +228,16 @@ authenticators_api2() ->
, minirest:ref(<<"jwt">>)
, minirest:ref(<<"scram">>)
]
},
examples => #{
example1 => #{
summary => <<"Example 1">>,
value => emqx_json:encode(?EXAMPLE_1)
},
example2 => #{
summary => <<"Example 2">>,
value => emqx_json:encode(?EXAMPLE_2)
}
}
}
}
@ -189,18 +247,27 @@ authenticators_api2() ->
description => <<"OK">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"returned_authenticator">>)
schema => minirest:ref(<<"returned_authenticator">>),
examples => #{
example1 => #{
summary => <<"Example 1">>,
value => emqx_json:encode(maps:put(id, <<"example 1">>, ?EXAMPLE_1))
},
example2 => #{
summary => <<"Example 2">>,
value => emqx_json:encode(maps:put(id, <<"example 2">>, ?EXAMPLE_2))
},
example3 => #{
summary => <<"Example 3">>,
value => emqx_json:encode(maps:put(id, <<"example 3">>, ?EXAMPLE_3))
}
}
}
}
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
<<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
<<"404">> => ?ERR_RESPONSE(<<"Not Found">>),
<<"409">> => ?ERR_RESPONSE(<<"Conflict">>)
}
},
delete => #{
@ -219,14 +286,7 @@ authenticators_api2() ->
<<"204">> => #{
description => <<"No Content">>
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
<<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
}
}
},
@ -266,14 +326,8 @@ position_api() ->
<<"204">> => #{
description => <<"No Content">>
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
<<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
<<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
}
}
},
@ -312,22 +366,8 @@ import_users_api() ->
<<"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">>)
}
}
}
<<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
<<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
}
}
},
@ -382,14 +422,8 @@ users_api() ->
}
}
},
<<"400">> => #{
description => <<"Bad Request">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
<<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
<<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
}
},
get => #{
@ -423,7 +457,8 @@ users_api() ->
}
}
}
}
},
<<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
}
}
},
@ -486,14 +521,8 @@ users2_api() ->
}
}
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
<<"400">> => ?ERR_RESPONSE(<<"Bad Request">>),
<<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
}
},
get => #{
@ -536,14 +565,7 @@ users2_api() ->
}
}
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
<<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
}
},
delete => #{
@ -570,14 +592,7 @@ users2_api() ->
<<"204">> => #{
description => <<"No Content">>
},
<<"404">> => #{
description => <<"Not Found">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"error">>)
}
}
}
<<"404">> => ?ERR_RESPONSE(<<"Not Found">>)
}
}
},
@ -1007,48 +1022,44 @@ authenticators2(delete, Request) ->
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) ->
NBody = emqx_json:decode(Body, [return_maps]),
Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"position">> => NBody},
#{nullable => true}, ["position"]),
#{position := #{position := Position}} = emqx_map_lib:unsafe_atom_key_map(Config),
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) ->
NBody = emqx_json:decode(Body, [return_maps]),
Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"filename">> => NBody},
#{nullable => true}, ["filename"]),
#{filename := #{filename := Filename}} = emqx_map_lib:unsafe_atom_key_map(Config),
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 ->
NBody = emqx_json:decode(Body, [return_maps]),
Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"user_info">> => NBody},
#{nullable => true}, ["user_info"]),
#{user_info := UserInfo} = emqx_map_lib:unsafe_atom_key_map(Config),
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
@ -1062,17 +1073,16 @@ 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
NBody = emqx_json:decode(Body, [return_maps]),
Config = hocon_schema:check_plain(emqx_authn_other_schema, #{<<"new_user_info">> => NBody},
#{nullable => true}, ["new_user_info"]),
#{new_user_info := NewUserInfo} = emqx_map_lib:unsafe_atom_key_map(Config),
case emqx_authn:update_user(?CHAIN, AuthenticatorID, UserID, NewUserInfo) 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),
@ -1106,6 +1116,6 @@ serialize_error({missing_parameter, Name}) ->
message => list_to_binary(
io_lib:format("The input parameter '~p' that is mandatory for processing this request is not supplied", [Name])
)}};
serialize_error(_) ->
serialize_error(Reason) ->
{400, #{code => <<"BAD_REQUEST">>,
message => <<"Todo">>}}.
message => list_to_binary(io_lib:format("Todo: ~p", [Reason]))}}.

View File

@ -133,8 +133,8 @@ destroy(#{user_group := UserGroup}) ->
end).
%% TODO: binary to atom
add_user(#{<<"user_id">> := UserID,
<<"password">> := Password}, #{user_group := UserGroup} = State) ->
add_user(#{user_id := UserID,
password := Password}, #{user_group := UserGroup} = State) ->
trans(
fun() ->
case mnesia:read(?TAB, {UserGroup, UserID}, write) of
@ -157,7 +157,7 @@ delete_user(UserID, #{user_group := UserGroup}) ->
end
end).
update_user(UserID, #{<<"password">> := Password},
update_user(UserID, #{password := Password},
#{user_group := UserGroup} = State) ->
trans(
fun() ->

View File

@ -178,8 +178,8 @@ import_users(Filename0, State) ->
{error, {unsupported_file_format, Extension}}
end.
add_user(#{<<"user_id">> := UserID,
<<"password">> := Password},
add_user(#{user_id := UserID,
password := Password},
#{user_group := UserGroup} = State) ->
trans(
fun() ->
@ -203,7 +203,7 @@ delete_user(UserID, #{user_group := UserGroup}) ->
end
end).
update_user(UserID, #{<<"password">> := Password},
update_user(UserID, #{password := Password},
#{user_group := UserGroup} = State) ->
trans(
fun() ->

View File

@ -0,0 +1,58 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_authn_other_schema).
-include("emqx_authn.hrl").
-include_lib("typerefl/include/types.hrl").
-behaviour(hocon_schema).
-export([ structs/0
, fields/1
]).
structs() -> [ "filename", "position", "user_info", "new_user_info"].
fields("filename") ->
[ {filename, fun filename/1} ];
fields("position") ->
[ {position, fun position/1} ];
fields("user_info") ->
[ {user_id, fun user_id/1}
, {password, fun password/1}
];
fields("new_user_info") ->
[ {password, fun password/1}
].
filename(type) -> string();
filename(nullable) -> false;
filename(_) -> undefined.
position(type) -> integer();
position(validate) -> [fun (Position) -> Position > 0 end];
position(nullable) -> false;
position(_) -> undefined.
user_id(type) -> binary();
user_id(nullable) -> false;
user_id(_) -> undefined.
password(type) -> binary();
password(nullable) -> false;
password(_) -> undefined.

View File

@ -54,34 +54,44 @@ t_authenticator(_) ->
AuthenticatorName1 = <<"myauthenticator1">>,
AuthenticatorConfig1 = #{name => AuthenticatorName1,
mechanism => 'password-based',
config => #{
server_type => 'built-in-database',
user_id_type => username,
password_hash_algorithm => #{
name => sha256
}}},
?assertEqual({ok, AuthenticatorConfig1}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)),
?assertEqual({ok, AuthenticatorConfig1}, ?AUTH:lookup_authenticator(?CHAIN, AuthenticatorName1)),
?assertEqual({ok, [AuthenticatorConfig1]}, ?AUTH:list_authenticators(?CHAIN)),
?assertEqual({error, {already_exists, {authenticator, AuthenticatorName1}}}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)),
}},
{ok, #{name := AuthenticatorName1, id := ID1}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1),
?assertMatch({ok, #{name := AuthenticatorName1}}, ?AUTH:lookup_authenticator(?CHAIN, ID1)),
?assertMatch({ok, [#{name := AuthenticatorName1}]}, ?AUTH:list_authenticators(?CHAIN)),
?assertEqual({error, name_has_be_used}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)),
AuthenticatorConfig2 = #{name => AuthenticatorName1,
mechanism => jwt,
use_jwks => false,
algorithm => 'hmac-based',
secret => <<"abcdef">>,
secret_base64_encoded => false,
verify_claims => []},
{ok, #{name := AuthenticatorName1, id := ID1, mechanism := jwt}} = ?AUTH:update_authenticator(?CHAIN, ID1, AuthenticatorConfig2),
ID2 = <<"random">>,
?assertEqual({error, {not_found, {authenticator, ID2}}}, ?AUTH:update_authenticator(?CHAIN, ID2, AuthenticatorConfig2)),
?assertEqual({error, name_has_be_used}, ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig2)),
AuthenticatorName2 = <<"myauthenticator2">>,
AuthenticatorConfig2 = AuthenticatorConfig1#{name => AuthenticatorName2},
?assertEqual({ok, AuthenticatorConfig2}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig2)),
?assertMatch({ok, #{id := ?CHAIN, authenticators := [AuthenticatorConfig1, AuthenticatorConfig2]}}, ?AUTH:lookup_chain(?CHAIN)),
?assertEqual({ok, AuthenticatorConfig2}, ?AUTH:lookup_authenticator(?CHAIN, AuthenticatorName2)),
?assertEqual({ok, [AuthenticatorConfig1, AuthenticatorConfig2]}, ?AUTH:list_authenticators(?CHAIN)),
AuthenticatorConfig3 = AuthenticatorConfig2#{name => AuthenticatorName2},
{ok, #{name := AuthenticatorName2, id := ID2, secret := <<"abcdef">>}} = ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig3),
?assertMatch({ok, #{name := AuthenticatorName2}}, ?AUTH:lookup_authenticator(?CHAIN, ID2)),
{ok, #{name := AuthenticatorName2, id := ID2, secret := <<"fedcba">>}} = ?AUTH:update_or_create_authenticator(?CHAIN, ID2, AuthenticatorConfig3#{secret := <<"fedcba">>}),
?assertEqual(ok, ?AUTH:move_authenticator_to_the_front(?CHAIN, AuthenticatorName2)),
?assertEqual({ok, [AuthenticatorConfig2, AuthenticatorConfig1]}, ?AUTH:list_authenticators(?CHAIN)),
?assertEqual(ok, ?AUTH:move_authenticator_to_the_end(?CHAIN, AuthenticatorName2)),
?assertEqual({ok, [AuthenticatorConfig1, AuthenticatorConfig2]}, ?AUTH:list_authenticators(?CHAIN)),
?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, AuthenticatorName2, 1)),
?assertEqual({ok, [AuthenticatorConfig2, AuthenticatorConfig1]}, ?AUTH:list_authenticators(?CHAIN)),
?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, AuthenticatorName2, 3)),
?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, AuthenticatorName2, 0)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName1)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName2)),
?assertMatch({ok, #{id := ?CHAIN, authenticators := [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}}, ?AUTH:lookup_chain(?CHAIN)),
?assertMatch({ok, [#{name := AuthenticatorName1}, #{name := AuthenticatorName2}]}, ?AUTH:list_authenticators(?CHAIN)),
?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 1)),
?assertMatch({ok, [#{name := AuthenticatorName2}, #{name := AuthenticatorName1}]}, ?AUTH:list_authenticators(?CHAIN)),
?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 3)),
?assertEqual({error, out_of_range}, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 0)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID2)),
?assertEqual({ok, []}, ?AUTH:list_authenticators(?CHAIN)),
ok.

View File

@ -39,15 +39,14 @@ end_per_suite(_) ->
t_jwt_authenticator(_) ->
AuthenticatorName = <<"myauthenticator">>,
Config = #{use_jwks => false,
Config = #{name => AuthenticatorName,
mechanism => jwt,
use_jwks => false,
algorithm => 'hmac-based',
secret => <<"abcdef">>,
secret_base64_encoded => false,
verify_claims => []},
AuthenticatorConfig = #{name => AuthenticatorName,
mechanism => jwt,
config => Config},
?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
{ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, Config),
Payload = #{<<"username">> => <<"myuser">>},
JWS = generate_jws('hmac-based', Payload, <<"abcdef">>),
@ -62,11 +61,11 @@ t_jwt_authenticator(_) ->
%% secret_base64_encoded
Config2 = Config#{secret => base64:encode(<<"abcdef">>),
secret_base64_encoded => true},
?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, AuthenticatorName, Config2)),
?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, ID, Config2)),
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)),
Config3 = Config#{verify_claims => [{<<"username">>, <<"${mqtt-username}">>}]},
?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, AuthenticatorName, Config3)),
?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, ID, Config3)),
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)),
?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo#{username => <<"otheruser">>}, ok)),
@ -109,7 +108,7 @@ t_jwt_authenticator(_) ->
ClientInfo8 = ClientInfo#{password => JWS8},
?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo8, ok)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)),
ok.
t_jwt_authenticator2(_) ->
@ -117,14 +116,13 @@ t_jwt_authenticator2(_) ->
PublicKey = list_to_binary(filename:join([Dir, "data/public_key.pem"])),
PrivateKey = list_to_binary(filename:join([Dir, "data/private_key.pem"])),
AuthenticatorName = <<"myauthenticator">>,
Config = #{use_jwks => false,
Config = #{name => AuthenticatorName,
mechanism => jwt,
use_jwks => false,
algorithm => 'public-key',
certificate => PublicKey,
verify_claims => []},
AuthenticatorConfig = #{name => AuthenticatorName,
mechanism => jwt,
config => Config},
?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
{ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, Config),
Payload = #{<<"username">> => <<"myuser">>},
JWS = generate_jws('public-key', Payload, PrivateKey),
@ -133,7 +131,7 @@ t_jwt_authenticator2(_) ->
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo, ok)),
?assertEqual({stop, {error, not_authorized}}, ?AUTH:authenticate(ClientInfo#{password => <<"badpassword">>}, ok)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)),
ok.
generate_jws('hmac-based', Payload, Secret) ->

View File

@ -41,18 +41,17 @@ t_mnesia_authenticator(_) ->
AuthenticatorName = <<"myauthenticator">>,
AuthenticatorConfig = #{name => AuthenticatorName,
mechanism => 'password-based',
config => #{
server_type => 'built-in-database',
user_id_type => username,
password_hash_algorithm => #{
name => sha256
}}},
?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
}},
{ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig),
UserInfo = #{<<"user_id">> => <<"myuser">>,
<<"password">> => <<"mypass">>},
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, AuthenticatorName, UserInfo)),
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, ID, UserInfo)),
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)),
ClientInfo = #{zone => external,
username => <<"myuser">>,
@ -70,39 +69,38 @@ t_mnesia_authenticator(_) ->
?assertEqual({error, bad_username_or_password}, emqx_access_control:authenticate(ClientInfo3)),
UserInfo2 = UserInfo#{<<"password">> => <<"mypass2">>},
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:update_user(?CHAIN, AuthenticatorName, <<"myuser">>, UserInfo2)),
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:update_user(?CHAIN, ID, <<"myuser">>, UserInfo2)),
ClientInfo4 = ClientInfo#{password => <<"mypass2">>},
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo4, ok)),
?assertEqual(ok, ?AUTH:delete_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
?assertEqual({error, not_found}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
?assertEqual(ok, ?AUTH:delete_user(?CHAIN, ID, <<"myuser">>)),
?assertEqual({error, not_found}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)),
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, AuthenticatorName, UserInfo)),
?assertMatch({ok, #{user_id := <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
?assertEqual({ok, #{user_id => <<"myuser">>}}, ?AUTH:add_user(?CHAIN, ID, UserInfo)),
?assertMatch({ok, #{user_id := <<"myuser">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser">>)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)),
?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
?assertMatch({error, not_found}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser">>)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
{ok, #{name := AuthenticatorName, id := ID1}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig),
?assertMatch({error, not_found}, ?AUTH:lookup_user(?CHAIN, ID1, <<"myuser">>)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)),
ok.
t_import(_) ->
AuthenticatorName = <<"myauthenticator">>,
AuthenticatorConfig = #{name => AuthenticatorName,
mechanism => 'password-based',
config => #{
server_type => 'built-in-database',
user_id_type => username,
password_hash_algorithm => #{
name => sha256
}}},
?assertEqual({ok, AuthenticatorConfig}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig)),
}},
{ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig),
Dir = code:lib_dir(emqx_authn, test),
?assertEqual(ok, ?AUTH:import_users(?CHAIN, AuthenticatorName, filename:join([Dir, "data/user-credentials.json"]))),
?assertEqual(ok, ?AUTH:import_users(?CHAIN, AuthenticatorName, filename:join([Dir, "data/user-credentials.csv"]))),
?assertMatch({ok, #{user_id := <<"myuser1">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser1">>)),
?assertMatch({ok, #{user_id := <<"myuser3">>}}, ?AUTH:lookup_user(?CHAIN, AuthenticatorName, <<"myuser3">>)),
?assertEqual(ok, ?AUTH:import_users(?CHAIN, ID, filename:join([Dir, "data/user-credentials.json"]))),
?assertEqual(ok, ?AUTH:import_users(?CHAIN, ID, filename:join([Dir, "data/user-credentials.csv"]))),
?assertMatch({ok, #{user_id := <<"myuser1">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser1">>)),
?assertMatch({ok, #{user_id := <<"myuser3">>}}, ?AUTH:lookup_user(?CHAIN, ID, <<"myuser3">>)),
ClientInfo1 = #{username => <<"myuser1">>,
password => <<"mypassword1">>},
@ -110,37 +108,35 @@ t_import(_) ->
ClientInfo2 = ClientInfo1#{username => <<"myuser3">>,
password => <<"mypassword3">>},
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo2, ok)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)),
ok.
t_multi_mnesia_authenticator(_) ->
AuthenticatorName1 = <<"myauthenticator1">>,
AuthenticatorConfig1 = #{name => AuthenticatorName1,
mechanism => 'password-based',
config => #{
server_type => 'built-in-database',
user_id_type => username,
password_hash_algorithm => #{
name => sha256
}}},
}},
AuthenticatorName2 = <<"myauthenticator2">>,
AuthenticatorConfig2 = #{name => AuthenticatorName2,
mechanism => 'password-based',
config => #{
server_type => 'built-in-database',
user_id_type => clientid,
password_hash_algorithm => #{
name => sha256
}}},
?assertEqual({ok, AuthenticatorConfig1}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1)),
?assertEqual({ok, AuthenticatorConfig2}, ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig2)),
}},
{ok, #{name := AuthenticatorName1, id := ID1}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig1),
{ok, #{name := AuthenticatorName2, id := ID2}} = ?AUTH:create_authenticator(?CHAIN, AuthenticatorConfig2),
?assertEqual({ok, #{user_id => <<"myuser">>}},
?AUTH:add_user(?CHAIN, AuthenticatorName1,
?AUTH:add_user(?CHAIN, ID1,
#{<<"user_id">> => <<"myuser">>,
<<"password">> => <<"mypass1">>})),
?assertEqual({ok, #{user_id => <<"myclient">>}},
?AUTH:add_user(?CHAIN, AuthenticatorName2,
?AUTH:add_user(?CHAIN, ID2,
#{<<"user_id">> => <<"myclient">>,
<<"password">> => <<"mypass2">>})),
@ -148,12 +144,12 @@ t_multi_mnesia_authenticator(_) ->
clientid => <<"myclient">>,
password => <<"mypass1">>},
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo1, ok)),
?assertEqual(ok, ?AUTH:move_authenticator_to_the_front(?CHAIN, AuthenticatorName2)),
?assertEqual(ok, ?AUTH:move_authenticator_to_the_nth(?CHAIN, ID2, 1)),
?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo1, ok)),
ClientInfo2 = ClientInfo1#{password => <<"mypass2">>},
?assertEqual({stop, ok}, ?AUTH:authenticate(ClientInfo2, ok)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName1)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, AuthenticatorName2)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID1)),
?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID2)),
ok.