feat(authn): authn mnesia rows fuzzy searching by `clientid` or `username`

This commit is contained in:
JimMoen 2022-03-07 11:27:10 +08:00
parent 16ec8fe289
commit 593e1a3efb
3 changed files with 67 additions and 18 deletions

View File

@ -378,8 +378,8 @@ lookup_user(ChainName, AuthenticatorID, UserID) ->
call({lookup_user, ChainName, AuthenticatorID, UserID}).
-spec list_users(chain_name(), authenticator_id(), map()) -> {ok, [user_info()]} | {error, term()}.
list_users(ChainName, AuthenticatorID, Params) ->
call({list_users, ChainName, AuthenticatorID, Params}).
list_users(ChainName, AuthenticatorID, FuzzyParams) ->
call({list_users, ChainName, AuthenticatorID, FuzzyParams}).
%%--------------------------------------------------------------------
%% gen_server callbacks
@ -476,8 +476,8 @@ handle_call({lookup_user, ChainName, AuthenticatorID, UserID}, _From, State) ->
Reply = call_authenticator(ChainName, AuthenticatorID, lookup_user, [UserID]),
reply(Reply, State);
handle_call({list_users, ChainName, AuthenticatorID, PageParams}, _From, State) ->
Reply = call_authenticator(ChainName, AuthenticatorID, list_users, [PageParams]),
handle_call({list_users, ChainName, AuthenticatorID, FuzzyParams}, _From, State) ->
Reply = call_authenticator(ChainName, AuthenticatorID, list_users, [FuzzyParams]),
reply(Reply, State);
handle_call(Req, _From, State) ->

View File

@ -380,7 +380,13 @@ schema("/authentication/:id/users") ->
parameters => [
param_auth_id(),
{page, mk(integer(), #{in => query, desc => <<"Page Index">>, required => false})},
{limit, mk(integer(), #{in => query, desc => <<"Page Limit">>, required => false})}
{limit, mk(integer(), #{in => query, desc => <<"Page Limit">>, required => false})},
{like_username, mk(binary(), #{ in => query
, desc => <<"Fuzzy search username">>
, required => false})},
{like_clientid, mk(binary(), #{ in => query
, desc => <<"Fuzzy search clientid">>
, required => false})}
],
responses => #{
200 => emqx_dashboard_swagger:schema_with_example(
@ -638,8 +644,8 @@ listener_authenticator_import_users(post, #{bindings := #{listener_id := _, id :
authenticator_users(post, #{bindings := #{id := AuthenticatorID}, body := UserInfo}) ->
add_user(?GLOBAL, AuthenticatorID, UserInfo);
authenticator_users(get, #{bindings := #{id := AuthenticatorID}, query_string := PageParams}) ->
list_users(?GLOBAL, AuthenticatorID, PageParams).
authenticator_users(get, #{bindings := #{id := AuthenticatorID}, query_string := QueryString}) ->
list_users(?GLOBAL, AuthenticatorID, QueryString).
authenticator_user(put, #{bindings := #{id := AuthenticatorID,
user_id := UserID}, body := UserInfo}) ->
@ -840,13 +846,9 @@ delete_user(ChainName, AuthenticatorID, UserID) ->
serialize_error({user_error, Reason})
end.
list_users(ChainName, AuthenticatorID, PageParams) ->
case emqx_authentication:list_users(ChainName, AuthenticatorID, PageParams) of
{ok, Users} ->
{200, Users};
{error, Reason} ->
serialize_error(Reason)
end.
list_users(ChainName, AuthenticatorID, QueryString) ->
Response = emqx_authentication:list_users(ChainName, AuthenticatorID, QueryString),
emqx_mgmt_util:generate_response(Response).
update_config(Path, ConfigRequest) ->
emqx_conf:update(Path, ConfigRequest, #{rawconf_with_defaults => true,

View File

@ -43,7 +43,9 @@
, list_users/2
]).
-export([format_user_info/1]).
-export([ query/4
, format_user_info/1
, group_match_spec/1]).
-type user_id_type() :: clientid | username.
-type user_group() :: binary().
@ -63,7 +65,10 @@
-boot_mnesia({mnesia, [boot]}).
-define(TAB, ?MODULE).
-define(FORMAT_FUN, {?MODULE, format_user_info}).
-define(AUTHN_QSCHEMA, [ {<<"like_username">>, binary}
, {<<"like_clientid">>, binary}
, {<<"user_group">>, binary}]).
-define(QUERY_FUN, {?MODULE, query}).
%%------------------------------------------------------------------------------
%% Mnesia bootstrap
@ -219,8 +224,42 @@ lookup_user(UserID, #{user_group := UserGroup}) ->
{error, not_found}
end.
list_users(PageParams, #{user_group := UserGroup}) ->
{ok, emqx_mgmt_api:paginate(?TAB, group_match_spec(UserGroup), PageParams, ?FORMAT_FUN)}.
list_users(QueryString, #{user_group := UserGroup}) ->
NQueryString = QueryString#{<<"user_group">> => UserGroup},
emqx_mgmt_api:node_query(node(), NQueryString, ?TAB, ?AUTHN_QSCHEMA, ?QUERY_FUN).
%%--------------------------------------------------------------------
%% Query Functions
query(Tab, {QString, []}, Continuation, Limit) ->
Ms = ms_from_qstring(QString),
emqx_mgmt_api:select_table_with_count(Tab, Ms, Continuation, Limit,
fun format_user_info/1);
query(Tab, {QString, FuzzyQString}, Continuation, Limit) ->
Ms = ms_from_qstring(QString),
FuzzyFilterFun = fuzzy_filter_fun(FuzzyQString),
emqx_mgmt_api:select_table_with_count(Tab, {Ms, FuzzyFilterFun}, Continuation, Limit,
fun format_user_info/1).
%%--------------------------------------------------------------------
%% Match funcs
%% Fuzzy username funcs
fuzzy_filter_fun(Fuzzy) ->
fun(MsRaws) when is_list(MsRaws) ->
lists:filter( fun(E) -> run_fuzzy_filter(E, Fuzzy) end
, MsRaws)
end.
run_fuzzy_filter(_, []) ->
true;
run_fuzzy_filter( E = #user_info{user_id = {_, UserID}}
, [{username, like, UsernameSubStr} | Fuzzy]) ->
binary:match(UserID, UsernameSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy);
run_fuzzy_filter( E = #user_info{user_id = {_, UserID}}
, [{clientid, like, ClientIDSubStr} | Fuzzy]) ->
binary:match(UserID, ClientIDSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy).
%%------------------------------------------------------------------------------
%% Internal functions
@ -352,6 +391,14 @@ to_binary(L) when is_list(L) ->
format_user_info(#user_info{user_id = {_, UserID}, is_superuser = IsSuperuser}) ->
#{user_id => UserID, is_superuser => IsSuperuser}.
ms_from_qstring(QString) ->
[Ms] = lists:foldl(fun({user_group, '=:=', UserGroup}, AccIn) ->
[group_match_spec(UserGroup) | AccIn];
(_, AccIn) ->
AccIn
end, [], QString),
Ms.
group_match_spec(UserGroup) ->
ets:fun2ms(
fun(#user_info{user_id = {Group, _}} = User) when Group =:= UserGroup ->