style: erlfmt apps/emqx_authn

This commit is contained in:
JimMoen 2022-04-01 02:09:42 +08:00
parent 9ffc58071d
commit aae2d01582
27 changed files with 3509 additions and 2597 deletions

View File

@ -1,23 +1,32 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
{deps, {deps, [
[ {emqx, {path, "../emqx"}} {emqx, {path, "../emqx"}},
, {emqx_connector, {path, "../emqx_connector"}} {emqx_connector, {path, "../emqx_connector"}}
]}. ]}.
{edoc_opts, [{preprocess, true}]}. {edoc_opts, [{preprocess, true}]}.
{erl_opts, [warn_unused_vars, {erl_opts, [
warn_unused_vars,
warn_shadow_vars, warn_shadow_vars,
warnings_as_errors, warnings_as_errors,
warn_unused_import, warn_unused_import,
warn_obsolete_guard, warn_obsolete_guard,
debug_info, debug_info,
{parse_transform}]}. {parse_transform}
]}.
{xref_checks, [undefined_function_calls, undefined_functions, {xref_checks, [
locals_not_used, deprecated_function_calls, undefined_function_calls,
warnings_as_errors, deprecated_functions]}. undefined_functions,
locals_not_used,
deprecated_function_calls,
warnings_as_errors,
deprecated_functions
]}.
{cover_enabled, true}. {cover_enabled, true}.
{cover_opts, [verbose]}. {cover_opts, [verbose]}.
{cover_export_enabled, true}. {cover_export_enabled, true}.
{project_plugins, [erlfmt]}.

View File

@ -1,13 +1,13 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
{application, emqx_authn, {application, emqx_authn, [
[{description, "EMQX Authentication"}, {description, "EMQX Authentication"},
{vsn, "0.1.0"}, {vsn, "0.1.0"},
{modules, []}, {modules, []},
{registered, [emqx_authn_sup, emqx_authn_registry]}, {registered, [emqx_authn_sup, emqx_authn_registry]},
{applications, [kernel,stdlib,emqx_resource,ehttpc,epgsql,mysql,jose]}, {applications, [kernel, stdlib, emqx_resource, ehttpc, epgsql, mysql, jose]},
{mod, {emqx_authn_app,[]}}, {mod, {emqx_authn_app, []}},
{env, []}, {env, []},
{licenses, ["Apache-2.0"]}, {licenses, ["Apache-2.0"]},
{maintainers, ["EMQX Team <contact@emqx.io>"]}, {maintainers, ["EMQX Team <contact@emqx.io>"]},
{links, [{"Homepage", "https://emqx.io/"}]} {links, [{"Homepage", "https://emqx.io/"}]}
]}. ]}.

View File

@ -16,28 +16,31 @@
-module(emqx_authn). -module(emqx_authn).
-export([ providers/0 -export([
, check_config/1 providers/0,
, check_config/2 check_config/1,
, check_configs/1 check_config/2,
]). check_configs/1
]).
-include("emqx_authn.hrl"). -include("emqx_authn.hrl").
providers() -> providers() ->
[ {{'password_based', 'built_in_database'}, emqx_authn_mnesia} [
, {{'password_based', mysql}, emqx_authn_mysql} {{'password_based', 'built_in_database'}, emqx_authn_mnesia},
, {{'password_based', postgresql}, emqx_authn_pgsql} {{'password_based', mysql}, emqx_authn_mysql},
, {{'password_based', mongodb}, emqx_authn_mongodb} {{'password_based', postgresql}, emqx_authn_pgsql},
, {{'password_based', redis}, emqx_authn_redis} {{'password_based', mongodb}, emqx_authn_mongodb},
, {{'password_based', 'http'}, emqx_authn_http} {{'password_based', redis}, emqx_authn_redis},
, {jwt, emqx_authn_jwt} {{'password_based', 'http'}, emqx_authn_http},
, {{scram, 'built_in_database'}, emqx_enhanced_authn_scram_mnesia} {jwt, emqx_authn_jwt},
{{scram, 'built_in_database'}, emqx_enhanced_authn_scram_mnesia}
]. ].
check_configs(C) when is_map(C) -> check_configs(C) when is_map(C) ->
check_configs([C]); check_configs([C]);
check_configs([]) -> []; check_configs([]) ->
[];
check_configs([Config | Configs]) -> check_configs([Config | Configs]) ->
[check_config(Config) | check_configs(Configs)]. [check_config(Config) | check_configs(Configs)].
@ -51,7 +54,8 @@ check_config(Config, Opts) ->
end. end.
do_check_config(#{<<"mechanism">> := Mec} = Config, Opts) -> do_check_config(#{<<"mechanism">> := Mec} = Config, Opts) ->
Key = case maps:get(<<"backend">>, Config, false) of Key =
case maps:get(<<"backend">>, Config, false) of
false -> atom(Mec); false -> atom(Mec);
Backend -> {atom(Mec), atom(Backend)} Backend -> {atom(Mec), atom(Backend)}
end, end,
@ -59,14 +63,17 @@ do_check_config(#{<<"mechanism">> := Mec} = Config, Opts) ->
false -> false ->
throw({unknown_handler, Key}); throw({unknown_handler, Key});
{_, ProviderModule} -> {_, ProviderModule} ->
hocon_tconf:check_plain(ProviderModule, #{?CONF_NS_BINARY => Config}, hocon_tconf:check_plain(
Opts#{atom_key => true}) ProviderModule,
#{?CONF_NS_BINARY => Config},
Opts#{atom_key => true}
)
end. end.
atom(Bin) -> atom(Bin) ->
try try
binary_to_existing_atom(Bin, utf8) binary_to_existing_atom(Bin, utf8)
catch catch
_ : _ -> _:_ ->
throw({unknown_auth_provider, Bin}) throw({unknown_auth_provider, Bin})
end. end.

File diff suppressed because it is too large Load Diff

View File

@ -21,9 +21,10 @@
-behaviour(application). -behaviour(application).
%% Application callbacks %% Application callbacks
-export([ start/2 -export([
, stop/1 start/2,
]). stop/1
]).
-include_lib("emqx/include/emqx_authentication.hrl"). -include_lib("emqx/include/emqx_authentication.hrl").
@ -55,9 +56,11 @@ initialize() ->
AuthConfig = emqx_authn:check_configs(RawAuthConfigs), AuthConfig = emqx_authn:check_configs(RawAuthConfigs),
?AUTHN:initialize_authentication( ?AUTHN:initialize_authentication(
ChainName, ChainName,
AuthConfig) AuthConfig
)
end, end,
chain_configs()). chain_configs()
).
deinitialize() -> deinitialize() ->
ok = ?AUTHN:deregister_providers(provider_types()), ok = ?AUTHN:deregister_providers(provider_types()),
@ -74,12 +77,13 @@ listener_chain_configs() ->
fun({ListenerID, _}) -> fun({ListenerID, _}) ->
{ListenerID, emqx:get_raw_config(auth_config_path(ListenerID), [])} {ListenerID, emqx:get_raw_config(auth_config_path(ListenerID), [])}
end, end,
emqx_listeners:list()). emqx_listeners:list()
).
auth_config_path(ListenerID) -> auth_config_path(ListenerID) ->
[<<"listeners">>] [<<"listeners">>] ++
++ binary:split(atom_to_binary(ListenerID), <<":">>) binary:split(atom_to_binary(ListenerID), <<":">>) ++
++ [?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY]. [?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_BINARY].
provider_types() -> provider_types() ->
lists:map(fun({Type, _Module}) -> Type end, emqx_authn:providers()). lists:map(fun({Type, _Module}) -> Type end, emqx_authn:providers()).

View File

