style: erlfmt apps/emqx_authn
This commit is contained in:
parent
9ffc58071d
commit
aae2d01582
|
@ -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]}.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
%% -*- 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]},
|
||||||
|
|
|
@ -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,8 +63,11 @@ 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) ->
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,8 +21,9 @@
|
||||||
-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()).
|
||||||
|
|
|
@ -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)
|
||||||
|
].
|
||||||
|
|
|
@ -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() ->
|
||||||
|
|
|
@ -18,8 +18,9 @@
|
||||||
|
|
||||||
-behaviour(supervisor).
|
-behaviour(supervisor).
|
||||||
|
|
||||||
-export([ start_link/0
|
-export([
|
||||||
, init/1
|
start_link/0,
|
||||||
|
init/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
start_link() ->
|
start_link() ->
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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,48 +120,57 @@ 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) ->
|
||||||
|
@ -168,57 +180,72 @@ test_authenticator(PathPrefix) ->
|
||||||
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}) ->
|
EqualFun = fun({M, V}) ->
|
||||||
?assertEqual(V, LookFun([<<"metrics">>,
|
?assertEqual(
|
||||||
M]
|
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, #{}),
|
||||||
|
|
||||||
|
|
|
@ -28,13 +28,13 @@
|
||||||
|
|
||||||
-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,20 +182,23 @@ 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).
|
||||||
|
|
||||||
|
@ -187,7 +206,8 @@ 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),
|
||||||
|
|
||||||
|
@ -266,12 +290,14 @@ samples() ->
|
||||||
},
|
},
|
||||||
|
|
||||||
%% 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 => #{},
|
||||||
|
@ -279,13 +305,17 @@ samples() ->
|
||||||
},
|
},
|
||||||
|
|
||||||
%% 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 => #{},
|
||||||
|
@ -293,13 +323,17 @@ samples() ->
|
||||||
},
|
},
|
||||||
|
|
||||||
%% 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 => #{},
|
||||||
|
@ -307,9 +341,11 @@ samples() ->
|
||||||
},
|
},
|
||||||
|
|
||||||
%% 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),
|
||||||
|
@ -323,9 +359,11 @@ samples() ->
|
||||||
},
|
},
|
||||||
|
|
||||||
%% 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,14 +371,15 @@ 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,
|
||||||
|
@ -349,7 +388,8 @@ samples() ->
|
||||||
},
|
},
|
||||||
|
|
||||||
%% 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}
|
||||||
|
@ -359,7 +399,8 @@ samples() ->
|
||||||
},
|
},
|
||||||
|
|
||||||
%% 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,
|
||||||
|
@ -368,7 +409,8 @@ samples() ->
|
||||||
},
|
},
|
||||||
|
|
||||||
%% 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,
|
||||||
|
@ -377,7 +419,8 @@ samples() ->
|
||||||
},
|
},
|
||||||
|
|
||||||
%% Handling error
|
%% Handling error
|
||||||
#{handler => fun(Req0, State) ->
|
#{
|
||||||
|
handler => fun(Req0, State) ->
|
||||||
error(woops),
|
error(woops),
|
||||||
{ok, Req0, State}
|
{ok, Req0, State}
|
||||||
end,
|
end,
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
-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
|
||||||
|
@ -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}.
|
||||||
|
|
||||||
|
|
|
@ -28,13 +28,13 @@
|
||||||
|
|
||||||
-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},
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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(),
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}.
|
}.
|
||||||
|
|
|
@ -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">>,
|
||||||
|
@ -324,7 +359,8 @@ user_seeds() ->
|
||||||
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">>,
|
||||||
|
@ -343,7 +379,8 @@ user_seeds() ->
|
||||||
result => {error, not_authorized}
|
result => {error, not_authorized}
|
||||||
},
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
|
data => #{
|
||||||
username => <<"bcrypt1">>,
|
username => <<"bcrypt1">>,
|
||||||
password_hash =>
|
password_hash =>
|
||||||
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
||||||
|
@ -361,7 +398,8 @@ user_seeds() ->
|
||||||
result => {error, not_authorized}
|
result => {error, not_authorized}
|
||||||
},
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
|
data => #{
|
||||||
username => <<"bcrypt2">>,
|
username => <<"bcrypt2">>,
|
||||||
password_hash =>
|
password_hash =>
|
||||||
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
||||||
|
|
|
@ -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">>,
|
||||||
|
|
|
@ -21,25 +21,33 @@
|
||||||
-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
|
||||||
}).
|
}).
|
||||||
|
@ -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}]).
|
||||||
|
|
|
@ -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",
|
||||||
|
@ -363,15 +425,18 @@ user_seeds() ->
|
||||||
|
|
||||||
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,17 +444,20 @@ 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").
|
||||||
|
@ -398,7 +466,8 @@ 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">>,
|
||||||
|
|
|
@ -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,8 +146,11 @@ 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)
|
||||||
}.
|
}.
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
].
|
].
|
||||||
|
|
|
@ -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",
|
||||||
|
@ -403,24 +466,28 @@ user_seeds() ->
|
||||||
|
|
||||||
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,12 +498,14 @@ 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"),
|
||||||
|
@ -446,7 +515,8 @@ 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">>,
|
||||||
|
|
|
@ -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">>,
|
||||||
|
|
|
@ -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">>,
|
||||||
|
@ -299,7 +342,8 @@ user_seeds() ->
|
||||||
},
|
},
|
||||||
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">>,
|
||||||
|
@ -336,7 +382,8 @@ user_seeds() ->
|
||||||
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">>,
|
||||||
|
@ -355,7 +402,8 @@ user_seeds() ->
|
||||||
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">>,
|
||||||
|
@ -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,
|
||||||
|
|
|
@ -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">>,
|
||||||
|
|
|
@ -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"])
|
||||||
|
}.
|
||||||
|
|
|
@ -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),
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue