fix: don't override authn users when import_user from authn.boostrap_file

This commit is contained in:
zhongwencool 2024-07-02 11:13:28 +08:00
parent dea2bf19b1
commit 4d912516c8
4 changed files with 75 additions and 74 deletions

View File

@ -0,0 +1,3 @@
user_id,password,is_superuser
myuser3,Password4,true
myuser4,Password3,false
1 user_id password is_superuser
2 myuser3 Password4 true
3 myuser4 Password3 false

View File

@ -171,67 +171,57 @@ do_destroy(UserGroup) ->
mnesia:select(?TAB, group_match_spec(UserGroup), write) mnesia:select(?TAB, group_match_spec(UserGroup), write)
). ).
import_users({PasswordType, Filename, FileData}, State) -> import_users(ImportSource, State) ->
import_users(ImportSource, State, #{override => true}).
import_users({PasswordType, Filename, FileData}, State, Opts) ->
Convertor = convertor(PasswordType, State), Convertor = convertor(PasswordType, State),
try try parse_import_users(Filename, FileData, Convertor) of
{_NewUsersCnt, Users} = parse_import_users(Filename, FileData, Convertor), Users ->
case length(Users) > 0 andalso do_import_users(Users) of case do_import_users(Users, Opts#{filename => Filename}) of
false -> ok ->
error(empty_users); ok;
ok -> {error, Reason} ->
ok; ?SLOG(
{error, Reason} -> warning,
_ = do_clean_imported_users(Users), #{
error(Reason) msg => "import_authn_users_failed",
end reason => Reason,
type => PasswordType,
filename => Filename
}
),
{error, Reason}
end
catch catch
error:Reason1:Stk -> error:Reason:Stk ->
?SLOG( ?SLOG(
warning, warning,
#{ #{
msg => "import_users_failed", msg => "parse_authn_users_failed",
reason => Reason1, reason => Reason,
type => PasswordType, type => PasswordType,
filename => Filename, filename => Filename,
stacktrace => Stk stacktrace => Stk
} }
), ),
{error, Reason1} {error, Reason}
end. end.
do_import_users(Users) -> do_import_users([], _Opts) ->
{error, empty_users};
do_import_users(Users, Opts) ->
trans( trans(
fun() -> fun() ->
lists:foreach( lists:foreach(
fun( fun(User) ->
#{ insert_user(User, Opts)
<<"user_group">> := UserGroup,
<<"user_id">> := UserID,
<<"password_hash">> := PasswordHash,
<<"salt">> := Salt,
<<"is_superuser">> := IsSuperuser
}
) ->
insert_user(UserGroup, UserID, PasswordHash, Salt, IsSuperuser)
end, end,
Users Users
) )
end end
). ).
do_clean_imported_users(Users) ->
lists:foreach(
fun(
#{
<<"user_group">> := UserGroup,
<<"user_id">> := UserID
}
) ->
mria:dirty_delete(?TAB, {UserGroup, UserID})
end,
Users
).
add_user( add_user(
UserInfo, UserInfo,
State State
@ -338,7 +328,14 @@ run_fuzzy_filter(
%% Internal functions %% Internal functions
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
insert_user(UserGroup, UserID, PasswordHash, Salt, IsSuperuser) -> insert_user(User, Opts) ->
#{
<<"user_group">> := UserGroup,
<<"user_id">> := UserID,
<<"password_hash">> := PasswordHash,
<<"salt">> := Salt,
<<"is_superuser">> := IsSuperuser
} = User,
UserInfoRecord = UserInfoRecord =
#user_info{user_id = DBUserID} = #user_info{user_id = DBUserID} =
user_info_record(UserGroup, UserID, PasswordHash, Salt, IsSuperuser), user_info_record(UserGroup, UserID, PasswordHash, Salt, IsSuperuser),
@ -348,14 +345,22 @@ insert_user(UserGroup, UserID, PasswordHash, Salt, IsSuperuser) ->
[UserInfoRecord] -> [UserInfoRecord] ->
ok; ok;
[_] -> [_] ->
Msg =
case maps:get(override, Opts, false) of
true ->
insert_user(UserInfoRecord),
"override_an_exists_userid_into_authentication_database_ok";
false ->
"import_an_exists_userid_into_authentication_database_failed"
end,
?SLOG(warning, #{ ?SLOG(warning, #{
msg => "bootstrap_authentication_overridden_in_the_built_in_database", msg => Msg,
user_id => UserID, user_id => UserID,
group_id => UserGroup, group_id => UserGroup,
bootstrap_file => maps:get(filename, Opts),
suggestion => suggestion =>
"If you have made changes in other way, remove the user_id from the bootstrap file." "If you've altered it differently, delete the user_id from the bootstrap file."
}), })
insert_user(UserInfoRecord)
end. end.
insert_user(#user_info{} = UserInfoRecord) -> insert_user(#user_info{} = UserInfoRecord) ->
@ -473,27 +478,7 @@ parse_import_users(Filename, FileData, Convertor) ->
end end
end, end,
ReaderFn = reader_fn(Filename, FileData), ReaderFn = reader_fn(Filename, FileData),
Users = Eval(ReaderFn), Eval(ReaderFn).
NewUsersCount =
lists:foldl(
fun(
#{
<<"user_group">> := UserGroup,
<<"user_id">> := UserID
},
Acc
) ->
case ets:member(?TAB, {UserGroup, UserID}) of
true ->
Acc;
false ->
Acc + 1
end
end,
0,
Users
),
{NewUsersCount, Users}.
reader_fn(prepared_user_list, List) when is_list(List) -> reader_fn(prepared_user_list, List) when is_list(List) ->
%% Example: [#{<<"user_id">> => <<>>, ...}] %% Example: [#{<<"user_id">> => <<>>, ...}]
@ -564,8 +549,7 @@ boostrap_user_from_file(Config, State) ->
FileName = emqx_schema:naive_env_interpolation(FileName0), FileName = emqx_schema:naive_env_interpolation(FileName0),
case file:read_file(FileName) of case file:read_file(FileName) of
{ok, FileData} -> {ok, FileData} ->
%% if there is a key conflict, override with the key which from the bootstrap file _ = import_users({Type, FileName, FileData}, State, #{override => false}),
_ = import_users({Type, FileName, FileData}, State),
ok; ok;
{error, Reason} -> {error, Reason} ->
?SLOG(warning, #{ ?SLOG(warning, #{

View File

@ -56,6 +56,7 @@ t_create(_) ->
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. ok.
t_bootstrap_file(_) -> t_bootstrap_file(_) ->
Config = config(), Config = config(),
%% hash to hash %% hash to hash
@ -102,16 +103,25 @@ t_bootstrap_file(_) ->
], ],
test_bootstrap_file(HashConfig, plain, <<"user-credentials-plain.json">>) test_bootstrap_file(HashConfig, plain, <<"user-credentials-plain.json">>)
), ),
Opts = #{clean => false},
Result = test_bootstrap_file(HashConfig, plain, <<"user-credentials-plain.csv">>, Opts),
?assertMatch( ?assertMatch(
[ [
{user_info, {_, <<"myuser3">>}, _, _, true}, {user_info, {_, <<"myuser3">>}, _, _, true},
{user_info, {_, <<"myuser4">>}, _, _, false} {user_info, {_, <<"myuser4">>}, _, _, false}
], ],
test_bootstrap_file(HashConfig, plain, <<"user-credentials-plain.csv">>) Result
),
%% Don't override the exist user id.
?assertMatch(
Result, test_bootstrap_file(HashConfig, plain, <<"user-credentials-plain_v2.csv">>)
), ),
ok. ok.
test_bootstrap_file(Config0, Type, File) -> test_bootstrap_file(Config0, Type, File) ->
test_bootstrap_file(Config0, Type, File, #{clean => true}).
test_bootstrap_file(Config0, Type, File, Opts) ->
{Type, Filename, _FileData} = sample_filename_and_data(Type, File), {Type, Filename, _FileData} = sample_filename_and_data(Type, File),
Config2 = Config0#{ Config2 = Config0#{
boostrap_file => Filename, boostrap_file => Filename,
@ -119,8 +129,13 @@ test_bootstrap_file(Config0, Type, File) ->
}, },
{ok, State0} = emqx_authn_mnesia:create(?AUTHN_ID, Config2), {ok, State0} = emqx_authn_mnesia:create(?AUTHN_ID, Config2),
Result = ets:tab2list(emqx_authn_mnesia), Result = ets:tab2list(emqx_authn_mnesia),
ok = emqx_authn_mnesia:destroy(State0), case maps:get(clean, Opts) of
?assertMatch([], ets:tab2list(emqx_authn_mnesia)), true ->
ok = emqx_authn_mnesia:destroy(State0),
?assertMatch([], ets:tab2list(emqx_authn_mnesia));
_ ->
ok
end,
Result. Result.
t_update(_) -> t_update(_) ->

View File

@ -11,9 +11,8 @@ user_id_type.label:
bootstrap_file.desc: bootstrap_file.desc:
"""The bootstrap file imports users into the built-in database. """The bootstrap file imports users into the built-in database.
The file content format is determined by `bootstrap_type`. It will not import a user ID that already exists in the database.
Remove the item from the bootstrap file when you have made changes in other way, The file content format is determined by `bootstrap_type`."""
otherwise, after restarting, the bootstrap item will be overridden again."""
bootstrap_file.label: bootstrap_file.label:
"""Bootstrap File Path""" """Bootstrap File Path"""