@ -18,21 +18,25 @@
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-type(simple_algorithm_name() :: plain | md5 | sha | sha256 | sha512). -type simple_algorithm_name() :: plain | md5 | sha | sha256 | sha512.
-type(salt_position() :: prefix | suffix). -type salt_position() :: prefix | suffix.
-type(simple_algorithm() :: #{name := simple_algorithm_name(), -type simple_algorithm() :: #{
salt_position := salt_position()}). name := simple_algorithm_name(),
salt_position := salt_position()
}.
-type(bcrypt_algorithm() :: #{name := bcrypt}). -type bcrypt_algorithm() :: #{name := bcrypt}.
-type(bcrypt_algorithm_rw() :: #{name := bcrypt, salt_rounds := integer()}). -type bcrypt_algorithm_rw() :: #{name := bcrypt, salt_rounds := integer()}.
-type(pbkdf2_algorithm() :: #{name := pbkdf2, -type pbkdf2_algorithm() :: #{
name := pbkdf2,
mac_fun := emqx_passwd:pbkdf2_mac_fun(), mac_fun := emqx_passwd:pbkdf2_mac_fun(),
iterations := pos_integer()}). iterations := pos_integer()
}.
-type(algorithm() :: simple_algorithm() | pbkdf2_algorithm() | bcrypt_algorithm()). -type algorithm() :: simple_algorithm() | pbkdf2_algorithm() | bcrypt_algorithm().
-type(algorithm_rw() :: simple_algorithm() | pbkdf2_algorithm() | bcrypt_algorithm_rw()). -type algorithm_rw() :: simple_algorithm() | pbkdf2_algorithm() | bcrypt_algorithm_rw().
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Hocon Schema %% Hocon Schema
@ -40,17 +44,23 @@
-behaviour(hocon_schema). -behaviour(hocon_schema).
-export([roots/0, -export([
roots/0,
fields/1, fields/1,
namespace/0]). namespace/0
]).
-export([type_ro/1, -export([
type_rw/1]). type_ro/1,
type_rw/1
]).
-export([init/1, -export([
init/1,
gen_salt/1, gen_salt/1,
hash/2, hash/2,
check_password/4]). check_password/4
]).
namespace() -> "authn-hash". namespace() -> "authn-hash".
roots() -> [pbkdf2, bcrypt, bcrypt_rw, other_algorithms]. roots() -> [pbkdf2, bcrypt, bcrypt_rw, other_algorithms].
@ -58,19 +68,20 @@ roots() -> [pbkdf2, bcrypt, bcrypt_rw, other_algorithms].
fields(bcrypt_rw) -> fields(bcrypt_rw) ->
fields(bcrypt) ++ fields(bcrypt) ++
[{salt_rounds, fun salt_rounds/1}]; [{salt_rounds, fun salt_rounds/1}];
fields(bcrypt) -> fields(bcrypt) ->
[{name, {enum, [bcrypt]}}]; [{name, {enum, [bcrypt]}}];
fields(pbkdf2) -> fields(pbkdf2) ->
[{name, {enum, [pbkdf2]}}, [
{name, {enum, [pbkdf2]}},
{mac_fun, {enum, [md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512]}}, {mac_fun, {enum, [md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512]}},
{iterations, integer()}, {iterations, integer()},
{dk_length, fun dk_length/1}]; {dk_length, fun dk_length/1}
];
fields(other_algorithms) -> fields(other_algorithms) ->
[{name, {enum, [plain, md5, sha, sha256, sha512]}}, [
{salt_position, fun salt_position/1}]. {name, {enum, [plain, md5, sha, sha256, sha512]}},
{salt_position, fun salt_position/1}
].
salt_position(type) -> {enum, [prefix, suffix]}; salt_position(type) -> {enum, [prefix, suffix]};
salt_position(desc) -> "Specifies whether the password salt is stored as a prefix or the suffix."; salt_position(desc) -> "Specifies whether the password salt is stored as a prefix or the suffix.";
@ -89,47 +100,56 @@ dk_length(_) -> undefined.
type_rw(type) -> type_rw(type) ->
hoconsc:union(rw_refs()); hoconsc:union(rw_refs());
type_rw(default) -> #{<<"name">> => sha256, <<"salt_position">> => prefix}; type_rw(default) ->
type_rw(_) -> undefined. #{<<"name">> => sha256, <<"salt_position">> => prefix};
type_rw(_) ->
undefined.
type_ro(type) -> type_ro(type) ->
hoconsc:union(ro_refs()); hoconsc:union(ro_refs());
type_ro(default) -> #{<<"name">> => sha256, <<"salt_position">> => prefix}; type_ro(default) ->
type_ro(_) -> undefined. #{<<"name">> => sha256, <<"salt_position">> => prefix};
type_ro(_) ->
undefined.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% APIs %% APIs
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
-spec(init(algorithm()) -> ok). -spec init(algorithm()) -> ok.
init(#{name := bcrypt}) -> init(#{name := bcrypt}) ->
{ok, _} = application:ensure_all_started(bcrypt), {ok, _} = application:ensure_all_started(bcrypt),
ok; ok;
init(#{name := _Other}) -> init(#{name := _Other}) ->
ok. ok.
-spec gen_salt(algorithm_rw()) -> emqx_passwd:salt().
-spec(gen_salt(algorithm_rw()) -> emqx_passwd:salt()).
gen_salt(#{name := plain}) -> gen_salt(#{name := plain}) ->
<<>>; <<>>;
gen_salt(#{name := bcrypt, gen_salt(#{
salt_rounds := Rounds}) -> name := bcrypt,
salt_rounds := Rounds
}) ->
{ok, Salt} = bcrypt:gen_salt(Rounds), {ok, Salt} = bcrypt:gen_salt(Rounds),
list_to_binary(Salt); list_to_binary(Salt);
gen_salt(#{name := Other}) when Other =/= plain, Other =/= bcrypt -> gen_salt(#{name := Other}) when Other =/= plain, Other =/= bcrypt ->
<<X:128/big-unsigned-integer>> = crypto:strong_rand_bytes(16), <<X:128/big-unsigned-integer>> = crypto:strong_rand_bytes(16),
iolist_to_binary(io_lib:format("~32.16.0b", [X])). iolist_to_binary(io_lib:format("~32.16.0b", [X])).
-spec hash(algorithm_rw(), emqx_passwd:password()) -> {emqx_passwd:hash(), emqx_passwd:salt()}.
-spec(hash(algorithm_rw(), emqx_passwd:password()) -> {emqx_passwd:hash(), emqx_passwd:salt()}).
hash(#{name := bcrypt, salt_rounds := _} = Algorithm, Password) -> hash(#{name := bcrypt, salt_rounds := _} = Algorithm, Password) ->
Salt0 = gen_salt(Algorithm), Salt0 = gen_salt(Algorithm),
Hash = emqx_passwd:hash({bcrypt, Salt0}, Password), Hash = emqx_passwd:hash({bcrypt, Salt0}, Password),
Salt = Hash, Salt = Hash,
{Hash, Salt}; {Hash, Salt};
hash(#{name := pbkdf2, hash(
#{
name := pbkdf2,
mac_fun := MacFun, mac_fun := MacFun,
iterations := Iterations} = Algorithm, Password) -> iterations := Iterations
} = Algorithm,
Password
) ->
Salt = gen_salt(Algorithm), Salt = gen_salt(Algorithm),
DKLength = maps:get(dk_length, Algorithm, undefined), DKLength = maps:get(dk_length, Algorithm, undefined),
Hash = emqx_passwd:hash({pbkdf2, MacFun, Salt, Iterations, DKLength}, Password), Hash = emqx_passwd:hash({pbkdf2, MacFun, Salt, Iterations, DKLength}, Password),
@ -139,18 +159,24 @@ hash(#{name := Other, salt_position := SaltPosition} = Algorithm, Password) ->
Hash = emqx_passwd:hash({Other, Salt, SaltPosition}, Password), Hash = emqx_passwd:hash({Other, Salt, SaltPosition}, Password),
{Hash, Salt}. {Hash, Salt}.
-spec check_password(
-spec(check_password(
algorithm(), algorithm(),
emqx_passwd:salt(), emqx_passwd:salt(),
emqx_passwd:hash(), emqx_passwd:hash(),
emqx_passwd:password()) -> boolean()). emqx_passwd:password()
) -> boolean().
check_password(#{name := bcrypt}, _Salt, PasswordHash, Password) -> check_password(#{name := bcrypt}, _Salt, PasswordHash, Password) ->
emqx_passwd:check_pass({bcrypt, PasswordHash}, PasswordHash, Password); emqx_passwd:check_pass({bcrypt, PasswordHash}, PasswordHash, Password);
check_password(#{name := pbkdf2, check_password(
#{
name := pbkdf2,
mac_fun := MacFun, mac_fun := MacFun,
iterations := Iterations} = Algorithm, iterations := Iterations
Salt, PasswordHash, Password) -> } = Algorithm,
Salt,
PasswordHash,
Password
) ->
DKLength = maps:get(dk_length, Algorithm, undefined), DKLength = maps:get(dk_length, Algorithm, undefined),
emqx_passwd:check_pass({pbkdf2, MacFun, Salt, Iterations, DKLength}, PasswordHash, Password); emqx_passwd:check_pass({pbkdf2, MacFun, Salt, Iterations, DKLength}, PasswordHash, Password);
check_password(#{name := Other, salt_position := SaltPosition}, Salt, PasswordHash, Password) -> check_password(#{name := Other, salt_position := SaltPosition}, Salt, PasswordHash, Password) ->
@ -161,11 +187,15 @@ check_password(#{name := Other, salt_position := SaltPosition}, Salt, PasswordHa
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
rw_refs() -> rw_refs() ->
[hoconsc:ref(?MODULE, bcrypt_rw), [
hoconsc:ref(?MODULE, bcrypt_rw),
hoconsc:ref(?MODULE, pbkdf2), hoconsc:ref(?MODULE, pbkdf2),
hoconsc:ref(?MODULE, other_algorithms)]. hoconsc:ref(?MODULE, other_algorithms)
].
ro_refs() -> ro_refs() ->
[hoconsc:ref(?MODULE, bcrypt), [
hoconsc:ref(?MODULE, bcrypt),
hoconsc:ref(?MODULE, pbkdf2), hoconsc:ref(?MODULE, pbkdf2),
hoconsc:ref(?MODULE, other_algorithms)]. hoconsc:ref(?MODULE, other_algorithms)
].

View File

@ -20,21 +20,20 @@
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-import(hoconsc, [mk/2, ref/2]). -import(hoconsc, [mk/2, ref/2]).
-export([ common_fields/0 -export([
, roots/0 common_fields/0,
, fields/1 roots/0,
, authenticator_type/0 fields/1,
, root_type/0 authenticator_type/0,
, mechanism/1 root_type/0,
, backend/1 mechanism/1,
]). backend/1
]).
roots() -> []. roots() -> [].
common_fields() -> common_fields() ->
[ {enable, fun enable/1} [{enable, fun enable/1}].
].
enable(type) -> boolean(); enable(type) -> boolean();
enable(default) -> true; enable(default) -> true;
@ -54,46 +53,60 @@ root_type() ->
hoconsc:array(authenticator_type()). hoconsc:array(authenticator_type()).
mechanism(Name) -> mechanism(Name) ->
hoconsc:mk(hoconsc:enum([Name]), hoconsc:mk(
#{ required => true hoconsc:enum([Name]),
, desc => "Authentication mechanism." #{
}). required => true,
desc => "Authentication mechanism."
}
).
backend(Name) -> backend(Name) ->
hoconsc:mk(hoconsc:enum([Name]), hoconsc:mk(
#{ required => true hoconsc:enum([Name]),
, desc => "Backend type." #{
}). required => true,
desc => "Backend type."
}
).
fields("metrics_status_fields") -> fields("metrics_status_fields") ->
[ {"metrics", mk(ref(?MODULE, "metrics"), #{desc => "The metrics of the resource"})} [
, {"node_metrics", mk(hoconsc:array(ref(?MODULE, "node_metrics")), {"metrics", mk(ref(?MODULE, "metrics"), #{desc => "The metrics of the resource"})},
#{ desc => "The metrics of the resource for each node" {"node_metrics",
})} mk(
, {"status", mk(status(), #{desc => "The status of the resource"})} hoconsc:array(ref(?MODULE, "node_metrics")),
, {"node_status", mk(hoconsc:array(ref(?MODULE, "node_status")), #{desc => "The metrics of the resource for each node"}
#{ desc => "The status of the resource for each node" )},
})} {"status", mk(status(), #{desc => "The status of the resource"})},
{"node_status",
mk(
hoconsc:array(ref(?MODULE, "node_status")),
#{desc => "The status of the resource for each node"}
)}
]; ];
fields("metrics") -> fields("metrics") ->
[ {"matched", mk(integer(), #{desc => "Count of this resource is queried"})} [
, {"success", mk(integer(), #{desc => "Count of query success"})} {"matched", mk(integer(), #{desc => "Count of this resource is queried"})},
, {"failed", mk(integer(), #{desc => "Count of query failed"})} {"success", mk(integer(), #{desc => "Count of query success"})},
, {"rate", mk(float(), #{desc => "The rate of matched, times/second"})} {"failed", mk(integer(), #{desc => "Count of query failed"})},
, {"rate_max", mk(float(), #{desc => "The max rate of matched, times/second"})} {"rate", mk(float(), #{desc => "The rate of matched, times/second"})},
, {"rate_last5m", mk(float(), {"rate_max", mk(float(), #{desc => "The max rate of matched, times/second"})},
#{desc => "The average rate of matched in the last 5 minutes, times/second"})} {"rate_last5m",
mk(
float(),
#{desc => "The average rate of matched in the last 5 minutes, times/second"}
)}
]; ];
fields("node_metrics") -> fields("node_metrics") ->
[ node_name() [
, {"metrics", mk(ref(?MODULE, "metrics"), #{})} node_name(),
{"metrics", mk(ref(?MODULE, "metrics"), #{})}
]; ];
fields("node_status") -> fields("node_status") ->
[ node_name() [
, {"status", mk(status(), #{desc => "Status of the node."})} node_name(),
{"status", mk(status(), #{desc => "Status of the node."})}
]. ].
status() -> status() ->

View File

@ -18,9 +18,10 @@
-behaviour(supervisor). -behaviour(supervisor).
-export([ start_link/0 -export([
, init/1 start_link/0,
]). init/1
]).
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).

View File

@ -19,26 +19,29 @@
-include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl").
-include_lib("emqx_authn.hrl"). -include_lib("emqx_authn.hrl").
-export([ check_password_from_selected_map/3 -export([
, parse_deep/1 check_password_from_selected_map/3,
, parse_str/1 parse_deep/1,
, parse_sql/2 parse_str/1,
, render_deep/2 parse_sql/2,
, render_str/2 render_deep/2,
, render_sql_params/2 render_str/2,
, is_superuser/1 render_sql_params/2,
, bin/1 is_superuser/1,
, ensure_apps_started/1 bin/1,
, cleanup_resources/0 ensure_apps_started/1,
, make_resource_id/1 cleanup_resources/0,
]). make_resource_id/1
]).
-define(AUTHN_PLACEHOLDERS, [?PH_USERNAME, -define(AUTHN_PLACEHOLDERS, [
?PH_USERNAME,
?PH_CLIENTID, ?PH_CLIENTID,
?PH_PASSWORD, ?PH_PASSWORD,
?PH_PEERHOST, ?PH_PEERHOST,
?PH_CERT_SUBJECT, ?PH_CERT_SUBJECT,
?PH_CERT_CN_NAME]). ?PH_CERT_CN_NAME
]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% APIs %% APIs
@ -47,12 +50,12 @@
check_password_from_selected_map(_Algorithm, _Selected, undefined) -> check_password_from_selected_map(_Algorithm, _Selected, undefined) ->
{error, bad_username_or_password}; {error, bad_username_or_password};
check_password_from_selected_map( check_password_from_selected_map(
Algorithm, #{<<"password_hash">> := Hash} = Selected, Password) -> Algorithm, #{<<"password_hash">> := Hash} = Selected, Password
) ->
Salt = maps:get(<<"salt">>, Selected, <<>>), Salt = maps:get(<<"salt">>, Selected, <<>>),
case emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, Password) of case emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, Password) of
true -> ok; true -> ok;
false -> false -> {error, bad_username_or_password}
{error, bad_username_or_password}
end. end.
parse_deep(Template) -> parse_deep(Template) ->
@ -64,26 +67,32 @@ parse_str(Template) ->
parse_sql(Template, ReplaceWith) -> parse_sql(Template, ReplaceWith) ->
emqx_placeholder:preproc_sql( emqx_placeholder:preproc_sql(
Template, Template,
#{replace_with => ReplaceWith, #{
placeholders => ?AUTHN_PLACEHOLDERS}). replace_with => ReplaceWith,
placeholders => ?AUTHN_PLACEHOLDERS
}
).
render_deep(Template, Credential) -> render_deep(Template, Credential) ->
emqx_placeholder:proc_tmpl_deep( emqx_placeholder:proc_tmpl_deep(
Template, Template,
Credential, Credential,
#{return => full_binary, var_trans => fun handle_var/2}). #{return => full_binary, var_trans => fun handle_var/2}
).
render_str(Template, Credential) -> render_str(Template, Credential) ->
emqx_placeholder:proc_tmpl( emqx_placeholder:proc_tmpl(
Template, Template,
Credential, Credential,
#{return => full_binary, var_trans => fun handle_var/2}). #{return => full_binary, var_trans => fun handle_var/2}
).
render_sql_params(ParamList, Credential) -> render_sql_params(ParamList, Credential) ->
emqx_placeholder:proc_tmpl( emqx_placeholder:proc_tmpl(
ParamList, ParamList,
Credential, Credential,
#{return => rawlist, var_trans => fun handle_sql_var/2}). #{return => rawlist, var_trans => fun handle_sql_var/2}
).
is_superuser(#{<<"is_superuser">> := <<"">>}) -> is_superuser(#{<<"is_superuser">> := <<"">>}) ->
#{is_superuser => false}; #{is_superuser => false};
@ -115,7 +124,8 @@ bin(X) -> X.
cleanup_resources() -> cleanup_resources() ->
lists:foreach( lists:foreach(
fun emqx_resource:remove_local/1, fun emqx_resource:remove_local/1,
emqx_resource:list_group_instances(?RESOURCE_GROUP)). emqx_resource:list_group_instances(?RESOURCE_GROUP)
).
make_resource_id(Name) -> make_resource_id(Name) ->
NameBin = bin(Name), NameBin = bin(Name),

View File

@ -26,12 +26,12 @@
-define(TCP_DEFAULT, 'tcp:default'). -define(TCP_DEFAULT, 'tcp:default').
-define( -define(assertAuthenticatorsMatch(Guard, Path),
assertAuthenticatorsMatch(Guard, Path),
(fun() -> (fun() ->
{ok, 200, Response} = request(get, uri(Path)), {ok, 200, Response} = request(get, uri(Path)),
?assertMatch(Guard, jiffy:decode(Response, [return_maps])) ?assertMatch(Guard, jiffy:decode(Response, [return_maps]))
end)()). end)()
).
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -43,11 +43,13 @@ init_per_testcase(_, Config) ->
{ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[?CONF_NS_ATOM], [?CONF_NS_ATOM],
?GLOBAL), ?GLOBAL
),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[listeners, tcp, default, ?CONF_NS_ATOM], [listeners, tcp, default, ?CONF_NS_ATOM],
?TCP_DEFAULT), ?TCP_DEFAULT
),
{atomic, ok} = mria:clear_table(emqx_authn_mnesia), {atomic, ok} = mria:clear_table(emqx_authn_mnesia),
Config. Config.
@ -56,7 +58,8 @@ init_per_suite(Config) ->
_ = application:load(emqx_conf), _ = application:load(emqx_conf),
ok = emqx_common_test_helpers:start_apps( ok = emqx_common_test_helpers:start_apps(
[emqx_authn, emqx_dashboard], [emqx_authn, emqx_dashboard],
fun set_special_configs/1), fun set_special_configs/1
),
?AUTHN:delete_chain(?GLOBAL), ?AUTHN:delete_chain(?GLOBAL),
{ok, Chains} = ?AUTHN:list_chains(), {ok, Chains} = ?AUTHN:list_chains(),
@ -117,108 +120,132 @@ t_listener_authenticator_import_users(_) ->
test_authenticator_import_users(["listeners", ?TCP_DEFAULT]). test_authenticator_import_users(["listeners", ?TCP_DEFAULT]).
test_authenticators(PathPrefix) -> test_authenticators(PathPrefix) ->
ValidConfig = emqx_authn_test_lib:http_example(), ValidConfig = emqx_authn_test_lib:http_example(),
{ok, 200, _} = request( {ok, 200, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS]), uri(PathPrefix ++ [?CONF_NS]),
ValidConfig), ValidConfig
),
{ok, 409, _} = request( {ok, 409, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS]), uri(PathPrefix ++ [?CONF_NS]),
ValidConfig), ValidConfig
),
InvalidConfig0 = ValidConfig#{method => <<"delete">>}, InvalidConfig0 = ValidConfig#{method => <<"delete">>},
{ok, 400, _} = request( {ok, 400, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS]), uri(PathPrefix ++ [?CONF_NS]),
InvalidConfig0), InvalidConfig0
),
InvalidConfig1 = ValidConfig#{method => <<"get">>, InvalidConfig1 = ValidConfig#{
headers => #{<<"content-type">> => <<"application/json">>}}, method => <<"get">>,
headers => #{<<"content-type">> => <<"application/json">>}
},
{ok, 400, _} = request( {ok, 400, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS]), uri(PathPrefix ++ [?CONF_NS]),
InvalidConfig1), InvalidConfig1
),
?assertAuthenticatorsMatch( ?assertAuthenticatorsMatch(
[#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}], [#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}],
PathPrefix ++ [?CONF_NS]). PathPrefix ++ [?CONF_NS]
).
test_authenticator(PathPrefix) -> test_authenticator(PathPrefix) ->
ValidConfig0 = emqx_authn_test_lib:http_example(), ValidConfig0 = emqx_authn_test_lib:http_example(),
{ok, 200, _} = request( {ok, 200, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS]), uri(PathPrefix ++ [?CONF_NS]),
ValidConfig0), ValidConfig0
),
{ok, 200, _} = request( {ok, 200, _} = request(
get, get,
uri(PathPrefix ++ [?CONF_NS, "password_based:http"])), uri(PathPrefix ++ [?CONF_NS, "password_based:http"])
),
{ok, 200, Res} = request( {ok, 200, Res} = request(
get, get,
uri(PathPrefix ++ [?CONF_NS, "password_based:http", "status"])), uri(PathPrefix ++ [?CONF_NS, "password_based:http", "status"])
),
{ok, RList} = emqx_json:safe_decode(Res), {ok, RList} = emqx_json:safe_decode(Res),
Snd = fun ({_, Val}) -> Val end, Snd = fun({_, Val}) -> Val end,
LookupVal = fun LookupV(List, RestJson) -> LookupVal = fun LookupV(List, RestJson) ->
case List of case List of
[Name] -> Snd(lists:keyfind(Name, 1, RestJson)); [Name] -> Snd(lists:keyfind(Name, 1, RestJson));
[Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson))) [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson)))
end end
end, end,
LookFun = fun (List) -> LookupVal(List, RList) end, LookFun = fun(List) -> LookupVal(List, RList) end,
MetricsList = [{<<"failed">>, 0}, MetricsList = [
{<<"failed">>, 0},
{<<"matched">>, 0}, {<<"matched">>, 0},
{<<"rate">>, 0.0}, {<<"rate">>, 0.0},
{<<"rate_last5m">>, 0.0}, {<<"rate_last5m">>, 0.0},
{<<"rate_max">>, 0.0}, {<<"rate_max">>, 0.0},
{<<"success">>, 0}], {<<"success">>, 0}
EqualFun = fun ({M, V}) -> ],
?assertEqual(V, LookFun([<<"metrics">>, EqualFun = fun({M, V}) ->
M] ?assertEqual(
V,
LookFun([
<<"metrics">>,
M
])
) )
) end, end,
lists:map(EqualFun, MetricsList), lists:map(EqualFun, MetricsList),
?assertEqual(<<"connected">>, ?assertEqual(
LookFun([<<"status">> <<"connected">>,
])), LookFun([<<"status">>])
),
{ok, 404, _} = request( {ok, 404, _} = request(
get, get,
uri(PathPrefix ++ [?CONF_NS, "password_based:redis"])), uri(PathPrefix ++ [?CONF_NS, "password_based:redis"])
),
{ok, 404, _} = request( {ok, 404, _} = request(
put, put,
uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database"]), uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database"]),
emqx_authn_test_lib:built_in_database_example()), emqx_authn_test_lib:built_in_database_example()
),
InvalidConfig0 = ValidConfig0#{method => <<"delete">>}, InvalidConfig0 = ValidConfig0#{method => <<"delete">>},
{ok, 400, _} = request( {ok, 400, _} = request(
put, put,
uri(PathPrefix ++ [?CONF_NS, "password_based:http"]), uri(PathPrefix ++ [?CONF_NS, "password_based:http"]),
InvalidConfig0), InvalidConfig0
),
InvalidConfig1 = ValidConfig0#{method => <<"get">>, InvalidConfig1 = ValidConfig0#{
headers => #{<<"content-type">> => <<"application/json">>}}, method => <<"get">>,
headers => #{<<"content-type">> => <<"application/json">>}
},
{ok, 400, _} = request( {ok, 400, _} = request(
put, put,
uri(PathPrefix ++ [?CONF_NS, "password_based:http"]), uri(PathPrefix ++ [?CONF_NS, "password_based:http"]),
InvalidConfig1), InvalidConfig1
),
ValidConfig1 = ValidConfig0#{pool_size => 9}, ValidConfig1 = ValidConfig0#{pool_size => 9},
{ok, 200, _} = request( {ok, 200, _} = request(
put, put,
uri(PathPrefix ++ [?CONF_NS, "password_based:http"]), uri(PathPrefix ++ [?CONF_NS, "password_based:http"]),
ValidConfig1), ValidConfig1
),
{ok, 404, _} = request( {ok, 404, _} = request(
delete, delete,
uri(PathPrefix ++ [?CONF_NS, "password_based:redis"])), uri(PathPrefix ++ [?CONF_NS, "password_based:redis"])
),
{ok, 204, _} = request( {ok, 204, _} = request(
delete, delete,
uri(PathPrefix ++ [?CONF_NS, "password_based:http"])), uri(PathPrefix ++ [?CONF_NS, "password_based:http"])
),
?assertAuthenticatorsMatch([], PathPrefix ++ [?CONF_NS]). ?assertAuthenticatorsMatch([], PathPrefix ++ [?CONF_NS]).
@ -228,22 +255,25 @@ test_authenticator_users(PathPrefix) ->
{ok, 200, _} = request( {ok, 200, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS]), uri(PathPrefix ++ [?CONF_NS]),
emqx_authn_test_lib:built_in_database_example()), emqx_authn_test_lib:built_in_database_example()
),
InvalidUsers = [ InvalidUsers = [
#{clientid => <<"u1">>, password => <<"p1">>}, #{clientid => <<"u1">>, password => <<"p1">>},
#{user_id => <<"u2">>}, #{user_id => <<"u2">>},
#{user_id => <<"u3">>, password => <<"p3">>, foobar => <<"foobar">>}], #{user_id => <<"u3">>, password => <<"p3">>, foobar => <<"foobar">>}
],
lists:foreach( lists:foreach(
fun(User) -> {ok, 400, _} = request(post, UsersUri, User) end, fun(User) -> {ok, 400, _} = request(post, UsersUri, User) end,
InvalidUsers), InvalidUsers
),
ValidUsers = [ ValidUsers = [
#{user_id => <<"u1">>, password => <<"p1">>}, #{user_id => <<"u1">>, password => <<"p1">>},
#{user_id => <<"u2">>, password => <<"p2">>, is_superuser => true}, #{user_id => <<"u2">>, password => <<"p2">>, is_superuser => true},
#{user_id => <<"u3">>, password => <<"p3">>}], #{user_id => <<"u3">>, password => <<"p3">>}
],
lists:foreach( lists:foreach(
fun(User) -> fun(User) ->
@ -251,31 +281,41 @@ test_authenticator_users(PathPrefix) ->
CreatedUser = jiffy:decode(UserData, [return_maps]), CreatedUser = jiffy:decode(UserData, [return_maps]),
?assertMatch(#{<<"user_id">> := _}, CreatedUser) ?assertMatch(#{<<"user_id">> := _}, CreatedUser)
end, end,
ValidUsers), ValidUsers
),
{ok, 200, Page1Data} = request(get, UsersUri ++ "?page=1&limit=2"), {ok, 200, Page1Data} = request(get, UsersUri ++ "?page=1&limit=2"),
#{<<"data">> := Page1Users, #{
<<"data">> := Page1Users,
<<"meta">> := <<"meta">> :=
#{<<"page">> := 1, #{
<<"page">> := 1,
<<"limit">> := 2, <<"limit">> := 2,
<<"count">> := 3}} = <<"count">> := 3
}
} =
jiffy:decode(Page1Data, [return_maps]), jiffy:decode(Page1Data, [return_maps]),
{ok, 200, Page2Data} = request(get, UsersUri ++ "?page=2&limit=2"), {ok, 200, Page2Data} = request(get, UsersUri ++ "?page=2&limit=2"),
#{<<"data">> := Page2Users, #{
<<"data">> := Page2Users,
<<"meta">> := <<"meta">> :=
#{<<"page">> := 2, #{
<<"page">> := 2,
<<"limit">> := 2, <<"limit">> := 2,
<<"count">> := 3}} = jiffy:decode(Page2Data, [return_maps]), <<"count">> := 3
}
} = jiffy:decode(Page2Data, [return_maps]),
?assertEqual(2, length(Page1Users)), ?assertEqual(2, length(Page1Users)),
?assertEqual(1, length(Page2Users)), ?assertEqual(1, length(Page2Users)),
?assertEqual( ?assertEqual(
[<<"u1">>, <<"u2">>, <<"u3">>], [<<"u1">>, <<"u2">>, <<"u3">>],
lists:usort([ UserId || #{<<"user_id">> := UserId} <- Page1Users ++ Page2Users])). lists:usort([UserId || #{<<"user_id">> := UserId} <- Page1Users ++ Page2Users])
).
test_authenticator_user(PathPrefix) -> test_authenticator_user(PathPrefix) ->
UsersUri = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "users"]), UsersUri = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "users"]),
@ -283,7 +323,8 @@ test_authenticator_user(PathPrefix) ->
{ok, 200, _} = request( {ok, 200, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS]), uri(PathPrefix ++ [?CONF_NS]),
emqx_authn_test_lib:built_in_database_example()), emqx_authn_test_lib:built_in_database_example()
),
User = #{user_id => <<"u1">>, password => <<"p1">>}, User = #{user_id => <<"u1">>, password => <<"p1">>},
{ok, 201, _} = request(post, UsersUri, User), {ok, 201, _} = request(post, UsersUri, User),
@ -300,17 +341,20 @@ test_authenticator_user(PathPrefix) ->
ValidUserUpdates = [ ValidUserUpdates = [
#{password => <<"p1">>}, #{password => <<"p1">>},
#{password => <<"p1">>, is_superuser => true}], #{password => <<"p1">>, is_superuser => true}
],
lists:foreach( lists:foreach(
fun(UserUpdate) -> {ok, 200, _} = request(put, UsersUri ++ "/u1", UserUpdate) end, fun(UserUpdate) -> {ok, 200, _} = request(put, UsersUri ++ "/u1", UserUpdate) end,
ValidUserUpdates), ValidUserUpdates
),
InvalidUserUpdates = [#{user_id => <<"u1">>, password => <<"p1">>}], InvalidUserUpdates = [#{user_id => <<"u1">>, password => <<"p1">>}],
lists:foreach( lists:foreach(
fun(UserUpdate) -> {ok, 400, _} = request(put, UsersUri ++ "/u1", UserUpdate) end, fun(UserUpdate) -> {ok, 400, _} = request(put, UsersUri ++ "/u1", UserUpdate) end,
InvalidUserUpdates), InvalidUserUpdates
),
{ok, 404, _} = request(delete, UsersUri ++ "/u123"), {ok, 404, _} = request(delete, UsersUri ++ "/u123"),
{ok, 204, _} = request(delete, UsersUri ++ "/u1"). {ok, 204, _} = request(delete, UsersUri ++ "/u1").
@ -327,9 +371,11 @@ test_authenticator_move(PathPrefix) ->
{ok, 200, _} = request( {ok, 200, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS]), uri(PathPrefix ++ [?CONF_NS]),
Conf) Conf
)
end, end,
AuthenticatorConfs), AuthenticatorConfs
),
?assertAuthenticatorsMatch( ?assertAuthenticatorsMatch(
[ [
@ -337,34 +383,40 @@ test_authenticator_move(PathPrefix) ->
#{<<"mechanism">> := <<"jwt">>}, #{<<"mechanism">> := <<"jwt">>},
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>} #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>}
], ],
PathPrefix ++ [?CONF_NS]), PathPrefix ++ [?CONF_NS]
),
%% Invalid moves %% Invalid moves
{ok, 400, _} = request( {ok, 400, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"up">>}), #{position => <<"up">>}
),
{ok, 400, _} = request( {ok, 400, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{}), #{}
),
{ok, 404, _} = request( {ok, 404, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"before:invalid">>}), #{position => <<"before:invalid">>}
),
{ok, 404, _} = request( {ok, 404, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"before:password_based:redis">>}), #{position => <<"before:password_based:redis">>}
),
{ok, 404, _} = request( {ok, 404, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"before:password_based:redis">>}), #{position => <<"before:password_based:redis">>}
),
%% Valid moves %% Valid moves
@ -372,7 +424,8 @@ test_authenticator_move(PathPrefix) ->
{ok, 204, _} = request( {ok, 204, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"front">>}), #{position => <<"front">>}
),
?assertAuthenticatorsMatch( ?assertAuthenticatorsMatch(
[ [
@ -380,13 +433,15 @@ test_authenticator_move(PathPrefix) ->
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}, #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>},
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>} #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>}
], ],
PathPrefix ++ [?CONF_NS]), PathPrefix ++ [?CONF_NS]
),
%% test rear %% test rear
{ok, 204, _} = request( {ok, 204, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"rear">>}), #{position => <<"rear">>}
),
?assertAuthenticatorsMatch( ?assertAuthenticatorsMatch(
[ [
@ -394,13 +449,15 @@ test_authenticator_move(PathPrefix) ->
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>}, #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>},
#{<<"mechanism">> := <<"jwt">>} #{<<"mechanism">> := <<"jwt">>}
], ],
PathPrefix ++ [?CONF_NS]), PathPrefix ++ [?CONF_NS]
),
%% test before %% test before
{ok, 204, _} = request( {ok, 204, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]), uri(PathPrefix ++ [?CONF_NS, "jwt", "move"]),
#{position => <<"before:password_based:built_in_database">>}), #{position => <<"before:password_based:built_in_database">>}
),
?assertAuthenticatorsMatch( ?assertAuthenticatorsMatch(
[ [
@ -408,13 +465,15 @@ test_authenticator_move(PathPrefix) ->
#{<<"mechanism">> := <<"jwt">>}, #{<<"mechanism">> := <<"jwt">>},
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>} #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>}
], ],
PathPrefix ++ [?CONF_NS]), PathPrefix ++ [?CONF_NS]
),
%% test after %% test after
{ok, 204, _} = request( {ok, 204, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS, "password_based%3Abuilt_in_database", "move"]), uri(PathPrefix ++ [?CONF_NS, "password_based%3Abuilt_in_database", "move"]),
#{position => <<"after:password_based:http">>}), #{position => <<"after:password_based:http">>}
),
?assertAuthenticatorsMatch( ?assertAuthenticatorsMatch(
[ [
@ -422,18 +481,20 @@ test_authenticator_move(PathPrefix) ->
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>}, #{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"built_in_database">>},
#{<<"mechanism">> := <<"jwt">>} #{<<"mechanism">> := <<"jwt">>}
], ],
PathPrefix ++ [?CONF_NS]). PathPrefix ++ [?CONF_NS]
).
test_authenticator_import_users(PathPrefix) -> test_authenticator_import_users(PathPrefix) ->
ImportUri = uri( ImportUri = uri(
PathPrefix ++ PathPrefix ++
[?CONF_NS, "password_based:built_in_database", "import_users"]), [?CONF_NS, "password_based:built_in_database", "import_users"]
),
{ok, 200, _} = request( {ok, 200, _} = request(
post, post,
uri(PathPrefix ++ [?CONF_NS]), uri(PathPrefix ++ [?CONF_NS]),
emqx_authn_test_lib:built_in_database_example()), emqx_authn_test_lib:built_in_database_example()
),
{ok, 400, _} = request(post, ImportUri, #{}), {ok, 400, _} = request(post, ImportUri, #{}),

View File

@ -28,12 +28,12 @@
-define(HTTP_PORT, 33333). -define(HTTP_PORT, 33333).
-define(HTTP_PATH, "/auth"). -define(HTTP_PATH, "/auth").
-define(CREDENTIALS, #{username => <<"plain">>, -define(CREDENTIALS, #{
username => <<"plain">>,
password => <<"plain">>, password => <<"plain">>,
listener => 'tcp:default', listener => 'tcp:default',
protocol => mqtt protocol => mqtt
}). }).
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -47,7 +47,8 @@ init_per_suite(Config) ->
end_per_suite(_) -> end_per_suite(_) ->
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
emqx_common_test_helpers:stop_apps([emqx_authn]), emqx_common_test_helpers:stop_apps([emqx_authn]),
application:stop(cowboy), application:stop(cowboy),
ok. ok.
@ -56,7 +57,8 @@ init_per_testcase(_Case, Config) ->
{ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
{ok, _} = emqx_authn_http_test_server:start_link(?HTTP_PORT, ?HTTP_PATH), {ok, _} = emqx_authn_http_test_server:start_link(?HTTP_PORT, ?HTTP_PATH),
Config. Config.
@ -72,7 +74,8 @@ t_create(_Config) ->
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
{ok, [#{provider := emqx_authn_http}]} = emqx_authentication:list_authenticators(?GLOBAL). {ok, [#{provider := emqx_authn_http}]} = emqx_authentication:list_authenticators(?GLOBAL).
@ -92,14 +95,16 @@ t_create_invalid(_Config) ->
try try
emqx:update_config( emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, Config}) {create_authenticator, ?GLOBAL, Config}
)
catch catch
throw:Error -> throw:Error ->
{error, Error} {error, Error}
end, end,
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL) {ok, []} = emqx_authentication:list_authenticators(?GLOBAL)
end, end,
InvalidConfigs). InvalidConfigs
).
t_authenticate(_Config) -> t_authenticate(_Config) ->
ok = lists:foreach( ok = lists:foreach(
@ -107,16 +112,20 @@ t_authenticate(_Config) ->
ct:pal("test_user_auth sample: ~p", [Sample]), ct:pal("test_user_auth sample: ~p", [Sample]),
test_user_auth(Sample) test_user_auth(Sample)
end, end,
samples()). samples()
).
test_user_auth(#{handler := Handler, test_user_auth(#{
handler := Handler,
config_params := SpecificConfgParams, config_params := SpecificConfgParams,
result := Result}) -> result := Result
}) ->
AuthConfig = maps:merge(raw_http_auth_config(), SpecificConfgParams), AuthConfig = maps:merge(raw_http_auth_config(), SpecificConfgParams),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
ok = emqx_authn_http_test_server:set_handler(Handler), ok = emqx_authn_http_test_server:set_handler(Handler),
@ -124,40 +133,47 @@ test_user_auth(#{handler := Handler,
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL). ?GLOBAL
).
t_destroy(_Config) -> t_destroy(_Config) ->
AuthConfig = raw_http_auth_config(), AuthConfig = raw_http_auth_config(),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
ok = emqx_authn_http_test_server:set_handler( ok = emqx_authn_http_test_server:set_handler(
fun(Req0, State) -> fun(Req0, State) ->
Req = cowboy_req:reply(200, Req0), Req = cowboy_req:reply(200, Req0),
{ok, Req, State} {ok, Req, State}
end), end
),
{ok, [#{provider := emqx_authn_http, state := State}]} {ok, [#{provider := emqx_authn_http, state := State}]} =
= emqx_authentication:list_authenticators(?GLOBAL), emqx_authentication:list_authenticators(?GLOBAL),
Credentials = maps:with([username, password], ?CREDENTIALS), Credentials = maps:with([username, password], ?CREDENTIALS),
{ok, _} = emqx_authn_http:authenticate( {ok, _} = emqx_authn_http:authenticate(
Credentials, Credentials,
State), State
),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
% Authenticator should not be usable anymore % Authenticator should not be usable anymore
?assertMatch( ?assertMatch(
ignore, ignore,
emqx_authn_http:authenticate( emqx_authn_http:authenticate(
Credentials, Credentials,
State)). State
)
).
t_update(_Config) -> t_update(_Config) ->
CorrectConfig = raw_http_auth_config(), CorrectConfig = raw_http_auth_config(),
@ -166,28 +182,32 @@ t_update(_Config) ->
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, IncorrectConfig}), {create_authenticator, ?GLOBAL, IncorrectConfig}
),
ok = emqx_authn_http_test_server:set_handler( ok = emqx_authn_http_test_server:set_handler(
fun(Req0, State) -> fun(Req0, State) ->
Req = cowboy_req:reply(200, Req0), Req = cowboy_req:reply(200, Req0),
{ok, Req, State} {ok, Req, State}
end), end
),
{error, not_authorized} = emqx_access_control:authenticate(?CREDENTIALS), {error, not_authorized} = emqx_access_control:authenticate(?CREDENTIALS),
% We update with config with correct query, provider should update and work properly % We update with config with correct query, provider should update and work properly
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{update_authenticator, ?GLOBAL, <<"password_based:http">>, CorrectConfig}), {update_authenticator, ?GLOBAL, <<"password_based:http">>, CorrectConfig}
),
{ok,_} = emqx_access_control:authenticate(?CREDENTIALS). {ok, _} = emqx_access_control:authenticate(?CREDENTIALS).
t_is_superuser(_Config) -> t_is_superuser(_Config) ->
Config = raw_http_auth_config(), Config = raw_http_auth_config(),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, Config}), {create_authenticator, ?GLOBAL, Config}
),
Checks = [ Checks = [
{json, <<"0">>, false}, {json, <<"0">>, false},
@ -210,11 +230,10 @@ t_is_superuser(_Config) ->
lists:foreach(fun test_is_superuser/1, Checks). lists:foreach(fun test_is_superuser/1, Checks).
test_is_superuser({Kind, Value, ExpectedValue}) -> test_is_superuser({Kind, Value, ExpectedValue}) ->
{ContentType, Res} =
{ContentType, Res} = case Kind of case Kind of
json -> json ->
{<<"application/json">>, {<<"application/json">>, jiffy:encode(#{is_superuser => Value})};
jiffy:encode(#{is_superuser => Value})};
form -> form ->
{<<"application/x-www-form-urlencoded">>, {<<"application/x-www-form-urlencoded">>,
iolist_to_binary([<<"is_superuser=">>, Value])} iolist_to_binary([<<"is_superuser=">>, Value])}
@ -226,13 +245,16 @@ test_is_superuser({Kind, Value, ExpectedValue}) ->
200, 200,
#{<<"content-type">> => ContentType}, #{<<"content-type">> => ContentType},
Res, Res,
Req0), Req0
),
{ok, Req, State} {ok, Req, State}
end), end
),
?assertMatch( ?assertMatch(
{ok, #{is_superuser := ExpectedValue}}, {ok, #{is_superuser := ExpectedValue}},
emqx_access_control:authenticate(?CREDENTIALS)). emqx_access_control:authenticate(?CREDENTIALS)
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -253,8 +275,10 @@ raw_http_auth_config() ->
samples() -> samples() ->
[ [
%% simple get request %% simple get request
#{handler => fun(Req0, State) -> #{
#{username := <<"plain">>, handler => fun(Req0, State) ->
#{
username := <<"plain">>,
password := <<"plain">> password := <<"plain">>
} = cowboy_req:match_qs([username, password], Req0), } = cowboy_req:match_qs([username, password], Req0),
@ -262,54 +286,66 @@ samples() ->
{ok, Req, State} {ok, Req, State}
end, end,
config_params => #{}, config_params => #{},
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
%% get request with json body response %% get request with json body response
#{handler => fun(Req0, State) -> #{
handler => fun(Req0, State) ->
Req = cowboy_req:reply( Req = cowboy_req:reply(
200, 200,
#{<<"content-type">> => <<"application/json">>}, #{<<"content-type">> => <<"application/json">>},
jiffy:encode(#{is_superuser => true}), jiffy:encode(#{is_superuser => true}),
Req0), Req0
),
{ok, Req, State} {ok, Req, State}
end, end,
config_params => #{}, config_params => #{},
result => {ok,#{is_superuser => true, user_property => #{}}} result => {ok, #{is_superuser => true, user_property => #{}}}
}, },
%% get request with url-form-encoded body response %% get request with url-form-encoded body response
#{handler => fun(Req0, State) -> #{
handler => fun(Req0, State) ->
Req = cowboy_req:reply( Req = cowboy_req:reply(
200, 200,
#{<<"content-type">> => #{
<<"application/x-www-form-urlencoded">>}, <<"content-type">> =>
<<"application/x-www-form-urlencoded">>
},
<<"is_superuser=true">>, <<"is_superuser=true">>,
Req0), Req0
),
{ok, Req, State} {ok, Req, State}
end, end,
config_params => #{}, config_params => #{},
result => {ok,#{is_superuser => true, user_property => #{}}} result => {ok, #{is_superuser => true, user_property => #{}}}
}, },
%% get request with response of unknown encoding %% get request with response of unknown encoding
#{handler => fun(Req0, State) -> #{
handler => fun(Req0, State) ->
Req = cowboy_req:reply( Req = cowboy_req:reply(
200, 200,
#{<<"content-type">> => #{
<<"test/plain">>}, <<"content-type">> =>
<<"test/plain">>
},
<<"is_superuser=true">>, <<"is_superuser=true">>,
Req0), Req0
),
{ok, Req, State} {ok, Req, State}
end, end,
config_params => #{}, config_params => #{},
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
%% simple post request, application/json %% simple post request, application/json
#{handler => fun(Req0, State) -> #{
handler => fun(Req0, State) ->
{ok, RawBody, Req1} = cowboy_req:read_body(Req0), {ok, RawBody, Req1} = cowboy_req:read_body(Req0),
#{<<"username">> := <<"plain">>, #{
<<"username">> := <<"plain">>,
<<"password">> := <<"plain">> <<"password">> := <<"plain">>
} = jiffy:decode(RawBody, [return_maps]), } = jiffy:decode(RawBody, [return_maps]),
Req = cowboy_req:reply(200, Req1), Req = cowboy_req:reply(200, Req1),
@ -319,13 +355,15 @@ samples() ->
method => post, method => post,
headers => #{<<"content-type">> => <<"application/json">>} headers => #{<<"content-type">> => <<"application/json">>}
}, },
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
%% simple post request, application/x-www-form-urlencoded %% simple post request, application/x-www-form-urlencoded
#{handler => fun(Req0, State) -> #{
handler => fun(Req0, State) ->
{ok, PostVars, Req1} = cowboy_req:read_urlencoded_body(Req0), {ok, PostVars, Req1} = cowboy_req:read_urlencoded_body(Req0),
#{<<"username">> := <<"plain">>, #{
<<"username">> := <<"plain">>,
<<"password">> := <<"plain">> <<"password">> := <<"plain">>
} = maps:from_list(PostVars), } = maps:from_list(PostVars),
Req = cowboy_req:reply(200, Req1), Req = cowboy_req:reply(200, Req1),
@ -333,56 +371,61 @@ samples() ->
end, end,
config_params => #{ config_params => #{
method => post, method => post,
headers => #{<<"content-type">> => headers => #{
<<"application/x-www-form-urlencoded">>} <<"content-type">> =>
}, <<"application/x-www-form-urlencoded">>
result => {ok,#{is_superuser => false}}
} }
},
result => {ok, #{is_superuser => false}}
}#{
%% 204 code %% 204 code
#{handler => fun(Req0, State) -> handler => fun(Req0, State) ->
Req = cowboy_req:reply(204, Req0), Req = cowboy_req:reply(204, Req0),
{ok, Req, State} {ok, Req, State}
end, end,
config_params => #{}, config_params => #{},
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
%% custom headers %% custom headers
#{handler => fun(Req0, State) -> #{
handler => fun(Req0, State) ->
<<"Test Value">> = cowboy_req:header(<<"x-test-header">>, Req0), <<"Test Value">> = cowboy_req:header(<<"x-test-header">>, Req0),
Req = cowboy_req:reply(200, Req0), Req = cowboy_req:reply(200, Req0),
{ok, Req, State} {ok, Req, State}
end, end,
config_params => #{}, config_params => #{},
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
%% 400 code %% 400 code
#{handler => fun(Req0, State) -> #{
handler => fun(Req0, State) ->
Req = cowboy_req:reply(400, Req0), Req = cowboy_req:reply(400, Req0),
{ok, Req, State} {ok, Req, State}
end, end,
config_params => #{}, config_params => #{},
result => {error,not_authorized} result => {error, not_authorized}
}, },
%% 500 code %% 500 code
#{handler => fun(Req0, State) -> #{
handler => fun(Req0, State) ->
Req = cowboy_req:reply(500, Req0), Req = cowboy_req:reply(500, Req0),
{ok, Req, State} {ok, Req, State}
end, end,
config_params => #{}, config_params => #{},
result => {error,not_authorized} result => {error, not_authorized}
}, },
%% Handling error %% Handling error
#{handler => fun(Req0, State) -> #{
handler => fun(Req0, State) ->
error(woops), error(woops),
{ok, Req0, State} {ok, Req0, State}
end, end,
config_params => #{}, config_params => #{},
result => {error,not_authorized} result => {error, not_authorized}
} }
]. ].

