fix: avoid duplicated apikey from data import

This commit is contained in:
JimMoen 2023-10-20 18:16:33 +08:00
parent dbffc080c2
commit d467289bb2
No known key found for this signature in database
GPG Key ID: 87A520B4F76BA86D
3 changed files with 76 additions and 29 deletions

View File

@ -31,6 +31,7 @@
]). ]).
-define(DEFAULT_APP_ID, <<"default_appid">>). -define(DEFAULT_APP_ID, <<"default_appid">>).
-define(DEFAULT_APP_KEY, <<"default_app_key">>).
-define(DEFAULT_APP_SECRET, <<"default_app_secret">>). -define(DEFAULT_APP_SECRET, <<"default_app_secret">>).
request_api(Method, Url, Auth) -> request_api(Method, Url, Auth) ->
@ -90,7 +91,12 @@ create_default_app() ->
Now = erlang:system_time(second), Now = erlang:system_time(second),
ExpiredAt = Now + timer:minutes(10), ExpiredAt = Now + timer:minutes(10),
emqx_mgmt_auth:create( emqx_mgmt_auth:create(
?DEFAULT_APP_ID, ?DEFAULT_APP_SECRET, true, ExpiredAt, <<"default app key for test">> ?DEFAULT_APP_ID,
?DEFAULT_APP_KEY,
?DEFAULT_APP_SECRET,
true,
ExpiredAt,
<<"default app key for test">>
). ).
delete_default_app() -> delete_default_app() ->

View File

@ -192,6 +192,7 @@ api_key(post, #{body := App}) ->
} = App, } = App,
ExpiredAt = ensure_expired_at(App), ExpiredAt = ensure_expired_at(App),
Desc = unicode:characters_to_binary(Desc0, unicode), Desc = unicode:characters_to_binary(Desc0, unicode),
%% create api_key with random api_key and api_secret from Dashboard
case emqx_mgmt_auth:create(Name, Enable, ExpiredAt, Desc) of case emqx_mgmt_auth:create(Name, Enable, ExpiredAt, Desc) of
{ok, NewApp} -> {ok, NewApp} ->
{200, emqx_mgmt_auth:format(NewApp)}; {200, emqx_mgmt_auth:format(NewApp)};

View File

