feat: support bootstrap_file on build-in-db authn

This commit is contained in:
zhongwencool 2024-06-26 09:07:10 +08:00
parent d8963c836e
commit 5265c3cc1f
5 changed files with 124 additions and 4 deletions

View File

@ -1,7 +1,7 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
{application, emqx_auth_mnesia, [ {application, emqx_auth_mnesia, [
{description, "EMQX Buitl-in Database Authentication and Authorization"}, {description, "EMQX Buitl-in Database Authentication and Authorization"},
{vsn, "0.1.5"}, {vsn, "0.1.6"},
{registered, []}, {registered, []},
{mod, {emqx_auth_mnesia_app, []}}, {mod, {emqx_auth_mnesia_app, []}},
{applications, [ {applications, [

View File

@ -116,7 +116,7 @@ create(
user_id_type := Type, user_id_type := Type,
password_hash_algorithm := Algorithm, password_hash_algorithm := Algorithm,
user_group := UserGroup user_group := UserGroup
} } = Config
) -> ) ->
ok = emqx_authn_password_hashing:init(Algorithm), ok = emqx_authn_password_hashing:init(Algorithm),
State = #{ State = #{
@ -124,6 +124,7 @@ create(
user_id_type => Type, user_id_type => Type,
password_hash_algorithm => Algorithm password_hash_algorithm => Algorithm
}, },
ok = boostrap_user_from_file(Config, State),
{ok, State}. {ok, State}.
update(Config, _State) -> update(Config, _State) ->
@ -537,3 +538,24 @@ find_password_hash(_, _, _) ->
is_superuser(#{<<"is_superuser">> := <<"true">>}) -> true; is_superuser(#{<<"is_superuser">> := <<"true">>}) -> true;
is_superuser(#{<<"is_superuser">> := true}) -> true; is_superuser(#{<<"is_superuser">> := true}) -> true;
is_superuser(_) -> false. is_superuser(_) -> false.
boostrap_user_from_file(Config, State) ->
case maps:get(boostrap_file, Config, <<>>) of
<<>> ->
ok;
FileName ->
#{boostrap_type := Type} = Config,
case file:read_file(FileName) of
{ok, FileData} ->
%% if there is a key conflict, override with the key which from the bootstrap file
_ = import_users({Type, FileName, FileData}, State),
ok;
{error, Reason} ->
?SLOG(warning, #{
msg => "boostrap_authn(built_in_database)_failed",
boostrap_file => FileName,
boostrap_type => Type,
reason => Reason
})
end
end.

View File

@ -46,7 +46,7 @@ select_union_member(_Kind, _Value) ->
fields(builtin_db) -> fields(builtin_db) ->
[ [
{password_hash_algorithm, fun emqx_authn_password_hashing:type_rw/1} {password_hash_algorithm, fun emqx_authn_password_hashing:type_rw/1}
] ++ common_fields(); ] ++ common_fields() ++ bootstrap_fields();
fields(builtin_db_api) -> fields(builtin_db_api) ->
[ [
{password_hash_algorithm, fun emqx_authn_password_hashing:type_rw_api/1} {password_hash_algorithm, fun emqx_authn_password_hashing:type_rw_api/1}
@ -69,3 +69,24 @@ common_fields() ->
{backend, emqx_authn_schema:backend(?AUTHN_BACKEND)}, {backend, emqx_authn_schema:backend(?AUTHN_BACKEND)},
{user_id_type, fun user_id_type/1} {user_id_type, fun user_id_type/1}
] ++ emqx_authn_schema:common_fields(). ] ++ emqx_authn_schema:common_fields().
bootstrap_fields() ->
[
{bootstrap_file,
?HOCON(
binary(),
#{
desc => ?DESC(bootstrap_file),
required => false,
default => <<>>
}
)},
{bootstrap_type,
?HOCON(
?ENUM([hash, plain]), #{
desc => ?DESC(bootstrap_type),
required => false,
default => <<"plain">>
}
)}
].

View File

@ -54,7 +54,74 @@ t_create(_) ->
{ok, _} = emqx_authn_mnesia:create(?AUTHN_ID, Config0), {ok, _} = emqx_authn_mnesia:create(?AUTHN_ID, Config0),
Config1 = Config0#{password_hash_algorithm => #{name => sha256}}, Config1 = Config0#{password_hash_algorithm => #{name => sha256}},
{ok, _} = emqx_authn_mnesia:create(?AUTHN_ID, Config1). {ok, _} = emqx_authn_mnesia:create(?AUTHN_ID, Config1),
ok.
t_bootstrap_file(_) ->
Config = config(),
%% hash to hash
HashConfig = Config#{password_hash_algorithm => #{name => sha256, salt_position => suffix}},
?assertMatch(
[
{user_info, {_, <<"myuser1">>}, _, _, true},
{user_info, {_, <<"myuser2">>}, _, _, false}
],
test_bootstrap_file(HashConfig, hash, <<"user-credentials.json">>)
),
?assertMatch(
[
{user_info, {_, <<"myuser3">>}, _, _, true},
{user_info, {_, <<"myuser4">>}, _, _, false}
],
test_bootstrap_file(HashConfig, hash, <<"user-credentials.csv">>)
),
%% plain to plain
PlainConfig = Config#{
password_hash_algorithm =>
#{name => plain, salt_position => disable}
},
?assertMatch(
[
{user_info, {_, <<"myuser1">>}, <<"password1">>, _, true},
{user_info, {_, <<"myuser2">>}, <<"password2">>, _, false}
],
test_bootstrap_file(PlainConfig, plain, <<"user-credentials-plain.json">>)
),
?assertMatch(
[
{user_info, {_, <<"myuser3">>}, <<"password3">>, _, true},
{user_info, {_, <<"myuser4">>}, <<"password4">>, _, false}
],
test_bootstrap_file(PlainConfig, plain, <<"user-credentials-plain.csv">>)
),
%% plain to hash
?assertMatch(
[
{user_info, {_, <<"myuser1">>}, _, _, true},
{user_info, {_, <<"myuser2">>}, _, _, false}
],
test_bootstrap_file(HashConfig, plain, <<"user-credentials-plain.json">>)
),
?assertMatch(
[
{user_info, {_, <<"myuser3">>}, _, _, true},
{user_info, {_, <<"myuser4">>}, _, _, false}
],
test_bootstrap_file(HashConfig, plain, <<"user-credentials-plain.csv">>)
),
ok.
test_bootstrap_file(Config0, Type, File) ->
{Type, Filename, _FileData} = sample_filename_and_data(Type, File),
Config2 = Config0#{
boostrap_file => Filename,
boostrap_type => Type
},
{ok, State0} = emqx_authn_mnesia:create(?AUTHN_ID, Config2),
Result = ets:tab2list(emqx_authn_mnesia),
ok = emqx_authn_mnesia:destroy(State0),
?assertMatch([], ets:tab2list(emqx_authn_mnesia)),
Result.
t_update(_) -> t_update(_) ->
Config0 = config(), Config0 = config(),

View File

@ -9,4 +9,14 @@ user_id_type.desc:
user_id_type.label: user_id_type.label:
"""Authentication ID Type""" """Authentication ID Type"""
bootstrap_file.desc:
"""The bootstrap file imports users into the built-in database."""
bootstrap_file.label:
"""Bootstrap File Path"""
bootstrap_type.desc:
"""plain: bootstrap_file.cvs should be `user_id,password,is_superuser`.
hash: bootstrap_file.cvs should be `user_id,password_hash,salt,is_superuser`"""
} }