View File

@ -26,11 +26,12 @@
-export([init/1]). -export([init/1]).
% API % API
-export([start_link/2, -export([
start_link/2,
start_link/3, start_link/3,
stop/0, stop/0,
set_handler/1 set_handler/1
]). ]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% API %% API
@ -57,7 +58,8 @@ init([Port, Path, SSLOpts]) ->
Dispatch = cowboy_router:compile( Dispatch = cowboy_router:compile(
[ [
{'_', [{Path, ?MODULE, []}]} {'_', [{Path, ?MODULE, []}]}
]), ]
),
ProtoOpts = #{env => #{dispatch => Dispatch}}, ProtoOpts = #{env => #{dispatch => Dispatch}},
@ -83,16 +85,21 @@ init(Req, State) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
transport_settings(Port, false) -> transport_settings(Port, false) ->
TransOpts = #{socket_opts => [{port, Port}], TransOpts = #{
connection_type => supervisor}, socket_opts => [{port, Port}],
connection_type => supervisor
},
{ranch_tcp, TransOpts, cowboy_clear}; {ranch_tcp, TransOpts, cowboy_clear};
transport_settings(Port, SSLOpts) -> transport_settings(Port, SSLOpts) ->
TransOpts = #{socket_opts => [{port, Port}, TransOpts = #{
socket_opts => [
{port, Port},
{next_protocols_advertised, [<<"h2">>, <<"http/1.1">>]}, {next_protocols_advertised, [<<"h2">>, <<"http/1.1">>]},
{alpn_preferred_protocols, [<<"h2">>, <<"http/1.1">>]} {alpn_preferred_protocols, [<<"h2">>, <<"http/1.1">>]}
| SSLOpts], | SSLOpts
connection_type => supervisor}, ],
connection_type => supervisor
},
{ranch_ssl, TransOpts, cowboy_tls}. {ranch_ssl, TransOpts, cowboy_tls}.
default_handler(Req0, State) -> default_handler(Req0, State) ->
@ -100,6 +107,6 @@ default_handler(Req0, State) ->
400, 400,
#{<<"content-type">> => <<"text/plain">>}, #{<<"content-type">> => <<"text/plain">>},
<<"">>, <<"">>,
Req0), Req0
),
{ok, Req, State}. {ok, Req, State}.

View File