@ -43,12 +43,12 @@
-export([ -export([
do_update/4, do_update/4,
do_delete/1, do_delete/1,
do_create_app/3, do_create_app/1,
do_force_create_app/3 do_force_create_app/1
]). ]).
-ifdef(TEST). -ifdef(TEST).
-export([create/5]). -export([create/6]).
-endif. -endif.
-define(APP, emqx_app). -define(APP, emqx_app).
@ -63,6 +63,8 @@
created_at = 0 :: integer() | '_' created_at = 0 :: integer() | '_'
}). }).
-define(DEFAULT_HASH_LEN, 16).
mnesia(boot) -> mnesia(boot) ->
ok = mria:create_table(?APP, [ ok = mria:create_table(?APP, [
{type, set}, {type, set},
@ -97,11 +99,12 @@ init_bootstrap_file() ->
create(Name, Enable, ExpiredAt, Desc) -> create(Name, Enable, ExpiredAt, Desc) ->
ApiSecret = generate_api_secret(), ApiSecret = generate_api_secret(),
create(Name, ApiSecret, Enable, ExpiredAt, Desc). ApiKey = generate_unique_api_key(Name),
create(Name, ApiKey, ApiSecret, Enable, ExpiredAt, Desc).
create(Name, ApiSecret, Enable, ExpiredAt, Desc) -> create(Name, ApiKey, ApiSecret, Enable, ExpiredAt, Desc) ->
case mnesia:table_info(?APP, size) < 100 of case mnesia:table_info(?APP, size) < 100 of
true -> create_app(Name, ApiSecret, Enable, ExpiredAt, Desc); true -> create_app(Name, ApiKey, ApiSecret, Enable, ExpiredAt, Desc);
false -> {error, "Maximum ApiKey"} false -> {error, "Maximum ApiKey"}
end. end.
@ -202,7 +205,7 @@ to_map(#?APP{name = N, api_key = K, enable = E, expired_at = ET, created_at = CT
is_expired(undefined) -> false; is_expired(undefined) -> false;
is_expired(ExpiredTime) -> ExpiredTime < erlang:system_time(second). is_expired(ExpiredTime) -> ExpiredTime < erlang:system_time(second).
create_app(Name, ApiSecret, Enable, ExpiredAt, Desc) -> create_app(Name, ApiKey, ApiSecret, Enable, ExpiredAt, Desc) ->
App = App =
#?APP{ #?APP{
name = Name, name = Name,
@ -211,7 +214,7 @@ create_app(Name, ApiSecret, Enable, ExpiredAt, Desc) ->
desc = Desc, desc = Desc,
created_at = erlang:system_time(second), created_at = erlang:system_time(second),
api_secret_hash = emqx_dashboard_admin:hash(ApiSecret), api_secret_hash = emqx_dashboard_admin:hash(ApiSecret),
api_key = list_to_binary(emqx_utils:gen_id(16)) api_key = ApiKey
}, },
case create_app(App) of case create_app(App) of
{ok, Res} -> {ok, Res} ->
@ -220,13 +223,13 @@ create_app(Name, ApiSecret, Enable, ExpiredAt, Desc) ->
Error Error
end. end.
create_app(App = #?APP{api_key = ApiKey, name = Name}) -> create_app(App) ->
trans(fun ?MODULE:do_create_app/3, [App, ApiKey, Name]). trans(fun ?MODULE:do_create_app/1, [App]).
force_create_app(NamePrefix, App = #?APP{api_key = ApiKey}) -> force_create_app(App) ->
trans(fun ?MODULE:do_force_create_app/3, [App, ApiKey, NamePrefix]). trans(fun ?MODULE:do_force_create_app/1, [App]).
do_create_app(App, ApiKey, Name) -> do_create_app(App = #?APP{api_key = ApiKey, name = Name}) ->
case mnesia:read(?APP, Name) of case mnesia:read(?APP, Name) of
[_] -> [_] ->
mnesia:abort(name_already_existed); mnesia:abort(name_already_existed);
@ -240,21 +243,56 @@ do_create_app(App, ApiKey, Name) ->
end end
end. end.
do_force_create_app(App, ApiKey, NamePrefix) -> do_force_create_app(App) ->
_ = maybe_cleanup_api_key(App),
ok = mnesia:write(App).
maybe_cleanup_api_key(#?APP{name = Name, api_key = ApiKey}) ->
case mnesia:match_object(?APP, #?APP{api_key = ApiKey, _ = '_'}, read) of case mnesia:match_object(?APP, #?APP{api_key = ApiKey, _ = '_'}, read) of
[] -> [] ->
NewName = generate_unique_name(NamePrefix), ok;
ok = mnesia:write(App#?APP{name = NewName});
[#?APP{name = Name}] -> [#?APP{name = Name}] ->
ok = mnesia:write(App#?APP{name = Name}) ?SLOG(debug, #{
msg => "same_apikey_detected",
info => <<"The last `KEY:SECRET` in bootstrap file will be used.">>
}),
ok;
[_App1] ->
?SLOG(info, #{
msg => "update_apikey_name_from_old_version",
info => <<"Update ApiKey name with new name rule, more information: xxx">>
}),
ok;
Existed ->
%% Duplicated or upgraded from old version:
%% Which `Name` and `ApiKey` are not related in old version.
%% So delete it/(them) and write a new record with a name strongly related to the apikey.
%% The apikeys generated from the file do not have names.
%% Generate a name for the apikey from the apikey itself by rule:
%% Use `from_bootstrap_file_` as the prefix, and the first 16 digits of the
%% sha512 hexadecimal value of the `ApiKey` as the suffix to form the name of the apikey.
%% e.g. The name of the apikey: `example-api-key:secret_xxxx` is `from_bootstrap_file_53280fb165b6cd37`
?SLOG(info, #{
msg => "duplicated_apikey_detected",
info => <<"Delete duplicated apikeys and write a new one from bootstrap file">>
}),
_ = lists:map(
fun(#?APP{name = N}) -> ok = mnesia:delete({?APP, N}) end, Existed
),
ok
end. end.
generate_unique_name(NamePrefix) -> hash_string_from_seed(Seed, PrefixLen) ->
New = list_to_binary(NamePrefix ++ emqx_utils:gen_id(16)), <<Integer:512>> = crypto:hash(sha512, Seed),
case mnesia:read(?APP, New) of list_to_binary(string:slice(io_lib:format("~128.16.0b", [Integer]), 0, PrefixLen)).
[] -> New;
_ -> generate_unique_name(NamePrefix) %% Form Dashboard API Key pannel, only `Name` provided for users
end. generate_unique_api_key(Name) ->
hash_string_from_seed(Name, ?DEFAULT_HASH_LEN).
%% Form BootStrap File, only `ApiKey` provided from file, no `Name`
generate_unique_name(NamePrefix, ApiKey) ->
<<NamePrefix/binary, (hash_string_from_seed(ApiKey, ?DEFAULT_HASH_LEN))/binary>>.
trans(Fun, Args) -> trans(Fun, Args) ->
case mria:transaction(?COMMON_SHARD, Fun, Args) of case mria:transaction(?COMMON_SHARD, Fun, Args) of
@ -300,22 +338,24 @@ init_bootstrap_file(File, Dev, MP) ->
end. end.
-define(BOOTSTRAP_TAG, <<"Bootstrapped From File">>). -define(BOOTSTRAP_TAG, <<"Bootstrapped From File">>).
-define(FROM_BOOTSTRAP_FILE_PREFIX, <<"from_bootstrap_file_">>).
add_bootstrap_file(File, Dev, MP, Line) -> add_bootstrap_file(File, Dev, MP, Line) ->
case file:read_line(Dev) of case file:read_line(Dev) of
{ok, Bin} -> {ok, Bin} ->
case re:run(Bin, MP, [global, {capture, all_but_first, binary}]) of case re:run(Bin, MP, [global, {capture, all_but_first, binary}]) of
{match, [[AppKey, ApiSecret]]} -> {match, [[ApiKey, ApiSecret]]} ->
App = App =
#?APP{ #?APP{
name = generate_unique_name(?FROM_BOOTSTRAP_FILE_PREFIX, ApiKey),
api_key = ApiKey,
api_secret_hash = emqx_dashboard_admin:hash(ApiSecret),
enable = true, enable = true,
expired_at = infinity,
desc = ?BOOTSTRAP_TAG, desc = ?BOOTSTRAP_TAG,
created_at = erlang:system_time(second), created_at = erlang:system_time(second),
api_secret_hash = emqx_dashboard_admin:hash(ApiSecret), expired_at = infinity
api_key = AppKey
}, },
case force_create_app("from_bootstrap_file_", App) of case force_create_app(App) of
{ok, ok} -> {ok, ok} ->
add_bootstrap_file(File, Dev, MP, Line + 1); add_bootstrap_file(File, Dev, MP, Line + 1);
{error, Reason} -> {error, Reason} ->