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_shadow_vars,
|
warn_unused_vars,
|
||||||
warnings_as_errors,
|
warn_shadow_vars,
|
||||||
warn_unused_import,
|
warnings_as_errors,
|
||||||
warn_obsolete_guard,
|
warn_unused_import,
|
||||||
debug_info,
|
warn_obsolete_guard,
|
||||||
{parse_transform}]}.
|
debug_info,
|
||||||
|
{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,13 +1,13 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{application, emqx_authn,
|
{application, emqx_authn, [
|
||||||
[{description, "EMQX Authentication"},
|
{description, "EMQX Authentication"},
|
||||||
{vsn, "0.1.0"},
|
{vsn, "0.1.0"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, [emqx_authn_sup, emqx_authn_registry]},
|
{registered, [emqx_authn_sup, emqx_authn_registry]},
|
||||||
{applications, [kernel,stdlib,emqx_resource,ehttpc,epgsql,mysql,jose]},
|
{applications, [kernel, stdlib, emqx_resource, ehttpc, epgsql, mysql, jose]},
|
||||||
{mod, {emqx_authn_app,[]}},
|
{mod, {emqx_authn_app, []}},
|
||||||
{env, []},
|
{env, []},
|
||||||
{licenses, ["Apache-2.0"]},
|
{licenses, ["Apache-2.0"]},
|
||||||
{maintainers, ["EMQX Team <contact@emqx.io>"]},
|
{maintainers, ["EMQX Team <contact@emqx.io>"]},
|
||||||
{links, [{"Homepage", "https://emqx.io/"}]}
|
{links, [{"Homepage", "https://emqx.io/"}]}
|
||||||
]}.
|
]}.
|
||||||
|
|
|
@ -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,22 +54,26 @@ 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 =
|
||||||
false -> atom(Mec);
|
case maps:get(<<"backend">>, Config, false) of
|
||||||
Backend -> {atom(Mec), atom(Backend)}
|
false -> atom(Mec);
|
||||||
end,
|
Backend -> {atom(Mec), atom(Backend)}
|
||||||
|
end,
|
||||||
case lists:keyfind(Key, 1, providers()) of
|
case lists:keyfind(Key, 1, providers()) of
|
||||||
false ->
|
false ->
|
||||||
throw({unknown_handler, Key});
|
throw({unknown_handler, Key});
|
||||||
{_, ProviderModule} ->
|
{_, ProviderModule} ->
|
||||||
hocon_tconf:check_plain(ProviderModule, #{?CONF_NS_BINARY => Config},
|
hocon_tconf:check_plain(
|
||||||
Opts#{atom_key => true})
|
ProviderModule,
|
||||||
|
#{?CONF_NS_BINARY => Config},
|
||||||
|
Opts#{atom_key => true}
|
||||||
|
)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
atom(Bin) ->
|
atom(Bin) ->
|
||||||
try
|
try
|
||||||
binary_to_existing_atom(Bin, utf8)
|
binary_to_existing_atom(Bin, utf8)
|
||||||
catch
|
catch
|
||||||
_ : _ ->
|
_:_ ->
|
||||||
throw({unknown_auth_provider, Bin})
|
throw({unknown_auth_provider, Bin})
|
||||||
end.
|
end.
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,9 +21,10 @@
|
||||||
-behaviour(application).
|
-behaviour(application).
|
||||||
|
|
||||||
%% Application callbacks
|
%% Application callbacks
|
||||||
-export([ start/2
|
-export([
|
||||||
, stop/1
|
start/2,
|
||||||
]).
|
stop/1
|
||||||
|
]).
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx_authentication.hrl").
|
-include_lib("emqx/include/emqx_authentication.hrl").
|
||||||
|
|
||||||
|
@ -51,13 +52,15 @@ initialize() ->
|
||||||
ok = ?AUTHN:register_providers(emqx_authn:providers()),
|
ok = ?AUTHN:register_providers(emqx_authn:providers()),
|
||||||
|
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun({ChainName, RawAuthConfigs}) ->
|
fun({ChainName, RawAuthConfigs}) ->
|
||||||
AuthConfig = emqx_authn:check_configs(RawAuthConfigs),
|
AuthConfig = emqx_authn:check_configs(RawAuthConfigs),
|
||||||
?AUTHN:initialize_authentication(
|
?AUTHN:initialize_authentication(
|
||||||
ChainName,
|
ChainName,
|
||||||
AuthConfig)
|
AuthConfig
|
||||||
end,
|
)
|
||||||
chain_configs()).
|
end,
|
||||||
|
chain_configs()
|
||||||
|
).
|
||||||
|
|
||||||
deinitialize() ->
|
deinitialize() ->
|
||||||
ok = ?AUTHN:deregister_providers(provider_types()),
|
ok = ?AUTHN:deregister_providers(provider_types()),
|
||||||
|
@ -71,15 +74,16 @@ global_chain_config() ->
|
||||||
|
|
||||||
listener_chain_configs() ->
|
listener_chain_configs() ->
|
||||||
lists:map(
|
lists:map(
|
||||||
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() :: #{
|
||||||
mac_fun := emqx_passwd:pbkdf2_mac_fun(),
|
name := pbkdf2,
|
||||||
iterations := pos_integer()}).
|
mac_fun := emqx_passwd:pbkdf2_mac_fun(),
|
||||||
|
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,37 +44,44 @@
|
||||||
|
|
||||||
-behaviour(hocon_schema).
|
-behaviour(hocon_schema).
|
||||||
|
|
||||||
-export([roots/0,
|
-export([
|
||||||
fields/1,
|
roots/0,
|
||||||
namespace/0]).
|
fields/1,
|
||||||
|
namespace/0
|
||||||
|
]).
|
||||||
|
|
||||||
-export([type_ro/1,
|
-export([
|
||||||
type_rw/1]).
|
type_ro/1,
|
||||||
|
type_rw/1
|
||||||
|
]).
|
||||||
|
|
||||||
-export([init/1,
|
-export([
|
||||||
gen_salt/1,
|
init/1,
|
||||||
hash/2,
|
gen_salt/1,
|
||||||
check_password/4]).
|
hash/2,
|
||||||
|
check_password/4
|
||||||
|
]).
|
||||||
|
|
||||||
namespace() -> "authn-hash".
|
namespace() -> "authn-hash".
|
||||||
roots() -> [pbkdf2, bcrypt, bcrypt_rw, other_algorithms].
|
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]}},
|
[
|
||||||
{mac_fun, {enum, [md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512]}},
|
{name, {enum, [pbkdf2]}},
|
||||||
{iterations, integer()},
|
{mac_fun, {enum, [md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512]}},
|
||||||
{dk_length, fun dk_length/1}];
|
{iterations, integer()},
|
||||||
|
{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(
|
||||||
mac_fun := MacFun,
|
#{
|
||||||
iterations := Iterations} = Algorithm, Password) ->
|
name := pbkdf2,
|
||||||
|
mac_fun := MacFun,
|
||||||
|
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()
|
||||||
emqx_passwd:password()) -> boolean()).
|
) -> 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(
|
||||||
mac_fun := MacFun,
|
#{
|
||||||
iterations := Iterations} = Algorithm,
|
name := pbkdf2,
|
||||||
Salt, PasswordHash, Password) ->
|
mac_fun := MacFun,
|
||||||
|
iterations := Iterations
|
||||||
|
} = 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, pbkdf2),
|
hoconsc:ref(?MODULE, bcrypt_rw),
|
||||||
hoconsc:ref(?MODULE, other_algorithms)].
|
hoconsc:ref(?MODULE, pbkdf2),
|
||||||
|
hoconsc:ref(?MODULE, other_algorithms)
|
||||||
|
].
|
||||||
|
|
||||||
ro_refs() ->
|
ro_refs() ->
|
||||||
[hoconsc:ref(?MODULE, bcrypt),
|
[
|
||||||
hoconsc:ref(?MODULE, pbkdf2),
|
hoconsc:ref(?MODULE, bcrypt),
|
||||||
hoconsc:ref(?MODULE, other_algorithms)].
|
hoconsc:ref(?MODULE, pbkdf2),
|
||||||
|
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,9 +18,10 @@
|
||||||
|
|
||||||
-behaviour(supervisor).
|
-behaviour(supervisor).
|
||||||
|
|
||||||
-export([ start_link/0
|
-export([
|
||||||
, init/1
|
start_link/0,
|
||||||
]).
|
init/1
|
||||||
|
]).
|
||||||
|
|
||||||
start_link() ->
|
start_link() ->
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||||
|
|
|
@ -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_CLIENTID,
|
?PH_USERNAME,
|
||||||
?PH_PASSWORD,
|
?PH_CLIENTID,
|
||||||
?PH_PEERHOST,
|
?PH_PASSWORD,
|
||||||
?PH_CERT_SUBJECT,
|
?PH_PEERHOST,
|
||||||
?PH_CERT_CN_NAME]).
|
?PH_CERT_SUBJECT,
|
||||||
|
?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) ->
|
||||||
|
@ -63,27 +66,33 @@ 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};
|
||||||
|
@ -114,8 +123,9 @@ 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).
|
||||||
|
@ -42,12 +42,14 @@ groups() ->
|
||||||
init_per_testcase(_, Config) ->
|
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.
|
||||||
|
@ -55,8 +57,9 @@ init_per_testcase(_, Config) ->
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
_ = application:load(emqx_conf),
|
_ = application:load(emqx_conf),
|
||||||
ok = emqx_common_test_helpers:start_apps(
|
ok = emqx_common_test_helpers:start_apps(
|
||||||
[emqx_authn, emqx_dashboard],
|
[emqx_authn, emqx_dashboard],
|
||||||
fun set_special_configs/1),
|
fun set_special_configs/1
|
||||||
|
),
|
||||||
|
|
||||||
?AUTHN:delete_chain(?GLOBAL),
|
?AUTHN:delete_chain(?GLOBAL),
|
||||||
{ok, Chains} = ?AUTHN:list_chains(),
|
{ok, Chains} = ?AUTHN:list_chains(),
|
||||||
|
@ -117,108 +120,132 @@ t_listener_authenticator_import_users(_) ->
|
||||||
test_authenticator_import_users(["listeners", ?TCP_DEFAULT]).
|
test_authenticator_import_users(["listeners", ?TCP_DEFAULT]).
|
||||||
|
|
||||||
test_authenticators(PathPrefix) ->
|
test_authenticators(PathPrefix) ->
|
||||||
|
|
||||||
ValidConfig = emqx_authn_test_lib:http_example(),
|
ValidConfig = emqx_authn_test_lib:http_example(),
|
||||||
{ok, 200, _} = request(
|
{ok, 200, _} = request(
|
||||||
post,
|
post,
|
||||||
uri(PathPrefix ++ [?CONF_NS]),
|
uri(PathPrefix ++ [?CONF_NS]),
|
||||||
ValidConfig),
|
ValidConfig
|
||||||
|
),
|
||||||
|
|
||||||
{ok, 409, _} = request(
|
{ok, 409, _} = request(
|
||||||
post,
|
post,
|
||||||
uri(PathPrefix ++ [?CONF_NS]),
|
uri(PathPrefix ++ [?CONF_NS]),
|
||||||
ValidConfig),
|
ValidConfig
|
||||||
|
),
|
||||||
|
|
||||||
InvalidConfig0 = ValidConfig#{method => <<"delete">>},
|
InvalidConfig0 = ValidConfig#{method => <<"delete">>},
|
||||||
{ok, 400, _} = request(
|
{ok, 400, _} = request(
|
||||||
post,
|
post,
|
||||||
uri(PathPrefix ++ [?CONF_NS]),
|
uri(PathPrefix ++ [?CONF_NS]),
|
||||||
InvalidConfig0),
|
InvalidConfig0
|
||||||
|
),
|
||||||
|
|
||||||
InvalidConfig1 = ValidConfig#{method => <<"get">>,
|
InvalidConfig1 = ValidConfig#{
|
||||||
headers => #{<<"content-type">> => <<"application/json">>}},
|
method => <<"get">>,
|
||||||
|
headers => #{<<"content-type">> => <<"application/json">>}
|
||||||
|
},
|
||||||
{ok, 400, _} = request(
|
{ok, 400, _} = request(
|
||||||
post,
|
post,
|
||||||
uri(PathPrefix ++ [?CONF_NS]),
|
uri(PathPrefix ++ [?CONF_NS]),
|
||||||
InvalidConfig1),
|
InvalidConfig1
|
||||||
|
),
|
||||||
|
|
||||||
?assertAuthenticatorsMatch(
|
?assertAuthenticatorsMatch(
|
||||||
[#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}],
|
[#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>}],
|
||||||
PathPrefix ++ [?CONF_NS]).
|
PathPrefix ++ [?CONF_NS]
|
||||||
|
).
|
||||||
|
|
||||||
test_authenticator(PathPrefix) ->
|
test_authenticator(PathPrefix) ->
|
||||||
ValidConfig0 = emqx_authn_test_lib:http_example(),
|
ValidConfig0 = emqx_authn_test_lib:http_example(),
|
||||||
{ok, 200, _} = request(
|
{ok, 200, _} = request(
|
||||||
post,
|
post,
|
||||||
uri(PathPrefix ++ [?CONF_NS]),
|
uri(PathPrefix ++ [?CONF_NS]),
|
||||||
ValidConfig0),
|
ValidConfig0
|
||||||
|
),
|
||||||
{ok, 200, _} = request(
|
{ok, 200, _} = request(
|
||||||
get,
|
get,
|
||||||
uri(PathPrefix ++ [?CONF_NS, "password_based:http"])),
|
uri(PathPrefix ++ [?CONF_NS, "password_based:http"])
|
||||||
|
),
|
||||||
|
|
||||||
{ok, 200, Res} = request(
|
{ok, 200, Res} = request(
|
||||||
get,
|
get,
|
||||||
uri(PathPrefix ++ [?CONF_NS, "password_based:http", "status"])),
|
uri(PathPrefix ++ [?CONF_NS, "password_based:http", "status"])
|
||||||
|
),
|
||||||
{ok, RList} = emqx_json:safe_decode(Res),
|
{ok, RList} = emqx_json:safe_decode(Res),
|
||||||
Snd = fun ({_, Val}) -> Val end,
|
Snd = fun({_, Val}) -> Val end,
|
||||||
LookupVal = fun LookupV(List, RestJson) ->
|
LookupVal = fun LookupV(List, RestJson) ->
|
||||||
case List of
|
case List of
|
||||||
[Name] -> Snd(lists:keyfind(Name, 1, RestJson));
|
[Name] -> Snd(lists:keyfind(Name, 1, RestJson));
|
||||||
[Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson)))
|
[Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson)))
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
LookFun = fun (List) -> LookupVal(List, RList) end,
|
LookFun = fun(List) -> LookupVal(List, RList) end,
|
||||||
MetricsList = [{<<"failed">>, 0},
|
MetricsList = [
|
||||||
{<<"matched">>, 0},
|
{<<"failed">>, 0},
|
||||||
{<<"rate">>, 0.0},
|
{<<"matched">>, 0},
|
||||||
{<<"rate_last5m">>, 0.0},
|
{<<"rate">>, 0.0},
|
||||||
{<<"rate_max">>, 0.0},
|
{<<"rate_last5m">>, 0.0},
|
||||||
{<<"success">>, 0}],
|
{<<"rate_max">>, 0.0},
|
||||||
EqualFun = fun ({M, V}) ->
|
{<<"success">>, 0}
|
||||||
?assertEqual(V, LookFun([<<"metrics">>,
|
],
|
||||||
M]
|
EqualFun = fun({M, V}) ->
|
||||||
)
|
?assertEqual(
|
||||||
) end,
|
V,
|
||||||
|
LookFun([
|
||||||
|
<<"metrics">>,
|
||||||
|
M
|
||||||
|
])
|
||||||
|
)
|
||||||
|
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]).
|
||||||
|
|
||||||
|
@ -226,64 +253,78 @@ test_authenticator_users(PathPrefix) ->
|
||||||
UsersUri = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "users"]),
|
UsersUri = uri(PathPrefix ++ [?CONF_NS, "password_based:built_in_database", "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()
|
||||||
|
),
|
||||||
|
|
||||||
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) ->
|
||||||
{ok, 201, UserData} = request(post, UsersUri, User),
|
{ok, 201, UserData} = request(post, UsersUri, User),
|
||||||
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,
|
#{
|
||||||
<<"meta">> :=
|
<<"data">> := Page1Users,
|
||||||
#{<<"page">> := 1,
|
<<"meta">> :=
|
||||||
<<"limit">> := 2,
|
#{
|
||||||
<<"count">> := 3}} =
|
<<"page">> := 1,
|
||||||
jiffy:decode(Page1Data, [return_maps]),
|
<<"limit">> := 2,
|
||||||
|
<<"count">> := 3
|
||||||
|
}
|
||||||
|
} =
|
||||||
|
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,
|
#{
|
||||||
<<"meta">> :=
|
<<"data">> := Page2Users,
|
||||||
#{<<"page">> := 2,
|
<<"meta">> :=
|
||||||
<<"limit">> := 2,
|
#{
|
||||||
<<"count">> := 3}} = jiffy:decode(Page2Data, [return_maps]),
|
<<"page">> := 2,
|
||||||
|
<<"limit">> := 2,
|
||||||
|
<<"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"]),
|
||||||
|
|
||||||
{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),
|
||||||
|
@ -299,141 +340,161 @@ test_authenticator_user(PathPrefix) ->
|
||||||
?assertNotMatch(#{<<"password">> := _}, FetchedUser),
|
?assertNotMatch(#{<<"password">> := _}, FetchedUser),
|
||||||
|
|
||||||
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").
|
||||||
|
|
||||||
test_authenticator_move(PathPrefix) ->
|
test_authenticator_move(PathPrefix) ->
|
||||||
AuthenticatorConfs = [
|
AuthenticatorConfs = [
|
||||||
emqx_authn_test_lib:http_example(),
|
emqx_authn_test_lib:http_example(),
|
||||||
emqx_authn_test_lib:jwt_example(),
|
emqx_authn_test_lib:jwt_example(),
|
||||||
emqx_authn_test_lib:built_in_database_example()
|
emqx_authn_test_lib:built_in_database_example()
|
||||||
],
|
],
|
||||||
|
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(Conf) ->
|
fun(Conf) ->
|
||||||
{ok, 200, _} = request(
|
{ok, 200, _} = request(
|
||||||
post,
|
post,
|
||||||
uri(PathPrefix ++ [?CONF_NS]),
|
uri(PathPrefix ++ [?CONF_NS]),
|
||||||
Conf)
|
Conf
|
||||||
end,
|
)
|
||||||
AuthenticatorConfs),
|
end,
|
||||||
|
AuthenticatorConfs
|
||||||
|
),
|
||||||
|
|
||||||
?assertAuthenticatorsMatch(
|
?assertAuthenticatorsMatch(
|
||||||
[
|
[
|
||||||
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>},
|
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>},
|
||||||
#{<<"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
|
||||||
|
|
||||||
%% test front
|
%% test front
|
||||||
{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(
|
||||||
[
|
[
|
||||||
#{<<"mechanism">> := <<"jwt">>},
|
#{<<"mechanism">> := <<"jwt">>},
|
||||||
#{<<"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(
|
||||||
[
|
[
|
||||||
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>},
|
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>},
|
||||||
#{<<"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(
|
||||||
[
|
[
|
||||||
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>},
|
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>},
|
||||||
#{<<"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(
|
||||||
[
|
[
|
||||||
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>},
|
#{<<"mechanism">> := <<"password_based">>, <<"backend">> := <<"http">>},
|
||||||
#{<<"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,12 +28,12 @@
|
||||||
|
|
||||||
-define(HTTP_PORT, 33333).
|
-define(HTTP_PORT, 33333).
|
||||||
-define(HTTP_PATH, "/auth").
|
-define(HTTP_PATH, "/auth").
|
||||||
-define(CREDENTIALS, #{username => <<"plain">>,
|
-define(CREDENTIALS, #{
|
||||||
password => <<"plain">>,
|
username => <<"plain">>,
|
||||||
listener => 'tcp:default',
|
password => <<"plain">>,
|
||||||
protocol => mqtt
|
listener => 'tcp:default',
|
||||||
}).
|
protocol => mqtt
|
||||||
|
}).
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
emqx_common_test_helpers:all(?MODULE).
|
emqx_common_test_helpers:all(?MODULE).
|
||||||
|
@ -46,8 +46,9 @@ 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.
|
||||||
|
@ -55,8 +56,9 @@ end_per_suite(_) ->
|
||||||
init_per_testcase(_Case, Config) ->
|
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.
|
||||||
|
|
||||||
|
@ -71,8 +73,9 @@ t_create(_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, [#{provider := emqx_authn_http}]} = emqx_authentication:list_authenticators(?GLOBAL).
|
{ok, [#{provider := emqx_authn_http}]} = emqx_authentication:list_authenticators(?GLOBAL).
|
||||||
|
|
||||||
|
@ -81,83 +84,96 @@ t_create_invalid(_Config) ->
|
||||||
|
|
||||||
InvalidConfigs =
|
InvalidConfigs =
|
||||||
[
|
[
|
||||||
AuthConfig#{headers => []},
|
AuthConfig#{headers => []},
|
||||||
AuthConfig#{method => delete}
|
AuthConfig#{method => delete}
|
||||||
],
|
],
|
||||||
|
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(Config) ->
|
fun(Config) ->
|
||||||
ct:pal("creating authenticator with invalid config: ~p", [Config]),
|
ct:pal("creating authenticator with invalid config: ~p", [Config]),
|
||||||
{error, _} =
|
{error, _} =
|
||||||
try
|
try
|
||||||
emqx:update_config(
|
emqx:update_config(
|
||||||
?PATH,
|
?PATH,
|
||||||
{create_authenticator, ?GLOBAL, Config})
|
{create_authenticator, ?GLOBAL, Config}
|
||||||
catch
|
)
|
||||||
throw:Error ->
|
catch
|
||||||
{error, Error}
|
throw:Error ->
|
||||||
end,
|
{error, Error}
|
||||||
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL)
|
end,
|
||||||
end,
|
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL)
|
||||||
InvalidConfigs).
|
end,
|
||||||
|
InvalidConfigs
|
||||||
|
).
|
||||||
|
|
||||||
t_authenticate(_Config) ->
|
t_authenticate(_Config) ->
|
||||||
ok = lists:foreach(
|
ok = lists:foreach(
|
||||||
fun(Sample) ->
|
fun(Sample) ->
|
||||||
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(#{
|
||||||
config_params := SpecificConfgParams,
|
handler := Handler,
|
||||||
result := Result}) ->
|
config_params := SpecificConfgParams,
|
||||||
|
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),
|
||||||
|
|
||||||
?assertEqual(Result, emqx_access_control:authenticate(?CREDENTIALS)),
|
?assertEqual(Result, emqx_access_control:authenticate(?CREDENTIALS)),
|
||||||
|
|
||||||
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(),
|
||||||
|
@ -165,74 +181,80 @@ t_update(_Config) ->
|
||||||
CorrectConfig#{url => <<"http://127.0.0.1:33333/invalid">>},
|
CorrectConfig#{url => <<"http://127.0.0.1:33333/invalid">>},
|
||||||
|
|
||||||
{ok, _} = emqx:update_config(
|
{ok, _} = emqx:update_config(
|
||||||
?PATH,
|
?PATH,
|
||||||
{create_authenticator, ?GLOBAL, IncorrectConfig}),
|
{create_authenticator, ?GLOBAL, IncorrectConfig}
|
||||||
|
),
|
||||||
|
|
||||||
ok = emqx_authn_http_test_server:set_handler(
|
ok = emqx_authn_http_test_server:set_handler(
|
||||||
fun(Req0, State) ->
|
fun(Req0, State) ->
|
||||||
Req = cowboy_req:reply(200, Req0),
|
Req = cowboy_req:reply(200, Req0),
|
||||||
{ok, Req, State}
|
{ok, Req, State}
|
||||||
end),
|
end
|
||||||
|
),
|
||||||
|
|
||||||
{error, not_authorized} = emqx_access_control:authenticate(?CREDENTIALS),
|
{error, not_authorized} = emqx_access_control:authenticate(?CREDENTIALS),
|
||||||
|
|
||||||
% We update with config with correct query, provider should update and work properly
|
% We update with config with correct query, provider should update and work properly
|
||||||
{ok, _} = emqx:update_config(
|
{ok, _} = emqx:update_config(
|
||||||
?PATH,
|
?PATH,
|
||||||
{update_authenticator, ?GLOBAL, <<"password_based:http">>, CorrectConfig}),
|
{update_authenticator, ?GLOBAL, <<"password_based:http">>, CorrectConfig}
|
||||||
|
),
|
||||||
|
|
||||||
{ok,_} = emqx_access_control:authenticate(?CREDENTIALS).
|
{ok, _} = emqx_access_control:authenticate(?CREDENTIALS).
|
||||||
|
|
||||||
t_is_superuser(_Config) ->
|
t_is_superuser(_Config) ->
|
||||||
Config = raw_http_auth_config(),
|
Config = raw_http_auth_config(),
|
||||||
{ok, _} = emqx:update_config(
|
{ok, _} = emqx:update_config(
|
||||||
?PATH,
|
?PATH,
|
||||||
{create_authenticator, ?GLOBAL, Config}),
|
{create_authenticator, ?GLOBAL, Config}
|
||||||
|
),
|
||||||
|
|
||||||
Checks = [
|
Checks = [
|
||||||
{json, <<"0">>, false},
|
{json, <<"0">>, false},
|
||||||
{json, <<"">>, false},
|
{json, <<"">>, false},
|
||||||
{json, null, false},
|
{json, null, false},
|
||||||
{json, 0, false},
|
{json, 0, false},
|
||||||
|
|
||||||
{json, <<"1">>, true},
|
{json, <<"1">>, true},
|
||||||
{json, <<"val">>, true},
|
{json, <<"val">>, true},
|
||||||
{json, 1, true},
|
{json, 1, true},
|
||||||
{json, 123, true},
|
{json, 123, true},
|
||||||
|
|
||||||
{form, <<"0">>, false},
|
{form, <<"0">>, false},
|
||||||
{form, <<"">>, false},
|
{form, <<"">>, false},
|
||||||
|
|
||||||
{form, <<"1">>, true},
|
{form, <<"1">>, true},
|
||||||
{form, <<"val">>, true}
|
{form, <<"val">>, true}
|
||||||
],
|
],
|
||||||
|
|
||||||
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])}
|
end,
|
||||||
end,
|
|
||||||
|
|
||||||
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(
|
Req = cowboy_req:reply(
|
||||||
200,
|
200,
|
||||||
#{<<"content-type">> => ContentType},
|
#{<<"content-type">> => ContentType},
|
||||||
Res,
|
Res,
|
||||||
Req0),
|
Req0
|
||||||
{ok, Req, State}
|
),
|
||||||
end),
|
{ok, Req, State}
|
||||||
|
end
|
||||||
|
),
|
||||||
|
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{ok, #{is_superuser := ExpectedValue}},
|
{ok, #{is_superuser := ExpectedValue}},
|
||||||
emqx_access_control:authenticate(?CREDENTIALS)).
|
emqx_access_control:authenticate(?CREDENTIALS)
|
||||||
|
).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
@ -252,138 +274,159 @@ raw_http_auth_config() ->
|
||||||
|
|
||||||
samples() ->
|
samples() ->
|
||||||
[
|
[
|
||||||
%% simple get request
|
%% simple get request
|
||||||
#{handler => fun(Req0, State) ->
|
#{
|
||||||
#{username := <<"plain">>,
|
handler => fun(Req0, State) ->
|
||||||
password := <<"plain">>
|
#{
|
||||||
} = cowboy_req:match_qs([username, password], Req0),
|
username := <<"plain">>,
|
||||||
|
password := <<"plain">>
|
||||||
|
} = cowboy_req:match_qs([username, password], Req0),
|
||||||
|
|
||||||
Req = cowboy_req:reply(200, Req0),
|
Req = cowboy_req:reply(200, Req0),
|
||||||
{ok, Req, State}
|
{ok, Req, State}
|
||||||
end,
|
end,
|
||||||
config_params => #{},
|
config_params => #{},
|
||||||
result => {ok,#{is_superuser => false}}
|
result => {ok, #{is_superuser => false}}
|
||||||
},
|
},
|
||||||
|
|
||||||
%% get request with json body response
|
%% get request with json body response
|
||||||
#{handler => fun(Req0, State) ->
|
#{
|
||||||
Req = cowboy_req:reply(
|
handler => fun(Req0, State) ->
|
||||||
200,
|
Req = cowboy_req:reply(
|
||||||
#{<<"content-type">> => <<"application/json">>},
|
200,
|
||||||
jiffy:encode(#{is_superuser => true}),
|
#{<<"content-type">> => <<"application/json">>},
|
||||||
Req0),
|
jiffy:encode(#{is_superuser => true}),
|
||||||
{ok, Req, State}
|
Req0
|
||||||
end,
|
),
|
||||||
config_params => #{},
|
{ok, Req, State}
|
||||||
result => {ok,#{is_superuser => true, user_property => #{}}}
|
end,
|
||||||
},
|
config_params => #{},
|
||||||
|
result => {ok, #{is_superuser => true, user_property => #{}}}
|
||||||
|
},
|
||||||
|
|
||||||
%% get request with url-form-encoded body response
|
%% get request with url-form-encoded body response
|
||||||
#{handler => fun(Req0, State) ->
|
#{
|
||||||
Req = cowboy_req:reply(
|
handler => fun(Req0, State) ->
|
||||||
200,
|
Req = cowboy_req:reply(
|
||||||
#{<<"content-type">> =>
|
200,
|
||||||
<<"application/x-www-form-urlencoded">>},
|
#{
|
||||||
<<"is_superuser=true">>,
|
<<"content-type">> =>
|
||||||
Req0),
|
<<"application/x-www-form-urlencoded">>
|
||||||
{ok, Req, State}
|
},
|
||||||
end,
|
<<"is_superuser=true">>,
|
||||||
config_params => #{},
|
Req0
|
||||||
result => {ok,#{is_superuser => true, user_property => #{}}}
|
),
|
||||||
},
|
{ok, Req, State}
|
||||||
|
end,
|
||||||
|
config_params => #{},
|
||||||
|
result => {ok, #{is_superuser => true, user_property => #{}}}
|
||||||
|
},
|
||||||
|
|
||||||
%% get request with response of unknown encoding
|
%% get request with response of unknown encoding
|
||||||
#{handler => fun(Req0, State) ->
|
#{
|
||||||
Req = cowboy_req:reply(
|
handler => fun(Req0, State) ->
|
||||||
200,
|
Req = cowboy_req:reply(
|
||||||
#{<<"content-type">> =>
|
200,
|
||||||
<<"test/plain">>},
|
#{
|
||||||
<<"is_superuser=true">>,
|
<<"content-type">> =>
|
||||||
Req0),
|
<<"test/plain">>
|
||||||
{ok, Req, State}
|
},
|
||||||
end,
|
<<"is_superuser=true">>,
|
||||||
config_params => #{},
|
Req0
|
||||||
result => {ok,#{is_superuser => false}}
|
),
|
||||||
},
|
{ok, Req, State}
|
||||||
|
end,
|
||||||
|
config_params => #{},
|
||||||
|
result => {ok, #{is_superuser => false}}
|
||||||
|
},
|
||||||
|
|
||||||
%% simple post request, application/json
|
%% simple post request, application/json
|
||||||
#{handler => fun(Req0, State) ->
|
#{
|
||||||
{ok, RawBody, Req1} = cowboy_req:read_body(Req0),
|
handler => fun(Req0, State) ->
|
||||||
#{<<"username">> := <<"plain">>,
|
{ok, RawBody, Req1} = cowboy_req:read_body(Req0),
|
||||||
<<"password">> := <<"plain">>
|
#{
|
||||||
} = jiffy:decode(RawBody, [return_maps]),
|
<<"username">> := <<"plain">>,
|
||||||
Req = cowboy_req:reply(200, Req1),
|
<<"password">> := <<"plain">>
|
||||||
{ok, Req, State}
|
} = jiffy:decode(RawBody, [return_maps]),
|
||||||
end,
|
Req = cowboy_req:reply(200, Req1),
|
||||||
config_params => #{
|
{ok, Req, State}
|
||||||
method => post,
|
end,
|
||||||
headers => #{<<"content-type">> => <<"application/json">>}
|
config_params => #{
|
||||||
},
|
method => post,
|
||||||
result => {ok,#{is_superuser => false}}
|
headers => #{<<"content-type">> => <<"application/json">>}
|
||||||
},
|
},
|
||||||
|
result => {ok, #{is_superuser => false}}
|
||||||
|
},
|
||||||
|
|
||||||
%% simple post request, application/x-www-form-urlencoded
|
%% simple post request, application/x-www-form-urlencoded
|
||||||
#{handler => fun(Req0, State) ->
|
#{
|
||||||
{ok, PostVars, Req1} = cowboy_req:read_urlencoded_body(Req0),
|
handler => fun(Req0, State) ->
|
||||||
#{<<"username">> := <<"plain">>,
|
{ok, PostVars, Req1} = cowboy_req:read_urlencoded_body(Req0),
|
||||||
<<"password">> := <<"plain">>
|
#{
|
||||||
} = maps:from_list(PostVars),
|
<<"username">> := <<"plain">>,
|
||||||
Req = cowboy_req:reply(200, Req1),
|
<<"password">> := <<"plain">>
|
||||||
{ok, Req, State}
|
} = maps:from_list(PostVars),
|
||||||
end,
|
Req = cowboy_req:reply(200, Req1),
|
||||||
config_params => #{
|
{ok, Req, State}
|
||||||
method => post,
|
end,
|
||||||
headers => #{<<"content-type">> =>
|
config_params => #{
|
||||||
<<"application/x-www-form-urlencoded">>}
|
method => post,
|
||||||
},
|
headers => #{
|
||||||
result => {ok,#{is_superuser => false}}
|
<<"content-type">> =>
|
||||||
}
|
<<"application/x-www-form-urlencoded">>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
result => {ok, #{is_superuser => false}}
|
||||||
|
}#{
|
||||||
|
%% 204 code
|
||||||
|
handler => fun(Req0, State) ->
|
||||||
|
Req = cowboy_req:reply(204, Req0),
|
||||||
|
{ok, Req, State}
|
||||||
|
end,
|
||||||
|
config_params => #{},
|
||||||
|
result => {ok, #{is_superuser => false}}
|
||||||
|
},
|
||||||
|
|
||||||
%% 204 code
|
%% custom headers
|
||||||
#{handler => fun(Req0, State) ->
|
#{
|
||||||
Req = cowboy_req:reply(204, Req0),
|
handler => fun(Req0, State) ->
|
||||||
{ok, Req, State}
|
<<"Test Value">> = cowboy_req:header(<<"x-test-header">>, Req0),
|
||||||
end,
|
Req = cowboy_req:reply(200, Req0),
|
||||||
config_params => #{},
|
{ok, Req, State}
|
||||||
result => {ok,#{is_superuser => false}}
|
end,
|
||||||
},
|
config_params => #{},
|
||||||
|
result => {ok, #{is_superuser => false}}
|
||||||
|
},
|
||||||
|
|
||||||
%% custom headers
|
%% 400 code
|
||||||
#{handler => fun(Req0, State) ->
|
#{
|
||||||
<<"Test Value">> = cowboy_req:header(<<"x-test-header">>, Req0),
|
handler => fun(Req0, State) ->
|
||||||
Req = cowboy_req:reply(200, Req0),
|
Req = cowboy_req:reply(400, Req0),
|
||||||
{ok, Req, State}
|
{ok, Req, State}
|
||||||
end,
|
end,
|
||||||
config_params => #{},
|
config_params => #{},
|
||||||
result => {ok,#{is_superuser => false}}
|
result => {error, not_authorized}
|
||||||
},
|
},
|
||||||
|
|
||||||
%% 400 code
|
%% 500 code
|
||||||
#{handler => fun(Req0, State) ->
|
#{
|
||||||
Req = cowboy_req:reply(400, Req0),
|
handler => fun(Req0, State) ->
|
||||||
{ok, Req, State}
|
Req = cowboy_req:reply(500, Req0),
|
||||||
end,
|
{ok, Req, State}
|
||||||
config_params => #{},
|
end,
|
||||||
result => {error,not_authorized}
|
config_params => #{},
|
||||||
},
|
result => {error, not_authorized}
|
||||||
|
},
|
||||||
|
|
||||||
%% 500 code
|
%% Handling error
|
||||||
#{handler => fun(Req0, State) ->
|
#{
|
||||||
Req = cowboy_req:reply(500, Req0),
|
handler => fun(Req0, State) ->
|
||||||
{ok, Req, State}
|
error(woops),
|
||||||
end,
|
{ok, Req0, State}
|
||||||
config_params => #{},
|
end,
|
||||||
result => {error,not_authorized}
|
config_params => #{},
|
||||||
},
|
result => {error, not_authorized}
|
||||||
|
}
|
||||||
%% Handling error
|
|
||||||
#{handler => fun(Req0, State) ->
|
|
||||||
error(woops),
|
|
||||||
{ok, Req0, State}
|
|
||||||
end,
|
|
||||||
config_params => #{},
|
|
||||||
result => {error,not_authorized}
|
|
||||||
}
|
|
||||||
].
|
].
|
||||||
|
|
||||||
start_apps(Apps) ->
|
start_apps(Apps) ->
|
||||||
|
|
|
@ -26,11 +26,12 @@
|
||||||
-export([init/1]).
|
-export([init/1]).
|
||||||
|
|
||||||
% API
|
% API
|
||||||
-export([start_link/2,
|
-export([
|
||||||
start_link/3,
|
start_link/2,
|
||||||
stop/0,
|
start_link/3,
|
||||||
set_handler/1
|
stop/0,
|
||||||
]).
|
set_handler/1
|
||||||
|
]).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% API
|
%% API
|
||||||
|
@ -55,9 +56,10 @@ set_handler(F) when is_function(F, 2) ->
|
||||||
|
|
||||||
init([Port, Path, SSLOpts]) ->
|
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,23 +85,28 @@ 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 = #{
|
||||||
{next_protocols_advertised, [<<"h2">>, <<"http/1.1">>]},
|
socket_opts => [
|
||||||
{alpn_preferred_protocols, [<<"h2">>, <<"http/1.1">>]}
|
{port, Port},
|
||||||
| SSLOpts],
|
{next_protocols_advertised, [<<"h2">>, <<"http/1.1">>]},
|
||||||
connection_type => supervisor},
|
{alpn_preferred_protocols, [<<"h2">>, <<"http/1.1">>]}
|
||||||
|
| SSLOpts
|
||||||
|
],
|
||||||
|
connection_type => supervisor
|
||||||
|
},
|
||||||
{ranch_ssl, TransOpts, cowboy_tls}.
|
{ranch_ssl, TransOpts, cowboy_tls}.
|
||||||
|
|
||||||
default_handler(Req0, State) ->
|
default_handler(Req0, State) ->
|
||||||
Req = cowboy_req:reply(
|
Req = cowboy_req:reply(
|
||||||
400,
|
400,
|
||||||
#{<<"content-type">> => <<"text/plain">>},
|
#{<<"content-type">> => <<"text/plain">>},
|
||||||
<<"">>,
|
<<"">>,
|
||||||
Req0),
|
Req0
|
||||||
|
),
|
||||||
{ok, Req, State}.
|
{ok, Req, State}.
|
||||||
|
|
||||||
|
|
|
@ -28,12 +28,12 @@
|
||||||
|
|
||||||
-define(HTTPS_PORT, 33333).
|
-define(HTTPS_PORT, 33333).
|
||||||
-define(HTTPS_PATH, "/auth").
|
-define(HTTPS_PATH, "/auth").
|
||||||
-define(CREDENTIALS, #{username => <<"plain">>,
|
-define(CREDENTIALS, #{
|
||||||
password => <<"plain">>,
|
username => <<"plain">>,
|
||||||
listener => 'tcp:default',
|
password => <<"plain">>,
|
||||||
protocol => mqtt
|
listener => 'tcp:default',
|
||||||
}).
|
protocol => mqtt
|
||||||
|
}).
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
emqx_common_test_helpers:all(?MODULE).
|
emqx_common_test_helpers:all(?MODULE).
|
||||||
|
@ -46,8 +46,9 @@ 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.
|
||||||
|
@ -55,8 +56,9 @@ end_per_suite(_) ->
|
||||||
init_per_testcase(_Case, Config) ->
|
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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.2">>],
|
<<"verify">> => <<"verify_peer">>,
|
||||||
<<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]}),
|
<<"versions">> => [<<"tlsv1.2">>],
|
||||||
|
<<"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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server-unknown-host">>,
|
||||||
<<"versions">> => [<<"tlsv1.2">>],
|
<<"verify">> => <<"verify_peer">>,
|
||||||
<<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]}),
|
<<"versions">> => [<<"tlsv1.2">>],
|
||||||
|
<<"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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.1">>]}),
|
<<"verify">> => <<"verify_peer">>,
|
||||||
|
<<"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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.2">>],
|
<<"verify">> => <<"verify_peer">>,
|
||||||
<<"ciphers">> => [<<"ECDHE-ECDSA-AES256-SHA384">>]}),
|
<<"versions">> => [<<"tlsv1.2">>],
|
||||||
|
<<"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
|
||||||
|
@ -121,8 +139,9 @@ 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">>,
|
||||||
|
@ -133,7 +152,7 @@ raw_https_auth_config(SpecificSSLOpts) ->
|
||||||
body => #{<<"username">> => ?PH_USERNAME, <<"password">> => ?PH_PASSWORD},
|
body => #{<<"username">> => ?PH_USERNAME, <<"password">> => ?PH_PASSWORD},
|
||||||
headers => #{<<"X-Test-Header">> => <<"Test Value">>},
|
headers => #{<<"X-Test-Header">> => <<"Test Value">>},
|
||||||
ssl => maps:merge(SSLOpts, SpecificSSLOpts)
|
ssl => maps:merge(SSLOpts, SpecificSSLOpts)
|
||||||
}.
|
}.
|
||||||
|
|
||||||
start_apps(Apps) ->
|
start_apps(Apps) ->
|
||||||
lists:foreach(fun application:ensure_all_started/1, Apps).
|
lists:foreach(fun application:ensure_all_started/1, Apps).
|
||||||
|
@ -147,15 +166,17 @@ 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")},
|
[
|
||||||
{certfile, cert_path("server.crt")},
|
{keyfile, cert_path("server.key")},
|
||||||
{cacertfile, cert_path("ca.crt")},
|
{certfile, cert_path("server.crt")},
|
||||||
{verify, verify_none},
|
{cacertfile, cert_path("ca.crt")},
|
||||||
{versions, ['tlsv1.2', 'tlsv1.3']},
|
{verify, verify_none},
|
||||||
{ciphers, ["ECDHE-RSA-AES256-GCM-SHA384", "TLS_CHACHA20_POLY1305_SHA256"]}
|
{versions, ['tlsv1.2', 'tlsv1.3']},
|
||||||
|
{ciphers, ["ECDHE-RSA-AES256-GCM-SHA384", "TLS_CHACHA20_POLY1305_SHA256"]}
|
||||||
].
|
].
|
||||||
|
|
|
@ -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 = #{
|
||||||
use_jwks => false,
|
mechanism => jwt,
|
||||||
algorithm => 'hmac-based',
|
use_jwks => false,
|
||||||
secret => Secret,
|
algorithm => 'hmac-based',
|
||||||
secret_base64_encoded => false,
|
secret => Secret,
|
||||||
verify_claims => []},
|
secret_base64_encoded => false,
|
||||||
|
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 = #{
|
||||||
use_jwks => false,
|
mechanism => jwt,
|
||||||
algorithm => 'public-key',
|
use_jwks => false,
|
||||||
certificate => PublicKey,
|
algorithm => 'public-key',
|
||||||
verify_claims => []},
|
certificate => PublicKey,
|
||||||
|
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,63 +196,77 @@ 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 = #{
|
||||||
algorithm => 'public-key',
|
mechanism => jwt,
|
||||||
ssl => #{enable => false},
|
algorithm => 'public-key',
|
||||||
verify_claims => [],
|
ssl => #{enable => false},
|
||||||
|
verify_claims => [],
|
||||||
|
|
||||||
use_jwks => true,
|
use_jwks => true,
|
||||||
endpoint => "https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT + 1) ++ ?JWKS_PATH,
|
endpoint => "https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT + 1) ++ ?JWKS_PATH,
|
||||||
refresh_interval => 1000
|
refresh_interval => 1000
|
||||||
},
|
},
|
||||||
|
|
||||||
ok = snabbkaffe:start_trace(),
|
ok = snabbkaffe:start_trace(),
|
||||||
|
|
||||||
{{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#{
|
||||||
"https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT) ++ ?JWKS_PATH,
|
endpoint =>
|
||||||
ssl => BadClientSSLOpts},
|
"https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT) ++ ?JWKS_PATH,
|
||||||
|
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},
|
||||||
|
|
||||||
ok = snabbkaffe:start_trace(),
|
ok = snabbkaffe:start_trace(),
|
||||||
|
|
||||||
{{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().
|
||||||
|
@ -229,15 +279,15 @@ jwks_handler(Req0, State) ->
|
||||||
JWK = jose_jwk:from_pem_file(test_rsa_key(public)),
|
JWK = jose_jwk:from_pem_file(test_rsa_key(public)),
|
||||||
JWKS = jose_jwk_set:to_map([JWK], #{}),
|
JWKS = jose_jwk_set:to_map([JWK], #{}),
|
||||||
Req = cowboy_req:reply(
|
Req = cowboy_req:reply(
|
||||||
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,32 +300,37 @@ 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),
|
||||||
JWS.
|
JWS.
|
||||||
|
|
||||||
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,
|
#{
|
||||||
verify => verify_peer,
|
enable => true,
|
||||||
server_name_indication => "authn-server"
|
verify => verify_peer,
|
||||||
}).
|
server_name_indication => "authn-server"
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
server_ssl_opts() ->
|
server_ssl_opts() ->
|
||||||
[{keyfile, cert_file("server.key")},
|
[
|
||||||
{certfile, cert_file("server.crt")},
|
{keyfile, cert_file("server.key")},
|
||||||
{cacertfile, cert_file("ca.crt")},
|
{certfile, cert_file("server.crt")},
|
||||||
{verify, verify_none}
|
{cacertfile, cert_file("ca.crt")},
|
||||||
|
{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(),
|
||||||
|
@ -110,7 +111,7 @@ t_destroy(_) ->
|
||||||
ok = emqx_authn_mnesia:destroy(State0),
|
ok = emqx_authn_mnesia:destroy(State0),
|
||||||
|
|
||||||
{ok, State1} = emqx_authn_mnesia:create(?AUTHN_ID, Config),
|
{ok, State1} = emqx_authn_mnesia:create(?AUTHN_ID, Config),
|
||||||
{error,not_found} = emqx_authn_mnesia:lookup_user(<<"u">>, State1),
|
{error, not_found} = emqx_authn_mnesia:lookup_user(<<"u">>, State1),
|
||||||
{ok, _} = emqx_authn_mnesia:lookup_user(<<"u">>, StateOther).
|
{ok, _} = emqx_authn_mnesia:lookup_user(<<"u">>, StateOther).
|
||||||
|
|
||||||
t_authenticate(_) ->
|
t_authenticate(_) ->
|
||||||
|
@ -121,14 +122,17 @@ t_authenticate(_) ->
|
||||||
{ok, _} = emqx_authn_mnesia:add_user(User, State),
|
{ok, _} = emqx_authn_mnesia:add_user(User, State),
|
||||||
|
|
||||||
{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
|
||||||
<<"u">>,
|
}} = emqx_authn_mnesia:update_user(
|
||||||
#{password => <<"p1">>, is_superuser => true},
|
<<"u">>,
|
||||||
State),
|
#{password => <<"p1">>, is_superuser => true},
|
||||||
|
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 => <<"u2">>, password => <<"p">>},
|
#{user_id => <<"u1">>, password => <<"p">>},
|
||||||
#{user_id => <<"u3">>, password => <<"p">>}],
|
#{user_id => <<"u2">>, 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 := _},
|
||||||
#{<<"page">> => 1, <<"limit">> => 2},
|
#{is_superuser := false, user_id := _}
|
||||||
State),
|
],
|
||||||
|
meta := #{page := 1, limit := 2, count := 3}
|
||||||
|
} = emqx_authn_mnesia:list_users(
|
||||||
|
#{<<"page">> => 1, <<"limit">> => 2},
|
||||||
|
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 := _}],
|
||||||
#{<<"page">> => 2, <<"limit">> => 2},
|
meta := #{page := 2, limit := 2, count := 3}
|
||||||
State),
|
} = emqx_authn_mnesia:list_users(
|
||||||
|
#{<<"page">> => 2, <<"limit">> => 2},
|
||||||
|
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(),
|
||||||
|
@ -206,36 +229,44 @@ t_import_users(_) ->
|
||||||
{ok, State} = emqx_authn_mnesia:create(?AUTHN_ID, Config),
|
{ok, State} = emqx_authn_mnesia:create(?AUTHN_ID, Config),
|
||||||
|
|
||||||
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').
|
||||||
|
|
||||||
|
@ -37,8 +36,9 @@ init_per_testcase(_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_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.
|
||||||
|
|
||||||
|
@ -58,8 +58,9 @@ 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]).
|
||||||
|
|
||||||
|
@ -71,8 +72,9 @@ t_create(_Config) ->
|
||||||
AuthConfig = raw_mongo_auth_config(),
|
AuthConfig = raw_mongo_auth_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).
|
||||||
|
|
||||||
|
@ -81,79 +83,93 @@ t_create_invalid(_Config) ->
|
||||||
|
|
||||||
InvalidConfigs =
|
InvalidConfigs =
|
||||||
[
|
[
|
||||||
AuthConfig#{mongo_type => <<"unknown">>},
|
AuthConfig#{mongo_type => <<"unknown">>},
|
||||||
AuthConfig#{selector => <<"{ \"username\": \"${username}\" }">>},
|
AuthConfig#{selector => <<"{ \"username\": \"${username}\" }">>},
|
||||||
AuthConfig#{w_mode => <<"unknown">>}
|
AuthConfig#{w_mode => <<"unknown">>}
|
||||||
],
|
],
|
||||||
|
|
||||||
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
|
||||||
|
).
|
||||||
|
|
||||||
t_authenticate(_Config) ->
|
t_authenticate(_Config) ->
|
||||||
ok = init_seeds(),
|
ok = init_seeds(),
|
||||||
ok = lists:foreach(
|
ok = lists:foreach(
|
||||||
fun(Sample) ->
|
fun(Sample) ->
|
||||||
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(#{
|
||||||
config_params := SpecificConfigParams,
|
credentials := Credentials0,
|
||||||
result := Result}) ->
|
config_params := SpecificConfigParams,
|
||||||
|
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',
|
||||||
protocol => mqtt
|
protocol => mqtt
|
||||||
},
|
},
|
||||||
?assertEqual(Result, emqx_access_control:authenticate(Credentials)),
|
?assertEqual(Result, emqx_access_control:authenticate(Credentials)),
|
||||||
|
|
||||||
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(),
|
||||||
AuthConfig = raw_mongo_auth_config(),
|
AuthConfig = raw_mongo_auth_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">>,
|
#{
|
||||||
password => <<"plain">>
|
username => <<"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">>,
|
#{
|
||||||
password => <<"plain">>
|
username => <<"plain">>,
|
||||||
},
|
password => <<"plain">>
|
||||||
State)),
|
},
|
||||||
|
State
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
ok = drop_seeds().
|
ok = drop_seeds().
|
||||||
|
|
||||||
|
@ -164,48 +180,55 @@ t_update(_Config) ->
|
||||||
CorrectConfig#{selector => #{<<"wrongfield">> => <<"wrongvalue">>}},
|
CorrectConfig#{selector => #{<<"wrongfield">> => <<"wrongvalue">>}},
|
||||||
|
|
||||||
{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">>,
|
#{
|
||||||
password => <<"plain">>,
|
username => <<"plain">>,
|
||||||
listener => 'tcp:default',
|
password => <<"plain">>,
|
||||||
protocol => mqtt
|
listener => 'tcp:default',
|
||||||
}),
|
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">>,
|
#{
|
||||||
password => <<"plain">>,
|
username => <<"plain">>,
|
||||||
listener => 'tcp:default',
|
password => <<"plain">>,
|
||||||
protocol => mqtt
|
listener => 'tcp:default',
|
||||||
}),
|
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},
|
||||||
{<<"">>, false},
|
{<<"">>, false},
|
||||||
{null, false},
|
{null, false},
|
||||||
{false, false},
|
{false, false},
|
||||||
{0, false},
|
{0, false},
|
||||||
|
|
||||||
{<<"1">>, true},
|
{<<"1">>, true},
|
||||||
{<<"val">>, true},
|
{<<"val">>, true},
|
||||||
{1, true},
|
{1, true},
|
||||||
{123, true},
|
{123, true},
|
||||||
{true, true}
|
{true, true}
|
||||||
],
|
],
|
||||||
|
|
||||||
lists:foreach(fun test_is_superuser/1, Checks).
|
lists:foreach(fun test_is_superuser/1, Checks).
|
||||||
|
|
||||||
|
@ -213,24 +236,25 @@ test_is_superuser({Value, ExpectedValue}) ->
|
||||||
{true, _} = mc_worker_api:delete(?MONGO_CLIENT, <<"users">>, #{}),
|
{true, _} = mc_worker_api:delete(?MONGO_CLIENT, <<"users">>, #{}),
|
||||||
|
|
||||||
UserData = #{
|
UserData = #{
|
||||||
username => <<"user">>,
|
username => <<"user">>,
|
||||||
password_hash => <<"plainsalt">>,
|
password_hash => <<"plainsalt">>,
|
||||||
salt => <<"salt">>,
|
salt => <<"salt">>,
|
||||||
is_superuser => Value
|
is_superuser => Value
|
||||||
},
|
},
|
||||||
|
|
||||||
{{true, _}, _} = mc_worker_api:insert(?MONGO_CLIENT, <<"users">>, [UserData]),
|
{{true, _}, _} = mc_worker_api:insert(?MONGO_CLIENT, <<"users">>, [UserData]),
|
||||||
|
|
||||||
Credentials = #{
|
Credentials = #{
|
||||||
listener => 'tcp:default',
|
listener => 'tcp:default',
|
||||||
protocol => mqtt,
|
protocol => mqtt,
|
||||||
username => <<"user">>,
|
username => <<"user">>,
|
||||||
password => <<"plain">>
|
password => <<"plain">>
|
||||||
},
|
},
|
||||||
|
|
||||||
?assertEqual(
|
?assertEqual(
|
||||||
{ok, #{is_superuser => ExpectedValue}},
|
{ok, #{is_superuser => ExpectedValue}},
|
||||||
emqx_access_control:authenticate(Credentials)).
|
emqx_access_control:authenticate(Credentials)
|
||||||
|
).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
@ -238,146 +262,160 @@ 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">>,
|
||||||
enable => <<"true">>,
|
salt_position => <<"suffix">>
|
||||||
|
},
|
||||||
|
enable => <<"true">>,
|
||||||
|
|
||||||
backend => <<"mongodb">>,
|
backend => <<"mongodb">>,
|
||||||
mongo_type => <<"single">>,
|
mongo_type => <<"single">>,
|
||||||
database => <<"mqtt">>,
|
database => <<"mqtt">>,
|
||||||
collection => <<"users">>,
|
collection => <<"users">>,
|
||||||
server => mongo_server(),
|
server => mongo_server(),
|
||||||
w_mode => <<"unsafe">>,
|
w_mode => <<"unsafe">>,
|
||||||
|
|
||||||
selector => #{<<"username">> => <<"${username}">>},
|
selector => #{<<"username">> => <<"${username}">>},
|
||||||
password_hash_field => <<"password_hash">>,
|
password_hash_field => <<"password_hash">>,
|
||||||
salt_field => <<"salt">>,
|
salt_field => <<"salt">>,
|
||||||
is_superuser_field => <<"is_superuser">>
|
is_superuser_field => <<"is_superuser">>
|
||||||
}.
|
}.
|
||||||
|
|
||||||
user_seeds() ->
|
user_seeds() ->
|
||||||
[#{data => #{
|
[
|
||||||
username => <<"plain">>,
|
#{
|
||||||
password_hash => <<"plainsalt">>,
|
data => #{
|
||||||
salt => <<"salt">>,
|
username => <<"plain">>,
|
||||||
is_superuser => <<"1">>
|
password_hash => <<"plainsalt">>,
|
||||||
},
|
salt => <<"salt">>,
|
||||||
credentials => #{
|
is_superuser => <<"1">>
|
||||||
username => <<"plain">>,
|
},
|
||||||
password => <<"plain">>
|
credentials => #{
|
||||||
},
|
username => <<"plain">>,
|
||||||
config_params => #{
|
password => <<"plain">>
|
||||||
},
|
},
|
||||||
result => {ok,#{is_superuser => true}}
|
config_params => #{},
|
||||||
},
|
result => {ok, #{is_superuser => true}}
|
||||||
|
|
||||||
#{data => #{
|
|
||||||
username => <<"md5">>,
|
|
||||||
password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>,
|
|
||||||
salt => <<"salt">>,
|
|
||||||
is_superuser => <<"0">>
|
|
||||||
},
|
|
||||||
credentials => #{
|
|
||||||
username => <<"md5">>,
|
|
||||||
password => <<"md5">>
|
|
||||||
},
|
|
||||||
config_params => #{
|
|
||||||
password_hash_algorithm => #{name => <<"md5">>,
|
|
||||||
salt_position => <<"suffix">> }
|
|
||||||
},
|
|
||||||
result => {ok,#{is_superuser => false}}
|
|
||||||
},
|
|
||||||
|
|
||||||
#{data => #{
|
|
||||||
username => <<"sha256">>,
|
|
||||||
password_hash => <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>,
|
|
||||||
salt => <<"salt">>,
|
|
||||||
is_superuser => 1
|
|
||||||
},
|
},
|
||||||
credentials => #{
|
|
||||||
clientid => <<"sha256">>,
|
|
||||||
password => <<"sha256">>
|
|
||||||
},
|
|
||||||
config_params => #{
|
|
||||||
selector => #{<<"username">> => <<"${clientid}">>},
|
|
||||||
password_hash_algorithm => #{name => <<"sha256">>,
|
|
||||||
salt_position => <<"prefix">>}
|
|
||||||
},
|
|
||||||
result => {ok,#{is_superuser => true}}
|
|
||||||
},
|
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt">>,
|
data => #{
|
||||||
password_hash =>
|
username => <<"md5">>,
|
||||||
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>,
|
||||||
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
salt => <<"salt">>,
|
||||||
is_superuser => 0
|
is_superuser => <<"0">>
|
||||||
},
|
},
|
||||||
credentials => #{
|
credentials => #{
|
||||||
username => <<"bcrypt">>,
|
username => <<"md5">>,
|
||||||
password => <<"bcrypt">>
|
password => <<"md5">>
|
||||||
},
|
},
|
||||||
config_params => #{
|
config_params => #{
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
password_hash_algorithm => #{
|
||||||
},
|
name => <<"md5">>,
|
||||||
result => {ok,#{is_superuser => false}}
|
salt_position => <<"suffix">>
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
result => {ok, #{is_superuser => false}}
|
||||||
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt0">>,
|
data => #{
|
||||||
password_hash =>
|
username => <<"sha256">>,
|
||||||
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
password_hash =>
|
||||||
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
<<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>,
|
||||||
is_superuser => <<"0">>
|
salt => <<"salt">>,
|
||||||
},
|
is_superuser => 1
|
||||||
credentials => #{
|
},
|
||||||
username => <<"bcrypt0">>,
|
credentials => #{
|
||||||
password => <<"bcrypt">>
|
clientid => <<"sha256">>,
|
||||||
},
|
password => <<"sha256">>
|
||||||
config_params => #{
|
},
|
||||||
% clientid variable & username credentials
|
config_params => #{
|
||||||
selector => #{<<"username">> => <<"${clientid}">>},
|
selector => #{<<"username">> => <<"${clientid}">>},
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
password_hash_algorithm => #{
|
||||||
},
|
name => <<"sha256">>,
|
||||||
result => {error,not_authorized}
|
salt_position => <<"prefix">>
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
result => {ok, #{is_superuser => true}}
|
||||||
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt1">>,
|
data => #{
|
||||||
password_hash =>
|
username => <<"bcrypt">>,
|
||||||
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
password_hash =>
|
||||||
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
||||||
is_superuser => <<"0">>
|
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
||||||
},
|
is_superuser => 0
|
||||||
credentials => #{
|
},
|
||||||
username => <<"bcrypt1">>,
|
credentials => #{
|
||||||
password => <<"bcrypt">>
|
username => <<"bcrypt">>,
|
||||||
},
|
password => <<"bcrypt">>
|
||||||
config_params => #{
|
},
|
||||||
selector => #{<<"userid">> => <<"${clientid}">>},
|
config_params => #{
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
},
|
},
|
||||||
result => {error,not_authorized}
|
result => {ok, #{is_superuser => false}}
|
||||||
},
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt2">>,
|
data => #{
|
||||||
password_hash =>
|
username => <<"bcrypt0">>,
|
||||||
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
password_hash =>
|
||||||
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
||||||
is_superuser => <<"0">>
|
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
||||||
},
|
is_superuser => <<"0">>
|
||||||
credentials => #{
|
},
|
||||||
username => <<"bcrypt2">>,
|
credentials => #{
|
||||||
% Wrong password
|
username => <<"bcrypt0">>,
|
||||||
password => <<"wrongpass">>
|
password => <<"bcrypt">>
|
||||||
},
|
},
|
||||||
config_params => #{
|
config_params => #{
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
% clientid variable & username credentials
|
||||||
},
|
selector => #{<<"username">> => <<"${clientid}">>},
|
||||||
result => {error,bad_username_or_password}
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
}
|
},
|
||||||
|
result => {error, not_authorized}
|
||||||
|
},
|
||||||
|
|
||||||
|
#{
|
||||||
|
data => #{
|
||||||
|
username => <<"bcrypt1">>,
|
||||||
|
password_hash =>
|
||||||
|
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
||||||
|
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
||||||
|
is_superuser => <<"0">>
|
||||||
|
},
|
||||||
|
credentials => #{
|
||||||
|
username => <<"bcrypt1">>,
|
||||||
|
password => <<"bcrypt">>
|
||||||
|
},
|
||||||
|
config_params => #{
|
||||||
|
selector => #{<<"userid">> => <<"${clientid}">>},
|
||||||
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {error, not_authorized}
|
||||||
|
},
|
||||||
|
|
||||||
|
#{
|
||||||
|
data => #{
|
||||||
|
username => <<"bcrypt2">>,
|
||||||
|
password_hash =>
|
||||||
|
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
||||||
|
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
||||||
|
is_superuser => <<"0">>
|
||||||
|
},
|
||||||
|
credentials => #{
|
||||||
|
username => <<"bcrypt2">>,
|
||||||
|
% Wrong password
|
||||||
|
password => <<"wrongpass">>
|
||||||
|
},
|
||||||
|
config_params => #{
|
||||||
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {error, bad_username_or_password}
|
||||||
|
}
|
||||||
].
|
].
|
||||||
|
|
||||||
init_seeds() ->
|
init_seeds() ->
|
||||||
|
@ -390,14 +428,14 @@ drop_seeds() ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
mongo_server() ->
|
mongo_server() ->
|
||||||
iolist_to_binary(io_lib:format("~s",[?MONGO_HOST])).
|
iolist_to_binary(io_lib:format("~s", [?MONGO_HOST])).
|
||||||
|
|
||||||
mongo_config() ->
|
mongo_config() ->
|
||||||
[
|
[
|
||||||
{database, <<"mqtt">>},
|
{database, <<"mqtt">>},
|
||||||
{host, ?MONGO_HOST},
|
{host, ?MONGO_HOST},
|
||||||
{port, ?MONGO_DEFAULT_PORT},
|
{port, ?MONGO_DEFAULT_PORT},
|
||||||
{register, ?MONGO_CLIENT}
|
{register, ?MONGO_CLIENT}
|
||||||
].
|
].
|
||||||
|
|
||||||
start_apps(Apps) ->
|
start_apps(Apps) ->
|
||||||
|
|
|
@ -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]).
|
||||||
|
@ -37,8 +36,9 @@ init_per_testcase(_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_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) ->
|
||||||
|
@ -54,8 +54,9 @@ 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,69 +73,90 @@ 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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.2">>],
|
<<"verify">> => <<"verify_peer">>,
|
||||||
<<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]}),
|
<<"versions">> => [<<"tlsv1.2">>],
|
||||||
fun({ok, _}, Trace) ->
|
<<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
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">>,
|
||||||
fun(_, Trace) ->
|
<<"verify">> => <<"verify_peer">>
|
||||||
?assertNotEqual(
|
}
|
||||||
[ok],
|
),
|
||||||
?projection(
|
fun(_, Trace) ->
|
||||||
status,
|
?assertNotEqual(
|
||||||
?of_kind(emqx_connector_mongo_health_check, Trace)))
|
[ok],
|
||||||
end).
|
?projection(
|
||||||
|
status,
|
||||||
|
?of_kind(emqx_connector_mongo_health_check, Trace)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
%% docker-compose-mongo-single-tls.yaml:
|
%% docker-compose-mongo-single-tls.yaml:
|
||||||
%% --tlsDisabledProtocols TLS1_0,TLS1_1
|
%% --tlsDisabledProtocols TLS1_0,TLS1_1
|
||||||
|
|
||||||
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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.1">>]}),
|
<<"verify">> => <<"verify_peer">>,
|
||||||
fun(_, Trace) ->
|
<<"versions">> => [<<"tlsv1.1">>]
|
||||||
?assertNotEqual(
|
}
|
||||||
[ok],
|
),
|
||||||
?projection(
|
fun(_, Trace) ->
|
||||||
status,
|
?assertNotEqual(
|
||||||
?of_kind(emqx_connector_mongo_health_check, Trace)))
|
[ok],
|
||||||
end).
|
?projection(
|
||||||
|
status,
|
||||||
|
?of_kind(emqx_connector_mongo_health_check, Trace)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
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'
|
||||||
|
|
||||||
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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.2">>],
|
<<"verify">> => <<"verify_peer">>,
|
||||||
<<"ciphers">> => [<<"DHE-RSA-AES256-GCM-SHA384">>]}),
|
<<"versions">> => [<<"tlsv1.2">>],
|
||||||
fun(_, Trace) ->
|
<<"ciphers">> => [<<"DHE-RSA-AES256-GCM-SHA384">>]
|
||||||
?assertNotEqual(
|
}
|
||||||
[ok],
|
),
|
||||||
?projection(
|
fun(_, Trace) ->
|
||||||
status,
|
?assertNotEqual(
|
||||||
?of_kind(emqx_connector_mongo_health_check, Trace)))
|
[ok],
|
||||||
end).
|
?projection(
|
||||||
|
status,
|
||||||
|
?of_kind(emqx_connector_mongo_health_check, Trace)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
@ -148,35 +170,38 @@ 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">>,
|
||||||
enable => <<"true">>,
|
salt_position => <<"suffix">>
|
||||||
|
},
|
||||||
|
enable => <<"true">>,
|
||||||
|
|
||||||
backend => <<"mongodb">>,
|
backend => <<"mongodb">>,
|
||||||
pool_size => 2,
|
pool_size => 2,
|
||||||
mongo_type => <<"single">>,
|
mongo_type => <<"single">>,
|
||||||
database => <<"mqtt">>,
|
database => <<"mqtt">>,
|
||||||
collection => <<"users">>,
|
collection => <<"users">>,
|
||||||
server => mongo_server(),
|
server => mongo_server(),
|
||||||
w_mode => <<"unsafe">>,
|
w_mode => <<"unsafe">>,
|
||||||
|
|
||||||
selector => #{<<"username">> => <<"${username}">>},
|
selector => #{<<"username">> => <<"${username}">>},
|
||||||
password_hash_field => <<"password_hash">>,
|
password_hash_field => <<"password_hash">>,
|
||||||
salt_field => <<"salt">>,
|
salt_field => <<"salt">>,
|
||||||
is_superuser_field => <<"is_superuser">>,
|
is_superuser_field => <<"is_superuser">>,
|
||||||
topology => #{
|
topology => #{
|
||||||
server_selection_timeout_ms => <<"10000ms">>
|
server_selection_timeout_ms => <<"10000ms">>
|
||||||
},
|
},
|
||||||
|
|
||||||
ssl => maps:merge(SSLOpts, SpecificSSLOpts)
|
ssl => maps:merge(SSLOpts, SpecificSSLOpts)
|
||||||
}.
|
}.
|
||||||
|
|
||||||
mongo_server() ->
|
mongo_server() ->
|
||||||
iolist_to_binary(io_lib:format("~s",[?MONGO_HOST])).
|
iolist_to_binary(io_lib:format("~s", [?MONGO_HOST])).
|
||||||
|
|
||||||
start_apps(Apps) ->
|
start_apps(Apps) ->
|
||||||
lists:foreach(fun application:ensure_all_started/1, Apps).
|
lists:foreach(fun application:ensure_all_started/1, Apps).
|
||||||
|
|
|
@ -21,28 +21,36 @@
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([start_link/2,
|
-export([
|
||||||
stop/1]).
|
start_link/2,
|
||||||
|
stop/1
|
||||||
|
]).
|
||||||
|
|
||||||
-export([send/2]).
|
-export([send/2]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
|
|
||||||
-export([init/1,
|
-export([
|
||||||
handle_call/3,
|
init/1,
|
||||||
handle_cast/2,
|
handle_call/3,
|
||||||
handle_info/2,
|
handle_cast/2,
|
||||||
terminate/2]).
|
handle_info/2,
|
||||||
|
terminate/2
|
||||||
|
]).
|
||||||
|
|
||||||
-define(TIMEOUT, 1000).
|
-define(TIMEOUT, 1000).
|
||||||
-define(TCP_OPTIONS, [binary, {packet, raw}, {active, once},
|
-define(TCP_OPTIONS, [
|
||||||
{nodelay, true}]).
|
binary,
|
||||||
|
{packet, raw},
|
||||||
|
{active, once},
|
||||||
|
{nodelay, true}
|
||||||
|
]).
|
||||||
|
|
||||||
-define(PARSE_OPTIONS,
|
-define(PARSE_OPTIONS, #{
|
||||||
#{strict_mode => false,
|
strict_mode => false,
|
||||||
max_size => ?MAX_PACKET_SIZE,
|
max_size => ?MAX_PACKET_SIZE,
|
||||||
version => ?MQTT_PROTO_V5
|
version => ?MQTT_PROTO_V5
|
||||||
}).
|
}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% API
|
%% API
|
||||||
|
@ -63,26 +71,30 @@ send(Pid, Packet) ->
|
||||||
|
|
||||||
init([Host, Port, Owner]) ->
|
init([Host, Port, Owner]) ->
|
||||||
{ok, Socket} = gen_tcp:connect(Host, Port, ?TCP_OPTIONS, ?TIMEOUT),
|
{ok, Socket} = gen_tcp:connect(Host, Port, ?TCP_OPTIONS, ?TIMEOUT),
|
||||||
{ok, #{owner => Owner,
|
{ok, #{
|
||||||
socket => Socket,
|
owner => Owner,
|
||||||
parse_state => emqx_frame:initial_parse_state(?PARSE_OPTIONS)
|
socket => Socket,
|
||||||
}}.
|
parse_state => emqx_frame:initial_parse_state(?PARSE_OPTIONS)
|
||||||
|
}}.
|
||||||
|
|
||||||
handle_info({tcp, _Sock, Data}, #{parse_state := PSt,
|
handle_info(
|
||||||
owner := Owner,
|
{tcp, _Sock, Data},
|
||||||
socket := Socket} = St) ->
|
#{
|
||||||
|
parse_state := PSt,
|
||||||
|
owner := Owner,
|
||||||
|
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}.
|
||||||
|
@ -100,16 +112,16 @@ terminate(_Reason, _St) ->
|
||||||
process_incoming(PSt, Data, Packets) ->
|
process_incoming(PSt, Data, Packets) ->
|
||||||
case emqx_frame:parse(Data, PSt) of
|
case emqx_frame:parse(Data, PSt) of
|
||||||
{more, NewPSt} ->
|
{more, NewPSt} ->
|
||||||
{NewPSt, lists:reverse(Packets)};
|
{NewPSt, lists:reverse(Packets)};
|
||||||
{ok, Packet, Rest, NewPSt} ->
|
{ok, Packet, Rest, NewPSt} ->
|
||||||
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}]).
|
||||||
|
|
|
@ -40,8 +40,9 @@ 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_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) ->
|
||||||
|
@ -59,11 +60,12 @@ init_per_suite(Config) ->
|
||||||
ok = emqx_common_test_helpers:start_apps([emqx_authn]),
|
ok = emqx_common_test_helpers:start_apps([emqx_authn]),
|
||||||
ok = start_apps([emqx_resource, emqx_connector]),
|
ok = start_apps([emqx_resource, emqx_connector]),
|
||||||
{ok, _} = emqx_resource:create_local(
|
{ok, _} = emqx_resource:create_local(
|
||||||
?MYSQL_RESOURCE,
|
?MYSQL_RESOURCE,
|
||||||
?RESOURCE_GROUP,
|
?RESOURCE_GROUP,
|
||||||
emqx_connector_mysql,
|
emqx_connector_mysql,
|
||||||
mysql_config(),
|
mysql_config(),
|
||||||
#{}),
|
#{}
|
||||||
|
),
|
||||||
Config;
|
Config;
|
||||||
false ->
|
false ->
|
||||||
{skip, no_mysql}
|
{skip, no_mysql}
|
||||||
|
@ -71,8 +73,9 @@ 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]).
|
||||||
|
@ -85,8 +88,9 @@ t_create(_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}]} = 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).
|
||||||
|
@ -96,108 +100,132 @@ t_create_invalid(_Config) ->
|
||||||
|
|
||||||
InvalidConfigs =
|
InvalidConfigs =
|
||||||
[
|
[
|
||||||
maps:without([server], AuthConfig),
|
maps:without([server], AuthConfig),
|
||||||
AuthConfig#{server => <<"unknownhost:3333">>},
|
AuthConfig#{server => <<"unknownhost:3333">>},
|
||||||
AuthConfig#{password => <<"wrongpass">>},
|
AuthConfig#{password => <<"wrongpass">>},
|
||||||
AuthConfig#{database => <<"wrongdatabase">>}
|
AuthConfig#{database => <<"wrongdatabase">>}
|
||||||
],
|
],
|
||||||
|
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
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),
|
),
|
||||||
{ok, _} = emqx_authentication:list_authenticators(?GLOBAL)
|
emqx_authn_test_lib:delete_config(?ResourceID),
|
||||||
end,
|
{ok, _} = emqx_authentication:list_authenticators(?GLOBAL)
|
||||||
InvalidConfigs).
|
end,
|
||||||
|
InvalidConfigs
|
||||||
|
).
|
||||||
|
|
||||||
t_authenticate(_Config) ->
|
t_authenticate(_Config) ->
|
||||||
ok = lists:foreach(
|
ok = lists:foreach(
|
||||||
fun(Sample) ->
|
fun(Sample) ->
|
||||||
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(#{
|
||||||
config_params := SpecificConfigParams,
|
credentials := Credentials0,
|
||||||
result := Result}) ->
|
config_params := SpecificConfigParams,
|
||||||
|
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',
|
||||||
protocol => mqtt
|
protocol => mqtt
|
||||||
},
|
},
|
||||||
|
|
||||||
?assertEqual(Result, emqx_access_control:authenticate(Credentials)),
|
?assertEqual(Result, emqx_access_control:authenticate(Credentials)),
|
||||||
|
|
||||||
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">>,
|
#{
|
||||||
password => <<"plain">>
|
username => <<"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">>,
|
#{
|
||||||
password => <<"plain">>
|
username => <<"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">>,
|
#{
|
||||||
password => <<"plain">>,
|
username => <<"plain">>,
|
||||||
listener => 'tcp:default',
|
password => <<"plain">>,
|
||||||
protocol => mqtt
|
listener => 'tcp:default',
|
||||||
}),
|
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">>,
|
#{
|
||||||
password => <<"plain">>,
|
username => <<"plain">>,
|
||||||
listener => 'tcp:default',
|
password => <<"plain">>,
|
||||||
protocol => mqtt
|
listener => 'tcp:default',
|
||||||
}).
|
protocol => mqtt
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
@ -205,207 +233,248 @@ 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">>,
|
||||||
enable => <<"true">>,
|
salt_position => <<"suffix">>
|
||||||
|
},
|
||||||
|
enable => <<"true">>,
|
||||||
|
|
||||||
backend => <<"mysql">>,
|
backend => <<"mysql">>,
|
||||||
database => <<"mqtt">>,
|
database => <<"mqtt">>,
|
||||||
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">>,
|
<<
|
||||||
server => mysql_server()
|
"SELECT password_hash, salt, is_superuser_str as is_superuser\n"
|
||||||
}.
|
" FROM users where username = ${username} LIMIT 1"
|
||||||
|
>>,
|
||||||
|
server => mysql_server()
|
||||||
|
}.
|
||||||
|
|
||||||
user_seeds() ->
|
user_seeds() ->
|
||||||
[#{data => #{
|
[
|
||||||
username => "plain",
|
#{
|
||||||
password_hash => "plainsalt",
|
data => #{
|
||||||
salt => "salt",
|
username => "plain",
|
||||||
is_superuser_str => "1"
|
password_hash => "plainsalt",
|
||||||
},
|
salt => "salt",
|
||||||
credentials => #{
|
is_superuser_str => "1"
|
||||||
username => <<"plain">>,
|
},
|
||||||
password => <<"plain">>},
|
credentials => #{
|
||||||
config_params => #{},
|
username => <<"plain">>,
|
||||||
result => {ok,#{is_superuser => true}}
|
password => <<"plain">>
|
||||||
},
|
},
|
||||||
|
config_params => #{},
|
||||||
#{data => #{
|
result => {ok, #{is_superuser => true}}
|
||||||
username => "md5",
|
|
||||||
password_hash => "9b4d0c43d206d48279e69b9ad7132e22",
|
|
||||||
salt => "salt",
|
|
||||||
is_superuser_str => "0"
|
|
||||||
},
|
|
||||||
credentials => #{
|
|
||||||
username => <<"md5">>,
|
|
||||||
password => <<"md5">>
|
|
||||||
},
|
|
||||||
config_params => #{
|
|
||||||
password_hash_algorithm => #{name => <<"md5">>,
|
|
||||||
salt_position => <<"suffix">>}
|
|
||||||
},
|
|
||||||
result => {ok,#{is_superuser => false}}
|
|
||||||
},
|
|
||||||
|
|
||||||
#{data => #{
|
|
||||||
username => "sha256",
|
|
||||||
password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf",
|
|
||||||
salt => "salt",
|
|
||||||
is_superuser_int => 1
|
|
||||||
},
|
},
|
||||||
credentials => #{
|
|
||||||
clientid => <<"sha256">>,
|
|
||||||
password => <<"sha256">>
|
|
||||||
},
|
|
||||||
config_params => #{
|
|
||||||
query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser
|
|
||||||
FROM users where username = ${clientid} LIMIT 1">>,
|
|
||||||
password_hash_algorithm => #{name => <<"sha256">>,
|
|
||||||
salt_position => <<"prefix">>}
|
|
||||||
},
|
|
||||||
result => {ok,#{is_superuser => true}}
|
|
||||||
},
|
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt">>,
|
data => #{
|
||||||
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
username => "md5",
|
||||||
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
password_hash => "9b4d0c43d206d48279e69b9ad7132e22",
|
||||||
is_superuser_int => 0
|
salt => "salt",
|
||||||
},
|
is_superuser_str => "0"
|
||||||
credentials => #{
|
},
|
||||||
username => <<"bcrypt">>,
|
credentials => #{
|
||||||
password => <<"bcrypt">>
|
username => <<"md5">>,
|
||||||
},
|
password => <<"md5">>
|
||||||
config_params => #{
|
},
|
||||||
query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser
|
config_params => #{
|
||||||
FROM users where username = ${username} LIMIT 1">>,
|
password_hash_algorithm => #{
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
name => <<"md5">>,
|
||||||
},
|
salt_position => <<"suffix">>
|
||||||
result => {ok,#{is_superuser => false}}
|
}
|
||||||
},
|
},
|
||||||
|
result => {ok, #{is_superuser => false}}
|
||||||
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt">>,
|
data => #{
|
||||||
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
username => "sha256",
|
||||||
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve"
|
password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf",
|
||||||
},
|
salt => "salt",
|
||||||
credentials => #{
|
is_superuser_int => 1
|
||||||
username => <<"bcrypt">>,
|
},
|
||||||
password => <<"bcrypt">>
|
credentials => #{
|
||||||
},
|
clientid => <<"sha256">>,
|
||||||
config_params => #{
|
password => <<"sha256">>
|
||||||
query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser
|
},
|
||||||
FROM users where username = ${username} LIMIT 1">>,
|
config_params => #{
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
query =>
|
||||||
},
|
<<
|
||||||
result => {ok,#{is_superuser => false}}
|
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
|
||||||
},
|
" FROM users where username = ${clientid} LIMIT 1"
|
||||||
|
>>,
|
||||||
|
password_hash_algorithm => #{
|
||||||
|
name => <<"sha256">>,
|
||||||
|
salt_position => <<"prefix">>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
result => {ok, #{is_superuser => true}}
|
||||||
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt0">>,
|
data => #{
|
||||||
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
username => <<"bcrypt">>,
|
||||||
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
||||||
is_superuser_str => "0"
|
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
||||||
},
|
is_superuser_int => 0
|
||||||
credentials => #{
|
},
|
||||||
username => <<"bcrypt0">>,
|
credentials => #{
|
||||||
password => <<"bcrypt">>
|
username => <<"bcrypt">>,
|
||||||
},
|
password => <<"bcrypt">>
|
||||||
config_params => #{
|
},
|
||||||
% clientid variable & username credentials
|
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 => <<"bcrypt">>}
|
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
|
||||||
},
|
" FROM users where username = ${username} LIMIT 1"
|
||||||
result => {error,not_authorized}
|
>>,
|
||||||
},
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {ok, #{is_superuser => false}}
|
||||||
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt1">>,
|
data => #{
|
||||||
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
username => <<"bcrypt">>,
|
||||||
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
||||||
is_superuser_str => "0"
|
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve"
|
||||||
},
|
},
|
||||||
credentials => #{
|
credentials => #{
|
||||||
username => <<"bcrypt1">>,
|
username => <<"bcrypt">>,
|
||||||
password => <<"bcrypt">>
|
password => <<"bcrypt">>
|
||||||
},
|
},
|
||||||
config_params => #{
|
config_params => #{
|
||||||
% Bad keys in query
|
query =>
|
||||||
query => <<"SELECT 1 AS unknown_field
|
<<
|
||||||
FROM users where username = ${username} LIMIT 1">>,
|
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
" FROM users where username = ${username} LIMIT 1"
|
||||||
},
|
>>,
|
||||||
result => {error,not_authorized}
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
},
|
},
|
||||||
|
result => {ok, #{is_superuser => false}}
|
||||||
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt2">>,
|
data => #{
|
||||||
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
username => <<"bcrypt0">>,
|
||||||
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
||||||
is_superuser => "0"
|
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
||||||
},
|
is_superuser_str => "0"
|
||||||
credentials => #{
|
},
|
||||||
username => <<"bcrypt2">>,
|
credentials => #{
|
||||||
% Wrong password
|
username => <<"bcrypt0">>,
|
||||||
password => <<"wrongpass">>
|
password => <<"bcrypt">>
|
||||||
},
|
},
|
||||||
config_params => #{
|
config_params => #{
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
% clientid variable & username credentials
|
||||||
},
|
query =>
|
||||||
result => {error,bad_username_or_password}
|
<<
|
||||||
}
|
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
|
||||||
|
" FROM users where username = ${clientid} LIMIT 1"
|
||||||
|
>>,
|
||||||
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {error, not_authorized}
|
||||||
|
},
|
||||||
|
|
||||||
|
#{
|
||||||
|
data => #{
|
||||||
|
username => <<"bcrypt1">>,
|
||||||
|
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
||||||
|
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
||||||
|
is_superuser_str => "0"
|
||||||
|
},
|
||||||
|
credentials => #{
|
||||||
|
username => <<"bcrypt1">>,
|
||||||
|
password => <<"bcrypt">>
|
||||||
|
},
|
||||||
|
config_params => #{
|
||||||
|
% Bad keys in query
|
||||||
|
query =>
|
||||||
|
<<
|
||||||
|
"SELECT 1 AS unknown_field\n"
|
||||||
|
" FROM users where username = ${username} LIMIT 1"
|
||||||
|
>>,
|
||||||
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {error, not_authorized}
|
||||||
|
},
|
||||||
|
|
||||||
|
#{
|
||||||
|
data => #{
|
||||||
|
username => <<"bcrypt2">>,
|
||||||
|
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
||||||
|
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
||||||
|
is_superuser => "0"
|
||||||
|
},
|
||||||
|
credentials => #{
|
||||||
|
username => <<"bcrypt2">>,
|
||||||
|
% Wrong password
|
||||||
|
password => <<"wrongpass">>
|
||||||
|
},
|
||||||
|
config_params => #{
|
||||||
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {error, bad_username_or_password}
|
||||||
|
}
|
||||||
].
|
].
|
||||||
|
|
||||||
init_seeds() ->
|
init_seeds() ->
|
||||||
ok = drop_seeds(),
|
ok = drop_seeds(),
|
||||||
ok = q("CREATE TABLE users(
|
ok = q(
|
||||||
username VARCHAR(255),
|
"CREATE TABLE users(\n"
|
||||||
password_hash VARCHAR(255),
|
" username VARCHAR(255),\n"
|
||||||
salt VARCHAR(255),
|
" password_hash VARCHAR(255),\n"
|
||||||
is_superuser_str VARCHAR(255),
|
" salt VARCHAR(255),\n"
|
||||||
is_superuser_int TINYINT)"),
|
" is_superuser_str VARCHAR(255),\n"
|
||||||
|
" is_superuser_int TINYINT)"
|
||||||
|
),
|
||||||
|
|
||||||
Fields = [username, password_hash, salt, is_superuser_str, is_superuser_int],
|
Fields = [username, password_hash, salt, is_superuser_str, is_superuser_int],
|
||||||
InsertQuery = "INSERT INTO users(username, password_hash, salt, "
|
InsertQuery =
|
||||||
" is_superuser_str, is_superuser_int) VALUES(?, ?, ?, ?, ?)",
|
"INSERT INTO users(username, password_hash, salt, "
|
||||||
|
" is_superuser_str, is_superuser_int) VALUES(?, ?, ?, ?, ?)",
|
||||||
|
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(#{data := Values}) ->
|
fun(#{data := Values}) ->
|
||||||
Params = [maps:get(F, Values, null) || F <- Fields],
|
Params = [maps:get(F, Values, null) || F <- Fields],
|
||||||
ok = q(InsertQuery, Params)
|
ok = q(InsertQuery, Params)
|
||||||
end,
|
end,
|
||||||
user_seeds()).
|
user_seeds()
|
||||||
|
).
|
||||||
|
|
||||||
q(Sql) ->
|
q(Sql) ->
|
||||||
emqx_resource:query(
|
emqx_resource:query(
|
||||||
?MYSQL_RESOURCE,
|
?MYSQL_RESOURCE,
|
||||||
{sql, Sql}).
|
{sql, Sql}
|
||||||
|
).
|
||||||
|
|
||||||
q(Sql, Params) ->
|
q(Sql, Params) ->
|
||||||
emqx_resource:query(
|
emqx_resource:query(
|
||||||
?MYSQL_RESOURCE,
|
?MYSQL_RESOURCE,
|
||||||
{sql, Sql, Params}).
|
{sql, Sql, Params}
|
||||||
|
).
|
||||||
|
|
||||||
drop_seeds() ->
|
drop_seeds() ->
|
||||||
ok = q("DROP TABLE IF EXISTS users").
|
ok = q("DROP TABLE IF EXISTS users").
|
||||||
|
|
||||||
mysql_server() ->
|
mysql_server() ->
|
||||||
iolist_to_binary(io_lib:format("~s",[?MYSQL_HOST])).
|
iolist_to_binary(io_lib:format("~s", [?MYSQL_HOST])).
|
||||||
|
|
||||||
mysql_config() ->
|
mysql_config() ->
|
||||||
#{auto_reconnect => true,
|
#{
|
||||||
database => <<"mqtt">>,
|
auto_reconnect => true,
|
||||||
username => <<"root">>,
|
database => <<"mqtt">>,
|
||||||
password => <<"public">>,
|
username => <<"root">>,
|
||||||
pool_size => 8,
|
password => <<"public">>,
|
||||||
server => {?MYSQL_HOST, ?MYSQL_DEFAULT_PORT},
|
pool_size => 8,
|
||||||
ssl => #{enable => false}
|
server => {?MYSQL_HOST, ?MYSQL_DEFAULT_PORT},
|
||||||
}.
|
ssl => #{enable => false}
|
||||||
|
}.
|
||||||
|
|
||||||
start_apps(Apps) ->
|
start_apps(Apps) ->
|
||||||
lists:foreach(fun application:ensure_all_started/1, Apps).
|
lists:foreach(fun application:ensure_all_started/1, Apps).
|
||||||
|
|
|
@ -39,8 +39,9 @@ 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_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) ->
|
||||||
|
@ -56,8 +57,9 @@ 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]).
|
||||||
|
|
||||||
|
@ -70,38 +72,53 @@ t_create(_Config) ->
|
||||||
%% -connect authn-server:3306 -starttls mysql \
|
%% -connect authn-server:3306 -starttls mysql \
|
||||||
%% -cert client.crt -key client.key -CAfile ca.crt
|
%% -cert client.crt -key client.key -CAfile ca.crt
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{ok, _},
|
{ok, _},
|
||||||
create_mysql_auth_with_ssl_opts(
|
create_mysql_auth_with_ssl_opts(
|
||||||
#{<<"server_name_indication">> => <<"authn-server">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.2">>],
|
<<"verify">> => <<"verify_peer">>,
|
||||||
<<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]})).
|
<<"versions">> => [<<"tlsv1.2">>],
|
||||||
|
<<"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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.1">>]})),
|
<<"verify">> => <<"verify_peer">>,
|
||||||
|
<<"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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.2">>],
|
<<"verify">> => <<"verify_peer">>,
|
||||||
<<"ciphers">> => [<<"ECDHE-ECDSA-AES128-GCM-SHA256">>]})).
|
<<"versions">> => [<<"tlsv1.2">>],
|
||||||
|
<<"ciphers">> => [<<"ECDHE-ECDSA-AES128-GCM-SHA256">>]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
@ -113,27 +130,33 @@ 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">>,
|
||||||
enable => <<"true">>,
|
salt_position => <<"suffix">>
|
||||||
|
},
|
||||||
|
enable => <<"true">>,
|
||||||
|
|
||||||
backend => <<"mysql">>,
|
backend => <<"mysql">>,
|
||||||
database => <<"mqtt">>,
|
database => <<"mqtt">>,
|
||||||
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">>,
|
<<
|
||||||
server => mysql_server(),
|
"SELECT password_hash, salt, is_superuser_str as is_superuser\n"
|
||||||
ssl => maps:merge(SSLOpts, SpecificSSLOpts)
|
" FROM users where username = ${username} LIMIT 1"
|
||||||
}.
|
>>,
|
||||||
|
server => mysql_server(),
|
||||||
|
ssl => maps:merge(SSLOpts, SpecificSSLOpts)
|
||||||
|
}.
|
||||||
|
|
||||||
mysql_server() ->
|
mysql_server() ->
|
||||||
iolist_to_binary(io_lib:format("~s",[?MYSQL_HOST])).
|
iolist_to_binary(io_lib:format("~s", [?MYSQL_HOST])).
|
||||||
|
|
||||||
start_apps(Apps) ->
|
start_apps(Apps) ->
|
||||||
lists:foreach(fun application:ensure_all_started/1, Apps).
|
lists:foreach(fun application:ensure_all_started/1, Apps).
|
||||||
|
|
|
@ -38,118 +38,150 @@ 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) ->
|
||||||
Salt = emqx_authn_password_hashing:gen_salt(Algorithm),
|
Salt = emqx_authn_password_hashing:gen_salt(Algorithm),
|
||||||
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,
|
#{
|
||||||
salt := Salt,
|
password_hash := Hash,
|
||||||
password := Password,
|
salt := Salt,
|
||||||
password_hash_algorithm := Algorithm
|
password := Password,
|
||||||
} = Sample) ->
|
password_hash_algorithm := Algorithm
|
||||||
|
} = 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">>).
|
||||||
|
|
||||||
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_hash_algorithm := Algorithm
|
#{
|
||||||
} = Sample) ->
|
password := Password,
|
||||||
|
password_hash_algorithm := Algorithm
|
||||||
|
} = 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">>,
|
#{
|
||||||
salt => <<"salt">>,
|
password_hash => <<"plainsalt">>,
|
||||||
password => <<"plain">>,
|
salt => <<"salt">>,
|
||||||
password_hash_algorithm => #{name => plain,
|
password => <<"plain">>,
|
||||||
salt_position => suffix}
|
password_hash_algorithm => #{
|
||||||
},
|
name => plain,
|
||||||
#{
|
salt_position => suffix
|
||||||
password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>,
|
}
|
||||||
salt => <<"salt">>,
|
},
|
||||||
password => <<"md5">>,
|
#{
|
||||||
password_hash_algorithm => #{name => md5,
|
password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>,
|
||||||
salt_position => suffix}
|
salt => <<"salt">>,
|
||||||
},
|
password => <<"md5">>,
|
||||||
#{
|
password_hash_algorithm => #{
|
||||||
password_hash => <<"c665d4c0a9e5498806b7d9fd0b417d272853660e">>,
|
name => md5,
|
||||||
salt => <<"salt">>,
|
salt_position => suffix
|
||||||
password => <<"sha">>,
|
}
|
||||||
password_hash_algorithm => #{name => sha,
|
},
|
||||||
salt_position => prefix}
|
#{
|
||||||
},
|
password_hash => <<"c665d4c0a9e5498806b7d9fd0b417d272853660e">>,
|
||||||
#{
|
salt => <<"salt">>,
|
||||||
password_hash => <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>,
|
password => <<"sha">>,
|
||||||
salt => <<"salt">>,
|
password_hash_algorithm => #{
|
||||||
password => <<"sha256">>,
|
name => sha,
|
||||||
password_hash_algorithm => #{name => sha256,
|
salt_position => prefix
|
||||||
salt_position => prefix}
|
}
|
||||||
},
|
},
|
||||||
#{
|
#{
|
||||||
password_hash => <<"a1509ab67bfacbad020927b5ac9d91e9100a82e33a0ebb01459367ce921c0aa8"
|
password_hash => <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>,
|
||||||
"157aa5652f94bc84fa3babc08283e44887d61c48bcf8ad7bcb3259ee7d0eafcd">>,
|
salt => <<"salt">>,
|
||||||
salt => <<"salt">>,
|
password => <<"sha256">>,
|
||||||
password => <<"sha512">>,
|
password_hash_algorithm => #{
|
||||||
password_hash_algorithm => #{name => sha512,
|
name => sha256,
|
||||||
salt_position => prefix}
|
salt_position => prefix
|
||||||
},
|
}
|
||||||
#{
|
},
|
||||||
password_hash => <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
#{
|
||||||
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
password_hash => <<
|
||||||
password => <<"bcrypt">>,
|
"a1509ab67bfacbad020927b5ac9d91e9100a82e33a0ebb01459367ce921c0aa8"
|
||||||
|
"157aa5652f94bc84fa3babc08283e44887d61c48bcf8ad7bcb3259ee7d0eafcd"
|
||||||
|
>>,
|
||||||
|
salt => <<"salt">>,
|
||||||
|
password => <<"sha512">>,
|
||||||
|
password_hash_algorithm => #{
|
||||||
|
name => sha512,
|
||||||
|
salt_position => prefix
|
||||||
|
}
|
||||||
|
},
|
||||||
|
#{
|
||||||
|
password_hash => <<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
||||||
|
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
||||||
|
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"
|
||||||
salt => <<"ATHENA.MIT.EDUraeburn">>,
|
"a05378b93244ec8f48a99e61ad799d86"
|
||||||
password => <<"password">>,
|
>>,
|
||||||
|
salt => <<"ATHENA.MIT.EDUraeburn">>,
|
||||||
|
password => <<"password">>,
|
||||||
|
|
||||||
password_hash_algorithm => #{name => pbkdf2,
|
password_hash_algorithm => #{
|
||||||
iterations => 2,
|
name => pbkdf2,
|
||||||
dk_length => 32,
|
iterations => 2,
|
||||||
mac_fun => sha}
|
dk_length => 32,
|
||||||
},
|
mac_fun => sha
|
||||||
#{
|
}
|
||||||
password_hash => <<"01dbee7f4a9e243e988b62c73cda935da05378b9">>,
|
},
|
||||||
salt => <<"ATHENA.MIT.EDUraeburn">>,
|
#{
|
||||||
password => <<"password">>,
|
password_hash => <<"01dbee7f4a9e243e988b62c73cda935da05378b9">>,
|
||||||
|
salt => <<"ATHENA.MIT.EDUraeburn">>,
|
||||||
|
password => <<"password">>,
|
||||||
|
|
||||||
password_hash_algorithm => #{name => pbkdf2,
|
password_hash_algorithm => #{
|
||||||
iterations => 2,
|
name => pbkdf2,
|
||||||
mac_fun => sha}
|
iterations => 2,
|
||||||
}
|
mac_fun => sha
|
||||||
|
}
|
||||||
|
}
|
||||||
].
|
].
|
||||||
|
|
|
@ -41,8 +41,9 @@ 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_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) ->
|
||||||
|
@ -60,11 +61,12 @@ init_per_suite(Config) ->
|
||||||
ok = emqx_common_test_helpers:start_apps([emqx_authn]),
|
ok = emqx_common_test_helpers:start_apps([emqx_authn]),
|
||||||
ok = start_apps([emqx_resource, emqx_connector]),
|
ok = start_apps([emqx_resource, emqx_connector]),
|
||||||
{ok, _} = emqx_resource:create_local(
|
{ok, _} = emqx_resource:create_local(
|
||||||
?PGSQL_RESOURCE,
|
?PGSQL_RESOURCE,
|
||||||
?RESOURCE_GROUP,
|
?RESOURCE_GROUP,
|
||||||
emqx_connector_pgsql,
|
emqx_connector_pgsql,
|
||||||
pgsql_config(),
|
pgsql_config(),
|
||||||
#{}),
|
#{}
|
||||||
|
),
|
||||||
Config;
|
Config;
|
||||||
false ->
|
false ->
|
||||||
{skip, no_pgsql}
|
{skip, no_pgsql}
|
||||||
|
@ -72,8 +74,9 @@ 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]).
|
||||||
|
@ -86,8 +89,9 @@ t_create(_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}]} = 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).
|
||||||
|
@ -97,131 +101,156 @@ t_create_invalid(_Config) ->
|
||||||
|
|
||||||
InvalidConfigs =
|
InvalidConfigs =
|
||||||
[
|
[
|
||||||
maps:without([server], AuthConfig),
|
maps:without([server], AuthConfig),
|
||||||
AuthConfig#{server => <<"unknownhost:3333">>},
|
AuthConfig#{server => <<"unknownhost:3333">>},
|
||||||
AuthConfig#{password => <<"wrongpass">>},
|
AuthConfig#{password => <<"wrongpass">>},
|
||||||
AuthConfig#{database => <<"wrongdatabase">>}
|
AuthConfig#{database => <<"wrongdatabase">>}
|
||||||
],
|
],
|
||||||
|
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
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),
|
),
|
||||||
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL)
|
emqx_authn_test_lib:delete_config(?ResourceID),
|
||||||
end,
|
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL)
|
||||||
InvalidConfigs).
|
end,
|
||||||
|
InvalidConfigs
|
||||||
|
).
|
||||||
|
|
||||||
t_authenticate(_Config) ->
|
t_authenticate(_Config) ->
|
||||||
ok = lists:foreach(
|
ok = lists:foreach(
|
||||||
fun(Sample) ->
|
fun(Sample) ->
|
||||||
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(#{
|
||||||
config_params := SpecificConfigParams,
|
credentials := Credentials0,
|
||||||
result := Result}) ->
|
config_params := SpecificConfigParams,
|
||||||
|
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',
|
||||||
protocol => mqtt
|
protocol => mqtt
|
||||||
},
|
},
|
||||||
|
|
||||||
?assertEqual(Result, emqx_access_control:authenticate(Credentials)),
|
?assertEqual(Result, emqx_access_control:authenticate(Credentials)),
|
||||||
|
|
||||||
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">>,
|
#{
|
||||||
password => <<"plain">>
|
username => <<"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">>,
|
#{
|
||||||
password => <<"plain">>
|
username => <<"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">>,
|
#{
|
||||||
password => <<"plain">>,
|
username => <<"plain">>,
|
||||||
listener => 'tcp:default',
|
password => <<"plain">>,
|
||||||
protocol => mqtt
|
listener => 'tcp:default',
|
||||||
}),
|
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">>,
|
#{
|
||||||
password => <<"plain">>,
|
username => <<"plain">>,
|
||||||
listener => 'tcp:default',
|
password => <<"plain">>,
|
||||||
protocol => mqtt
|
listener => 'tcp:default',
|
||||||
}).
|
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},
|
||||||
{is_superuser_str, "", false},
|
{is_superuser_str, "", false},
|
||||||
{is_superuser_str, null, false},
|
{is_superuser_str, null, false},
|
||||||
{is_superuser_str, "1", true},
|
{is_superuser_str, "1", true},
|
||||||
{is_superuser_str, "val", true},
|
{is_superuser_str, "val", true},
|
||||||
|
|
||||||
{is_superuser_int, 0, false},
|
{is_superuser_int, 0, false},
|
||||||
{is_superuser_int, null, false},
|
{is_superuser_int, null, false},
|
||||||
{is_superuser_int, 1, true},
|
{is_superuser_int, 1, true},
|
||||||
{is_superuser_int, 123, true},
|
{is_superuser_int, 123, true},
|
||||||
|
|
||||||
{is_superuser_bool, false, false},
|
{is_superuser_bool, false, false},
|
||||||
{is_superuser_bool, null, false},
|
{is_superuser_bool, null, false},
|
||||||
{is_superuser_bool, true, true}
|
{is_superuser_bool, true, true}
|
||||||
],
|
],
|
||||||
|
|
||||||
lists:foreach(fun test_is_superuser/1, Checks).
|
lists:foreach(fun test_is_superuser/1, Checks).
|
||||||
|
|
||||||
|
@ -229,32 +258,36 @@ test_is_superuser({Field, Value, ExpectedValue}) ->
|
||||||
{ok, _} = q("DELETE FROM users"),
|
{ok, _} = q("DELETE FROM users"),
|
||||||
|
|
||||||
UserData = #{
|
UserData = #{
|
||||||
username => "user",
|
username => "user",
|
||||||
password_hash => "plainsalt",
|
password_hash => "plainsalt",
|
||||||
salt => "salt",
|
salt => "salt",
|
||||||
Field => Value
|
Field => Value
|
||||||
},
|
},
|
||||||
|
|
||||||
ok = create_user(UserData),
|
ok = create_user(UserData),
|
||||||
|
|
||||||
Query = "SELECT password_hash, salt, " ++ atom_to_list(Field) ++ " as is_superuser "
|
Query =
|
||||||
"FROM users where username = ${username} LIMIT 1",
|
"SELECT password_hash, salt, " ++ atom_to_list(Field) ++
|
||||||
|
" as is_superuser "
|
||||||
|
"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',
|
||||||
protocol => mqtt,
|
protocol => mqtt,
|
||||||
username => <<"user">>,
|
username => <<"user">>,
|
||||||
password => <<"plain">>
|
password => <<"plain">>
|
||||||
},
|
},
|
||||||
|
|
||||||
?assertEqual(
|
?assertEqual(
|
||||||
{ok, #{is_superuser => ExpectedValue}},
|
{ok, #{is_superuser => ExpectedValue}},
|
||||||
emqx_access_control:authenticate(Credentials)).
|
emqx_access_control:authenticate(Credentials)
|
||||||
|
).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
@ -262,167 +295,201 @@ 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">>,
|
||||||
enable => <<"true">>,
|
salt_position => <<"suffix">>
|
||||||
|
},
|
||||||
|
enable => <<"true">>,
|
||||||
|
|
||||||
backend => <<"postgresql">>,
|
backend => <<"postgresql">>,
|
||||||
database => <<"mqtt">>,
|
database => <<"mqtt">>,
|
||||||
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">>,
|
<<
|
||||||
server => pgsql_server()
|
"SELECT password_hash, salt, is_superuser_str as is_superuser\n"
|
||||||
}.
|
" FROM users where username = ${username} LIMIT 1"
|
||||||
|
>>,
|
||||||
|
server => pgsql_server()
|
||||||
|
}.
|
||||||
|
|
||||||
user_seeds() ->
|
user_seeds() ->
|
||||||
[#{data => #{
|
[
|
||||||
username => "plain",
|
#{
|
||||||
password_hash => "plainsalt",
|
data => #{
|
||||||
salt => "salt",
|
username => "plain",
|
||||||
is_superuser_str => "1"
|
password_hash => "plainsalt",
|
||||||
},
|
salt => "salt",
|
||||||
credentials => #{
|
is_superuser_str => "1"
|
||||||
username => <<"plain">>,
|
},
|
||||||
password => <<"plain">>},
|
credentials => #{
|
||||||
config_params => #{},
|
username => <<"plain">>,
|
||||||
result => {ok,#{is_superuser => true}}
|
password => <<"plain">>
|
||||||
},
|
},
|
||||||
|
config_params => #{},
|
||||||
#{data => #{
|
result => {ok, #{is_superuser => true}}
|
||||||
username => "md5",
|
|
||||||
password_hash => "9b4d0c43d206d48279e69b9ad7132e22",
|
|
||||||
salt => "salt",
|
|
||||||
is_superuser_str => "0"
|
|
||||||
},
|
|
||||||
credentials => #{
|
|
||||||
username => <<"md5">>,
|
|
||||||
password => <<"md5">>
|
|
||||||
},
|
|
||||||
config_params => #{
|
|
||||||
password_hash_algorithm => #{name => <<"md5">>,
|
|
||||||
salt_position => <<"suffix">>}
|
|
||||||
},
|
|
||||||
result => {ok,#{is_superuser => false}}
|
|
||||||
},
|
|
||||||
|
|
||||||
#{data => #{
|
|
||||||
username => "sha256",
|
|
||||||
password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf",
|
|
||||||
salt => "salt",
|
|
||||||
is_superuser_int => 1
|
|
||||||
},
|
},
|
||||||
credentials => #{
|
|
||||||
clientid => <<"sha256">>,
|
|
||||||
password => <<"sha256">>
|
|
||||||
},
|
|
||||||
config_params => #{
|
|
||||||
query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser
|
|
||||||
FROM users where username = ${clientid} LIMIT 1">>,
|
|
||||||
password_hash_algorithm => #{name => <<"sha256">>,
|
|
||||||
salt_position => <<"prefix">>}
|
|
||||||
},
|
|
||||||
result => {ok,#{is_superuser => true}}
|
|
||||||
},
|
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt">>,
|
data => #{
|
||||||
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
username => "md5",
|
||||||
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
password_hash => "9b4d0c43d206d48279e69b9ad7132e22",
|
||||||
is_superuser_int => 0
|
salt => "salt",
|
||||||
},
|
is_superuser_str => "0"
|
||||||
credentials => #{
|
},
|
||||||
username => <<"bcrypt">>,
|
credentials => #{
|
||||||
password => <<"bcrypt">>
|
username => <<"md5">>,
|
||||||
},
|
password => <<"md5">>
|
||||||
config_params => #{
|
},
|
||||||
query => <<"SELECT password_hash, salt, is_superuser_int as is_superuser
|
config_params => #{
|
||||||
FROM users where username = ${username} LIMIT 1">>,
|
password_hash_algorithm => #{
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
name => <<"md5">>,
|
||||||
},
|
salt_position => <<"suffix">>
|
||||||
result => {ok,#{is_superuser => false}}
|
}
|
||||||
},
|
},
|
||||||
|
result => {ok, #{is_superuser => false}}
|
||||||
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt0">>,
|
data => #{
|
||||||
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
username => "sha256",
|
||||||
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
password_hash => "ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf",
|
||||||
is_superuser_str => "0"
|
salt => "salt",
|
||||||
},
|
is_superuser_int => 1
|
||||||
credentials => #{
|
},
|
||||||
username => <<"bcrypt0">>,
|
credentials => #{
|
||||||
password => <<"bcrypt">>
|
clientid => <<"sha256">>,
|
||||||
},
|
password => <<"sha256">>
|
||||||
config_params => #{
|
},
|
||||||
% clientid variable & username credentials
|
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 => <<"bcrypt">>}
|
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
|
||||||
},
|
" FROM users where username = ${clientid} LIMIT 1"
|
||||||
result => {error,not_authorized}
|
>>,
|
||||||
},
|
password_hash_algorithm => #{
|
||||||
|
name => <<"sha256">>,
|
||||||
|
salt_position => <<"prefix">>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
result => {ok, #{is_superuser => true}}
|
||||||
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt1">>,
|
data => #{
|
||||||
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
username => <<"bcrypt">>,
|
||||||
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
||||||
is_superuser_str => "0"
|
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
||||||
},
|
is_superuser_int => 0
|
||||||
credentials => #{
|
},
|
||||||
username => <<"bcrypt1">>,
|
credentials => #{
|
||||||
password => <<"bcrypt">>
|
username => <<"bcrypt">>,
|
||||||
},
|
password => <<"bcrypt">>
|
||||||
config_params => #{
|
},
|
||||||
% Bad keys in query
|
config_params => #{
|
||||||
query => <<"SELECT 1 AS unknown_field
|
query =>
|
||||||
FROM users where username = ${username} LIMIT 1">>,
|
<<
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
|
||||||
},
|
" FROM users where username = ${username} LIMIT 1"
|
||||||
result => {error,not_authorized}
|
>>,
|
||||||
},
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {ok, #{is_superuser => false}}
|
||||||
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
username => <<"bcrypt2">>,
|
data => #{
|
||||||
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
username => <<"bcrypt0">>,
|
||||||
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
||||||
is_superuser => "0"
|
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
||||||
},
|
is_superuser_str => "0"
|
||||||
credentials => #{
|
},
|
||||||
username => <<"bcrypt2">>,
|
credentials => #{
|
||||||
% Wrong password
|
username => <<"bcrypt0">>,
|
||||||
password => <<"wrongpass">>
|
password => <<"bcrypt">>
|
||||||
},
|
},
|
||||||
config_params => #{
|
config_params => #{
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
% clientid variable & username credentials
|
||||||
},
|
query =>
|
||||||
result => {error,bad_username_or_password}
|
<<
|
||||||
}
|
"SELECT password_hash, salt, is_superuser_int as is_superuser\n"
|
||||||
|
" FROM users where username = ${clientid} LIMIT 1"
|
||||||
|
>>,
|
||||||
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {error, not_authorized}
|
||||||
|
},
|
||||||
|
|
||||||
|
#{
|
||||||
|
data => #{
|
||||||
|
username => <<"bcrypt1">>,
|
||||||
|
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
||||||
|
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
||||||
|
is_superuser_str => "0"
|
||||||
|
},
|
||||||
|
credentials => #{
|
||||||
|
username => <<"bcrypt1">>,
|
||||||
|
password => <<"bcrypt">>
|
||||||
|
},
|
||||||
|
config_params => #{
|
||||||
|
% Bad keys in query
|
||||||
|
query =>
|
||||||
|
<<
|
||||||
|
"SELECT 1 AS unknown_field\n"
|
||||||
|
" FROM users where username = ${username} LIMIT 1"
|
||||||
|
>>,
|
||||||
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {error, not_authorized}
|
||||||
|
},
|
||||||
|
|
||||||
|
#{
|
||||||
|
data => #{
|
||||||
|
username => <<"bcrypt2">>,
|
||||||
|
password_hash => "$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u",
|
||||||
|
salt => "$2b$12$wtY3h20mUjjmeaClpqZVve",
|
||||||
|
is_superuser => "0"
|
||||||
|
},
|
||||||
|
credentials => #{
|
||||||
|
username => <<"bcrypt2">>,
|
||||||
|
% Wrong password
|
||||||
|
password => <<"wrongpass">>
|
||||||
|
},
|
||||||
|
config_params => #{
|
||||||
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {error, bad_username_or_password}
|
||||||
|
}
|
||||||
].
|
].
|
||||||
|
|
||||||
init_seeds() ->
|
init_seeds() ->
|
||||||
ok = drop_seeds(),
|
ok = drop_seeds(),
|
||||||
{ok, _, _} = q("CREATE TABLE users(
|
{ok, _, _} = q(
|
||||||
username varchar(255),
|
"CREATE TABLE users(\n"
|
||||||
password_hash varchar(255),
|
" username varchar(255),\n"
|
||||||
salt varchar(255),
|
" password_hash varchar(255),\n"
|
||||||
is_superuser_str varchar(255),
|
" salt varchar(255),\n"
|
||||||
is_superuser_int smallint,
|
" is_superuser_str varchar(255),\n"
|
||||||
is_superuser_bool boolean)"),
|
" is_superuser_int smallint,\n"
|
||||||
|
" is_superuser_bool boolean)"
|
||||||
|
),
|
||||||
|
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(#{data := Values}) ->
|
fun(#{data := Values}) ->
|
||||||
ok = create_user(Values)
|
ok = create_user(Values)
|
||||||
end,
|
end,
|
||||||
user_seeds()).
|
user_seeds()
|
||||||
|
).
|
||||||
|
|
||||||
create_user(Values) ->
|
create_user(Values) ->
|
||||||
Fields = [username, password_hash, salt, is_superuser_str, is_superuser_int, is_superuser_bool],
|
Fields = [username, password_hash, salt, is_superuser_str, is_superuser_int, is_superuser_bool],
|
||||||
|
|
||||||
InsertQuery = "INSERT INTO users(username, password_hash, salt,"
|
InsertQuery =
|
||||||
"is_superuser_str, is_superuser_int, is_superuser_bool) "
|
"INSERT INTO users(username, password_hash, salt,"
|
||||||
"VALUES($1, $2, $3, $4, $5, $6)",
|
"is_superuser_str, is_superuser_int, is_superuser_bool) "
|
||||||
|
"VALUES($1, $2, $3, $4, $5, $6)",
|
||||||
|
|
||||||
Params = [maps:get(F, Values, null) || F <- Fields],
|
Params = [maps:get(F, Values, null) || F <- Fields],
|
||||||
{ok, 1} = q(InsertQuery, Params),
|
{ok, 1} = q(InsertQuery, Params),
|
||||||
|
@ -430,30 +497,33 @@ create_user(Values) ->
|
||||||
|
|
||||||
q(Sql) ->
|
q(Sql) ->
|
||||||
emqx_resource:query(
|
emqx_resource:query(
|
||||||
?PGSQL_RESOURCE,
|
?PGSQL_RESOURCE,
|
||||||
{query, Sql}).
|
{query, Sql}
|
||||||
|
).
|
||||||
|
|
||||||
q(Sql, Params) ->
|
q(Sql, Params) ->
|
||||||
emqx_resource:query(
|
emqx_resource:query(
|
||||||
?PGSQL_RESOURCE,
|
?PGSQL_RESOURCE,
|
||||||
{query, Sql, Params}).
|
{query, Sql, Params}
|
||||||
|
).
|
||||||
|
|
||||||
drop_seeds() ->
|
drop_seeds() ->
|
||||||
{ok, _, _} = q("DROP TABLE IF EXISTS users"),
|
{ok, _, _} = q("DROP TABLE IF EXISTS users"),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
pgsql_server() ->
|
pgsql_server() ->
|
||||||
iolist_to_binary(io_lib:format("~s",[?PGSQL_HOST])).
|
iolist_to_binary(io_lib:format("~s", [?PGSQL_HOST])).
|
||||||
|
|
||||||
pgsql_config() ->
|
pgsql_config() ->
|
||||||
#{auto_reconnect => true,
|
#{
|
||||||
database => <<"mqtt">>,
|
auto_reconnect => true,
|
||||||
username => <<"root">>,
|
database => <<"mqtt">>,
|
||||||
password => <<"public">>,
|
username => <<"root">>,
|
||||||
pool_size => 8,
|
password => <<"public">>,
|
||||||
server => {?PGSQL_HOST, ?PGSQL_DEFAULT_PORT},
|
pool_size => 8,
|
||||||
ssl => #{enable => false}
|
server => {?PGSQL_HOST, ?PGSQL_DEFAULT_PORT},
|
||||||
}.
|
ssl => #{enable => false}
|
||||||
|
}.
|
||||||
|
|
||||||
start_apps(Apps) ->
|
start_apps(Apps) ->
|
||||||
lists:foreach(fun application:ensure_all_started/1, Apps).
|
lists:foreach(fun application:ensure_all_started/1, Apps).
|
||||||
|
|
|
@ -39,8 +39,9 @@ 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_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) ->
|
||||||
|
@ -56,8 +57,9 @@ 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]).
|
||||||
|
|
||||||
|
@ -70,38 +72,53 @@ t_create(_Config) ->
|
||||||
%% -starttls postgres -connect authn-server:5432 \
|
%% -starttls postgres -connect authn-server:5432 \
|
||||||
%% -cert client.crt -key client.key -CAfile ca.crt
|
%% -cert client.crt -key client.key -CAfile ca.crt
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{ok, _},
|
{ok, _},
|
||||||
create_pgsql_auth_with_ssl_opts(
|
create_pgsql_auth_with_ssl_opts(
|
||||||
#{<<"server_name_indication">> => <<"authn-server">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.2">>],
|
<<"verify">> => <<"verify_peer">>,
|
||||||
<<"ciphers">> => [<<"ECDHE-RSA-AES256-GCM-SHA384">>]})).
|
<<"versions">> => [<<"tlsv1.2">>],
|
||||||
|
<<"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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.1">>]})),
|
<<"verify">> => <<"verify_peer">>,
|
||||||
|
<<"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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.2">>],
|
<<"verify">> => <<"verify_peer">>,
|
||||||
<<"ciphers">> => [<<"ECDHE-ECDSA-AES128-GCM-SHA256">>]})).
|
<<"versions">> => [<<"tlsv1.2">>],
|
||||||
|
<<"ciphers">> => [<<"ECDHE-ECDSA-AES128-GCM-SHA256">>]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
@ -113,26 +130,29 @@ 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">>,
|
||||||
enable => <<"true">>,
|
salt_position => <<"suffix">>
|
||||||
|
},
|
||||||
|
enable => <<"true">>,
|
||||||
|
|
||||||
backend => <<"postgresql">>,
|
backend => <<"postgresql">>,
|
||||||
database => <<"mqtt">>,
|
database => <<"mqtt">>,
|
||||||
username => <<"root">>,
|
username => <<"root">>,
|
||||||
password => <<"public">>,
|
password => <<"public">>,
|
||||||
|
|
||||||
query => <<"SELECT 1">>,
|
query => <<"SELECT 1">>,
|
||||||
server => pgsql_server(),
|
server => pgsql_server(),
|
||||||
ssl => maps:merge(SSLOpts, SpecificSSLOpts)
|
ssl => maps:merge(SSLOpts, SpecificSSLOpts)
|
||||||
}.
|
}.
|
||||||
|
|
||||||
pgsql_server() ->
|
pgsql_server() ->
|
||||||
iolist_to_binary(io_lib:format("~s",[?PGSQL_HOST])).
|
iolist_to_binary(io_lib:format("~s", [?PGSQL_HOST])).
|
||||||
|
|
||||||
start_apps(Apps) ->
|
start_apps(Apps) ->
|
||||||
lists:foreach(fun application:ensure_all_started/1, Apps).
|
lists:foreach(fun application:ensure_all_started/1, Apps).
|
||||||
|
|
|
@ -40,8 +40,9 @@ 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_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) ->
|
||||||
|
@ -59,11 +60,12 @@ init_per_suite(Config) ->
|
||||||
ok = emqx_common_test_helpers:start_apps([emqx_authn]),
|
ok = emqx_common_test_helpers:start_apps([emqx_authn]),
|
||||||
ok = start_apps([emqx_resource, emqx_connector]),
|
ok = start_apps([emqx_resource, emqx_connector]),
|
||||||
{ok, _} = emqx_resource:create_local(
|
{ok, _} = emqx_resource:create_local(
|
||||||
?REDIS_RESOURCE,
|
?REDIS_RESOURCE,
|
||||||
?RESOURCE_GROUP,
|
?RESOURCE_GROUP,
|
||||||
emqx_connector_redis,
|
emqx_connector_redis,
|
||||||
redis_config(),
|
redis_config(),
|
||||||
#{}),
|
#{}
|
||||||
|
),
|
||||||
Config;
|
Config;
|
||||||
false ->
|
false ->
|
||||||
{skip, no_redis}
|
{skip, no_redis}
|
||||||
|
@ -71,8 +73,9 @@ 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]).
|
||||||
|
@ -86,8 +89,9 @@ 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).
|
||||||
|
|
||||||
|
@ -95,126 +99,152 @@ t_create_invalid(_Config) ->
|
||||||
AuthConfig = raw_redis_auth_config(),
|
AuthConfig = raw_redis_auth_config(),
|
||||||
InvalidConfigs =
|
InvalidConfigs =
|
||||||
[
|
[
|
||||||
AuthConfig#{
|
AuthConfig#{
|
||||||
cmd => <<"MGET password_hash:${username} salt:${username}">>},
|
cmd => <<"MGET password_hash:${username} salt:${username}">>
|
||||||
AuthConfig#{
|
},
|
||||||
cmd => <<"HMGET mqtt_user:${username} password_hash invalid_field">>},
|
AuthConfig#{
|
||||||
AuthConfig#{
|
cmd => <<"HMGET mqtt_user:${username} password_hash invalid_field">>
|
||||||
cmd => <<"HMGET mqtt_user:${username} salt is_superuser">>}
|
},
|
||||||
|
AuthConfig#{
|
||||||
|
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 =
|
||||||
[
|
[
|
||||||
maps:without([server], AuthConfig),
|
maps:without([server], AuthConfig),
|
||||||
AuthConfig#{server => <<"unknownhost:3333">>},
|
AuthConfig#{server => <<"unknownhost:3333">>},
|
||||||
AuthConfig#{password => <<"wrongpass">>},
|
AuthConfig#{password => <<"wrongpass">>},
|
||||||
AuthConfig#{database => <<"5678">>}
|
AuthConfig#{database => <<"5678">>}
|
||||||
],
|
],
|
||||||
|
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
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),
|
),
|
||||||
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL)
|
emqx_authn_test_lib:delete_config(?ResourceID),
|
||||||
end,
|
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL)
|
||||||
InvalidConfigs1).
|
end,
|
||||||
|
InvalidConfigs1
|
||||||
|
).
|
||||||
|
|
||||||
t_authenticate(_Config) ->
|
t_authenticate(_Config) ->
|
||||||
ok = lists:foreach(
|
ok = lists:foreach(
|
||||||
fun(Sample) ->
|
fun(Sample) ->
|
||||||
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(#{
|
||||||
config_params := SpecificConfigParams,
|
credentials := Credentials0,
|
||||||
result := Result}) ->
|
config_params := SpecificConfigParams,
|
||||||
|
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',
|
||||||
protocol => mqtt
|
protocol => mqtt
|
||||||
},
|
},
|
||||||
|
|
||||||
?assertEqual(Result, emqx_access_control:authenticate(Credentials)),
|
?assertEqual(Result, emqx_access_control:authenticate(Credentials)),
|
||||||
|
|
||||||
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">>,
|
#{
|
||||||
password => <<"plain">>
|
username => <<"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">>,
|
#{
|
||||||
password => <<"plain">>
|
username => <<"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">>,
|
#{
|
||||||
password => <<"plain">>,
|
username => <<"plain">>,
|
||||||
listener => 'tcp:default',
|
password => <<"plain">>,
|
||||||
protocol => mqtt
|
listener => 'tcp:default',
|
||||||
}),
|
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">>,
|
#{
|
||||||
password => <<"plain">>,
|
username => <<"plain">>,
|
||||||
listener => 'tcp:default',
|
password => <<"plain">>,
|
||||||
protocol => mqtt
|
listener => 'tcp:default',
|
||||||
}).
|
protocol => mqtt
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
@ -222,194 +252,218 @@ 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">>,
|
||||||
enable => <<"true">>,
|
salt_position => <<"suffix">>
|
||||||
|
},
|
||||||
|
enable => <<"true">>,
|
||||||
|
|
||||||
backend => <<"redis">>,
|
backend => <<"redis">>,
|
||||||
cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>,
|
cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>,
|
||||||
database => <<"1">>,
|
database => <<"1">>,
|
||||||
password => <<"public">>,
|
password => <<"public">>,
|
||||||
server => redis_server()
|
server => redis_server()
|
||||||
}.
|
}.
|
||||||
|
|
||||||
user_seeds() ->
|
user_seeds() ->
|
||||||
[#{data => #{
|
[
|
||||||
password_hash => <<"plainsalt">>,
|
#{
|
||||||
salt => <<"salt">>,
|
data => #{
|
||||||
is_superuser => <<"1">>
|
password_hash => <<"plainsalt">>,
|
||||||
},
|
salt => <<"salt">>,
|
||||||
credentials => #{
|
is_superuser => <<"1">>
|
||||||
username => <<"plain">>,
|
},
|
||||||
password => <<"plain">>},
|
credentials => #{
|
||||||
key => <<"mqtt_user:plain">>,
|
username => <<"plain">>,
|
||||||
config_params => #{},
|
password => <<"plain">>
|
||||||
result => {ok,#{is_superuser => true}}
|
},
|
||||||
},
|
key => <<"mqtt_user:plain">>,
|
||||||
|
config_params => #{},
|
||||||
#{data => #{
|
result => {ok, #{is_superuser => true}}
|
||||||
password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>,
|
|
||||||
salt => <<"salt">>,
|
|
||||||
is_superuser => <<"0">>
|
|
||||||
},
|
|
||||||
credentials => #{
|
|
||||||
username => <<"md5">>,
|
|
||||||
password => <<"md5">>
|
|
||||||
},
|
|
||||||
key => <<"mqtt_user:md5">>,
|
|
||||||
config_params => #{
|
|
||||||
password_hash_algorithm => #{name => <<"md5">>,
|
|
||||||
salt_position => <<"suffix">>}
|
|
||||||
},
|
|
||||||
result => {ok,#{is_superuser => false}}
|
|
||||||
},
|
|
||||||
|
|
||||||
#{data => #{
|
|
||||||
password_hash => <<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>,
|
|
||||||
salt => <<"salt">>,
|
|
||||||
is_superuser => <<"1">>
|
|
||||||
},
|
},
|
||||||
credentials => #{
|
|
||||||
clientid => <<"sha256">>,
|
|
||||||
password => <<"sha256">>
|
|
||||||
},
|
|
||||||
key => <<"mqtt_user:sha256">>,
|
|
||||||
config_params => #{
|
|
||||||
cmd => <<"HMGET mqtt_user:${clientid} password_hash salt is_superuser">>,
|
|
||||||
password_hash_algorithm => #{name => <<"sha256">>,
|
|
||||||
salt_position => <<"prefix">>}
|
|
||||||
},
|
|
||||||
result => {ok,#{is_superuser => true}}
|
|
||||||
},
|
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
password_hash =>
|
data => #{
|
||||||
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
password_hash => <<"9b4d0c43d206d48279e69b9ad7132e22">>,
|
||||||
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
salt => <<"salt">>,
|
||||||
is_superuser => <<"0">>
|
is_superuser => <<"0">>
|
||||||
},
|
},
|
||||||
credentials => #{
|
credentials => #{
|
||||||
username => <<"bcrypt">>,
|
username => <<"md5">>,
|
||||||
password => <<"bcrypt">>
|
password => <<"md5">>
|
||||||
},
|
},
|
||||||
key => <<"mqtt_user:bcrypt">>,
|
key => <<"mqtt_user:md5">>,
|
||||||
config_params => #{
|
config_params => #{
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
password_hash_algorithm => #{
|
||||||
},
|
name => <<"md5">>,
|
||||||
result => {ok,#{is_superuser => false}}
|
salt_position => <<"suffix">>
|
||||||
},
|
}
|
||||||
#{data => #{
|
},
|
||||||
password_hash => <<"01dbee7f4a9e243e988b62c73cda935da05378b9">>,
|
result => {ok, #{is_superuser => false}}
|
||||||
salt => <<"ATHENA.MIT.EDUraeburn">>,
|
},
|
||||||
is_superuser => <<"0">>
|
|
||||||
},
|
|
||||||
credentials => #{
|
|
||||||
username => <<"pbkdf2">>,
|
|
||||||
password => <<"password">>
|
|
||||||
},
|
|
||||||
key => <<"mqtt_user:pbkdf2">>,
|
|
||||||
config_params => #{
|
|
||||||
password_hash_algorithm => #{name => <<"pbkdf2">>,
|
|
||||||
iterations => 2,
|
|
||||||
mac_fun => sha
|
|
||||||
}
|
|
||||||
},
|
|
||||||
result => {ok,#{is_superuser => false}}
|
|
||||||
},
|
|
||||||
#{data => #{
|
|
||||||
password_hash =>
|
|
||||||
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
|
||||||
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
|
||||||
is_superuser => <<"0">>
|
|
||||||
},
|
|
||||||
credentials => #{
|
|
||||||
username => <<"bcrypt0">>,
|
|
||||||
password => <<"bcrypt">>
|
|
||||||
},
|
|
||||||
key => <<"mqtt_user:bcrypt0">>,
|
|
||||||
config_params => #{
|
|
||||||
% clientid variable & username credentials
|
|
||||||
cmd => <<"HMGET mqtt_client:${clientid} password_hash salt is_superuser">>,
|
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
|
||||||
},
|
|
||||||
result => {error,not_authorized}
|
|
||||||
},
|
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
password_hash =>
|
data => #{
|
||||||
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
password_hash =>
|
||||||
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
<<"ac63a624e7074776d677dd61a003b8c803eb11db004d0ec6ae032a5d7c9c5caf">>,
|
||||||
is_superuser => <<"0">>
|
salt => <<"salt">>,
|
||||||
},
|
is_superuser => <<"1">>
|
||||||
credentials => #{
|
},
|
||||||
username => <<"bcrypt1">>,
|
credentials => #{
|
||||||
password => <<"bcrypt">>
|
clientid => <<"sha256">>,
|
||||||
},
|
password => <<"sha256">>
|
||||||
key => <<"mqtt_user:bcrypt1">>,
|
},
|
||||||
config_params => #{
|
key => <<"mqtt_user:sha256">>,
|
||||||
% Bad key in cmd
|
config_params => #{
|
||||||
cmd => <<"HMGET badkey:${username} password_hash salt is_superuser">>,
|
cmd => <<"HMGET mqtt_user:${clientid} password_hash salt is_superuser">>,
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
password_hash_algorithm => #{
|
||||||
},
|
name => <<"sha256">>,
|
||||||
result => {error,not_authorized}
|
salt_position => <<"prefix">>
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
result => {ok, #{is_superuser => true}}
|
||||||
|
},
|
||||||
|
|
||||||
#{data => #{
|
#{
|
||||||
password_hash =>
|
data => #{
|
||||||
|
password_hash =>
|
||||||
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
||||||
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
||||||
is_superuser => <<"0">>
|
is_superuser => <<"0">>
|
||||||
},
|
},
|
||||||
credentials => #{
|
credentials => #{
|
||||||
username => <<"bcrypt2">>,
|
username => <<"bcrypt">>,
|
||||||
% Wrong password
|
password => <<"bcrypt">>
|
||||||
password => <<"wrongpass">>
|
},
|
||||||
},
|
key => <<"mqtt_user:bcrypt">>,
|
||||||
key => <<"mqtt_user:bcrypt2">>,
|
config_params => #{
|
||||||
config_params => #{
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>,
|
},
|
||||||
password_hash_algorithm => #{name => <<"bcrypt">>}
|
result => {ok, #{is_superuser => false}}
|
||||||
},
|
},
|
||||||
result => {error,bad_username_or_password}
|
#{
|
||||||
}
|
data => #{
|
||||||
|
password_hash => <<"01dbee7f4a9e243e988b62c73cda935da05378b9">>,
|
||||||
|
salt => <<"ATHENA.MIT.EDUraeburn">>,
|
||||||
|
is_superuser => <<"0">>
|
||||||
|
},
|
||||||
|
credentials => #{
|
||||||
|
username => <<"pbkdf2">>,
|
||||||
|
password => <<"password">>
|
||||||
|
},
|
||||||
|
key => <<"mqtt_user:pbkdf2">>,
|
||||||
|
config_params => #{
|
||||||
|
password_hash_algorithm => #{
|
||||||
|
name => <<"pbkdf2">>,
|
||||||
|
iterations => 2,
|
||||||
|
mac_fun => sha
|
||||||
|
}
|
||||||
|
},
|
||||||
|
result => {ok, #{is_superuser => false}}
|
||||||
|
},
|
||||||
|
#{
|
||||||
|
data => #{
|
||||||
|
password_hash =>
|
||||||
|
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
||||||
|
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
||||||
|
is_superuser => <<"0">>
|
||||||
|
},
|
||||||
|
credentials => #{
|
||||||
|
username => <<"bcrypt0">>,
|
||||||
|
password => <<"bcrypt">>
|
||||||
|
},
|
||||||
|
key => <<"mqtt_user:bcrypt0">>,
|
||||||
|
config_params => #{
|
||||||
|
% clientid variable & username credentials
|
||||||
|
cmd => <<"HMGET mqtt_client:${clientid} password_hash salt is_superuser">>,
|
||||||
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {error, not_authorized}
|
||||||
|
},
|
||||||
|
|
||||||
|
#{
|
||||||
|
data => #{
|
||||||
|
password_hash =>
|
||||||
|
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
||||||
|
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
||||||
|
is_superuser => <<"0">>
|
||||||
|
},
|
||||||
|
credentials => #{
|
||||||
|
username => <<"bcrypt1">>,
|
||||||
|
password => <<"bcrypt">>
|
||||||
|
},
|
||||||
|
key => <<"mqtt_user:bcrypt1">>,
|
||||||
|
config_params => #{
|
||||||
|
% Bad key in cmd
|
||||||
|
cmd => <<"HMGET badkey:${username} password_hash salt is_superuser">>,
|
||||||
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {error, not_authorized}
|
||||||
|
},
|
||||||
|
|
||||||
|
#{
|
||||||
|
data => #{
|
||||||
|
password_hash =>
|
||||||
|
<<"$2b$12$wtY3h20mUjjmeaClpqZVveDWGlHzCGsvuThMlneGHA7wVeFYyns2u">>,
|
||||||
|
salt => <<"$2b$12$wtY3h20mUjjmeaClpqZVve">>,
|
||||||
|
is_superuser => <<"0">>
|
||||||
|
},
|
||||||
|
credentials => #{
|
||||||
|
username => <<"bcrypt2">>,
|
||||||
|
% Wrong password
|
||||||
|
password => <<"wrongpass">>
|
||||||
|
},
|
||||||
|
key => <<"mqtt_user:bcrypt2">>,
|
||||||
|
config_params => #{
|
||||||
|
cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>,
|
||||||
|
password_hash_algorithm => #{name => <<"bcrypt">>}
|
||||||
|
},
|
||||||
|
result => {error, bad_username_or_password}
|
||||||
|
}
|
||||||
].
|
].
|
||||||
|
|
||||||
init_seeds() ->
|
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(
|
||||||
q(["HSET", UserKey, atom_to_list(Key), Value])
|
fun({Key, Value}) ->
|
||||||
end,
|
q(["HSET", UserKey, atom_to_list(Key), Value])
|
||||||
maps:to_list(Values))
|
end,
|
||||||
end,
|
maps:to_list(Values)
|
||||||
user_seeds()).
|
)
|
||||||
|
end,
|
||||||
|
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,
|
#{
|
||||||
database => 1,
|
auto_reconnect => true,
|
||||||
pool_size => 8,
|
database => 1,
|
||||||
redis_type => single,
|
pool_size => 8,
|
||||||
password => "public",
|
redis_type => single,
|
||||||
server => {?REDIS_HOST, ?REDIS_DEFAULT_PORT},
|
password => "public",
|
||||||
ssl => #{enable => false}
|
server => {?REDIS_HOST, ?REDIS_DEFAULT_PORT},
|
||||||
}.
|
ssl => #{enable => false}
|
||||||
|
}.
|
||||||
|
|
||||||
start_apps(Apps) ->
|
start_apps(Apps) ->
|
||||||
lists:foreach(fun application:ensure_all_started/1, Apps).
|
lists:foreach(fun application:ensure_all_started/1, Apps).
|
||||||
|
|
|
@ -39,8 +39,9 @@ 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_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) ->
|
||||||
|
@ -56,8 +57,9 @@ 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]).
|
||||||
|
|
||||||
|
@ -67,39 +69,55 @@ end_per_suite(_Config) ->
|
||||||
|
|
||||||
t_create(_Config) ->
|
t_create(_Config) ->
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{ok, _},
|
{ok, _},
|
||||||
create_redis_auth_with_ssl_opts(
|
create_redis_auth_with_ssl_opts(
|
||||||
#{<<"server_name_indication">> => <<"authn-server">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.3">>],
|
<<"verify">> => <<"verify_peer">>,
|
||||||
<<"ciphers">> => [<<"TLS_CHACHA20_POLY1305_SHA256">>]})).
|
<<"versions">> => [<<"tlsv1.3">>],
|
||||||
|
<<"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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server-unknown-host">>,
|
||||||
<<"versions">> => [<<"tlsv1.3">>],
|
<<"verify">> => <<"verify_peer">>,
|
||||||
<<"ciphers">> => [<<"TLS_CHACHA20_POLY1305_SHA256">>]})),
|
<<"versions">> => [<<"tlsv1.3">>],
|
||||||
|
<<"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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.1">>, <<"tlsv1.2">>]})),
|
<<"verify">> => <<"verify_peer">>,
|
||||||
|
<<"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">>,
|
#{
|
||||||
<<"verify">> => <<"verify_peer">>,
|
<<"server_name_indication">> => <<"authn-server">>,
|
||||||
<<"versions">> => [<<"tlsv1.3">>],
|
<<"verify">> => <<"verify_peer">>,
|
||||||
<<"ciphers">> => [<<"TLS_AES_128_GCM_SHA256">>]})).
|
<<"versions">> => [<<"tlsv1.3">>],
|
||||||
|
<<"ciphers">> => [<<"TLS_AES_128_GCM_SHA256">>]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
@ -111,24 +129,27 @@ 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">>,
|
||||||
enable => <<"true">>,
|
salt_position => <<"suffix">>
|
||||||
|
},
|
||||||
|
enable => <<"true">>,
|
||||||
|
|
||||||
backend => <<"redis">>,
|
backend => <<"redis">>,
|
||||||
cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>,
|
cmd => <<"HMGET mqtt_user:${username} password_hash salt is_superuser">>,
|
||||||
database => <<"1">>,
|
database => <<"1">>,
|
||||||
password => <<"public">>,
|
password => <<"public">>,
|
||||||
server => redis_server(),
|
server => redis_server(),
|
||||||
ssl => maps:merge(SSLOpts, SpecificSSLOpts)
|
ssl => maps:merge(SSLOpts, SpecificSSLOpts)
|
||||||
}.
|
}.
|
||||||
|
|
||||||
redis_server() ->
|
redis_server() ->
|
||||||
iolist_to_binary(io_lib:format("~s:~b",[?REDIS_HOST, ?REDIS_TLS_PORT])).
|
iolist_to_binary(io_lib:format("~s:~b", [?REDIS_HOST, ?REDIS_TLS_PORT])).
|
||||||
|
|
||||||
start_apps(Apps) ->
|
start_apps(Apps) ->
|
||||||
lists:foreach(fun application:ensure_all_started/1, Apps).
|
lists:foreach(fun application:ensure_all_started/1, Apps).
|
||||||
|
|
|
@ -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"]),
|
#{
|
||||||
certfile => filename:join([Dir, "data/certs", "client.crt"]),
|
keyfile => filename:join([Dir, "data/certs", "client.key"]),
|
||||||
cacertfile => filename:join([Dir, "data/certs", "ca.crt"])}.
|
certfile => filename:join([Dir, "data/certs", "client.crt"]),
|
||||||
|
cacertfile => filename:join([Dir, "data/certs", "ca.crt"])
|
||||||
|
}.
|
||||||
|
|
|
@ -26,14 +26,16 @@
|
||||||
|
|
||||||
-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).
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
_ = application:load(emqx_conf),
|
_ = application:load(emqx_conf),
|
||||||
ok = emqx_common_test_helpers:start_apps([emqx_authn]),
|
ok = emqx_common_test_helpers:start_apps([emqx_authn]),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
|
@ -44,8 +46,9 @@ 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),
|
||||||
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) ->
|
||||||
|
@ -64,23 +67,25 @@ 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 = #{
|
||||||
<<"mechanism">> => <<"scram">>,
|
<<"mechanism">> => <<"scram">>,
|
||||||
<<"backend">> => <<"built_in_database">>,
|
<<"backend">> => <<"built_in_database">>,
|
||||||
<<"algorithm">> => <<"sha271828">>,
|
<<"algorithm">> => <<"sha271828">>,
|
||||||
<<"iteration_count">> => <<"4096">>
|
<<"iteration_count">> => <<"4096">>
|
||||||
},
|
},
|
||||||
|
|
||||||
{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).
|
||||||
|
|
||||||
|
@ -96,39 +101,47 @@ t_authenticate(_Config) ->
|
||||||
ClientFirstMessage = esasl_scram:client_first_message(Username),
|
ClientFirstMessage = esasl_scram:client_first_message(Username),
|
||||||
|
|
||||||
ConnectPacket = ?CONNECT_PACKET(
|
ConnectPacket = ?CONNECT_PACKET(
|
||||||
#mqtt_packet_connect{
|
#mqtt_packet_connect{
|
||||||
proto_ver = ?MQTT_PROTO_V5,
|
proto_ver = ?MQTT_PROTO_V5,
|
||||||
properties = #{
|
properties = #{
|
||||||
'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,
|
#{
|
||||||
password => Password,
|
client_first_message => ClientFirstMessage,
|
||||||
algorithm => Algorithm}
|
password => Password,
|
||||||
|
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}
|
||||||
|
@ -146,13 +159,14 @@ t_authenticate_bad_username(_Config) ->
|
||||||
ClientFirstMessage = esasl_scram:client_first_message(<<"badusername">>),
|
ClientFirstMessage = esasl_scram:client_first_message(<<"badusername">>),
|
||||||
|
|
||||||
ConnectPacket = ?CONNECT_PACKET(
|
ConnectPacket = ?CONNECT_PACKET(
|
||||||
#mqtt_packet_connect{
|
#mqtt_packet_connect{
|
||||||
proto_ver = ?MQTT_PROTO_V5,
|
proto_ver = ?MQTT_PROTO_V5,
|
||||||
properties = #{
|
properties = #{
|
||||||
'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),
|
||||||
|
|
||||||
|
@ -170,32 +184,39 @@ t_authenticate_bad_password(_Config) ->
|
||||||
ClientFirstMessage = esasl_scram:client_first_message(Username),
|
ClientFirstMessage = esasl_scram:client_first_message(Username),
|
||||||
|
|
||||||
ConnectPacket = ?CONNECT_PACKET(
|
ConnectPacket = ?CONNECT_PACKET(
|
||||||
#mqtt_packet_connect{
|
#mqtt_packet_connect{
|
||||||
proto_ver = ?MQTT_PROTO_V5,
|
proto_ver = ?MQTT_PROTO_V5,
|
||||||
properties = #{
|
properties = #{
|
||||||
'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,
|
#{
|
||||||
password => <<"badpassword">>,
|
client_first_message => ClientFirstMessage,
|
||||||
algorithm => Algorithm}
|
password => <<"badpassword">>,
|
||||||
|
algorithm => Algorithm
|
||||||
|
}
|
||||||
),
|
),
|
||||||
|
|
||||||
AuthContinuePacket = ?AUTH_PACKET(
|
AuthContinuePacket = ?AUTH_PACKET(
|
||||||
?RC_CONTINUE_AUTHENTICATION,
|
?RC_CONTINUE_AUTHENTICATION,
|
||||||
#{'Authentication-Method' => <<"SCRAM-SHA-512">>,
|
#{
|
||||||
'Authentication-Data' => ClientFinalMessage}),
|
'Authentication-Method' => <<"SCRAM-SHA-512">>,
|
||||||
|
'Authentication-Data' => ClientFinalMessage
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
ok = emqx_authn_mqtt_test_client:send(Pid, AuthContinuePacket),
|
ok = emqx_authn_mqtt_test_client:send(Pid, AuthContinuePacket),
|
||||||
|
|
||||||
|
@ -218,7 +239,7 @@ t_destroy(_) ->
|
||||||
ok = emqx_enhanced_authn_scram_mnesia:destroy(State0),
|
ok = emqx_enhanced_authn_scram_mnesia:destroy(State0),
|
||||||
|
|
||||||
{ok, State1} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config),
|
{ok, State1} = emqx_enhanced_authn_scram_mnesia:create(<<"id">>, Config),
|
||||||
{error,not_found} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State1),
|
{error, not_found} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State1),
|
||||||
{ok, _} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, StateOther).
|
{ok, _} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, StateOther).
|
||||||
|
|
||||||
t_add_user(_) ->
|
t_add_user(_) ->
|
||||||
|
@ -248,12 +269,14 @@ t_update_user(_) ->
|
||||||
{ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(User, State),
|
{ok, _} = emqx_enhanced_authn_scram_mnesia:add_user(User, State),
|
||||||
{ok, #{is_superuser := false}} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State),
|
{ok, #{is_superuser := false}} = emqx_enhanced_authn_scram_mnesia:lookup_user(<<"u">>, State),
|
||||||
|
|
||||||
{ok,
|
{ok, #{
|
||||||
#{user_id := <<"u">>,
|
user_id := <<"u">>,
|
||||||
is_superuser := true}} = emqx_enhanced_authn_scram_mnesia:update_user(
|
is_superuser := true
|
||||||
<<"u">>,
|
}} = emqx_enhanced_authn_scram_mnesia:update_user(
|
||||||
#{password => <<"p1">>, is_superuser => true},
|
<<"u">>,
|
||||||
State),
|
#{password => <<"p1">>, is_superuser => true},
|
||||||
|
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 => <<"u2">>, password => <<"p">>},
|
#{user_id => <<"u1">>, password => <<"p">>},
|
||||||
#{user_id => <<"u3">>, password => <<"p">>}],
|
#{user_id => <<"u2">>, 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],
|
||||||
#{<<"page">> => 1, <<"limit">> => 2},
|
meta := #{page := 1, limit := 2, count := 3}
|
||||||
State),
|
} = emqx_enhanced_authn_scram_mnesia:list_users(
|
||||||
#{data := [?USER_MAP],
|
#{<<"page">> => 1, <<"limit">> => 2},
|
||||||
meta := #{page := 2, limit := 2, count := 3}} = emqx_enhanced_authn_scram_mnesia:list_users(
|
State
|
||||||
#{<<"page">> => 2, <<"limit">> => 2},
|
),
|
||||||
State),
|
#{
|
||||||
#{data := [#{user_id := <<"u1">>,
|
data := [?USER_MAP],
|
||||||
is_superuser := _}],
|
meta := #{page := 2, limit := 2, count := 3}
|
||||||
meta := #{page := 1, limit := 3, count := 1}} = emqx_enhanced_authn_scram_mnesia:list_users(
|
} = emqx_enhanced_authn_scram_mnesia:list_users(
|
||||||
#{ <<"page">> => 1
|
#{<<"page">> => 2, <<"limit">> => 2},
|
||||||
, <<"limit">> => 3
|
State
|
||||||
, <<"like_username">> => <<"1">>},
|
),
|
||||||
State).
|
#{
|
||||||
|
data := [
|
||||||
|
#{
|
||||||
|
user_id := <<"u1">>,
|
||||||
|
is_superuser := _
|
||||||
|
}
|
||||||
|
],
|
||||||
|
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_data => ClientFirstMessage,
|
auth_method => <<"SCRAM-SHA-512">>,
|
||||||
auth_cache => #{}
|
auth_data => ClientFirstMessage,
|
||||||
},
|
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,
|
#{
|
||||||
password => Password,
|
client_first_message => ClientFirstMessage,
|
||||||
algorithm => sha512}
|
password => Password,
|
||||||
|
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_data => ClientFinalMessage,
|
auth_method => <<"SCRAM-SHA-512">>,
|
||||||
auth_cache => ServerCache
|
auth_data => ClientFinalMessage,
|
||||||
},
|
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,18 +385,17 @@ test_is_superuser(UserInfo, ExpectedIsSuperuser) ->
|
||||||
|
|
||||||
ok = emqx_enhanced_authn_scram_mnesia:destroy(State).
|
ok = emqx_enhanced_authn_scram_mnesia:destroy(State).
|
||||||
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
config() ->
|
config() ->
|
||||||
#{
|
#{
|
||||||
mechanism => <<"scram">>,
|
mechanism => <<"scram">>,
|
||||||
backend => <<"built_in_database">>,
|
backend => <<"built_in_database">>,
|
||||||
algorithm => sha512,
|
algorithm => sha512,
|
||||||
iteration_count => 4096
|
iteration_count => 4096
|
||||||
}.
|
}.
|
||||||
|
|
||||||
raw_config(Algorithm) ->
|
raw_config(Algorithm) ->
|
||||||
#{
|
#{
|
||||||
|
@ -361,14 +409,16 @@ init_auth(Username, Password, Algorithm) ->
|
||||||
Config = raw_config(Algorithm),
|
Config = raw_config(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