@ -28,12 +28,12 @@
-define(HTTPS_PORT, 33333). -define(HTTPS_PORT, 33333).
-define(HTTPS_PATH, "/auth"). -define(HTTPS_PATH, "/auth").
-define(CREDENTIALS, #{username => <<"plain">>, -define(CREDENTIALS, #{
username => <<"plain">>,
password => <<"plain">>, password => <<"plain">>,
listener => 'tcp:default', listener => 'tcp:default',
protocol => mqtt protocol => mqtt
}). }).
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -47,7 +47,8 @@ init_per_suite(Config) ->
end_per_suite(_) -> end_per_suite(_) ->
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
emqx_common_test_helpers:stop_apps([emqx_authn]), emqx_common_test_helpers:stop_apps([emqx_authn]),
application:stop(cowboy), application:stop(cowboy),
ok. ok.
@ -56,7 +57,8 @@ init_per_testcase(_Case, Config) ->
{ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
{ok, _} = emqx_authn_http_test_server:start_link(?HTTPS_PORT, ?HTTPS_PATH, server_ssl_opts()), {ok, _} = emqx_authn_http_test_server:start_link(?HTTPS_PORT, ?HTTPS_PATH, server_ssl_opts()),
ok = emqx_authn_http_test_server:set_handler(fun cowboy_handler/2), ok = emqx_authn_http_test_server:set_handler(fun cowboy_handler/2),
Config. Config.
@ -70,46 +72,62 @@ end_per_testcase(_Case, _Config) ->
t_create(_Config) -> t_create(_Config) ->
{ok, _} = create_https_auth_with_ssl_opts( {ok, _} = create_https_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.2">>], <<"versions">> => [<<"tlsv1.2">>],
<<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]}), <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]
}
),
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
emqx_access_control:authenticate(?CREDENTIALS)). emqx_access_control:authenticate(?CREDENTIALS)
).
t_create_invalid_domain(_Config) -> t_create_invalid_domain(_Config) ->
{ok, _} = create_https_auth_with_ssl_opts( {ok, _} = create_https_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server-unknown-host">>, #{
<<"server_name_indication">> => <<"authn-server-unknown-host">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.2">>], <<"versions">> => [<<"tlsv1.2">>],
<<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]}), <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]
}
),
?assertEqual( ?assertEqual(
{error, not_authorized}, {error, not_authorized},
emqx_access_control:authenticate(?CREDENTIALS)). emqx_access_control:authenticate(?CREDENTIALS)
).
t_create_invalid_version(_Config) -> t_create_invalid_version(_Config) ->
{ok, _} = create_https_auth_with_ssl_opts( {ok, _} = create_https_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.1">>]}), <<"versions">> => [<<"tlsv1.1">>]
}
),
?assertEqual( ?assertEqual(
{error, not_authorized}, {error, not_authorized},
emqx_access_control:authenticate(?CREDENTIALS)). emqx_access_control:authenticate(?CREDENTIALS)
).
t_create_invalid_ciphers(_Config) -> t_create_invalid_ciphers(_Config) ->
{ok, _} = create_https_auth_with_ssl_opts( {ok, _} = create_https_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.2">>], <<"versions">> => [<<"tlsv1.2">>],
<<"ciphers">> => [<<"ECDHE-ECDSA-AES256-SHA384">>]}), <<"ciphers">> => [<<"ECDHE-ECDSA-AES256-SHA384">>]
}
),
?assertEqual( ?assertEqual(
{error, not_authorized}, {error, not_authorized},
emqx_access_control:authenticate(?CREDENTIALS)). emqx_access_control:authenticate(?CREDENTIALS)
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -122,7 +140,8 @@ create_https_auth_with_ssl_opts(SpecificSSLOpts) ->
raw_https_auth_config(SpecificSSLOpts) -> raw_https_auth_config(SpecificSSLOpts) ->
SSLOpts = maps:merge( SSLOpts = maps:merge(
emqx_authn_test_lib:client_ssl_cert_opts(), emqx_authn_test_lib:client_ssl_cert_opts(),
#{enable => <<"true">>}), #{enable => <<"true">>}
),
#{ #{
mechanism => <<"password_based">>, mechanism => <<"password_based">>,
enable => <<"true">>, enable => <<"true">>,
@ -148,11 +167,13 @@ cert_path(FileName) ->
cowboy_handler(Req0, State) -> cowboy_handler(Req0, State) ->
Req = cowboy_req:reply( Req = cowboy_req:reply(
200, 200,
Req0), Req0
),
{ok, Req, State}. {ok, Req, State}.
server_ssl_opts() -> server_ssl_opts() ->
[{keyfile, cert_path("server.key")}, [
{keyfile, cert_path("server.key")},
{certfile, cert_path("server.crt")}, {certfile, cert_path("server.crt")},
{cacertfile, cert_path("ca.crt")}, {cacertfile, cert_path("ca.crt")},
{verify, verify_none}, {verify, verify_none},

View File

@ -28,7 +28,6 @@
-define(JWKS_PORT, 33333). -define(JWKS_PORT, 33333).
-define(JWKS_PATH, "/jwks.json"). -define(JWKS_PATH, "/jwks.json").
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -51,24 +50,30 @@ end_per_suite(_) ->
t_jwt_authenticator_hmac_based(_) -> t_jwt_authenticator_hmac_based(_) ->
Secret = <<"abcdef">>, Secret = <<"abcdef">>,
Config = #{mechanism => jwt, Config = #{
mechanism => jwt,
use_jwks => false, use_jwks => false,
algorithm => 'hmac-based', algorithm => 'hmac-based',
secret => Secret, secret => Secret,
secret_base64_encoded => false, secret_base64_encoded => false,
verify_claims => []}, verify_claims => []
},
{ok, State} = emqx_authn_jwt:create(?AUTHN_ID, Config), {ok, State} = emqx_authn_jwt:create(?AUTHN_ID, Config),
Payload = #{<<"username">> => <<"myuser">>}, Payload = #{<<"username">> => <<"myuser">>},
JWS = generate_jws('hmac-based', Payload, Secret), JWS = generate_jws('hmac-based', Payload, Secret),
Credential = #{username => <<"myuser">>, Credential = #{
password => JWS}, username => <<"myuser">>,
password => JWS
},
?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State)), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State)),
Payload1 = #{<<"username">> => <<"myuser">>, <<"is_superuser">> => true}, Payload1 = #{<<"username">> => <<"myuser">>, <<"is_superuser">> => true},
JWS1 = generate_jws('hmac-based', Payload1, Secret), JWS1 = generate_jws('hmac-based', Payload1, Secret),
Credential1 = #{username => <<"myuser">>, Credential1 = #{
password => JWS1}, username => <<"myuser">>,
password => JWS1
},
?assertEqual({ok, #{is_superuser => true}}, emqx_authn_jwt:authenticate(Credential1, State)), ?assertEqual({ok, #{is_superuser => true}}, emqx_authn_jwt:authenticate(Credential1, State)),
BadJWS = generate_jws('hmac-based', Payload, <<"bad_secret">>), BadJWS = generate_jws('hmac-based', Payload, <<"bad_secret">>),
@ -76,59 +81,84 @@ t_jwt_authenticator_hmac_based(_) ->
?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential2, State)), ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential2, State)),
%% secret_base64_encoded %% secret_base64_encoded
Config2 = Config#{secret => base64:encode(Secret), Config2 = Config#{
secret_base64_encoded => true}, secret => base64:encode(Secret),
secret_base64_encoded => true
},
{ok, State2} = emqx_authn_jwt:update(Config2, State), {ok, State2} = emqx_authn_jwt:update(Config2, State),
?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State2)), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State2)),
%% invalid secret %% invalid secret
BadConfig = Config#{secret => <<"emqxsecret">>, BadConfig = Config#{
secret_base64_encoded => true}, secret => <<"emqxsecret">>,
secret_base64_encoded => true
},
{error, {invalid_parameter, secret}} = emqx_authn_jwt:create(?AUTHN_ID, BadConfig), {error, {invalid_parameter, secret}} = emqx_authn_jwt:create(?AUTHN_ID, BadConfig),
Config3 = Config#{verify_claims => [{<<"username">>, <<"${username}">>}]}, Config3 = Config#{verify_claims => [{<<"username">>, <<"${username}">>}]},
{ok, State3} = emqx_authn_jwt:update(Config3, State2), {ok, State3} = emqx_authn_jwt:update(Config3, State2),
?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State3)), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State3)),
?assertEqual({error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential#{username => <<"otheruser">>}, State3)), ?assertEqual(
{error, bad_username_or_password},
emqx_authn_jwt:authenticate(Credential#{username => <<"otheruser">>}, State3)
),
%% Expiration %% Expiration
Payload3 = #{ <<"username">> => <<"myuser">> Payload3 = #{
, <<"exp">> => erlang:system_time(second) - 60}, <<"username">> => <<"myuser">>,
<<"exp">> => erlang:system_time(second) - 60
},
JWS3 = generate_jws('hmac-based', Payload3, Secret), JWS3 = generate_jws('hmac-based', Payload3, Secret),
Credential3 = Credential#{password => JWS3}, Credential3 = Credential#{password => JWS3},
?assertEqual({error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential3, State3)), ?assertEqual(
{error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential3, State3)
),
Payload4 = #{ <<"username">> => <<"myuser">> Payload4 = #{
, <<"exp">> => erlang:system_time(second) + 60}, <<"username">> => <<"myuser">>,
<<"exp">> => erlang:system_time(second) + 60
},
JWS4 = generate_jws('hmac-based', Payload4, Secret), JWS4 = generate_jws('hmac-based', Payload4, Secret),
Credential4 = Credential#{password => JWS4}, Credential4 = Credential#{password => JWS4},
?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential4, State3)), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential4, State3)),
%% Issued At %% Issued At
Payload5 = #{ <<"username">> => <<"myuser">> Payload5 = #{
, <<"iat">> => erlang:system_time(second) - 60}, <<"username">> => <<"myuser">>,
<<"iat">> => erlang:system_time(second) - 60
},
JWS5 = generate_jws('hmac-based', Payload5, Secret), JWS5 = generate_jws('hmac-based', Payload5, Secret),
Credential5 = Credential#{password => JWS5}, Credential5 = Credential#{password => JWS5},
?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential5, State3)), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential5, State3)),
Payload6 = #{ <<"username">> => <<"myuser">> Payload6 = #{
, <<"iat">> => erlang:system_time(second) + 60}, <<"username">> => <<"myuser">>,
<<"iat">> => erlang:system_time(second) + 60
},
JWS6 = generate_jws('hmac-based', Payload6, Secret), JWS6 = generate_jws('hmac-based', Payload6, Secret),
Credential6 = Credential#{password => JWS6}, Credential6 = Credential#{password => JWS6},
?assertEqual({error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential6, State3)), ?assertEqual(
{error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential6, State3)
),
%% Not Before %% Not Before
Payload7 = #{ <<"username">> => <<"myuser">> Payload7 = #{
, <<"nbf">> => erlang:system_time(second) - 60}, <<"username">> => <<"myuser">>,
<<"nbf">> => erlang:system_time(second) - 60
},
JWS7 = generate_jws('hmac-based', Payload7, Secret), JWS7 = generate_jws('hmac-based', Payload7, Secret),
Credential7 = Credential6#{password => JWS7}, Credential7 = Credential6#{password => JWS7},
?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential7, State3)), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential7, State3)),
Payload8 = #{ <<"username">> => <<"myuser">> Payload8 = #{
, <<"nbf">> => erlang:system_time(second) + 60}, <<"username">> => <<"myuser">>,
<<"nbf">> => erlang:system_time(second) + 60
},
JWS8 = generate_jws('hmac-based', Payload8, Secret), JWS8 = generate_jws('hmac-based', Payload8, Secret),
Credential8 = Credential#{password => JWS8}, Credential8 = Credential#{password => JWS8},
?assertEqual({error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential8, State3)), ?assertEqual(
{error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential8, State3)
),
?assertEqual(ok, emqx_authn_jwt:destroy(State3)), ?assertEqual(ok, emqx_authn_jwt:destroy(State3)),
ok. ok.
@ -136,19 +166,25 @@ t_jwt_authenticator_hmac_based(_) ->
t_jwt_authenticator_public_key(_) -> t_jwt_authenticator_public_key(_) ->
PublicKey = test_rsa_key(public), PublicKey = test_rsa_key(public),
PrivateKey = test_rsa_key(private), PrivateKey = test_rsa_key(private),
Config = #{mechanism => jwt, Config = #{
mechanism => jwt,
use_jwks => false, use_jwks => false,
algorithm => 'public-key', algorithm => 'public-key',
certificate => PublicKey, certificate => PublicKey,
verify_claims => []}, verify_claims => []
},
{ok, State} = emqx_authn_jwt:create(?AUTHN_ID, Config), {ok, State} = emqx_authn_jwt:create(?AUTHN_ID, Config),
Payload = #{<<"username">> => <<"myuser">>}, Payload = #{<<"username">> => <<"myuser">>},
JWS = generate_jws('public-key', Payload, PrivateKey), JWS = generate_jws('public-key', Payload, PrivateKey),
Credential = #{username => <<"myuser">>, Credential = #{
password => JWS}, username => <<"myuser">>,
password => JWS
},
?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State)), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State)),
?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State)), ?assertEqual(
ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State)
),
?assertEqual(ok, emqx_authn_jwt:destroy(State)), ?assertEqual(ok, emqx_authn_jwt:destroy(State)),
ok. ok.
@ -160,10 +196,13 @@ t_jwks_renewal(_Config) ->
PrivateKey = test_rsa_key(private), PrivateKey = test_rsa_key(private),
Payload = #{<<"username">> => <<"myuser">>}, Payload = #{<<"username">> => <<"myuser">>},
JWS = generate_jws('public-key', Payload, PrivateKey), JWS = generate_jws('public-key', Payload, PrivateKey),
Credential = #{username => <<"myuser">>, Credential = #{
password => JWS}, username => <<"myuser">>,
password => JWS
},
BadConfig0 = #{mechanism => jwt, BadConfig0 = #{
mechanism => jwt,
algorithm => 'public-key', algorithm => 'public-key',
ssl => #{enable => false}, ssl => #{enable => false},
verify_claims => [], verify_claims => [],
@ -178,31 +217,39 @@ t_jwks_renewal(_Config) ->
{{ok, State0}, _} = ?wait_async_action( {{ok, State0}, _} = ?wait_async_action(
emqx_authn_jwt:create(?AUTHN_ID, BadConfig0), emqx_authn_jwt:create(?AUTHN_ID, BadConfig0),
#{?snk_kind := jwks_endpoint_response}, #{?snk_kind := jwks_endpoint_response},
10000), 10000
),
ok = snabbkaffe:stop(), ok = snabbkaffe:stop(),
?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential, State0)), ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential, State0)),
?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State0)), ?assertEqual(
ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State0)
),
ClientSSLOpts = client_ssl_opts(), ClientSSLOpts = client_ssl_opts(),
BadClientSSLOpts = ClientSSLOpts#{server_name_indication => "authn-server-unknown-host"}, BadClientSSLOpts = ClientSSLOpts#{server_name_indication => "authn-server-unknown-host"},
BadConfig1 = BadConfig0#{endpoint => BadConfig1 = BadConfig0#{
endpoint =>
"https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT) ++ ?JWKS_PATH, "https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT) ++ ?JWKS_PATH,
ssl => BadClientSSLOpts}, ssl => BadClientSSLOpts
},
ok = snabbkaffe:start_trace(), ok = snabbkaffe:start_trace(),
{{ok, State1}, _} = ?wait_async_action( {{ok, State1}, _} = ?wait_async_action(
emqx_authn_jwt:create(?AUTHN_ID, BadConfig1), emqx_authn_jwt:create(?AUTHN_ID, BadConfig1),
#{?snk_kind := jwks_endpoint_response}, #{?snk_kind := jwks_endpoint_response},
10000), 10000
),
ok = snabbkaffe:stop(), ok = snabbkaffe:stop(),
?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential, State1)), ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential, State1)),
?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State0)), ?assertEqual(
ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State0)
),
GoodConfig = BadConfig1#{ssl => ClientSSLOpts}, GoodConfig = BadConfig1#{ssl => ClientSSLOpts},
@ -211,12 +258,15 @@ t_jwks_renewal(_Config) ->
{{ok, State2}, _} = ?wait_async_action( {{ok, State2}, _} = ?wait_async_action(
emqx_authn_jwt:update(GoodConfig, State1), emqx_authn_jwt:update(GoodConfig, State1),
#{?snk_kind := jwks_endpoint_response}, #{?snk_kind := jwks_endpoint_response},
10000), 10000
),
ok = snabbkaffe:stop(), ok = snabbkaffe:stop(),
?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State2)), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State2)),
?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State2)), ?assertEqual(
ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State2)
),
?assertEqual(ok, emqx_authn_jwt:destroy(State2)), ?assertEqual(ok, emqx_authn_jwt:destroy(State2)),
ok = emqx_authn_http_test_server:stop(). ok = emqx_authn_http_test_server:stop().
@ -232,12 +282,12 @@ jwks_handler(Req0, State) ->
200, 200,
#{<<"content-type">> => <<"application/json">>}, #{<<"content-type">> => <<"application/json">>},
jiffy:encode(JWKS), jiffy:encode(JWKS),
Req0), Req0
),
{ok, Req, State}. {ok, Req, State}.
test_rsa_key(public) -> test_rsa_key(public) ->
data_file("public_key.pem"); data_file("public_key.pem");
test_rsa_key(private) -> test_rsa_key(private) ->
data_file("private_key.pem"). data_file("private_key.pem").
@ -250,16 +300,18 @@ cert_file(Name) ->
generate_jws('hmac-based', Payload, Secret) -> generate_jws('hmac-based', Payload, Secret) ->
JWK = jose_jwk:from_oct(Secret), JWK = jose_jwk:from_oct(Secret),
Header = #{ <<"alg">> => <<"HS256">> Header = #{
, <<"typ">> => <<"JWT">> <<"alg">> => <<"HS256">>,
<<"typ">> => <<"JWT">>
}, },
Signed = jose_jwt:sign(JWK, Header, Payload), Signed = jose_jwt:sign(JWK, Header, Payload),
{_, JWS} = jose_jws:compact(Signed), {_, JWS} = jose_jws:compact(Signed),
JWS; JWS;
generate_jws('public-key', Payload, PrivateKey) -> generate_jws('public-key', Payload, PrivateKey) ->
JWK = jose_jwk:from_pem_file(PrivateKey), JWK = jose_jwk:from_pem_file(PrivateKey),
Header = #{ <<"alg">> => <<"RS256">> Header = #{
, <<"typ">> => <<"JWT">> <<"alg">> => <<"RS256">>,
<<"typ">> => <<"JWT">>
}, },
Signed = jose_jwt:sign(JWK, Header, Payload), Signed = jose_jwt:sign(JWK, Header, Payload),
{_, JWS} = jose_jws:compact(Signed), {_, JWS} = jose_jws:compact(Signed),
@ -268,13 +320,16 @@ generate_jws('public-key', Payload, PrivateKey) ->
client_ssl_opts() -> client_ssl_opts() ->
maps:merge( maps:merge(
emqx_authn_test_lib:client_ssl_cert_opts(), emqx_authn_test_lib:client_ssl_cert_opts(),
#{enable => true, #{
enable => true,
verify => verify_peer, verify => verify_peer,
server_name_indication => "authn-server" server_name_indication => "authn-server"
}). }
).
server_ssl_opts() -> server_ssl_opts() ->
[{keyfile, cert_file("server.key")}, [
{keyfile, cert_file("server.key")},
{certfile, cert_file("server.crt")}, {certfile, cert_file("server.crt")},
{cacertfile, cert_file("ca.crt")}, {cacertfile, cert_file("ca.crt")},
{verify, verify_none} {verify, verify_none}

View File

@ -76,7 +76,8 @@ t_check_schema(_Config) ->
?assertException( ?assertException(
throw, throw,
{emqx_authn_mnesia, _}, {emqx_authn_mnesia, _},
hocon_tconf:check_plain(emqx_authn_mnesia, ?CONF(ConfigNotOk))). hocon_tconf:check_plain(emqx_authn_mnesia, ?CONF(ConfigNotOk))
).
t_create(_) -> t_create(_) ->
Config0 = config(), Config0 = config(),
@ -110,7 +111,7 @@ t_destroy(_) ->
ok = emqx_authn_mnesia:destroy(State0), ok = emqx_authn_mnesia:destroy(State0),
{ok, State1} = emqx_authn_mnesia:create(?AUTHN_ID, Config), {ok, State1} = emqx_authn_mnesia:create(?AUTHN_ID, Config),
{error,not_found} = emqx_authn_mnesia:lookup_user(<<"u">>, State1), {error, not_found} = emqx_authn_mnesia:lookup_user(<<"u">>, State1),
{ok, _} = emqx_authn_mnesia:lookup_user(<<"u">>, StateOther). {ok, _} = emqx_authn_mnesia:lookup_user(<<"u">>, StateOther).
t_authenticate(_) -> t_authenticate(_) ->
@ -122,13 +123,16 @@ t_authenticate(_) ->
{ok, _} = emqx_authn_mnesia:authenticate( {ok, _} = emqx_authn_mnesia:authenticate(
#{username => <<"u">>, password => <<"p">>}, #{username => <<"u">>, password => <<"p">>},
State), State
),
{error, bad_username_or_password} = emqx_authn_mnesia:authenticate( {error, bad_username_or_password} = emqx_authn_mnesia:authenticate(
#{username => <<"u">>, password => <<"badpass">>}, #{username => <<"u">>, password => <<"badpass">>},
State), State
),
ignore = emqx_authn_mnesia:authenticate( ignore = emqx_authn_mnesia:authenticate(
#{clientid => <<"u">>, password => <<"p">>}, #{clientid => <<"u">>, password => <<"p">>},
State). State
).
t_add_user(_) -> t_add_user(_) ->
Config = config(), Config = config(),
@ -157,16 +161,19 @@ t_update_user(_) ->
{ok, _} = emqx_authn_mnesia:add_user(User, State), {ok, _} = emqx_authn_mnesia:add_user(User, State),
{error, not_found} = emqx_authn_mnesia:update_user(<<"u1">>, #{password => <<"p1">>}, State), {error, not_found} = emqx_authn_mnesia:update_user(<<"u1">>, #{password => <<"p1">>}, State),
{ok, {ok, #{
#{user_id := <<"u">>, user_id := <<"u">>,
is_superuser := true}} = emqx_authn_mnesia:update_user( is_superuser := true
}} = emqx_authn_mnesia:update_user(
<<"u">>, <<"u">>,
#{password => <<"p1">>, is_superuser => true}, #{password => <<"p1">>, is_superuser => true},
State), State
),
{ok, _} = emqx_authn_mnesia:authenticate( {ok, _} = emqx_authn_mnesia:authenticate(
#{username => <<"u">>, password => <<"p1">>}, #{username => <<"u">>, password => <<"p1">>},
State), State
),
{ok, #{is_superuser := true}} = emqx_authn_mnesia:lookup_user(<<"u">>, State). {ok, #{is_superuser := true}} = emqx_authn_mnesia:lookup_user(<<"u">>, State).
@ -174,31 +181,47 @@ t_list_users(_) ->
Config = config(), Config = config(),
{ok, State} = emqx_authn_mnesia:create(?AUTHN_ID, Config), {ok, State} = emqx_authn_mnesia:create(?AUTHN_ID, Config),
Users = [#{user_id => <<"u1">>, password => <<"p">>}, Users = [
#{user_id => <<"u1">>, password => <<"p">>},
#{user_id => <<"u2">>, password => <<"p">>}, #{user_id => <<"u2">>, password => <<"p">>},
#{user_id => <<"u3">>, password => <<"p">>}], #{user_id => <<"u3">>, password => <<"p">>}
],
lists:foreach( lists:foreach(
fun(U) -> {ok, _} = emqx_authn_mnesia:add_user(U, State) end, fun(U) -> {ok, _} = emqx_authn_mnesia:add_user(U, State) end,
Users), Users
),
#{data := [#{is_superuser := false,user_id := _}, #{
#{is_superuser := false,user_id := _}], data := [
meta := #{page := 1, limit := 2, count := 3}} = emqx_authn_mnesia:list_users( #{is_superuser := false, user_id := _},
#{is_superuser := false, user_id := _}
],
meta := #{page := 1, limit := 2, count := 3}
} = emqx_authn_mnesia:list_users(
#{<<"page">> => 1, <<"limit">> => 2}, #{<<"page">> => 1, <<"limit">> => 2},
State), State
),
#{data := [#{is_superuser := false,user_id := _}], #{
meta := #{page := 2, limit := 2, count := 3}} = emqx_authn_mnesia:list_users( data := [#{is_superuser := false, user_id := _}],
meta := #{page := 2, limit := 2, count := 3}
} = emqx_authn_mnesia:list_users(
#{<<"page">> => 2, <<"limit">> => 2}, #{<<"page">> => 2, <<"limit">> => 2},
State), State
),
#{data := [#{is_superuser := false,user_id := <<"u3">>}], #{
meta := #{page := 1, limit := 20, count := 1}} = emqx_authn_mnesia:list_users( data := [#{is_superuser := false, user_id := <<"u3">>}],
#{ <<"page">> => 1 meta := #{page := 1, limit := 20, count := 1}
, <<"limit">> => 20 } = emqx_authn_mnesia:list_users(
, <<"like_username">> => <<"3">>}, #{
State). <<"page">> => 1,
<<"limit">> => 20,
<<"like_username">> => <<"3">>
},
State
).
t_import_users(_) -> t_import_users(_) ->
Config0 = config(), Config0 = config(),
@ -207,35 +230,43 @@ t_import_users(_) ->
ok = emqx_authn_mnesia:import_users( ok = emqx_authn_mnesia:import_users(
data_filename(<<"user-credentials.json">>), data_filename(<<"user-credentials.json">>),
State), State
),
ok = emqx_authn_mnesia:import_users( ok = emqx_authn_mnesia:import_users(
data_filename(<<"user-credentials.csv">>), data_filename(<<"user-credentials.csv">>),
State), State
),
{error, {unsupported_file_format, _}} = emqx_authn_mnesia:import_users( {error, {unsupported_file_format, _}} = emqx_authn_mnesia:import_users(
<<"/file/with/unknown.extension">>, <<"/file/with/unknown.extension">>,
State), State
),
{error, unknown_file_format} = emqx_authn_mnesia:import_users( {error, unknown_file_format} = emqx_authn_mnesia:import_users(
<<"/file/with/no/extension">>, <<"/file/with/no/extension">>,
State), State
),
{error, enoent} = emqx_authn_mnesia:import_users( {error, enoent} = emqx_authn_mnesia:import_users(
<<"/file/that/not/exist.json">>, <<"/file/that/not/exist.json">>,
State), State
),
{error, bad_format} = emqx_authn_mnesia:import_users( {error, bad_format} = emqx_authn_mnesia:import_users(
data_filename(<<"user-credentials-malformed-0.json">>), data_filename(<<"user-credentials-malformed-0.json">>),
State), State
),
{error, {_, invalid_json}} = emqx_authn_mnesia:import_users( {error, {_, invalid_json}} = emqx_authn_mnesia:import_users(
data_filename(<<"user-credentials-malformed-1.json">>), data_filename(<<"user-credentials-malformed-1.json">>),
State), State
),
{error, bad_format} = emqx_authn_mnesia:import_users( {error, bad_format} = emqx_authn_mnesia:import_users(
data_filename(<<"user-credentials-malformed.csv">>), data_filename(<<"user-credentials-malformed.csv">>),
State). State
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -246,7 +277,10 @@ data_filename(Name) ->
filename:join([Dir, <<"data">>, Name]). filename:join([Dir, <<"data">>, Name]).
config() -> config() ->
#{user_id_type => username, #{
password_hash_algorithm => #{name => bcrypt, user_id_type => username,
salt_rounds => 8} password_hash_algorithm => #{
name => bcrypt,
salt_rounds => 8
}
}. }.

View File

@ -24,7 +24,6 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-define(MONGO_HOST, "mongo"). -define(MONGO_HOST, "mongo").
-define(MONGO_CLIENT, 'emqx_authn_mongo_SUITE_client'). -define(MONGO_CLIENT, 'emqx_authn_mongo_SUITE_client').
@ -38,7 +37,8 @@ init_per_testcase(_TestCase, Config) ->
emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authentication:initialize_authentication(?GLOBAL, []),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
{ok, _} = mc_worker_api:connect(mongo_config()), {ok, _} = mc_worker_api:connect(mongo_config()),
Config. Config.
@ -59,7 +59,8 @@ init_per_suite(Config) ->
end_per_suite(_Config) -> end_per_suite(_Config) ->
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
ok = stop_apps([emqx_resource, emqx_connector]), ok = stop_apps([emqx_resource, emqx_connector]),
ok = emqx_common_test_helpers:stop_apps([emqx_authn]). ok = emqx_common_test_helpers:stop_apps([emqx_authn]).
@ -72,7 +73,8 @@ t_create(_Config) ->
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
{ok, [#{provider := emqx_authn_mongodb}]} = emqx_authentication:list_authenticators(?GLOBAL). {ok, [#{provider := emqx_authn_mongodb}]} = emqx_authentication:list_authenticators(?GLOBAL).
@ -90,11 +92,13 @@ t_create_invalid(_Config) ->
fun(Config) -> fun(Config) ->
{error, _} = emqx:update_config( {error, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, Config}), {create_authenticator, ?GLOBAL, Config}
),
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL) {ok, []} = emqx_authentication:list_authenticators(?GLOBAL)
end, end,
InvalidConfigs). InvalidConfigs
).
t_authenticate(_Config) -> t_authenticate(_Config) ->
ok = init_seeds(), ok = init_seeds(),
@ -103,17 +107,21 @@ t_authenticate(_Config) ->
ct:pal("test_user_auth sample: ~p", [Sample]), ct:pal("test_user_auth sample: ~p", [Sample]),
test_user_auth(Sample) test_user_auth(Sample)
end, end,
user_seeds()), user_seeds()
),
ok = drop_seeds(). ok = drop_seeds().
test_user_auth(#{credentials := Credentials0, test_user_auth(#{
credentials := Credentials0,
config_params := SpecificConfigParams, config_params := SpecificConfigParams,
result := Result}) -> result := Result
}) ->
AuthConfig = maps:merge(raw_mongo_auth_config(), SpecificConfigParams), AuthConfig = maps:merge(raw_mongo_auth_config(), SpecificConfigParams),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
Credentials = Credentials0#{ Credentials = Credentials0#{
listener => 'tcp:default', listener => 'tcp:default',
@ -123,7 +131,8 @@ test_user_auth(#{credentials := Credentials0,
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL). ?GLOBAL
).
t_destroy(_Config) -> t_destroy(_Config) ->
ok = init_seeds(), ok = init_seeds(),
@ -131,29 +140,36 @@ t_destroy(_Config) ->
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
{ok, [#{provider := emqx_authn_mongodb, state := State}]} {ok, [#{provider := emqx_authn_mongodb, state := State}]} =
= emqx_authentication:list_authenticators(?GLOBAL), emqx_authentication:list_authenticators(?GLOBAL),
{ok, _} = emqx_authn_mongodb:authenticate( {ok, _} = emqx_authn_mongodb:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">> password => <<"plain">>
}, },
State), State
),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
% Authenticator should not be usable anymore % Authenticator should not be usable anymore
?assertMatch( ?assertMatch(
ignore, ignore,
emqx_authn_mongodb:authenticate( emqx_authn_mongodb:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">> password => <<"plain">>
}, },
State)), State
)
),
ok = drop_seeds(). ok = drop_seeds().
@ -165,33 +181,40 @@ t_update(_Config) ->
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, IncorrectConfig}), {create_authenticator, ?GLOBAL, IncorrectConfig}
),
{error, not_authorized} = emqx_access_control:authenticate( {error, not_authorized} = emqx_access_control:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">>, password => <<"plain">>,
listener => 'tcp:default', listener => 'tcp:default',
protocol => mqtt protocol => mqtt
}), }
),
% We update with config with correct selector, provider should update and work properly % We update with config with correct selector, provider should update and work properly
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{update_authenticator, ?GLOBAL, <<"password_based:mongodb">>, CorrectConfig}), {update_authenticator, ?GLOBAL, <<"password_based:mongodb">>, CorrectConfig}
),
{ok,_} = emqx_access_control:authenticate( {ok, _} = emqx_access_control:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">>, password => <<"plain">>,
listener => 'tcp:default', listener => 'tcp:default',
protocol => mqtt protocol => mqtt
}), }
),
ok = drop_seeds(). ok = drop_seeds().
t_is_superuser(_Config) -> t_is_superuser(_Config) ->
Config = raw_mongo_auth_config(), Config = raw_mongo_auth_config(),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, Config}), {create_authenticator, ?GLOBAL, Config}
),
Checks = [ Checks = [
{<<"0">>, false}, {<<"0">>, false},
@ -230,7 +253,8 @@ test_is_superuser({Value, ExpectedValue}) ->
?assertEqual( ?assertEqual(
{ok, #{is_superuser => ExpectedValue}}, {ok, #{is_superuser => ExpectedValue}},
emqx_access_control:authenticate(Credentials)). emqx_access_control:authenticate(Credentials)
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -239,8 +263,10 @@ test_is_superuser({Value, ExpectedValue}) ->
raw_mongo_auth_config() -> raw_mongo_auth_config() ->
#{ #{
mechanism => <<"password_based">>, mechanism => <<"password_based">>,
password_hash_algorithm => #{name => <<"plain">>, password_hash_algorithm => #{
salt_position => <<"suffix">>}, name => <<"plain">>,
salt_position => <<"suffix">>
},
enable => <<"true">>, enable => <<"true">>,
backend => <<"mongodb">>, backend => <<"mongodb">>,
@ -257,7 +283,9 @@ raw_mongo_auth_config() ->
}. }.
user_seeds() -> user_seeds() ->
[#{data => #{ [
#{
data => #{
username => <<"plain">>, username => <<"plain">>,
password_hash => <<"plainsalt">>, password_hash => <<"plainsalt">>,
salt => <<"salt">>, salt => <<"salt">>,
@ -267,12 +295,12 @@ user_seeds() ->
username => <<"plain">>, username => <<"plain">>,
password => <<"plain">> password => <<"plain">>
}, },
config_params => #{ config_params => #{},
}, result => {ok, #{is_superuser => true}}
result => {ok,#{is_superuser => true}}
}, },
#{data => #{ #{
data => #{
username => <<"md5">>, username => <<"md5">>,
password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>, password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>,
salt => <<"salt">>, salt => <<"salt">>,
@ -283,15 +311,19 @@ user_seeds() ->
password => <<"md5">> password => <<"md5">>
}, },
config_params => #{ config_params => #{
password_hash_algorithm => #{name => <<"md5">>, password_hash_algorithm => #{
salt_position => <<"suffix">> } name => <<"md5">>,
salt_position => <<"suffix">>
}
}, },
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
#{data => #{ #{
data => #{
username => <<"sha256">>, username => <<"sha256">>,
password_hash => <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>, password_hash =>
<<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>,
salt => <<"salt">>, salt => <<"salt">>,
is_superuser => 1 is_superuser => 1
}, },
@ -301,13 +333,16 @@ user_seeds() ->
}, },
config_params => #{ config_params => #{
selector => #{<<"username">> => <<"${clientid}">>}, selector => #{<<"username">> => <<"${clientid}">>},
password_hash_algorithm => #{name => <<"sha256">>, password_hash_algorithm => #{
salt_position => <<"prefix">>} name => <<"sha256">>,
salt_position => <<"prefix">>
}
}, },
result => {ok,#{is_superuser => true}} result => {ok, #{is_superuser => true}}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt">>, username => <<"bcrypt">>,
password_hash => password_hash =>
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
@ -321,10 +356,11 @@ user_seeds() ->
config_params => #{ config_params => #{
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt0">>, username => <<"bcrypt0">>,
password_hash => password_hash =>
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
@ -340,10 +376,11 @@ user_seeds() ->
selector => #{<<"username">> => <<"${clientid}">>}, selector => #{<<"username">> => <<"${clientid}">>},
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {error,not_authorized} result => {error, not_authorized}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt1">>, username => <<"bcrypt1">>,
password_hash => password_hash =>
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
@ -358,10 +395,11 @@ user_seeds() ->
selector => #{<<"userid">> => <<"${clientid}">>}, selector => #{<<"userid">> => <<"${clientid}">>},
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {error,not_authorized} result => {error, not_authorized}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt2">>, username => <<"bcrypt2">>,
password_hash => password_hash =>
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
@ -376,7 +414,7 @@ user_seeds() ->
config_params => #{ config_params => #{
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {error,bad_username_or_password} result => {error, bad_username_or_password}
} }
]. ].
@ -390,7 +428,7 @@ drop_seeds() ->
ok. ok.
mongo_server() -> mongo_server() ->
iolist_to_binary(io_lib:format("~s",[?MONGO_HOST])). iolist_to_binary(io_lib:format("~s", [?MONGO_HOST])).
mongo_config() -> mongo_config() ->
[ [

View File

@ -25,7 +25,6 @@
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("snabbkaffe/include/snabbkaffe.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl").
-define(MONGO_HOST, "mongo-tls"). -define(MONGO_HOST, "mongo-tls").
-define(PATH, [authentication]). -define(PATH, [authentication]).
@ -38,7 +37,8 @@ init_per_testcase(_TestCase, Config) ->
emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authentication:initialize_authentication(?GLOBAL, []),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
Config. Config.
init_per_suite(Config) -> init_per_suite(Config) ->
@ -55,7 +55,8 @@ init_per_suite(Config) ->
end_per_suite(_Config) -> end_per_suite(_Config) ->
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
ok = stop_apps([emqx_resource, emqx_connector]), ok = stop_apps([emqx_resource, emqx_connector]),
ok = emqx_common_test_helpers:stop_apps([emqx_authn]). ok = emqx_common_test_helpers:stop_apps([emqx_authn]).
@ -73,32 +74,42 @@ end_per_suite(_Config) ->
t_create(_Config) -> t_create(_Config) ->
?check_trace( ?check_trace(
create_mongo_auth_with_ssl_opts( create_mongo_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.2">>], <<"versions">> => [<<"tlsv1.2">>],
<<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]}), <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]
}
),
fun({ok, _}, Trace) -> fun({ok, _}, Trace) ->
?assertMatch( ?assertMatch(
[ok | _], [ok | _],
?projection( ?projection(
status, status,
?of_kind(emqx_connector_mongo_health_check, Trace))) ?of_kind(emqx_connector_mongo_health_check, Trace)
end). )
)
end
).
t_create_invalid_server_name(_Config) -> t_create_invalid_server_name(_Config) ->
?check_trace( ?check_trace(
create_mongo_auth_with_ssl_opts( create_mongo_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server-unknown-host">>, #{
<<"verify">> => <<"verify_peer">>}), <<"server_name_indication">> => <<"authn-server-unknown-host">>,
<<"verify">> => <<"verify_peer">>
}
),
fun(_, Trace) -> fun(_, Trace) ->
?assertNotEqual( ?assertNotEqual(
[ok], [ok],
?projection( ?projection(
status, status,
?of_kind(emqx_connector_mongo_health_check, Trace))) ?of_kind(emqx_connector_mongo_health_check, Trace)
end). )
)
end
).
%% docker-compose-mongo-single-tls.yaml: %% docker-compose-mongo-single-tls.yaml:
%% --tlsDisabledProtocols TLS1_0,TLS1_1 %% --tlsDisabledProtocols TLS1_0,TLS1_1
@ -106,17 +117,22 @@ t_create_invalid_server_name(_Config) ->
t_create_invalid_version(_Config) -> t_create_invalid_version(_Config) ->
?check_trace( ?check_trace(
create_mongo_auth_with_ssl_opts( create_mongo_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.1">>]}), <<"versions">> => [<<"tlsv1.1">>]
}
),
fun(_, Trace) -> fun(_, Trace) ->
?assertNotEqual( ?assertNotEqual(
[ok], [ok],
?projection( ?projection(
status, status,
?of_kind(emqx_connector_mongo_health_check, Trace))) ?of_kind(emqx_connector_mongo_health_check, Trace)
end). )
)
end
).
%% docker-compose-mongo-single-tls.yaml: %% docker-compose-mongo-single-tls.yaml:
%% --setParameter opensslCipherConfig='HIGH:!EXPORT:!aNULL:!DHE:!kDHE@STRENGTH' %% --setParameter opensslCipherConfig='HIGH:!EXPORT:!aNULL:!DHE:!kDHE@STRENGTH'
@ -124,17 +140,23 @@ t_create_invalid_version(_Config) ->
t_invalid_ciphers(_Config) -> t_invalid_ciphers(_Config) ->
?check_trace( ?check_trace(
create_mongo_auth_with_ssl_opts( create_mongo_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.2">>], <<"versions">> => [<<"tlsv1.2">>],
<<"ciphers">> => [<<"DHE-RSA-AES256-GCM-SHA384">>]}), <<"ciphers">> => [<<"DHE-RSA-AES256-GCM-SHA384">>]
}
),
fun(_, Trace) -> fun(_, Trace) ->
?assertNotEqual( ?assertNotEqual(
[ok], [ok],
?projection( ?projection(
status, status,
?of_kind(emqx_connector_mongo_health_check, Trace))) ?of_kind(emqx_connector_mongo_health_check, Trace)
end). )
)
end
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -149,11 +171,14 @@ create_mongo_auth_with_ssl_opts(SpecificSSLOpts) ->
raw_mongo_auth_config(SpecificSSLOpts) -> raw_mongo_auth_config(SpecificSSLOpts) ->
SSLOpts = maps:merge( SSLOpts = maps:merge(
emqx_authn_test_lib:client_ssl_cert_opts(), emqx_authn_test_lib:client_ssl_cert_opts(),
#{enable => <<"true">>}), #{enable => <<"true">>}
),
#{ #{
mechanism => <<"password_based">>, mechanism => <<"password_based">>,
password_hash_algorithm => #{name => <<"plain">>, password_hash_algorithm => #{
salt_position => <<"suffix">>}, name => <<"plain">>,
salt_position => <<"suffix">>
},
enable => <<"true">>, enable => <<"true">>,
backend => <<"mongodb">>, backend => <<"mongodb">>,
@ -176,7 +201,7 @@ raw_mongo_auth_config(SpecificSSLOpts) ->
}. }.
mongo_server() -> mongo_server() ->
iolist_to_binary(io_lib:format("~s",[?MONGO_HOST])). iolist_to_binary(io_lib:format("~s", [?MONGO_HOST])).
start_apps(Apps) -> start_apps(Apps) ->
lists:foreach(fun application:ensure_all_started/1, Apps). lists:foreach(fun application:ensure_all_started/1, Apps).

View File

@ -21,28 +21,36 @@
-include_lib("emqx/include/emqx_mqtt.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl").
%% API %% API
-export([start_link/2, -export([
stop/1]). start_link/2,
stop/1
]).
-export([send/2]). -export([send/2]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, -export([
init/1,
handle_call/3, handle_call/3,
handle_cast/2, handle_cast/2,
handle_info/2, handle_info/2,
terminate/2]). terminate/2
]).
-define(TIMEOUT, 1000). -define(TIMEOUT, 1000).
-define(TCP_OPTIONS, [binary, {packet, raw}, {active, once}, -define(TCP_OPTIONS, [
{nodelay, true}]). binary,
{packet, raw},
{active, once},
{nodelay, true}
]).
-define(PARSE_OPTIONS, -define(PARSE_OPTIONS, #{
#{strict_mode => false, strict_mode => false,
max_size => ?MAX_PACKET_SIZE, max_size => ?MAX_PACKET_SIZE,
version => ?MQTT_PROTO_V5 version => ?MQTT_PROTO_V5
}). }).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
@ -63,26 +71,30 @@ send(Pid, Packet) ->
init([Host, Port, Owner]) -> init([Host, Port, Owner]) ->
{ok, Socket} = gen_tcp:connect(Host, Port, ?TCP_OPTIONS, ?TIMEOUT), {ok, Socket} = gen_tcp:connect(Host, Port, ?TCP_OPTIONS, ?TIMEOUT),
{ok, #{owner => Owner, {ok, #{
owner => Owner,
socket => Socket, socket => Socket,
parse_state => emqx_frame:initial_parse_state(?PARSE_OPTIONS) parse_state => emqx_frame:initial_parse_state(?PARSE_OPTIONS)
}}. }}.
handle_info({tcp, _Sock, Data}, #{parse_state := PSt, handle_info(
{tcp, _Sock, Data},
#{
parse_state := PSt,
owner := Owner, owner := Owner,
socket := Socket} = St) -> socket := Socket
} = St
) ->
{NewPSt, Packets} = process_incoming(PSt, Data, []), {NewPSt, Packets} = process_incoming(PSt, Data, []),
ok = deliver(Owner, Packets), ok = deliver(Owner, Packets),
ok = run_sock(Socket), ok = run_sock(Socket),
{noreply, St#{parse_state => NewPSt}}; {noreply, St#{parse_state => NewPSt}};
handle_info({tcp_closed, _Sock}, St) -> handle_info({tcp_closed, _Sock}, St) ->
{stop, normal, St}. {stop, normal, St}.
handle_call({send, Packet}, _From, #{socket := Socket} = St) -> handle_call({send, Packet}, _From, #{socket := Socket} = St) ->
ok = gen_tcp:send(Socket, emqx_frame:serialize(Packet, ?MQTT_PROTO_V5)), ok = gen_tcp:send(Socket, emqx_frame:serialize(Packet, ?MQTT_PROTO_V5)),
{reply, ok, St}; {reply, ok, St};
handle_call(stop, _From, #{socket := Socket} = St) -> handle_call(stop, _From, #{socket := Socket} = St) ->
ok = gen_tcp:close(Socket), ok = gen_tcp:close(Socket),
{stop, normal, ok, St}. {stop, normal, ok, St}.
@ -105,11 +117,11 @@ process_incoming(PSt, Data, Packets) ->
process_incoming(NewPSt, Rest, [Packet | Packets]) process_incoming(NewPSt, Rest, [Packet | Packets])
end. end.
deliver(_Owner, []) -> ok; deliver(_Owner, []) ->
ok;
deliver(Owner, [Packet | Packets]) -> deliver(Owner, [Packet | Packets]) ->
Owner ! {packet, Packet}, Owner ! {packet, Packet},
deliver(Owner, Packets). deliver(Owner, Packets).
run_sock(Socket) -> run_sock(Socket) ->
inet:setopts(Socket, [{active, once}]). inet:setopts(Socket, [{active, once}]).

View File

@ -41,7 +41,8 @@ init_per_testcase(_, Config) ->
emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authentication:initialize_authentication(?GLOBAL, []),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
Config. Config.
init_per_group(require_seeds, Config) -> init_per_group(require_seeds, Config) ->
@ -63,7 +64,8 @@ init_per_suite(Config) ->
?RESOURCE_GROUP, ?RESOURCE_GROUP,
emqx_connector_mysql, emqx_connector_mysql,
mysql_config(), mysql_config(),
#{}), #{}
),
Config; Config;
false -> false ->
{skip, no_mysql} {skip, no_mysql}
@ -72,7 +74,8 @@ init_per_suite(Config) ->
end_per_suite(_Config) -> end_per_suite(_Config) ->
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
ok = emqx_resource:remove_local(?MYSQL_RESOURCE), ok = emqx_resource:remove_local(?MYSQL_RESOURCE),
ok = stop_apps([emqx_resource, emqx_connector]), ok = stop_apps([emqx_resource, emqx_connector]),
ok = emqx_common_test_helpers:stop_apps([emqx_authn]). ok = emqx_common_test_helpers:stop_apps([emqx_authn]).
@ -86,7 +89,8 @@ t_create(_Config) ->
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
{ok, [#{provider := emqx_authn_mysql}]} = emqx_authentication:list_authenticators(?GLOBAL), {ok, [#{provider := emqx_authn_mysql}]} = emqx_authentication:list_authenticators(?GLOBAL),
emqx_authn_test_lib:delete_config(?ResourceID). emqx_authn_test_lib:delete_config(?ResourceID).
@ -106,11 +110,13 @@ t_create_invalid(_Config) ->
fun(Config) -> fun(Config) ->
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, Config}), {create_authenticator, ?GLOBAL, Config}
),
emqx_authn_test_lib:delete_config(?ResourceID), emqx_authn_test_lib:delete_config(?ResourceID),
{ok, _} = emqx_authentication:list_authenticators(?GLOBAL) {ok, _} = emqx_authentication:list_authenticators(?GLOBAL)
end, end,
InvalidConfigs). InvalidConfigs
).
t_authenticate(_Config) -> t_authenticate(_Config) ->
ok = lists:foreach( ok = lists:foreach(
@ -118,16 +124,20 @@ t_authenticate(_Config) ->
ct:pal("test_user_auth sample: ~p", [Sample]), ct:pal("test_user_auth sample: ~p", [Sample]),
test_user_auth(Sample) test_user_auth(Sample)
end, end,
user_seeds()). user_seeds()
).
test_user_auth(#{credentials := Credentials0, test_user_auth(#{
credentials := Credentials0,
config_params := SpecificConfigParams, config_params := SpecificConfigParams,
result := Result}) -> result := Result
}) ->
AuthConfig = maps:merge(raw_mysql_auth_config(), SpecificConfigParams), AuthConfig = maps:merge(raw_mysql_auth_config(), SpecificConfigParams),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
Credentials = Credentials0#{ Credentials = Credentials0#{
listener => 'tcp:default', listener => 'tcp:default',
@ -138,66 +148,84 @@ test_user_auth(#{credentials := Credentials0,
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL). ?GLOBAL
).
t_destroy(_Config) -> t_destroy(_Config) ->
AuthConfig = raw_mysql_auth_config(), AuthConfig = raw_mysql_auth_config(),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
{ok, [#{provider := emqx_authn_mysql, state := State}]} {ok, [#{provider := emqx_authn_mysql, state := State}]} =
= emqx_authentication:list_authenticators(?GLOBAL), emqx_authentication:list_authenticators(?GLOBAL),
{ok, _} = emqx_authn_mysql:authenticate( {ok, _} = emqx_authn_mysql:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">> password => <<"plain">>
}, },
State), State
),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
% Authenticator should not be usable anymore % Authenticator should not be usable anymore
?assertMatch( ?assertMatch(
ignore, ignore,
emqx_authn_mysql:authenticate( emqx_authn_mysql:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">> password => <<"plain">>
}, },
State)). State
)
).
t_update(_Config) -> t_update(_Config) ->
CorrectConfig = raw_mysql_auth_config(), CorrectConfig = raw_mysql_auth_config(),
IncorrectConfig = IncorrectConfig =
CorrectConfig#{ CorrectConfig#{
query => <<"SELECT password_hash, salt, is_superuser_str as is_superuser query =>
FROM wrong_table where username = ${username} LIMIT 1">>}, <<
"SELECT password_hash, salt, is_superuser_str as is_superuser\n"
" FROM wrong_table where username = ${username} LIMIT 1"
>>
},
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, IncorrectConfig}), {create_authenticator, ?GLOBAL, IncorrectConfig}
),
{error, not_authorized} = emqx_access_control:authenticate( {error, not_authorized} = emqx_access_control:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">>, password => <<"plain">>,
listener => 'tcp:default', listener => 'tcp:default',
protocol => mqtt protocol => mqtt
}), }
),
% We update with config with correct query, provider should update and work properly % We update with config with correct query, provider should update and work properly
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{update_authenticator, ?GLOBAL, <<"password_based:mysql">>, CorrectConfig}), {update_authenticator, ?GLOBAL, <<"password_based:mysql">>, CorrectConfig}
),
{ok,_} = emqx_access_control:authenticate( {ok, _} = emqx_access_control:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">>, password => <<"plain">>,
listener => 'tcp:default', listener => 'tcp:default',
protocol => mqtt protocol => mqtt
}). }
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -206,8 +234,10 @@ t_update(_Config) ->
raw_mysql_auth_config() -> raw_mysql_auth_config() ->
#{ #{
mechanism => <<"password_based">>, mechanism => <<"password_based">>,
password_hash_algorithm => #{name => <<"plain">>, password_hash_algorithm => #{
salt_position => <<"suffix">>}, name => <<"plain">>,
salt_position => <<"suffix">>
},
enable => <<"true">>, enable => <<"true">>,
backend => <<"mysql">>, backend => <<"mysql">>,
@ -215,13 +245,18 @@ raw_mysql_auth_config() ->
username => <<"root">>, username => <<"root">>,
password => <<"public">>, password => <<"public">>,
query => <<"SELECT password_hash, salt, is_superuser_str as is_superuser query =>
FROM users where username = ${username} LIMIT 1">>, <<
"SELECT password_hash, salt, is_superuser_str as is_superuser\n"
" FROM users where username = ${username} LIMIT 1"
>>,
server => mysql_server() server => mysql_server()
}. }.
user_seeds() -> user_seeds() ->
[#{data => #{ [
#{
data => #{
username => "plain", username => "plain",
password_hash => "plainsalt", password_hash => "plainsalt",
salt => "salt", salt => "salt",
@ -229,12 +264,14 @@ user_seeds() ->
}, },
credentials => #{ credentials => #{
username => <<"plain">>, username => <<"plain">>,
password => <<"plain">>}, password => <<"plain">>
},
config_params => #{}, config_params => #{},
result => {ok,#{is_superuser => true}} result => {ok, #{is_superuser => true}}
}, },
#{data => #{ #{
data => #{
username => "md5", username => "md5",
password_hash => "9b4d0c43d206d48279e69b9ad7132e22", password_hash => "9b4d0c43d206d48279e69b9ad7132e22",
salt => "salt", salt => "salt",
@ -245,13 +282,16 @@ user_seeds() ->
password => <<"md5">> password => <<"md5">>
}, },
config_params => #{ config_params => #{
password_hash_algorithm => #{name => <<"md5">>, password_hash_algorithm => #{
salt_position => <<"suffix">>} name => <<"md5">>,
salt_position => <<"suffix">>
}
}, },
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
#{data => #{ #{
data => #{
username => "sha256", username => "sha256",
password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf", password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf",
salt => "salt", salt => "salt",
@ -262,15 +302,21 @@ user_seeds() ->
password => <<"sha256">> password => <<"sha256">>
}, },
config_params => #{ config_params => #{
query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser query =>
FROM users where username = ${clientid} LIMIT 1">>, <<
password_hash_algorithm => #{name => <<"sha256">>, "SELECT password_hash, salt, is_superuser_int as is_superuser\n"
salt_position => <<"prefix">>} " FROM users where username = ${clientid} LIMIT 1"
>>,
password_hash_algorithm => #{
name => <<"sha256">>,
salt_position => <<"prefix">>
}
}, },
result => {ok,#{is_superuser => true}} result => {ok, #{is_superuser => true}}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt">>, username => <<"bcrypt">>,
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
@ -281,14 +327,18 @@ user_seeds() ->
password => <<"bcrypt">> password => <<"bcrypt">>
}, },
config_params => #{ config_params => #{
query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser query =>
FROM users where username = ${username} LIMIT 1">>, <<
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
" FROM users where username = ${username} LIMIT 1"
>>,
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt">>, username => <<"bcrypt">>,
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve" salt => "$2b$12$wtY3h20mUjjmeaClpqZVve"
@ -298,14 +348,18 @@ user_seeds() ->
password => <<"bcrypt">> password => <<"bcrypt">>
}, },
config_params => #{ config_params => #{
query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser query =>
FROM users where username = ${username} LIMIT 1">>, <<
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
" FROM users where username = ${username} LIMIT 1"
>>,
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt0">>, username => <<"bcrypt0">>,
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
@ -317,14 +371,18 @@ user_seeds() ->
}, },
config_params => #{ config_params => #{
% clientid variable & username credentials % clientid variable & username credentials
query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser query =>
FROM users where username = ${clientid} LIMIT 1">>, <<
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
" FROM users where username = ${clientid} LIMIT 1"
>>,
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {error,not_authorized} result => {error, not_authorized}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt1">>, username => <<"bcrypt1">>,
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
@ -336,14 +394,18 @@ user_seeds() ->
}, },
config_params => #{ config_params => #{
% Bad keys in query % Bad keys in query
query => <<"SELECT 1 AS unknown_field query =>
FROM users where username = ${username} LIMIT 1">>, <<
"SELECT 1 AS unknown_field\n"
" FROM users where username = ${username} LIMIT 1"
>>,
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {error,not_authorized} result => {error, not_authorized}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt2">>, username => <<"bcrypt2">>,
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
@ -357,21 +419,24 @@ user_seeds() ->
config_params => #{ config_params => #{
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {error,bad_username_or_password} result => {error, bad_username_or_password}
} }
]. ].
init_seeds() -> init_seeds() ->
ok = drop_seeds(), ok = drop_seeds(),
ok = q("CREATE TABLE users( ok = q(
username VARCHAR(255), "CREATE TABLE users(\n"
password_hash VARCHAR(255), " username VARCHAR(255),\n"
salt VARCHAR(255), " password_hash VARCHAR(255),\n"
is_superuser_str VARCHAR(255), " salt VARCHAR(255),\n"
is_superuser_int TINYINT)"), " is_superuser_str VARCHAR(255),\n"
" is_superuser_int TINYINT)"
),
Fields = [username, password_hash, salt, is_superuser_str, is_superuser_int], Fields = [username, password_hash, salt, is_superuser_str, is_superuser_int],
InsertQuery = "INSERT INTO users(username, password_hash, salt, " InsertQuery =
"INSERT INTO users(username, password_hash, salt, "
" is_superuser_str, is_superuser_int) VALUES(?, ?, ?, ?, ?)", " is_superuser_str, is_superuser_int) VALUES(?, ?, ?, ?, ?)",
lists:foreach( lists:foreach(
@ -379,26 +444,30 @@ init_seeds() ->
Params = [maps:get(F, Values, null) || F <- Fields], Params = [maps:get(F, Values, null) || F <- Fields],
ok = q(InsertQuery, Params) ok = q(InsertQuery, Params)
end, end,
user_seeds()). user_seeds()
).
q(Sql) -> q(Sql) ->
emqx_resource:query( emqx_resource:query(
?MYSQL_RESOURCE, ?MYSQL_RESOURCE,
{sql, Sql}). {sql, Sql}
).
q(Sql, Params) -> q(Sql, Params) ->
emqx_resource:query( emqx_resource:query(
?MYSQL_RESOURCE, ?MYSQL_RESOURCE,
{sql, Sql, Params}). {sql, Sql, Params}
).
drop_seeds() -> drop_seeds() ->
ok = q("DROP TABLE IF EXISTS users"). ok = q("DROP TABLE IF EXISTS users").
mysql_server() -> mysql_server() ->
iolist_to_binary(io_lib:format("~s",[?MYSQL_HOST])). iolist_to_binary(io_lib:format("~s", [?MYSQL_HOST])).
mysql_config() -> mysql_config() ->
#{auto_reconnect => true, #{
auto_reconnect => true,
database => <<"mqtt">>, database => <<"mqtt">>,
username => <<"root">>, username => <<"root">>,
password => <<"public">>, password => <<"public">>,

View File

@ -40,7 +40,8 @@ init_per_testcase(_, Config) ->
emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authentication:initialize_authentication(?GLOBAL, []),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
Config. Config.
init_per_suite(Config) -> init_per_suite(Config) ->
@ -57,7 +58,8 @@ init_per_suite(Config) ->
end_per_suite(_Config) -> end_per_suite(_Config) ->
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
ok = stop_apps([emqx_resource, emqx_connector]), ok = stop_apps([emqx_resource, emqx_connector]),
ok = emqx_common_test_helpers:stop_apps([emqx_authn]). ok = emqx_common_test_helpers:stop_apps([emqx_authn]).
@ -72,36 +74,51 @@ t_create(_Config) ->
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
create_mysql_auth_with_ssl_opts( create_mysql_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.2">>], <<"versions">> => [<<"tlsv1.2">>],
<<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]})). <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]
}
)
).
t_create_invalid(_Config) -> t_create_invalid(_Config) ->
%% invalid server_name %% invalid server_name
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
create_mysql_auth_with_ssl_opts( create_mysql_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server-unknown-host">>, #{
<<"verify">> => <<"verify_peer">>})), <<"server_name_indication">> => <<"authn-server-unknown-host">>,
<<"verify">> => <<"verify_peer">>
}
)
),
emqx_authn_test_lib:delete_config(?ResourceID), emqx_authn_test_lib:delete_config(?ResourceID),
%% incompatible versions %% incompatible versions
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
create_mysql_auth_with_ssl_opts( create_mysql_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.1">>]})), <<"versions">> => [<<"tlsv1.1">>]
}
)
),
emqx_authn_test_lib:delete_config(?ResourceID), emqx_authn_test_lib:delete_config(?ResourceID),
%% incompatible ciphers %% incompatible ciphers
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
create_mysql_auth_with_ssl_opts( create_mysql_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.2">>], <<"versions">> => [<<"tlsv1.2">>],
<<"ciphers">> => [<<"ECDHE-ECDSA-AES128-GCM-SHA256">>]})). <<"ciphers">> => [<<"ECDHE-ECDSA-AES128-GCM-SHA256">>]
}
)
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -114,11 +131,14 @@ create_mysql_auth_with_ssl_opts(SpecificSSLOpts) ->
raw_mysql_auth_config(SpecificSSLOpts) -> raw_mysql_auth_config(SpecificSSLOpts) ->
SSLOpts = maps:merge( SSLOpts = maps:merge(
emqx_authn_test_lib:client_ssl_cert_opts(), emqx_authn_test_lib:client_ssl_cert_opts(),
#{enable => <<"true">>}), #{enable => <<"true">>}
),
#{ #{
mechanism => <<"password_based">>, mechanism => <<"password_based">>,
password_hash_algorithm => #{name => <<"plain">>, password_hash_algorithm => #{
salt_position => <<"suffix">>}, name => <<"plain">>,
salt_position => <<"suffix">>
},
enable => <<"true">>, enable => <<"true">>,
backend => <<"mysql">>, backend => <<"mysql">>,
@ -126,14 +146,17 @@ raw_mysql_auth_config(SpecificSSLOpts) ->
username => <<"root">>, username => <<"root">>,
password => <<"public">>, password => <<"public">>,
query => <<"SELECT password_hash, salt, is_superuser_str as is_superuser query =>
FROM users where username = ${username} LIMIT 1">>, <<
"SELECT password_hash, salt, is_superuser_str as is_superuser\n"
" FROM users where username = ${username} LIMIT 1"
>>,
server => mysql_server(), server => mysql_server(),
ssl => maps:merge(SSLOpts, SpecificSSLOpts) ssl => maps:merge(SSLOpts, SpecificSSLOpts)
}. }.
mysql_server() -> mysql_server() ->
iolist_to_binary(io_lib:format("~s",[?MYSQL_HOST])). iolist_to_binary(io_lib:format("~s", [?MYSQL_HOST])).
start_apps(Apps) -> start_apps(Apps) ->
lists:foreach(fun application:ensure_all_started/1, Apps). lists:foreach(fun application:ensure_all_started/1, Apps).

View File

@ -38,8 +38,9 @@ end_per_suite(_Config) ->
ok. ok.
t_gen_salt(_Config) -> t_gen_salt(_Config) ->
Algorithms = [#{name => Type, salt_position => suffix} || Type <- ?SIMPLE_HASHES] Algorithms =
++ [#{name => bcrypt, salt_rounds => 10}], [#{name => Type, salt_position => suffix} || Type <- ?SIMPLE_HASHES] ++
[#{name => bcrypt, salt_rounds => 10}],
lists:foreach( lists:foreach(
fun(Algorithm) -> fun(Algorithm) ->
@ -47,29 +48,35 @@ t_gen_salt(_Config) ->
ct:pal("gen_salt(~p): ~p", [Algorithm, Salt]), ct:pal("gen_salt(~p): ~p", [Algorithm, Salt]),
?assert(is_binary(Salt)) ?assert(is_binary(Salt))
end, end,
Algorithms). Algorithms
).
t_init(_Config) -> t_init(_Config) ->
Algorithms = [#{name => Type, salt_position => suffix} || Type <- ?SIMPLE_HASHES] Algorithms =
++ [#{name => bcrypt, salt_rounds => 10}], [#{name => Type, salt_position => suffix} || Type <- ?SIMPLE_HASHES] ++
[#{name => bcrypt, salt_rounds => 10}],
lists:foreach( lists:foreach(
fun(Algorithm) -> fun(Algorithm) ->
ok = emqx_authn_password_hashing:init(Algorithm) ok = emqx_authn_password_hashing:init(Algorithm)
end, end,
Algorithms). Algorithms
).
t_check_password(_Config) -> t_check_password(_Config) ->
lists:foreach( lists:foreach(
fun test_check_password/1, fun test_check_password/1,
hash_examples()). hash_examples()
).
test_check_password(#{ test_check_password(
#{
password_hash := Hash, password_hash := Hash,
salt := Salt, salt := Salt,
password := Password, password := Password,
password_hash_algorithm := Algorithm password_hash_algorithm := Algorithm
} = Sample) -> } = Sample
) ->
ct:pal("t_check_password sample: ~p", [Sample]), ct:pal("t_check_password sample: ~p", [Sample]),
true = emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, Password), true = emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, Password),
false = emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, <<"wrongpass">>). false = emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, <<"wrongpass">>).
@ -77,79 +84,104 @@ test_check_password(#{
t_hash(_Config) -> t_hash(_Config) ->
lists:foreach( lists:foreach(
fun test_hash/1, fun test_hash/1,
hash_examples()). hash_examples()
).
test_hash(#{password := Password, test_hash(
#{
password := Password,
password_hash_algorithm := Algorithm password_hash_algorithm := Algorithm
} = Sample) -> } = Sample
) ->
ct:pal("t_hash sample: ~p", [Sample]), ct:pal("t_hash sample: ~p", [Sample]),
{Hash, Salt} = emqx_authn_password_hashing:hash(Algorithm, Password), {Hash, Salt} = emqx_authn_password_hashing:hash(Algorithm, Password),
true = emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, Password). true = emqx_authn_password_hashing:check_password(Algorithm, Salt, Hash, Password).
hash_examples() -> hash_examples() ->
[#{ [
#{
password_hash => <<"plainsalt">>, password_hash => <<"plainsalt">>,
salt => <<"salt">>, salt => <<"salt">>,
password => <<"plain">>, password => <<"plain">>,
password_hash_algorithm => #{name => plain, password_hash_algorithm => #{
salt_position => suffix} name => plain,
salt_position => suffix
}
}, },
#{ #{
password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>, password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>,
salt => <<"salt">>, salt => <<"salt">>,
password => <<"md5">>, password => <<"md5">>,
password_hash_algorithm => #{name => md5, password_hash_algorithm => #{
salt_position => suffix} name => md5,
salt_position => suffix
}
}, },
#{ #{
password_hash => <<"c665d4c0a9e5498806b7d9fd0b417d272853660e">>, password_hash => <<"c665d4c0a9e5498806b7d9fd0b417d272853660e">>,
salt => <<"salt">>, salt => <<"salt">>,
password => <<"sha">>, password => <<"sha">>,
password_hash_algorithm => #{name => sha, password_hash_algorithm => #{
salt_position => prefix} name => sha,
salt_position => prefix
}
}, },
#{ #{
password_hash => <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>, password_hash => <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>,
salt => <<"salt">>, salt => <<"salt">>,
password => <<"sha256">>, password => <<"sha256">>,
password_hash_algorithm => #{name => sha256, password_hash_algorithm => #{
salt_position => prefix} name => sha256,
salt_position => prefix
}
}, },
#{ #{
password_hash => <<"a1509ab67bfacbad020927b5ac9d91e9100a82e33a0ebb01459367ce921c0aa8" password_hash => <<
"157aa5652f94bc84fa3babc08283e44887d61c48bcf8ad7bcb3259ee7d0eafcd">>, "a1509ab67bfacbad020927b5ac9d91e9100a82e33a0ebb01459367ce921c0aa8"
"157aa5652f94bc84fa3babc08283e44887d61c48bcf8ad7bcb3259ee7d0eafcd"
>>,
salt => <<"salt">>, salt => <<"salt">>,
password => <<"sha512">>, password => <<"sha512">>,
password_hash_algorithm => #{name => sha512, password_hash_algorithm => #{
salt_position => prefix} name => sha512,
salt_position => prefix
}
}, },
#{ #{
password_hash => <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, password_hash => <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
password => <<"bcrypt">>, password => <<"bcrypt">>,
password_hash_algorithm => #{name => bcrypt, password_hash_algorithm => #{
salt_rounds => 10} name => bcrypt,
salt_rounds => 10
}
}, },
#{ #{
password_hash => <<"01dbee7f4a9e243e988b62c73cda935d" password_hash => <<
"a05378b93244ec8f48a99e61ad799d86">>, "01dbee7f4a9e243e988b62c73cda935d"
"a05378b93244ec8f48a99e61ad799d86"
>>,
salt => <<"ATHENA.MIT.EDUraeburn">>, salt => <<"ATHENA.MIT.EDUraeburn">>,
password => <<"password">>, password => <<"password">>,
password_hash_algorithm => #{name => pbkdf2, password_hash_algorithm => #{
name => pbkdf2,
iterations => 2, iterations => 2,
dk_length => 32, dk_length => 32,
mac_fun => sha} mac_fun => sha
}
}, },
#{ #{
password_hash => <<"01dbee7f4a9e243e988b62c73cda935da05378b9">>, password_hash => <<"01dbee7f4a9e243e988b62c73cda935da05378b9">>,
salt => <<"ATHENA.MIT.EDUraeburn">>, salt => <<"ATHENA.MIT.EDUraeburn">>,
password => <<"password">>, password => <<"password">>,
password_hash_algorithm => #{name => pbkdf2, password_hash_algorithm => #{
name => pbkdf2,
iterations => 2, iterations => 2,
mac_fun => sha} mac_fun => sha
}
} }
]. ].

View File

@ -42,7 +42,8 @@ init_per_testcase(_, Config) ->
emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authentication:initialize_authentication(?GLOBAL, []),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
Config. Config.
init_per_group(require_seeds, Config) -> init_per_group(require_seeds, Config) ->
@ -64,7 +65,8 @@ init_per_suite(Config) ->
?RESOURCE_GROUP, ?RESOURCE_GROUP,
emqx_connector_pgsql, emqx_connector_pgsql,
pgsql_config(), pgsql_config(),
#{}), #{}
),
Config; Config;
false -> false ->
{skip, no_pgsql} {skip, no_pgsql}
@ -73,7 +75,8 @@ init_per_suite(Config) ->
end_per_suite(_Config) -> end_per_suite(_Config) ->
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
ok = emqx_resource:remove_local(?PGSQL_RESOURCE), ok = emqx_resource:remove_local(?PGSQL_RESOURCE),
ok = stop_apps([emqx_resource, emqx_connector]), ok = stop_apps([emqx_resource, emqx_connector]),
ok = emqx_common_test_helpers:stop_apps([emqx_authn]). ok = emqx_common_test_helpers:stop_apps([emqx_authn]).
@ -87,7 +90,8 @@ t_create(_Config) ->
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
{ok, [#{provider := emqx_authn_pgsql}]} = emqx_authentication:list_authenticators(?GLOBAL), {ok, [#{provider := emqx_authn_pgsql}]} = emqx_authentication:list_authenticators(?GLOBAL),
emqx_authn_test_lib:delete_config(?ResourceID). emqx_authn_test_lib:delete_config(?ResourceID).
@ -107,11 +111,13 @@ t_create_invalid(_Config) ->
fun(Config) -> fun(Config) ->
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, Config}), {create_authenticator, ?GLOBAL, Config}
),
emqx_authn_test_lib:delete_config(?ResourceID), emqx_authn_test_lib:delete_config(?ResourceID),
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL) {ok, []} = emqx_authentication:list_authenticators(?GLOBAL)
end, end,
InvalidConfigs). InvalidConfigs
).
t_authenticate(_Config) -> t_authenticate(_Config) ->
ok = lists:foreach( ok = lists:foreach(
@ -119,16 +125,20 @@ t_authenticate(_Config) ->
ct:pal("test_user_auth sample: ~p", [Sample]), ct:pal("test_user_auth sample: ~p", [Sample]),
test_user_auth(Sample) test_user_auth(Sample)
end, end,
user_seeds()). user_seeds()
).
test_user_auth(#{credentials := Credentials0, test_user_auth(#{
credentials := Credentials0,
config_params := SpecificConfigParams, config_params := SpecificConfigParams,
result := Result}) -> result := Result
}) ->
AuthConfig = maps:merge(raw_pgsql_auth_config(), SpecificConfigParams), AuthConfig = maps:merge(raw_pgsql_auth_config(), SpecificConfigParams),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
Credentials = Credentials0#{ Credentials = Credentials0#{
listener => 'tcp:default', listener => 'tcp:default',
@ -139,72 +149,91 @@ test_user_auth(#{credentials := Credentials0,
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL). ?GLOBAL
).
t_destroy(_Config) -> t_destroy(_Config) ->
AuthConfig = raw_pgsql_auth_config(), AuthConfig = raw_pgsql_auth_config(),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
{ok, [#{provider := emqx_authn_pgsql, state := State}]} {ok, [#{provider := emqx_authn_pgsql, state := State}]} =
= emqx_authentication:list_authenticators(?GLOBAL), emqx_authentication:list_authenticators(?GLOBAL),
{ok, _} = emqx_authn_pgsql:authenticate( {ok, _} = emqx_authn_pgsql:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">> password => <<"plain">>
}, },
State), State
),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
% Authenticator should not be usable anymore % Authenticator should not be usable anymore
?assertMatch( ?assertMatch(
ignore, ignore,
emqx_authn_pgsql:authenticate( emqx_authn_pgsql:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">> password => <<"plain">>
}, },
State)). State
)
).
t_update(_Config) -> t_update(_Config) ->
CorrectConfig = raw_pgsql_auth_config(), CorrectConfig = raw_pgsql_auth_config(),
IncorrectConfig = IncorrectConfig =
CorrectConfig#{ CorrectConfig#{
query => <<"SELECT password_hash, salt, is_superuser_str as is_superuser query =>
FROM users where username = ${username} LIMIT 0">>}, <<
"SELECT password_hash, salt, is_superuser_str as is_superuser\n"
" FROM users where username = ${username} LIMIT 0"
>>
},
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, IncorrectConfig}), {create_authenticator, ?GLOBAL, IncorrectConfig}
),
{error, not_authorized} = emqx_access_control:authenticate( {error, not_authorized} = emqx_access_control:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">>, password => <<"plain">>,
listener => 'tcp:default', listener => 'tcp:default',
protocol => mqtt protocol => mqtt
}), }
),
% We update with config with correct query, provider should update and work properly % We update with config with correct query, provider should update and work properly
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{update_authenticator, ?GLOBAL, <<"password_based:postgresql">>, CorrectConfig}), {update_authenticator, ?GLOBAL, <<"password_based:postgresql">>, CorrectConfig}
),
{ok,_} = emqx_access_control:authenticate( {ok, _} = emqx_access_control:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">>, password => <<"plain">>,
listener => 'tcp:default', listener => 'tcp:default',
protocol => mqtt protocol => mqtt
}). }
).
t_is_superuser(_Config) -> t_is_superuser(_Config) ->
Config = raw_pgsql_auth_config(), Config = raw_pgsql_auth_config(),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, Config}), {create_authenticator, ?GLOBAL, Config}
),
Checks = [ Checks = [
{is_superuser_str, "0", false}, {is_superuser_str, "0", false},
@ -237,13 +266,16 @@ test_is_superuser({Field, Value, ExpectedValue}) ->
ok = create_user(UserData), ok = create_user(UserData),
Query = "SELECT password_hash, salt, " ++ atom_to_list(Field) ++ " as is_superuser " Query =
"SELECT password_hash, salt, " ++ atom_to_list(Field) ++
" as is_superuser "
"FROM users where username = ${username} LIMIT 1", "FROM users where username = ${username} LIMIT 1",
Config = maps:put(query, Query, raw_pgsql_auth_config()), Config = maps:put(query, Query, raw_pgsql_auth_config()),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{update_authenticator, ?GLOBAL, <<"password_based:postgresql">>, Config}), {update_authenticator, ?GLOBAL, <<"password_based:postgresql">>, Config}
),
Credentials = #{ Credentials = #{
listener => 'tcp:default', listener => 'tcp:default',
@ -254,7 +286,8 @@ test_is_superuser({Field, Value, ExpectedValue}) ->
?assertEqual( ?assertEqual(
{ok, #{is_superuser => ExpectedValue}}, {ok, #{is_superuser => ExpectedValue}},
emqx_access_control:authenticate(Credentials)). emqx_access_control:authenticate(Credentials)
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -263,8 +296,10 @@ test_is_superuser({Field, Value, ExpectedValue}) ->
raw_pgsql_auth_config() -> raw_pgsql_auth_config() ->
#{ #{
mechanism => <<"password_based">>, mechanism => <<"password_based">>,
password_hash_algorithm => #{name => <<"plain">>, password_hash_algorithm => #{
salt_position => <<"suffix">>}, name => <<"plain">>,
salt_position => <<"suffix">>
},
enable => <<"true">>, enable => <<"true">>,
backend => <<"postgresql">>, backend => <<"postgresql">>,
@ -272,13 +307,18 @@ raw_pgsql_auth_config() ->
username => <<"root">>, username => <<"root">>,
password => <<"public">>, password => <<"public">>,
query => <<"SELECT password_hash, salt, is_superuser_str as is_superuser query =>
FROM users where username = ${username} LIMIT 1">>, <<
"SELECT password_hash, salt, is_superuser_str as is_superuser\n"
" FROM users where username = ${username} LIMIT 1"
>>,
server => pgsql_server() server => pgsql_server()
}. }.
user_seeds() -> user_seeds() ->
[#{data => #{ [
#{
data => #{
username => "plain", username => "plain",
password_hash => "plainsalt", password_hash => "plainsalt",
salt => "salt", salt => "salt",
@ -286,12 +326,14 @@ user_seeds() ->
}, },
credentials => #{ credentials => #{
username => <<"plain">>, username => <<"plain">>,
password => <<"plain">>}, password => <<"plain">>
},
config_params => #{}, config_params => #{},
result => {ok,#{is_superuser => true}} result => {ok, #{is_superuser => true}}
}, },
#{data => #{ #{
data => #{
username => "md5", username => "md5",
password_hash => "9b4d0c43d206d48279e69b9ad7132e22", password_hash => "9b4d0c43d206d48279e69b9ad7132e22",
salt => "salt", salt => "salt",
@ -302,13 +344,16 @@ user_seeds() ->
password => <<"md5">> password => <<"md5">>
}, },
config_params => #{ config_params => #{
password_hash_algorithm => #{name => <<"md5">>, password_hash_algorithm => #{
salt_position => <<"suffix">>} name => <<"md5">>,
salt_position => <<"suffix">>
}
}, },
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
#{data => #{ #{
data => #{
username => "sha256", username => "sha256",
password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf", password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf",
salt => "salt", salt => "salt",
@ -319,15 +364,21 @@ user_seeds() ->
password => <<"sha256">> password => <<"sha256">>
}, },
config_params => #{ config_params => #{
query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser query =>
FROM users where username = ${clientid} LIMIT 1">>, <<
password_hash_algorithm => #{name => <<"sha256">>, "SELECT password_hash, salt, is_superuser_int as is_superuser\n"
salt_position => <<"prefix">>} " FROM users where username = ${clientid} LIMIT 1"
>>,
password_hash_algorithm => #{
name => <<"sha256">>,
salt_position => <<"prefix">>
}
}, },
result => {ok,#{is_superuser => true}} result => {ok, #{is_superuser => true}}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt">>, username => <<"bcrypt">>,
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
@ -338,14 +389,18 @@ user_seeds() ->
password => <<"bcrypt">> password => <<"bcrypt">>
}, },
config_params => #{ config_params => #{
query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser query =>
FROM users where username = ${username} LIMIT 1">>, <<
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
" FROM users where username = ${username} LIMIT 1"
>>,
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt0">>, username => <<"bcrypt0">>,
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
@ -357,14 +412,18 @@ user_seeds() ->
}, },
config_params => #{ config_params => #{
% clientid variable & username credentials % clientid variable & username credentials
query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser query =>
FROM users where username = ${clientid} LIMIT 1">>, <<
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
" FROM users where username = ${clientid} LIMIT 1"
>>,
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {error,not_authorized} result => {error, not_authorized}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt1">>, username => <<"bcrypt1">>,
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
@ -376,14 +435,18 @@ user_seeds() ->
}, },
config_params => #{ config_params => #{
% Bad keys in query % Bad keys in query
query => <<"SELECT 1 AS unknown_field query =>
FROM users where username = ${username} LIMIT 1">>, <<
"SELECT 1 AS unknown_field\n"
" FROM users where username = ${username} LIMIT 1"
>>,
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {error,not_authorized} result => {error, not_authorized}
}, },
#{data => #{ #{
data => #{
username => <<"bcrypt2">>, username => <<"bcrypt2">>,
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u", password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve", salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
@ -397,30 +460,34 @@ user_seeds() ->
config_params => #{ config_params => #{
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {error,bad_username_or_password} result => {error, bad_username_or_password}
} }
]. ].
init_seeds() -> init_seeds() ->
ok = drop_seeds(), ok = drop_seeds(),
{ok, _, _} = q("CREATE TABLE users( {ok, _, _} = q(
username varchar(255), "CREATE TABLE users(\n"
password_hash varchar(255), " username varchar(255),\n"
salt varchar(255), " password_hash varchar(255),\n"
is_superuser_str varchar(255), " salt varchar(255),\n"
is_superuser_int smallint, " is_superuser_str varchar(255),\n"
is_superuser_bool boolean)"), " is_superuser_int smallint,\n"
" is_superuser_bool boolean)"
),
lists:foreach( lists:foreach(
fun(#{data := Values}) -> fun(#{data := Values}) ->
ok = create_user(Values) ok = create_user(Values)
end, end,
user_seeds()). user_seeds()
).
create_user(Values) -> create_user(Values) ->
Fields = [username, password_hash, salt, is_superuser_str, is_superuser_int, is_superuser_bool], Fields = [username, password_hash, salt, is_superuser_str, is_superuser_int, is_superuser_bool],
InsertQuery = "INSERT INTO users(username, password_hash, salt," InsertQuery =
"INSERT INTO users(username, password_hash, salt,"
"is_superuser_str, is_superuser_int, is_superuser_bool) " "is_superuser_str, is_superuser_int, is_superuser_bool) "
"VALUES($1, $2, $3, $4, $5, $6)", "VALUES($1, $2, $3, $4, $5, $6)",
@ -431,22 +498,25 @@ create_user(Values) ->
q(Sql) -> q(Sql) ->
emqx_resource:query( emqx_resource:query(
?PGSQL_RESOURCE, ?PGSQL_RESOURCE,
{query, Sql}). {query, Sql}
).
q(Sql, Params) -> q(Sql, Params) ->
emqx_resource:query( emqx_resource:query(
?PGSQL_RESOURCE, ?PGSQL_RESOURCE,
{query, Sql, Params}). {query, Sql, Params}
).
drop_seeds() -> drop_seeds() ->
{ok, _, _} = q("DROP TABLE IF EXISTS users"), {ok, _, _} = q("DROP TABLE IF EXISTS users"),
ok. ok.
pgsql_server() -> pgsql_server() ->
iolist_to_binary(io_lib:format("~s",[?PGSQL_HOST])). iolist_to_binary(io_lib:format("~s", [?PGSQL_HOST])).
pgsql_config() -> pgsql_config() ->
#{auto_reconnect => true, #{
auto_reconnect => true,
database => <<"mqtt">>, database => <<"mqtt">>,
username => <<"root">>, username => <<"root">>,
password => <<"public">>, password => <<"public">>,

View File

@ -40,7 +40,8 @@ init_per_testcase(_, Config) ->
emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authentication:initialize_authentication(?GLOBAL, []),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
Config. Config.
init_per_suite(Config) -> init_per_suite(Config) ->
@ -57,7 +58,8 @@ init_per_suite(Config) ->
end_per_suite(_Config) -> end_per_suite(_Config) ->
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
ok = stop_apps([emqx_resource, emqx_connector]), ok = stop_apps([emqx_resource, emqx_connector]),
ok = emqx_common_test_helpers:stop_apps([emqx_authn]). ok = emqx_common_test_helpers:stop_apps([emqx_authn]).
@ -72,36 +74,51 @@ t_create(_Config) ->
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
create_pgsql_auth_with_ssl_opts( create_pgsql_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.2">>], <<"versions">> => [<<"tlsv1.2">>],
<<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]})). <<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]
}
)
).
t_create_invalid(_Config) -> t_create_invalid(_Config) ->
%% invalid server_name %% invalid server_name
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
create_pgsql_auth_with_ssl_opts( create_pgsql_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server-unknown-host">>, #{
<<"verify">> => <<"verify_peer">>})), <<"server_name_indication">> => <<"authn-server-unknown-host">>,
<<"verify">> => <<"verify_peer">>
}
)
),
emqx_authn_test_lib:delete_config(?ResourceID), emqx_authn_test_lib:delete_config(?ResourceID),
%% incompatible versions %% incompatible versions
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
create_pgsql_auth_with_ssl_opts( create_pgsql_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.1">>]})), <<"versions">> => [<<"tlsv1.1">>]
}
)
),
emqx_authn_test_lib:delete_config(?ResourceID), emqx_authn_test_lib:delete_config(?ResourceID),
%% incompatible ciphers %% incompatible ciphers
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
create_pgsql_auth_with_ssl_opts( create_pgsql_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.2">>], <<"versions">> => [<<"tlsv1.2">>],
<<"ciphers">> => [<<"ECDHE-ECDSA-AES128-GCM-SHA256">>]})). <<"ciphers">> => [<<"ECDHE-ECDSA-AES128-GCM-SHA256">>]
}
)
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -114,11 +131,14 @@ create_pgsql_auth_with_ssl_opts(SpecificSSLOpts) ->
raw_pgsql_auth_config(SpecificSSLOpts) -> raw_pgsql_auth_config(SpecificSSLOpts) ->
SSLOpts = maps:merge( SSLOpts = maps:merge(
emqx_authn_test_lib:client_ssl_cert_opts(), emqx_authn_test_lib:client_ssl_cert_opts(),
#{enable => <<"true">>}), #{enable => <<"true">>}
),
#{ #{
mechanism => <<"password_based">>, mechanism => <<"password_based">>,
password_hash_algorithm => #{name => <<"plain">>, password_hash_algorithm => #{
salt_position => <<"suffix">>}, name => <<"plain">>,
salt_position => <<"suffix">>
},
enable => <<"true">>, enable => <<"true">>,
backend => <<"postgresql">>, backend => <<"postgresql">>,
@ -132,7 +152,7 @@ raw_pgsql_auth_config(SpecificSSLOpts) ->
}. }.
pgsql_server() -> pgsql_server() ->
iolist_to_binary(io_lib:format("~s",[?PGSQL_HOST])). iolist_to_binary(io_lib:format("~s", [?PGSQL_HOST])).
start_apps(Apps) -> start_apps(Apps) ->
lists:foreach(fun application:ensure_all_started/1, Apps). lists:foreach(fun application:ensure_all_started/1, Apps).

View File

@ -41,7 +41,8 @@ init_per_testcase(_, Config) ->
emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authentication:initialize_authentication(?GLOBAL, []),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
Config. Config.
init_per_group(require_seeds, Config) -> init_per_group(require_seeds, Config) ->
@ -63,7 +64,8 @@ init_per_suite(Config) ->
?RESOURCE_GROUP, ?RESOURCE_GROUP,
emqx_connector_redis, emqx_connector_redis,
redis_config(), redis_config(),
#{}), #{}
),
Config; Config;
false -> false ->
{skip, no_redis} {skip, no_redis}
@ -72,7 +74,8 @@ init_per_suite(Config) ->
end_per_suite(_Config) -> end_per_suite(_Config) ->
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
ok = emqx_resource:remove_local(?REDIS_RESOURCE), ok = emqx_resource:remove_local(?REDIS_RESOURCE),
ok = stop_apps([emqx_resource, emqx_connector]), ok = stop_apps([emqx_resource, emqx_connector]),
ok = emqx_common_test_helpers:stop_apps([emqx_authn]). ok = emqx_common_test_helpers:stop_apps([emqx_authn]).
@ -87,7 +90,8 @@ t_create(_Config) ->
AuthConfig = raw_redis_auth_config(), AuthConfig = raw_redis_auth_config(),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
{ok, [#{provider := emqx_authn_redis}]} = emqx_authentication:list_authenticators(?GLOBAL). {ok, [#{provider := emqx_authn_redis}]} = emqx_authentication:list_authenticators(?GLOBAL).
@ -96,21 +100,26 @@ t_create_invalid(_Config) ->
InvalidConfigs = InvalidConfigs =
[ [
AuthConfig#{ AuthConfig#{
cmd => <<"MGET password_hash:${username} salt:${username}">>}, cmd => <<"MGET password_hash:${username} salt:${username}">>
},
AuthConfig#{ AuthConfig#{
cmd => <<"HMGET mqtt_user:${username} password_hash invalid_field">>}, cmd => <<"HMGET mqtt_user:${username} password_hash invalid_field">>
},
AuthConfig#{ AuthConfig#{
cmd => <<"HMGET mqtt_user:${username} salt is_superuser">>} cmd => <<"HMGET mqtt_user:${username} salt is_superuser">>
}
], ],
lists:foreach( lists:foreach(
fun(Config) -> fun(Config) ->
{error, _} = emqx:update_config( {error, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, Config}), {create_authenticator, ?GLOBAL, Config}
),
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL) {ok, []} = emqx_authentication:list_authenticators(?GLOBAL)
end, end,
InvalidConfigs), InvalidConfigs
),
InvalidConfigs1 = InvalidConfigs1 =
[ [
@ -124,11 +133,13 @@ t_create_invalid(_Config) ->
fun(Config) -> fun(Config) ->
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, Config}), {create_authenticator, ?GLOBAL, Config}
),
emqx_authn_test_lib:delete_config(?ResourceID), emqx_authn_test_lib:delete_config(?ResourceID),
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL) {ok, []} = emqx_authentication:list_authenticators(?GLOBAL)
end, end,
InvalidConfigs1). InvalidConfigs1
).
t_authenticate(_Config) -> t_authenticate(_Config) ->
ok = lists:foreach( ok = lists:foreach(
@ -136,16 +147,20 @@ t_authenticate(_Config) ->
ct:pal("test_user_auth sample: ~p", [Sample]), ct:pal("test_user_auth sample: ~p", [Sample]),
test_user_auth(Sample) test_user_auth(Sample)
end, end,
user_seeds()). user_seeds()
).
test_user_auth(#{credentials := Credentials0, test_user_auth(#{
credentials := Credentials0,
config_params := SpecificConfigParams, config_params := SpecificConfigParams,
result := Result}) -> result := Result
}) ->
AuthConfig = maps:merge(raw_redis_auth_config(), SpecificConfigParams), AuthConfig = maps:merge(raw_redis_auth_config(), SpecificConfigParams),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
Credentials = Credentials0#{ Credentials = Credentials0#{
listener => 'tcp:default', listener => 'tcp:default',
@ -156,65 +171,80 @@ test_user_auth(#{credentials := Credentials0,
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL). ?GLOBAL
).
t_destroy(_Config) -> t_destroy(_Config) ->
AuthConfig = raw_redis_auth_config(), AuthConfig = raw_redis_auth_config(),
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}), {create_authenticator, ?GLOBAL, AuthConfig}
),
{ok, [#{provider := emqx_authn_redis, state := State}]} {ok, [#{provider := emqx_authn_redis, state := State}]} =
= emqx_authentication:list_authenticators(?GLOBAL), emqx_authentication:list_authenticators(?GLOBAL),
{ok, _} = emqx_authn_redis:authenticate( {ok, _} = emqx_authn_redis:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">> password => <<"plain">>
}, },
State), State
),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
% Authenticator should not be usable anymore % Authenticator should not be usable anymore
?assertMatch( ?assertMatch(
ignore, ignore,
emqx_authn_redis:authenticate( emqx_authn_redis:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">> password => <<"plain">>
}, },
State)). State
)
).
t_update(_Config) -> t_update(_Config) ->
CorrectConfig = raw_redis_auth_config(), CorrectConfig = raw_redis_auth_config(),
IncorrectConfig = IncorrectConfig =
CorrectConfig#{ CorrectConfig#{
cmd => <<"HMGET invalid_key:${username} password_hash salt is_superuser">>}, cmd => <<"HMGET invalid_key:${username} password_hash salt is_superuser">>
},
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, IncorrectConfig}), {create_authenticator, ?GLOBAL, IncorrectConfig}
),
{error, not_authorized} = emqx_access_control:authenticate( {error, not_authorized} = emqx_access_control:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">>, password => <<"plain">>,
listener => 'tcp:default', listener => 'tcp:default',
protocol => mqtt protocol => mqtt
}), }
),
% We update with config with correct query, provider should update and work properly % We update with config with correct query, provider should update and work properly
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{update_authenticator, ?GLOBAL, <<"password_based:redis">>, CorrectConfig}), {update_authenticator, ?GLOBAL, <<"password_based:redis">>, CorrectConfig}
),
{ok,_} = emqx_access_control:authenticate( {ok, _} = emqx_access_control:authenticate(
#{username => <<"plain">>, #{
username => <<"plain">>,
password => <<"plain">>, password => <<"plain">>,
listener => 'tcp:default', listener => 'tcp:default',
protocol => mqtt protocol => mqtt
}). }
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -223,8 +253,10 @@ t_update(_Config) ->
raw_redis_auth_config() -> raw_redis_auth_config() ->
#{ #{
mechanism => <<"password_based">>, mechanism => <<"password_based">>,
password_hash_algorithm => #{name => <<"plain">>, password_hash_algorithm => #{
salt_position => <<"suffix">>}, name => <<"plain">>,
salt_position => <<"suffix">>
},
enable => <<"true">>, enable => <<"true">>,
backend => <<"redis">>, backend => <<"redis">>,
@ -235,20 +267,24 @@ raw_redis_auth_config() ->
}. }.
user_seeds() -> user_seeds() ->
[#{data => #{ [
#{
data => #{
password_hash => <<"plainsalt">>, password_hash => <<"plainsalt">>,
salt => <<"salt">>, salt => <<"salt">>,
is_superuser => <<"1">> is_superuser => <<"1">>
}, },
credentials => #{ credentials => #{
username => <<"plain">>, username => <<"plain">>,
password => <<"plain">>}, password => <<"plain">>
},
key => <<"mqtt_user:plain">>, key => <<"mqtt_user:plain">>,
config_params => #{}, config_params => #{},
result => {ok,#{is_superuser => true}} result => {ok, #{is_superuser => true}}
}, },
#{data => #{ #{
data => #{
password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>, password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>,
salt => <<"salt">>, salt => <<"salt">>,
is_superuser => <<"0">> is_superuser => <<"0">>
@ -259,14 +295,18 @@ user_seeds() ->
}, },
key => <<"mqtt_user:md5">>, key => <<"mqtt_user:md5">>,
config_params => #{ config_params => #{
password_hash_algorithm => #{name => <<"md5">>, password_hash_algorithm => #{
salt_position => <<"suffix">>} name => <<"md5">>,
salt_position => <<"suffix">>
}
}, },
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
#{data => #{ #{
password_hash => <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>, data => #{
password_hash =>
<<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>,
salt => <<"salt">>, salt => <<"salt">>,
is_superuser => <<"1">> is_superuser => <<"1">>
}, },
@ -277,13 +317,16 @@ user_seeds() ->
key => <<"mqtt_user:sha256">>, key => <<"mqtt_user:sha256">>,
config_params => #{ config_params => #{
cmd => <<"HMGET mqtt_user:${clientid} password_hash salt is_superuser">>, cmd => <<"HMGET mqtt_user:${clientid} password_hash salt is_superuser">>,
password_hash_algorithm => #{name => <<"sha256">>, password_hash_algorithm => #{
salt_position => <<"prefix">>} name => <<"sha256">>,
salt_position => <<"prefix">>
}
}, },
result => {ok,#{is_superuser => true}} result => {ok, #{is_superuser => true}}
}, },
#{data => #{ #{
data => #{
password_hash => password_hash =>
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
@ -297,9 +340,10 @@ user_seeds() ->
config_params => #{ config_params => #{
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
#{data => #{ #{
data => #{
password_hash => <<"01dbee7f4a9e243e988b62c73cda935da05378b9">>, password_hash => <<"01dbee7f4a9e243e988b62c73cda935da05378b9">>,
salt => <<"ATHENA.MIT.EDUraeburn">>, salt => <<"ATHENA.MIT.EDUraeburn">>,
is_superuser => <<"0">> is_superuser => <<"0">>
@ -310,14 +354,16 @@ user_seeds() ->
}, },
key => <<"mqtt_user:pbkdf2">>, key => <<"mqtt_user:pbkdf2">>,
config_params => #{ config_params => #{
password_hash_algorithm => #{name => <<"pbkdf2">>, password_hash_algorithm => #{
name => <<"pbkdf2">>,
iterations => 2, iterations => 2,
mac_fun => sha mac_fun => sha
} }
}, },
result => {ok,#{is_superuser => false}} result => {ok, #{is_superuser => false}}
}, },
#{data => #{ #{
data => #{
password_hash => password_hash =>
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
@ -333,10 +379,11 @@ user_seeds() ->
cmd => <<"HMGET mqtt_client:${clientid} password_hash salt is_superuser">>, cmd => <<"HMGET mqtt_client:${clientid} password_hash salt is_superuser">>,
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {error,not_authorized} result => {error, not_authorized}
}, },
#{data => #{ #{
data => #{
password_hash => password_hash =>
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
@ -352,10 +399,11 @@ user_seeds() ->
cmd => <<"HMGET badkey:${username} password_hash salt is_superuser">>, cmd => <<"HMGET badkey:${username} password_hash salt is_superuser">>,
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {error,not_authorized} result => {error, not_authorized}
}, },
#{data => #{ #{
data => #{
password_hash => password_hash =>
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>, <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>, salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
@ -371,7 +419,7 @@ user_seeds() ->
cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>, cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>,
password_hash_algorithm => #{name => <<"bcrypt">>} password_hash_algorithm => #{name => <<"bcrypt">>}
}, },
result => {error,bad_username_or_password} result => {error, bad_username_or_password}
} }
]. ].
@ -379,30 +427,36 @@ init_seeds() ->
ok = drop_seeds(), ok = drop_seeds(),
lists:foreach( lists:foreach(
fun(#{key := UserKey, data := Values}) -> fun(#{key := UserKey, data := Values}) ->
lists:foreach(fun({Key, Value}) -> lists:foreach(
fun({Key, Value}) ->
q(["HSET", UserKey, atom_to_list(Key), Value]) q(["HSET", UserKey, atom_to_list(Key), Value])
end, end,
maps:to_list(Values)) maps:to_list(Values)
)
end, end,
user_seeds()). user_seeds()
).
q(Command) -> q(Command) ->
emqx_resource:query( emqx_resource:query(
?REDIS_RESOURCE, ?REDIS_RESOURCE,
{cmd, Command}). {cmd, Command}
).
drop_seeds() -> drop_seeds() ->
lists:foreach( lists:foreach(
fun(#{key := UserKey}) -> fun(#{key := UserKey}) ->
q(["DEL", UserKey]) q(["DEL", UserKey])
end, end,
user_seeds()). user_seeds()
).
redis_server() -> redis_server() ->
iolist_to_binary(io_lib:format("~s",[?REDIS_HOST])). iolist_to_binary(io_lib:format("~s", [?REDIS_HOST])).
redis_config() -> redis_config() ->
#{auto_reconnect => true, #{
auto_reconnect => true,
database => 1, database => 1,
pool_size => 8, pool_size => 8,
redis_type => single, redis_type => single,

View File

@ -40,7 +40,8 @@ init_per_testcase(_, Config) ->
emqx_authentication:initialize_authentication(?GLOBAL, []), emqx_authentication:initialize_authentication(?GLOBAL, []),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
Config. Config.
init_per_suite(Config) -> init_per_suite(Config) ->
@ -57,7 +58,8 @@ init_per_suite(Config) ->
end_per_suite(_Config) -> end_per_suite(_Config) ->
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
ok = stop_apps([emqx_resource, emqx_connector]), ok = stop_apps([emqx_resource, emqx_connector]),
ok = emqx_common_test_helpers:stop_apps([emqx_authn]). ok = emqx_common_test_helpers:stop_apps([emqx_authn]).
@ -69,37 +71,53 @@ t_create(_Config) ->
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
create_redis_auth_with_ssl_opts( create_redis_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.3">>], <<"versions">> => [<<"tlsv1.3">>],
<<"ciphers">> => [<<"TLS_CHACHA20_POLY1305_SHA256">>]})). <<"ciphers">> => [<<"TLS_CHACHA20_POLY1305_SHA256">>]
}
)
).
t_create_invalid(_Config) -> t_create_invalid(_Config) ->
%% invalid server_name %% invalid server_name
?assertMatch( ?assertMatch(
{ok, _}, {ok, _},
create_redis_auth_with_ssl_opts( create_redis_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server-unknown-host">>, #{
<<"server_name_indication">> => <<"authn-server-unknown-host">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.3">>], <<"versions">> => [<<"tlsv1.3">>],
<<"ciphers">> => [<<"TLS_CHACHA20_POLY1305_SHA256">>]})), <<"ciphers">> => [<<"TLS_CHACHA20_POLY1305_SHA256">>]
}
)
),
%% incompatible versions %% incompatible versions
?assertMatch( ?assertMatch(
{error, _}, {error, _},
create_redis_auth_with_ssl_opts( create_redis_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.1">>, <<"tlsv1.2">>]})), <<"versions">> => [<<"tlsv1.1">>, <<"tlsv1.2">>]
}
)
),
%% incompatible ciphers %% incompatible ciphers
?assertMatch( ?assertMatch(
{error, _}, {error, _},
create_redis_auth_with_ssl_opts( create_redis_auth_with_ssl_opts(
#{<<"server_name_indication">> => <<"authn-server">>, #{
<<"server_name_indication">> => <<"authn-server">>,
<<"verify">> => <<"verify_peer">>, <<"verify">> => <<"verify_peer">>,
<<"versions">> => [<<"tlsv1.3">>], <<"versions">> => [<<"tlsv1.3">>],
<<"ciphers">> => [<<"TLS_AES_128_GCM_SHA256">>]})). <<"ciphers">> => [<<"TLS_AES_128_GCM_SHA256">>]
}
)
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -112,11 +130,14 @@ create_redis_auth_with_ssl_opts(SpecificSSLOpts) ->
raw_redis_auth_config(SpecificSSLOpts) -> raw_redis_auth_config(SpecificSSLOpts) ->
SSLOpts = maps:merge( SSLOpts = maps:merge(
emqx_authn_test_lib:client_ssl_cert_opts(), emqx_authn_test_lib:client_ssl_cert_opts(),
#{enable => <<"true">>}), #{enable => <<"true">>}
),
#{ #{
mechanism => <<"password_based">>, mechanism => <<"password_based">>,
password_hash_algorithm => #{name => <<"plain">>, password_hash_algorithm => #{
salt_position => <<"suffix">>}, name => <<"plain">>,
salt_position => <<"suffix">>
},
enable => <<"true">>, enable => <<"true">>,
backend => <<"redis">>, backend => <<"redis">>,
@ -128,7 +149,7 @@ raw_redis_auth_config(SpecificSSLOpts) ->
}. }.
redis_server() -> redis_server() ->
iolist_to_binary(io_lib:format("~s:~b",[?REDIS_HOST, ?REDIS_TLS_PORT])). iolist_to_binary(io_lib:format("~s:~b", [?REDIS_HOST, ?REDIS_TLS_PORT])).
start_apps(Apps) -> start_apps(Apps) ->
lists:foreach(fun application:ensure_all_started/1, Apps). lists:foreach(fun application:ensure_all_started/1, Apps).

View File

@ -36,16 +36,19 @@ jwt_example() ->
delete_authenticators(Path, Chain) -> delete_authenticators(Path, Chain) ->
case emqx_authentication:list_authenticators(Chain) of case emqx_authentication:list_authenticators(Chain) of
{error, _} -> ok; {error, _} ->
ok;
{ok, Authenticators} -> {ok, Authenticators} ->
lists:foreach( lists:foreach(
fun(#{id := ID}) -> fun(#{id := ID}) ->
emqx:update_config( emqx:update_config(
Path, Path,
{delete_authenticator, Chain, ID}, {delete_authenticator, Chain, ID},
#{rawconf_with_defaults => true}) #{rawconf_with_defaults => true}
)
end, end,
Authenticators) Authenticators
)
end. end.
delete_config(ID) -> delete_config(ID) ->
@ -53,10 +56,13 @@ delete_config(ID) ->
emqx:update_config( emqx:update_config(
[authentication], [authentication],
{delete_authenticator, ?GLOBAL, ID}, {delete_authenticator, ?GLOBAL, ID},
#{rawconf_with_defaults => false}). #{rawconf_with_defaults => false}
).
client_ssl_cert_opts() -> client_ssl_cert_opts() ->
Dir = code:lib_dir(emqx_authn, test), Dir = code:lib_dir(emqx_authn, test),
#{keyfile => filename:join([Dir, "data/certs", "client.key"]), #{
keyfile => filename:join([Dir, "data/certs", "client.key"]),
certfile => filename:join([Dir, "data/certs", "client.crt"]), certfile => filename:join([Dir, "data/certs", "client.crt"]),
cacertfile => filename:join([Dir, "data/certs", "ca.crt"])}. cacertfile => filename:join([Dir, "data/certs", "ca.crt"])
}.

View File

@ -26,8 +26,10 @@
-define(PATH, [authentication]). -define(PATH, [authentication]).
-define(USER_MAP, #{user_id := _, -define(USER_MAP, #{
is_superuser := _}). user_id := _,
is_superuser := _
}).
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
@ -45,7 +47,8 @@ init_per_testcase(_Case, Config) ->
mria:clear_table(emqx_enhanced_authn_scram_mnesia), mria:clear_table(emqx_enhanced_authn_scram_mnesia),
emqx_authn_test_lib:delete_authenticators( emqx_authn_test_lib:delete_authenticators(
[authentication], [authentication],
?GLOBAL), ?GLOBAL
),
Config. Config.
end_per_testcase(_Case, Config) -> end_per_testcase(_Case, Config) ->
@ -65,10 +68,11 @@ t_create(_Config) ->
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, ValidConfig}), {create_authenticator, ?GLOBAL, ValidConfig}
),
{ok, [#{provider := emqx_enhanced_authn_scram_mnesia}]} {ok, [#{provider := emqx_enhanced_authn_scram_mnesia}]} =
= emqx_authentication:list_authenticators(?GLOBAL). emqx_authentication:list_authenticators(?GLOBAL).
t_create_invalid(_Config) -> t_create_invalid(_Config) ->
InvalidConfig = #{ InvalidConfig = #{
@ -80,7 +84,8 @@ t_create_invalid(_Config) ->
{error, _} = emqx:update_config( {error, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, InvalidConfig}), {create_authenticator, ?GLOBAL, InvalidConfig}
),
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL). {ok, []} = emqx_authentication:list_authenticators(?GLOBAL).
@ -102,33 +107,41 @@ t_authenticate(_Config) ->
'Authentication-Method' => <<"SCRAM-SHA-512">>, 'Authentication-Method' => <<"SCRAM-SHA-512">>,
'Authentication-Data' => ClientFirstMessage 'Authentication-Data' => ClientFirstMessage
} }
}), }
),
ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket), ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket),
?AUTH_PACKET( ?AUTH_PACKET(
?RC_CONTINUE_AUTHENTICATION, ?RC_CONTINUE_AUTHENTICATION,
#{'Authentication-Data' := ServerFirstMessage}) = receive_packet(), #{'Authentication-Data' := ServerFirstMessage}
) = receive_packet(),
{continue, ClientFinalMessage, ClientCache} = {continue, ClientFinalMessage, ClientCache} =
esasl_scram:check_server_first_message( esasl_scram:check_server_first_message(
ServerFirstMessage, ServerFirstMessage,
#{client_first_message => ClientFirstMessage, #{
client_first_message => ClientFirstMessage,
password => Password, password => Password,
algorithm => Algorithm} algorithm => Algorithm
}
), ),
AuthContinuePacket = ?AUTH_PACKET( AuthContinuePacket = ?AUTH_PACKET(
?RC_CONTINUE_AUTHENTICATION, ?RC_CONTINUE_AUTHENTICATION,
#{'Authentication-Method' => <<"SCRAM-SHA-512">>, #{
'Authentication-Data' => ClientFinalMessage}), 'Authentication-Method' => <<"SCRAM-SHA-512">>,
'Authentication-Data' => ClientFinalMessage
}
),
ok = emqx_authn_mqtt_test_client:send(Pid, AuthContinuePacket), ok = emqx_authn_mqtt_test_client:send(Pid, AuthContinuePacket),
?CONNACK_PACKET( ?CONNACK_PACKET(
?RC_SUCCESS, ?RC_SUCCESS,
_, _,
#{'Authentication-Data' := ServerFinalMessage}) = receive_packet(), #{'Authentication-Data' := ServerFinalMessage}
) = receive_packet(),
ok = esasl_scram:check_server_final_message( ok = esasl_scram:check_server_final_message(
ServerFinalMessage, ClientCache#{algorithm => Algorithm} ServerFinalMessage, ClientCache#{algorithm => Algorithm}
@ -152,7 +165,8 @@ t_authenticate_bad_username(_Config) ->
'Authentication-Method' => <<"SCRAM-SHA-512">>, 'Authentication-Method' => <<"SCRAM-SHA-512">>,
'Authentication-Data' => ClientFirstMessage 'Authentication-Data' => ClientFirstMessage
} }
}), }
),
ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket), ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket),
@ -176,26 +190,33 @@ t_authenticate_bad_password(_Config) ->
'Authentication-Method' => <<"SCRAM-SHA-512">>, 'Authentication-Method' => <<"SCRAM-SHA-512">>,
'Authentication-Data' => ClientFirstMessage 'Authentication-Data' => ClientFirstMessage
} }
}), }
),
ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket), ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket),
?AUTH_PACKET( ?AUTH_PACKET(
?RC_CONTINUE_AUTHENTICATION, ?RC_CONTINUE_AUTHENTICATION,
#{'Authentication-Data' := ServerFirstMessage}) = receive_packet(), #{'Authentication-Data' := ServerFirstMessage}
) = receive_packet(),
{continue, ClientFinalMessage, _ClientCache} = {continue, ClientFinalMessage, _ClientCache} =
esasl_scram:check_server_first_message( esasl_scram:check_server_first_message(
ServerFirstMessage, ServerFirstMessage,
#{client_first_message => ClientFirstMessage, #{
client_first_message => ClientFirstMessage,
password => <<"badpassword">>, password => <<"badpassword">>,
algorithm => Algorithm} algorithm => Algorithm
}
), ),
AuthContinuePacket = ?AUTH_PACKET( AuthContinuePacket = ?AUTH_PACKET(
?RC_CONTINUE_AUTHENTICATION, ?RC_CONTINUE_AUTHENTICATION,
#{'Authentication-Method' => <<"SCRAM-SHA-512">>, #{
'Authentication-Data' => ClientFinalMessage}), 'Authentication-Method' => <<"SCRAM-SHA-512">>,
'Authentication-Data' => ClientFinalMessage
}
),
ok = emqx_authn_mqtt_test_client:send(Pid, AuthContinuePacket), ok = emqx_authn_mqtt_test_client:send(Pid, AuthContinuePacket),
@ -218,7 +239,7 @@ t_destroy(_) ->
ok = emqx_enhanced_authn_scram_mnesia:destroy(State0), ok = emqx_enhanced_authn_scram_mnesia:destroy(State0),
{ok, State1} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config), {ok, State1} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config),
{error,not_found} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State1), {error, not_found} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State1),
{ok, _} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, StateOther). {ok, _} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, StateOther).
t_add_user(_) -> t_add_user(_) ->
@ -248,12 +269,14 @@ t_update_user(_) ->
{ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(User, State), {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(User, State),
{ok, #{is_superuser := false}} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State), {ok, #{is_superuser := false}} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State),
{ok, {ok, #{
#{user_id := <<"u">>, user_id := <<"u">>,
is_superuser := true}} = emqx_enhanced_authn_scram_mnesia:update_user( is_superuser := true
}} = emqx_enhanced_authn_scram_mnesia:update_user(
<<"u">>, <<"u">>,
#{password => <<"p1">>, is_superuser => true}, #{password => <<"p1">>, is_superuser => true},
State), State
),
{ok, #{is_superuser := true}} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State). {ok, #{is_superuser := true}} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State).
@ -261,29 +284,47 @@ t_list_users(_) ->
Config = config(), Config = config(),
{ok, State} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config), {ok, State} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config),
Users = [#{user_id => <<"u1">>, password => <<"p">>}, Users = [
#{user_id => <<"u1">>, password => <<"p">>},
#{user_id => <<"u2">>, password => <<"p">>}, #{user_id => <<"u2">>, password => <<"p">>},
#{user_id => <<"u3">>, password => <<"p">>}], #{user_id => <<"u3">>, password => <<"p">>}
],
lists:foreach( lists:foreach(
fun(U) -> {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(U, State) end, fun(U) -> {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(U, State) end,
Users), Users
),
#{data := [?USER_MAP, ?USER_MAP], #{
meta := #{page := 1, limit := 2, count := 3}} = emqx_enhanced_authn_scram_mnesia:list_users( data := [?USER_MAP, ?USER_MAP],
meta := #{page := 1, limit := 2, count := 3}
} = emqx_enhanced_authn_scram_mnesia:list_users(
#{<<"page">> => 1, <<"limit">> => 2}, #{<<"page">> => 1, <<"limit">> => 2},
State), State
#{data := [?USER_MAP], ),
meta := #{page := 2, limit := 2, count := 3}} = emqx_enhanced_authn_scram_mnesia:list_users( #{
data := [?USER_MAP],
meta := #{page := 2, limit := 2, count := 3}
} = emqx_enhanced_authn_scram_mnesia:list_users(
#{<<"page">> => 2, <<"limit">> => 2}, #{<<"page">> => 2, <<"limit">> => 2},
State), State
#{data := [#{user_id := <<"u1">>, ),
is_superuser := _}], #{
meta := #{page := 1, limit := 3, count := 1}} = emqx_enhanced_authn_scram_mnesia:list_users( data := [
#{ <<"page">> => 1 #{
, <<"limit">> => 3 user_id := <<"u1">>,
, <<"like_username">> => <<"1">>}, is_superuser := _
State). }
],
meta := #{page := 1, limit := 3, count := 1}
} = emqx_enhanced_authn_scram_mnesia:list_users(
#{
<<"page">> => 1,
<<"limit">> => 3,
<<"like_username">> => <<"1">>
},
State
).
t_is_superuser(_Config) -> t_is_superuser(_Config) ->
ok = test_is_superuser(#{is_superuser => false}, false), ok = test_is_superuser(#{is_superuser => false}, false),
@ -297,36 +338,44 @@ test_is_superuser(UserInfo, ExpectedIsSuperuser) ->
Username = <<"u">>, Username = <<"u">>,
Password = <<"p">>, Password = <<"p">>,
UserInfo0 = UserInfo#{user_id => Username, UserInfo0 = UserInfo#{
password => Password}, user_id => Username,
password => Password
},
{ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(UserInfo0, State), {ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(UserInfo0, State),
ClientFirstMessage = esasl_scram:client_first_message(Username), ClientFirstMessage = esasl_scram:client_first_message(Username),
{continue, ServerFirstMessage, ServerCache} {continue, ServerFirstMessage, ServerCache} =
= emqx_enhanced_authn_scram_mnesia:authenticate( emqx_enhanced_authn_scram_mnesia:authenticate(
#{auth_method => <<"SCRAM-SHA-512">>, #{
auth_method => <<"SCRAM-SHA-512">>,
auth_data => ClientFirstMessage, auth_data => ClientFirstMessage,
auth_cache => #{} auth_cache => #{}
}, },
State), State
),
{continue, ClientFinalMessage, ClientCache} = {continue, ClientFinalMessage, ClientCache} =
esasl_scram:check_server_first_message( esasl_scram:check_server_first_message(
ServerFirstMessage, ServerFirstMessage,
#{client_first_message => ClientFirstMessage, #{
client_first_message => ClientFirstMessage,
password => Password, password => Password,
algorithm => sha512} algorithm => sha512
}
), ),
{ok, UserInfo1, ServerFinalMessage} {ok, UserInfo1, ServerFinalMessage} =
= emqx_enhanced_authn_scram_mnesia:authenticate( emqx_enhanced_authn_scram_mnesia:authenticate(
#{auth_method => <<"SCRAM-SHA-512">>, #{
auth_method => <<"SCRAM-SHA-512">>,
auth_data => ClientFinalMessage, auth_data => ClientFinalMessage,
auth_cache => ServerCache auth_cache => ServerCache
}, },
State), State
),
ok = esasl_scram:check_server_final_message( ok = esasl_scram:check_server_final_message(
ServerFinalMessage, ClientCache#{algorithm => sha512} ServerFinalMessage, ClientCache#{algorithm => sha512}
@ -336,7 +385,6 @@ test_is_superuser(UserInfo, ExpectedIsSuperuser) ->
ok = emqx_enhanced_authn_scram_mnesia:destroy(State). ok = emqx_enhanced_authn_scram_mnesia:destroy(State).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -362,13 +410,15 @@ init_auth(Username, Password, Algorithm) ->
{ok, _} = emqx:update_config( {ok, _} = emqx:update_config(
?PATH, ?PATH,
{create_authenticator, ?GLOBAL, Config}), {create_authenticator, ?GLOBAL, Config}
),
{ok, [#{state := State}]} = emqx_authentication:list_authenticators(?GLOBAL), {ok, [#{state := State}]} = emqx_authentication:list_authenticators(?GLOBAL),
emqx_enhanced_authn_scram_mnesia:add_user( emqx_enhanced_authn_scram_mnesia:add_user(
#{user_id => Username, password => Password}, #{user_id => Username, password => Password},
State). State
).
receive_packet() -> receive_packet() ->
receive receive