Merge pull request #7969 from zhongwencool/auth-acl-fitler-support
feat: auth acl fitler support
This commit is contained in:
commit
84fa6bfaeb
|
@ -21,6 +21,7 @@ File format:
|
||||||
* Return a client_identifier_not_valid error when username is empty and username_as_clientid is set to true [#7862]
|
* Return a client_identifier_not_valid error when username is empty and username_as_clientid is set to true [#7862]
|
||||||
* Add more rule engine date functions: format_date/3, format_date/4, date_to_unix_ts/4 [#7894]
|
* Add more rule engine date functions: format_date/3, format_date/4, date_to_unix_ts/4 [#7894]
|
||||||
* Add proto_name and proto_ver fields for $event/client_disconnected event.
|
* Add proto_name and proto_ver fields for $event/client_disconnected event.
|
||||||
|
* Mnesia auth/acl http api support multiple condition queries.
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
* List subscription topic (/api/v4/subscriptions), the result do not match with multiple conditions.
|
* List subscription topic (/api/v4/subscriptions), the result do not match with multiple conditions.
|
||||||
|
|
|
@ -96,18 +96,24 @@
|
||||||
, delete/2
|
, delete/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-define(CLIENTID_SCHEMA, [{<<"clientid">>, binary}, {<<"_like_clientid">>, binary}] ++ ?COMMON_SCHEMA).
|
||||||
|
-define(USERNAME_SCHEMA, [{<<"username">>, binary}, {<<"_like_username">>, binary}] ++ ?COMMON_SCHEMA).
|
||||||
|
-define(COMMON_SCHEMA, [{<<"topic">>, binary}, {<<"action">>, atom}, {<<"access">>, atom}]).
|
||||||
|
|
||||||
list_clientid(_Bindings, Params) ->
|
list_clientid(_Bindings, Params) ->
|
||||||
Table = emqx_acl_mnesia_db:login_acl_table(clientid),
|
{_, Params1 = {_Qs, _Fuzzy}} = emqx_mgmt_api:params2qs(Params, ?CLIENTID_SCHEMA),
|
||||||
return({ok, emqx_auth_mnesia_api:paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}).
|
Table = emqx_acl_mnesia_db:login_acl_table(clientid, Params1),
|
||||||
|
return({ok, paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}).
|
||||||
|
|
||||||
list_username(_Bindings, Params) ->
|
list_username(_Bindings, Params) ->
|
||||||
Table = emqx_acl_mnesia_db:login_acl_table(username),
|
{_, Params1 = {_Qs, _Fuzzy}} = emqx_mgmt_api:params2qs(Params, ?USERNAME_SCHEMA),
|
||||||
return({ok, emqx_auth_mnesia_api:paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}).
|
Table = emqx_acl_mnesia_db:login_acl_table(username, Params1),
|
||||||
|
return({ok, paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}).
|
||||||
|
|
||||||
list_all(_Bindings, Params) ->
|
list_all(_Bindings, Params) ->
|
||||||
Table = emqx_acl_mnesia_db:login_acl_table(all),
|
{_, Params1 = {_Qs, _Fuzzy}} = emqx_mgmt_api:params2qs(Params, ?COMMON_SCHEMA),
|
||||||
return({ok, emqx_auth_mnesia_api:paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}).
|
Table = emqx_acl_mnesia_db:login_acl_table(all, Params1),
|
||||||
|
return({ok, paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}).
|
||||||
|
|
||||||
lookup(#{clientid := Clientid}, _Params) ->
|
lookup(#{clientid := Clientid}, _Params) ->
|
||||||
return({ok, format(emqx_acl_mnesia_db:lookup_acl({clientid, urldecode(Clientid)}))});
|
return({ok, format(emqx_acl_mnesia_db:lookup_acl({clientid, urldecode(Clientid)}))});
|
||||||
|
@ -170,7 +176,11 @@ delete(#{topic := Topic}, _) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
count(QH) ->
|
count(QH) ->
|
||||||
qlc:fold(fun(_, Count) -> Count + 1 end, 0, QH).
|
Count = qlc:fold(fun(_, Sum) -> Sum + 1 end, 0, QH),
|
||||||
|
case is_integer(Count) of
|
||||||
|
true -> Count;
|
||||||
|
false -> 0
|
||||||
|
end.
|
||||||
|
|
||||||
format({{clientid, Clientid}, Topic, Action, Access, _CreatedAt}) ->
|
format({{clientid, Clientid}, Topic, Action, Access, _CreatedAt}) ->
|
||||||
#{clientid => Clientid, topic => Topic, action => Action, access => Access};
|
#{clientid => Clientid, topic => Topic, action => Action, access => Access};
|
||||||
|
@ -222,3 +232,27 @@ format_msg(Message) when is_tuple(Message) ->
|
||||||
|
|
||||||
urldecode(S) ->
|
urldecode(S) ->
|
||||||
emqx_http_lib:uri_decode(S).
|
emqx_http_lib:uri_decode(S).
|
||||||
|
|
||||||
|
paginate_qh(Qh, Count, Params, ComparingFun, RowFun) ->
|
||||||
|
Page = page(Params),
|
||||||
|
Limit = limit(Params),
|
||||||
|
Cursor = qlc:cursor(Qh),
|
||||||
|
case Page > 1 of
|
||||||
|
true ->
|
||||||
|
_ = qlc:next_answers(Cursor, (Page - 1) * Limit),
|
||||||
|
ok;
|
||||||
|
false -> ok
|
||||||
|
end,
|
||||||
|
Rows = qlc:next_answers(Cursor, Limit),
|
||||||
|
qlc:delete_cursor(Cursor),
|
||||||
|
#{meta => #{page => Page, limit => Limit, count => Count},
|
||||||
|
data => [RowFun(Row) || Row <- lists:sort(ComparingFun, Rows)]}.
|
||||||
|
|
||||||
|
page(Params) ->
|
||||||
|
binary_to_integer(proplists:get_value(<<"_page">>, Params, <<"1">>)).
|
||||||
|
|
||||||
|
limit(Params) ->
|
||||||
|
case proplists:get_value(<<"_limit">>, Params) of
|
||||||
|
undefined -> 50;
|
||||||
|
Size -> binary_to_integer(Size)
|
||||||
|
end.
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
, remove_acl/2
|
, remove_acl/2
|
||||||
, merge_acl_records/3
|
, merge_acl_records/3
|
||||||
, login_acl_table/1
|
, login_acl_table/1
|
||||||
|
, login_acl_table/2
|
||||||
, is_migration_started/0
|
, is_migration_started/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
@ -124,7 +125,7 @@ all_acls_export() ->
|
||||||
|
|
||||||
{atomic, Records} = mnesia:transaction(
|
{atomic, Records} = mnesia:transaction(
|
||||||
fun() ->
|
fun() ->
|
||||||
QH = acl_table(MatchSpecNew, MatchSpecOld, fun mnesia:table/2, fun lookup_mnesia/2),
|
QH = acl_table(MatchSpecNew, MatchSpecOld, {#{}, #{}}, fun mnesia:table/2, fun lookup_mnesia/2),
|
||||||
qlc:eval(QH)
|
qlc:eval(QH)
|
||||||
end),
|
end),
|
||||||
Records.
|
Records.
|
||||||
|
@ -132,9 +133,15 @@ all_acls_export() ->
|
||||||
%% @doc QLC table of logins matching spec
|
%% @doc QLC table of logins matching spec
|
||||||
-spec(login_acl_table(acl_target_type()) -> qlc:query_handle()).
|
-spec(login_acl_table(acl_target_type()) -> qlc:query_handle()).
|
||||||
login_acl_table(AclTargetType) ->
|
login_acl_table(AclTargetType) ->
|
||||||
MatchSpecNew = login_match_spec_new(AclTargetType),
|
login_acl_table(AclTargetType, {[], []}).
|
||||||
MatchSpecOld = login_match_spec_old(AclTargetType),
|
|
||||||
acl_table(MatchSpecNew, MatchSpecOld, fun ets:table/2, fun lookup_ets/2).
|
login_acl_table(AclTargetType, {Qs, Fuzzy}) ->
|
||||||
|
ToMap = fun({Type, Symbol, Val}, Acc) -> Acc#{{Type, Symbol} => Val} end,
|
||||||
|
Qs1 = lists:foldl(ToMap, #{}, Qs),
|
||||||
|
Fuzzy1 = lists:foldl(ToMap, #{}, Fuzzy),
|
||||||
|
MatchSpecNew = login_match_spec_new(AclTargetType, Qs1),
|
||||||
|
MatchSpecOld = login_match_spec_old(AclTargetType, Qs1),
|
||||||
|
acl_table(MatchSpecNew, MatchSpecOld, {Qs1, Fuzzy1}, fun ets:table/2, fun lookup_ets/2).
|
||||||
|
|
||||||
%% @doc Combine old `emqx_acl` ACL records with a new `emqx_acl2` ACL record for a given login
|
%% @doc Combine old `emqx_acl` ACL records with a new `emqx_acl2` ACL record for a given login
|
||||||
-spec(merge_acl_records(acl_target(), [#?ACL_TABLE{}], [#?ACL_TABLE2{}]) -> #?ACL_TABLE2{}).
|
-spec(merge_acl_records(acl_target(), [#?ACL_TABLE{}], [#?ACL_TABLE2{}]) -> #?ACL_TABLE2{}).
|
||||||
|
@ -223,27 +230,39 @@ comparing({_, _, _, _, CreatedAt1},
|
||||||
{_, _, _, _, CreatedAt2}) ->
|
{_, _, _, _, CreatedAt2}) ->
|
||||||
CreatedAt1 >= CreatedAt2.
|
CreatedAt1 >= CreatedAt2.
|
||||||
|
|
||||||
login_match_spec_old(all) ->
|
login_match_spec_old(Type) -> login_match_spec_old(Type, #{}).
|
||||||
|
|
||||||
|
login_match_spec_old(all, _) ->
|
||||||
ets:fun2ms(fun(#?ACL_TABLE{filter = {all, _}} = Record) ->
|
ets:fun2ms(fun(#?ACL_TABLE{filter = {all, _}} = Record) ->
|
||||||
Record
|
Record
|
||||||
end);
|
end);
|
||||||
|
|
||||||
login_match_spec_old(Type) when (Type =:= username) or (Type =:= clientid) ->
|
login_match_spec_old(Type, Params) when (Type =:= username) orelse (Type =:= clientid) ->
|
||||||
ets:fun2ms(fun(#?ACL_TABLE{filter = {{RecordType, _}, _}} = Record)
|
case maps:get({Type, '=:='}, Params, undefined) of
|
||||||
when RecordType =:= Type -> Record
|
undefined ->
|
||||||
end).
|
ets:fun2ms(fun(#?ACL_TABLE{filter = {{RType, _}, _}} = Rec) when RType =:= Type -> Rec end);
|
||||||
|
Val ->
|
||||||
|
ets:fun2ms(fun(#?ACL_TABLE{filter = {{RType, RVal}, _}} = Rec)
|
||||||
|
when RType =:= Type andalso RVal =:= Val -> Rec end)
|
||||||
|
end.
|
||||||
|
|
||||||
login_match_spec_new(all) ->
|
login_match_spec_new(Type) -> login_match_spec_new(Type, #{}).
|
||||||
|
|
||||||
|
login_match_spec_new(all, _) ->
|
||||||
ets:fun2ms(fun(#?ACL_TABLE2{who = all} = Record) ->
|
ets:fun2ms(fun(#?ACL_TABLE2{who = all} = Record) ->
|
||||||
Record
|
Record
|
||||||
end);
|
end);
|
||||||
|
|
||||||
login_match_spec_new(Type) when (Type =:= username) or (Type =:= clientid) ->
|
login_match_spec_new(Type, Params) when (Type =:= username) orelse (Type =:= clientid) ->
|
||||||
ets:fun2ms(fun(#?ACL_TABLE2{who = {RecordType, _}} = Record)
|
case maps:get({Type, '=:='}, Params, undefined) of
|
||||||
when RecordType =:= Type -> Record
|
undefined ->
|
||||||
end).
|
ets:fun2ms(fun(#?ACL_TABLE2{who = {RType, _}} = Rec) when RType =:= Type -> Rec end);
|
||||||
|
Val ->
|
||||||
|
ets:fun2ms(fun(#?ACL_TABLE2{who = {RType, RVal}} = Rec)
|
||||||
|
when RType =:= Type andalso RVal =:= Val -> Rec end)
|
||||||
|
end.
|
||||||
|
|
||||||
acl_table(MatchSpecNew, MatchSpecOld, TableFun, LookupFun) ->
|
acl_table(MatchSpecNew, MatchSpecOld, Params, TableFun, LookupFun) ->
|
||||||
TraverseFun =
|
TraverseFun =
|
||||||
fun() ->
|
fun() ->
|
||||||
CursorNew =
|
CursorNew =
|
||||||
|
@ -252,7 +271,7 @@ acl_table(MatchSpecNew, MatchSpecOld, TableFun, LookupFun) ->
|
||||||
CursorOld =
|
CursorOld =
|
||||||
qlc:cursor(
|
qlc:cursor(
|
||||||
TableFun(?ACL_TABLE, [{traverse, {select, MatchSpecOld}}])),
|
TableFun(?ACL_TABLE, [{traverse, {select, MatchSpecOld}}])),
|
||||||
traverse_new(CursorNew, CursorOld, #{}, LookupFun)
|
traverse_new(CursorNew, CursorOld, Params, #{}, LookupFun)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
qlc:table(TraverseFun, []).
|
qlc:table(TraverseFun, []).
|
||||||
|
@ -265,12 +284,12 @@ acl_table(MatchSpecNew, MatchSpecOld, TableFun, LookupFun) ->
|
||||||
% After migration, number of such logins is zero, so traversing starts working in
|
% After migration, number of such logins is zero, so traversing starts working in
|
||||||
% constant memory.
|
% constant memory.
|
||||||
|
|
||||||
traverse_new(CursorNew, CursorOld, FoundKeys, LookupFun) ->
|
traverse_new(CursorNew, CursorOld, Params, FoundKeys, LookupFun) ->
|
||||||
Acls = qlc:next_answers(CursorNew, 1),
|
Acls = qlc:next_answers(CursorNew, 1),
|
||||||
case Acls of
|
case Acls of
|
||||||
[] ->
|
[] ->
|
||||||
qlc:delete_cursor(CursorNew),
|
qlc:delete_cursor(CursorNew),
|
||||||
traverse_old(CursorOld, FoundKeys);
|
traverse_old(CursorOld, Params, FoundKeys);
|
||||||
[#?ACL_TABLE2{who = Login, rules = Rules} = Acl] ->
|
[#?ACL_TABLE2{who = Login, rules = Rules} = Acl] ->
|
||||||
Keys = lists:usort([{Login, Topic} || {_, _, Topic, _} <- Rules]),
|
Keys = lists:usort([{Login, Topic} || {_, _, Topic, _} <- Rules]),
|
||||||
OldRecs = lists:flatmap(fun(Key) -> LookupFun(?ACL_TABLE, Key) end, Keys),
|
OldRecs = lists:flatmap(fun(Key) -> LookupFun(?ACL_TABLE, Key) end, Keys),
|
||||||
|
@ -281,27 +300,57 @@ traverse_new(CursorNew, CursorOld, FoundKeys, LookupFun) ->
|
||||||
OldRecs),
|
OldRecs),
|
||||||
case acl_to_list(MergedAcl) of
|
case acl_to_list(MergedAcl) of
|
||||||
[] ->
|
[] ->
|
||||||
traverse_new(CursorNew, CursorOld, NewFoundKeys, LookupFun);
|
traverse_new(CursorNew, CursorOld, Params, NewFoundKeys, LookupFun);
|
||||||
List ->
|
List ->
|
||||||
List ++ fun() -> traverse_new(CursorNew, CursorOld, NewFoundKeys, LookupFun) end
|
filter_params(List, Params) ++
|
||||||
|
fun() -> traverse_new(CursorNew, CursorOld, Params, NewFoundKeys, LookupFun) end
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
traverse_old(CursorOld, FoundKeys) ->
|
filter_params(List, {Qs, Fuzzy}) ->
|
||||||
|
case maps:size(Qs) =:= 0 andalso maps:size(Fuzzy) =:= 0 of
|
||||||
|
false ->
|
||||||
|
Topic = maps:get({topic, '=:='}, Qs, undefined),
|
||||||
|
Action = maps:get({action, '=:='}, Qs, undefined),
|
||||||
|
Access = maps:get({access, '=:='}, Qs, undefined),
|
||||||
|
lists:filter(fun({Target, Topic0, Action0, Access0, _CreatedAt}) ->
|
||||||
|
CheckList = [{Topic, Topic0}, {Action, Action0}, {Access, Access0}],
|
||||||
|
case lists:all(fun is_match/1, CheckList) of
|
||||||
|
true ->
|
||||||
|
case Target of
|
||||||
|
{Type, Login} ->
|
||||||
|
case maps:get({Type, 'like'}, Fuzzy, <<>>) of
|
||||||
|
<<>> -> true;
|
||||||
|
LikeSchema -> binary:match(Login, LikeSchema) =/= nomatch
|
||||||
|
end;
|
||||||
|
all -> true
|
||||||
|
end;
|
||||||
|
false -> false
|
||||||
|
end
|
||||||
|
end, List);
|
||||||
|
true -> List
|
||||||
|
end.
|
||||||
|
|
||||||
|
is_match({Schema, Val}) ->
|
||||||
|
Schema =:= undefined orelse Schema =:= Val.
|
||||||
|
|
||||||
|
traverse_old(CursorOld, Params, FoundKeys) ->
|
||||||
OldAcls = qlc:next_answers(CursorOld),
|
OldAcls = qlc:next_answers(CursorOld),
|
||||||
case OldAcls of
|
case OldAcls of
|
||||||
[] ->
|
[] ->
|
||||||
qlc:delete_cursor(CursorOld),
|
qlc:delete_cursor(CursorOld),
|
||||||
[];
|
[];
|
||||||
_ ->
|
_ ->
|
||||||
Records = [ {Login, Topic, Action, Access, CreatedAt}
|
Records = [{Login, Topic, Action, Access, CreatedAt}
|
||||||
|| #?ACL_TABLE{filter = {Login, Topic}, action = LegacyAction, access = Access, created_at = CreatedAt} <- OldAcls,
|
|| #?ACL_TABLE{filter = {Login, Topic}, action = LegacyAction, access = Access, created_at = CreatedAt} <- OldAcls,
|
||||||
{_, Action, _, _} <- normalize_rule({Access, LegacyAction, Topic, CreatedAt}),
|
{_, Action, _, _} <- normalize_rule({Access, LegacyAction, Topic, CreatedAt}),
|
||||||
not maps:is_key({Login, Topic}, FoundKeys)
|
not maps:is_key({Login, Topic}, FoundKeys)
|
||||||
],
|
],
|
||||||
case Records of
|
case Records of
|
||||||
[] -> traverse_old(CursorOld, FoundKeys);
|
[] -> traverse_old(CursorOld, Params, FoundKeys);
|
||||||
List -> List ++ fun() -> traverse_old(CursorOld, FoundKeys) end
|
List ->
|
||||||
|
filter_params(List, Params)
|
||||||
|
++ fun() -> traverse_old(CursorOld, Params, FoundKeys) end
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{application, emqx_auth_mnesia,
|
{application, emqx_auth_mnesia,
|
||||||
[{description, "EMQ X Authentication with Mnesia"},
|
[{description, "EMQ X Authentication with Mnesia"},
|
||||||
{vsn, "4.3.6"}, % strict semver, bump manually
|
{vsn, "4.3.7"}, % strict semver, bump manually
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [kernel,stdlib,mnesia]},
|
{applications, [kernel,stdlib,mnesia]},
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
%% Unless you know what you are doing, DO NOT edit manually!!
|
%% Unless you know what you are doing, DO NOT edit manually!!
|
||||||
{VSN,
|
{VSN,
|
||||||
[{"4.3.5",
|
[{<<"4\\.3\\.[5-6]">>,
|
||||||
[{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_auth_mnesia,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_acl_mnesia_db,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_acl_mnesia_api,brutal_purge,soft_purge,[]}]},
|
||||||
{<<"4\\.3\\.[0-3]">>,
|
{<<"4\\.3\\.[0-3]">>,
|
||||||
[{load_module,emqx_auth_mnesia_cli,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_auth_mnesia_cli,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_auth_mnesia,brutal_purge,soft_purge,[]},
|
{load_module,emqx_auth_mnesia,brutal_purge,soft_purge,[]},
|
||||||
|
@ -19,13 +20,16 @@
|
||||||
{"4.3.4",
|
{"4.3.4",
|
||||||
[{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_auth_mnesia,brutal_purge,soft_purge,[]},
|
{load_module,emqx_auth_mnesia,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_acl_mnesia_db,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_acl_mnesia_api,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_auth_mnesia_cli,brutal_purge,soft_purge,[]},
|
{load_module,emqx_auth_mnesia_cli,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_acl_mnesia,brutal_purge,soft_purge,[]},
|
{load_module,emqx_acl_mnesia,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_auth_mnesia_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_auth_mnesia_app,brutal_purge,soft_purge,[]}]},
|
||||||
{<<".*">>,[]}],
|
{<<".*">>,[]}],
|
||||||
[{"4.3.5",
|
[{<<"4\\.3\\.[5-6]">>,
|
||||||
[{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_auth_mnesia,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_acl_mnesia_db,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_acl_mnesia_api,brutal_purge,soft_purge,[]}]},
|
||||||
{<<"4\\.3\\.[0-3]">>,
|
{<<"4\\.3\\.[0-3]">>,
|
||||||
[{load_module,emqx_auth_mnesia_cli,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_auth_mnesia_cli,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_auth_mnesia,brutal_purge,soft_purge,[]},
|
{load_module,emqx_auth_mnesia,brutal_purge,soft_purge,[]},
|
||||||
|
@ -40,8 +44,10 @@
|
||||||
{delete_module,emqx_acl_mnesia_db}]},
|
{delete_module,emqx_acl_mnesia_db}]},
|
||||||
{"4.3.4",
|
{"4.3.4",
|
||||||
[{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_acl_mnesia_api,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_auth_mnesia,brutal_purge,soft_purge,[]},
|
{load_module,emqx_auth_mnesia,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_auth_mnesia_cli,brutal_purge,soft_purge,[]},
|
{load_module,emqx_auth_mnesia_cli,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_acl_mnesia_db,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_acl_mnesia,brutal_purge,soft_purge,[]},
|
{load_module,emqx_acl_mnesia,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_auth_mnesia_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_auth_mnesia_app,brutal_purge,soft_purge,[]}]},
|
||||||
{<<".*">>,[]}]}.
|
{<<".*">>,[]}]}.
|
||||||
|
|
|
@ -18,18 +18,20 @@
|
||||||
|
|
||||||
-include_lib("stdlib/include/qlc.hrl").
|
-include_lib("stdlib/include/qlc.hrl").
|
||||||
-include_lib("stdlib/include/ms_transform.hrl").
|
-include_lib("stdlib/include/ms_transform.hrl").
|
||||||
|
-include("emqx_auth_mnesia.hrl").
|
||||||
|
|
||||||
-define(TABLE, emqx_user).
|
-define(TABLE, emqx_user).
|
||||||
|
|
||||||
-import(proplists, [get_value/2]).
|
-import(proplists, [get_value/2]).
|
||||||
-import(minirest, [return/1]).
|
-import(minirest, [return/1]).
|
||||||
-export([paginate_qh/5]).
|
|
||||||
|
|
||||||
-export([ list_clientid/2
|
-export([ list_clientid/2
|
||||||
, lookup_clientid/2
|
, lookup_clientid/2
|
||||||
, add_clientid/2
|
, add_clientid/2
|
||||||
, update_clientid/2
|
, update_clientid/2
|
||||||
, delete_clientid/2
|
, delete_clientid/2
|
||||||
|
, query_clientid/3
|
||||||
|
, query_username/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-rest_api(#{name => list_clientid,
|
-rest_api(#{name => list_clientid,
|
||||||
|
@ -109,13 +111,28 @@
|
||||||
descr => "Delete username in the cluster"
|
descr => "Delete username in the cluster"
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
-define(CLIENTID_SCHEMA, {?TABLE,
|
||||||
|
[
|
||||||
|
{<<"clientid">>, binary},
|
||||||
|
{<<"_like_clientid">>, binary}
|
||||||
|
]}).
|
||||||
|
|
||||||
|
-define(USERNAME_SCHEMA, {?TABLE,
|
||||||
|
[
|
||||||
|
{<<"username">>, binary},
|
||||||
|
{<<"_like_username">>, binary}
|
||||||
|
]}).
|
||||||
|
|
||||||
|
-define(query_clientid, {?MODULE, query_clientid}).
|
||||||
|
-define(query_username, {?MODULE, query_username}).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Auth Clientid Api
|
%% Auth Clientid Api
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
list_clientid(_Bindings, Params) ->
|
list_clientid(_Bindings, Params) ->
|
||||||
MatchSpec = ets:fun2ms(fun({?TABLE, {clientid, Clientid}, Password, CreatedAt}) -> {?TABLE, {clientid, Clientid}, Password, CreatedAt} end),
|
SortFun = fun(#{created_at := C1}, #{created_at := C2}) -> C1 > C2 end,
|
||||||
return({ok, paginate(?TABLE, MatchSpec, Params, fun emqx_auth_mnesia_cli:comparing/2, fun({?TABLE, {clientid, X}, _, _}) -> #{clientid => X} end)}).
|
return({ok, emqx_mgmt_api:node_query(node(), Params, ?CLIENTID_SCHEMA, ?query_clientid, SortFun)}).
|
||||||
|
|
||||||
lookup_clientid(#{clientid := Clientid}, _Params) ->
|
lookup_clientid(#{clientid := Clientid}, _Params) ->
|
||||||
return({ok, format(emqx_auth_mnesia_cli:lookup_user({clientid, urldecode(Clientid)}))}).
|
return({ok, format(emqx_auth_mnesia_cli:lookup_user({clientid, urldecode(Clientid)}))}).
|
||||||
|
@ -164,8 +181,8 @@ delete_clientid(#{clientid := Clientid}, _) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
list_username(_Bindings, Params) ->
|
list_username(_Bindings, Params) ->
|
||||||
MatchSpec = ets:fun2ms(fun({?TABLE, {username, Username}, Password, CreatedAt}) -> {?TABLE, {username, Username}, Password, CreatedAt} end),
|
SortFun = fun(#{created_at := C1}, #{created_at := C2}) -> C1 > C2 end,
|
||||||
return({ok, paginate(?TABLE, MatchSpec, Params, fun emqx_auth_mnesia_cli:comparing/2, fun({?TABLE, {username, X}, _, _}) -> #{username => X} end)}).
|
return({ok, emqx_mgmt_api:node_query(node(), Params, ?USERNAME_SCHEMA, ?query_username, SortFun)}).
|
||||||
|
|
||||||
lookup_username(#{username := Username}, _Params) ->
|
lookup_username(#{username := Username}, _Params) ->
|
||||||
return({ok, format(emqx_auth_mnesia_cli:lookup_user({username, urldecode(Username)}))}).
|
return({ok, format(emqx_auth_mnesia_cli:lookup_user({username, urldecode(Username)}))}).
|
||||||
|
@ -211,57 +228,52 @@ delete_username(#{username := Username}, _) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Paging Query
|
%% Paging Query
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
query_clientid(Qs, Start, Limit) -> query(clientid, Qs, Start, Limit).
|
||||||
|
query_username(Qs, Start, Limit) -> query(username, Qs, Start, Limit).
|
||||||
|
|
||||||
paginate(Table, MatchSpec, Params, ComparingFun, RowFun) ->
|
query(Type, {Qs, []}, Start, Limit) ->
|
||||||
Qh = query_handle(Table, MatchSpec),
|
Ms = qs2ms(Type, Qs),
|
||||||
Count = count(Table, MatchSpec),
|
emqx_mgmt_api:select_table(?TABLE, Ms, Start, Limit, fun format/1);
|
||||||
paginate_qh(Qh, Count, Params, ComparingFun, RowFun).
|
|
||||||
|
|
||||||
paginate_qh(Qh, Count, Params, ComparingFun, RowFun) ->
|
query(Type, {Qs, Fuzzy}, Start, Limit) ->
|
||||||
Page = page(Params),
|
Ms = qs2ms(Type, Qs),
|
||||||
Limit = limit(Params),
|
MatchFun = match_fun(Ms, Fuzzy),
|
||||||
Cursor = qlc:cursor(Qh),
|
emqx_mgmt_api:traverse_table(?TABLE, MatchFun, Start, Limit, fun format/1).
|
||||||
case Page > 1 of
|
|
||||||
true ->
|
|
||||||
_ = qlc:next_answers(Cursor, (Page - 1) * Limit),
|
|
||||||
ok;
|
|
||||||
false -> ok
|
|
||||||
end,
|
|
||||||
Rows = qlc:next_answers(Cursor, Limit),
|
|
||||||
qlc:delete_cursor(Cursor),
|
|
||||||
#{meta => #{page => Page, limit => Limit, count => Count},
|
|
||||||
data => [RowFun(Row) || Row <- lists:sort(ComparingFun, Rows)]}.
|
|
||||||
|
|
||||||
query_handle(Table, MatchSpec) when is_atom(Table) ->
|
-spec qs2ms(clientid | username, list()) -> ets:match_spec().
|
||||||
Options = {traverse, {select, MatchSpec}},
|
qs2ms(Type, Qs) ->
|
||||||
qlc:q([R || R <- ets:table(Table, Options)]).
|
Init = #?TABLE{login = {Type, '_'}, password = '_', created_at = '_'},
|
||||||
|
MatchHead = lists:foldl(fun(Q, Acc) -> match_ms(Q, Acc) end, Init, Qs),
|
||||||
|
[{MatchHead, [], ['$_']}].
|
||||||
|
|
||||||
count(Table, MatchSpec) when is_atom(Table) ->
|
match_ms({Type, '=:=', Value}, MatchHead) -> MatchHead#?TABLE{login = {Type, Value}};
|
||||||
[{MatchPattern, Where, _Re}] = MatchSpec,
|
match_ms(_, MatchHead) -> MatchHead.
|
||||||
NMatchSpec = [{MatchPattern, Where, [true]}],
|
|
||||||
ets:select_count(Table, NMatchSpec).
|
|
||||||
|
|
||||||
page(Params) ->
|
match_fun(Ms, Fuzzy) ->
|
||||||
binary_to_integer(proplists:get_value(<<"_page">>, Params, <<"1">>)).
|
MsC = ets:match_spec_compile(Ms),
|
||||||
|
fun(Rows) ->
|
||||||
limit(Params) ->
|
Ls = ets:match_spec_run(Rows, MsC),
|
||||||
case proplists:get_value(<<"_limit">>, Params) of
|
lists:filter(fun(E) -> run_fuzzy_match(E, Fuzzy) end, Ls)
|
||||||
undefined -> 10;
|
|
||||||
Size -> binary_to_integer(Size)
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
run_fuzzy_match(_, []) -> true;
|
||||||
|
run_fuzzy_match(E = #?TABLE{login = {Key, Str}}, [{Key, like, SubStr}|Fuzzy]) ->
|
||||||
|
binary:match(Str, SubStr) /= nomatch andalso run_fuzzy_match(E, Fuzzy);
|
||||||
|
run_fuzzy_match(_E, [{_Key, like, _SubStr}| _Fuzzy]) -> false.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Interval Funcs
|
%% Interval Funcs
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
format([{?TABLE, {clientid, ClientId}, _Password, _InterTime}]) ->
|
format([{?TABLE, {clientid, ClientId}, _Password, CreatedAt}]) ->
|
||||||
#{clientid => ClientId};
|
#{clientid => ClientId, created_at => CreatedAt};
|
||||||
|
|
||||||
format([{?TABLE, {username, Username}, _Password, _InterTime}]) ->
|
format([{?TABLE, {username, Username}, _Password, CreatedAt}]) ->
|
||||||
#{username => Username};
|
#{username => Username, created_at => CreatedAt};
|
||||||
|
|
||||||
format([]) ->
|
format([]) ->
|
||||||
#{}.
|
#{};
|
||||||
|
format(User) -> format([User]).
|
||||||
|
|
||||||
validate([], []) ->
|
validate([], []) ->
|
||||||
ok;
|
ok;
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
|
||||||
-import(emqx_ct_http, [ request_api/3
|
-import(emqx_ct_http, [ request_api/3
|
||||||
|
, request_api/4
|
||||||
, request_api/5
|
, request_api/5
|
||||||
, get_http_data/1
|
, get_http_data/1
|
||||||
, create_default_app/0
|
, create_default_app/0
|
||||||
|
@ -357,13 +358,31 @@ t_rest_api(_Config) ->
|
||||||
<<"topic">> => <<"topic/C">>,
|
<<"topic">> => <<"topic/C">>,
|
||||||
<<"action">> => <<"pubsub">>,
|
<<"action">> => <<"pubsub">>,
|
||||||
<<"access">> => <<"deny">>
|
<<"access">> => <<"deny">>
|
||||||
|
},
|
||||||
|
#{<<"clientid">> => <<"good_clientid1">>,
|
||||||
|
<<"topic">> => <<"topic/D">>,
|
||||||
|
<<"action">> => <<"pubsub">>,
|
||||||
|
<<"access">> => <<"deny">>
|
||||||
}],
|
}],
|
||||||
{ok, _} = request_http_rest_add([], Params1),
|
{ok, _} = request_http_rest_add([], Params1),
|
||||||
|
|
||||||
{ok, Re1} = request_http_rest_list(["clientid", "test_clientid"]),
|
{ok, Re1} = request_http_rest_list(["clientid", "test_clientid"]),
|
||||||
?assertMatch(4, length(get_http_data(Re1))),
|
?assertMatch(4, length(get_http_data(Re1))),
|
||||||
|
{ok, Re11} = request_http_rest_list(["clientid"], "_like_clientid=good"),
|
||||||
|
?assertMatch(2, length(get_http_data(Re11))),
|
||||||
|
{ok, Re12} = request_http_rest_list(["clientid"], "_like_clientid=clientid"),
|
||||||
|
?assertMatch(6, length(get_http_data(Re12))),
|
||||||
|
{ok, Re13} = request_http_rest_list(["clientid"], "_like_clientid=clientid&action=pub"),
|
||||||
|
?assertMatch(3, length(get_http_data(Re13))),
|
||||||
|
{ok, Re14} = request_http_rest_list(["clientid"], "_like_clientid=clientid&access=deny"),
|
||||||
|
?assertMatch(4, length(get_http_data(Re14))),
|
||||||
|
{ok, Re15} = request_http_rest_list(["clientid"], "_like_clientid=clientid&topic=topic/A"),
|
||||||
|
?assertMatch(1, length(get_http_data(Re15))),
|
||||||
|
|
||||||
{ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/A"]),
|
{ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/A"]),
|
||||||
{ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/B"]),
|
{ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/B"]),
|
||||||
{ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/C"]),
|
{ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/C"]),
|
||||||
|
{ok, _} = request_http_rest_delete(["clientid", "good_clientid1", "topic", "topic/D"]),
|
||||||
{ok, Res1} = request_http_rest_list(["clientid"]),
|
{ok, Res1} = request_http_rest_list(["clientid"]),
|
||||||
?assertMatch([], get_http_data(Res1)),
|
?assertMatch([], get_http_data(Res1)),
|
||||||
|
|
||||||
|
@ -381,13 +400,30 @@ t_rest_api(_Config) ->
|
||||||
<<"topic">> => <<"topic/C">>,
|
<<"topic">> => <<"topic/C">>,
|
||||||
<<"action">> => <<"pubsub">>,
|
<<"action">> => <<"pubsub">>,
|
||||||
<<"access">> => <<"deny">>
|
<<"access">> => <<"deny">>
|
||||||
|
},
|
||||||
|
#{<<"username">> => <<"good_username">>,
|
||||||
|
<<"topic">> => <<"topic/D">>,
|
||||||
|
<<"action">> => <<"pubsub">>,
|
||||||
|
<<"access">> => <<"deny">>
|
||||||
}],
|
}],
|
||||||
{ok, _} = request_http_rest_add([], Params2),
|
{ok, _} = request_http_rest_add([], Params2),
|
||||||
{ok, Re2} = request_http_rest_list(["username", "test_username"]),
|
{ok, Re2} = request_http_rest_list(["username", "test_username"]),
|
||||||
?assertMatch(4, length(get_http_data(Re2))),
|
?assertMatch(4, length(get_http_data(Re2))),
|
||||||
|
{ok, Re21} = request_http_rest_list(["username"], "_like_username=good"),
|
||||||
|
?assertMatch(2, length(get_http_data(Re21))),
|
||||||
|
{ok, Re22} = request_http_rest_list(["username"], "_like_username=username"),
|
||||||
|
?assertMatch(6, length(get_http_data(Re22))),
|
||||||
|
{ok, Re23} = request_http_rest_list(["username"], "_like_username=username&action=pub"),
|
||||||
|
?assertMatch(3, length(get_http_data(Re23))),
|
||||||
|
{ok, Re24} = request_http_rest_list(["username"], "_like_username=username&access=deny"),
|
||||||
|
?assertMatch(4, length(get_http_data(Re24))),
|
||||||
|
{ok, Re25} = request_http_rest_list(["username"], "_like_username=username&topic=topic/A"),
|
||||||
|
?assertMatch(1, length(get_http_data(Re25))),
|
||||||
|
|
||||||
{ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/A"]),
|
{ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/A"]),
|
||||||
{ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/B"]),
|
{ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/B"]),
|
||||||
{ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/C"]),
|
{ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/C"]),
|
||||||
|
{ok, _} = request_http_rest_delete(["username", "good_username", "topic", "topic/D"]),
|
||||||
{ok, Res2} = request_http_rest_list(["username"]),
|
{ok, Res2} = request_http_rest_list(["username"]),
|
||||||
?assertMatch([], get_http_data(Res2)),
|
?assertMatch([], get_http_data(Res2)),
|
||||||
|
|
||||||
|
@ -402,13 +438,29 @@ t_rest_api(_Config) ->
|
||||||
#{<<"topic">> => <<"topic/C">>,
|
#{<<"topic">> => <<"topic/C">>,
|
||||||
<<"action">> => <<"pubsub">>,
|
<<"action">> => <<"pubsub">>,
|
||||||
<<"access">> => <<"deny">>
|
<<"access">> => <<"deny">>
|
||||||
}],
|
},
|
||||||
|
#{<<"topic">> => <<"topic/D">>,
|
||||||
|
<<"action">> => <<"pubsub">>,
|
||||||
|
<<"access">> => <<"deny">>
|
||||||
|
}
|
||||||
|
],
|
||||||
{ok, _} = request_http_rest_add([], Params3),
|
{ok, _} = request_http_rest_add([], Params3),
|
||||||
|
|
||||||
{ok, Re3} = request_http_rest_list(["$all"]),
|
{ok, Re3} = request_http_rest_list(["$all"]),
|
||||||
?assertMatch(4, length(get_http_data(Re3))),
|
?assertMatch(6, length(get_http_data(Re3))),
|
||||||
|
{ok, Re31} = request_http_rest_list(["$all"], "topic=topic/A"),
|
||||||
|
?assertMatch(1, length(get_http_data(Re31))),
|
||||||
|
{ok, Re32} = request_http_rest_list(["$all"], "action=sub"),
|
||||||
|
?assertMatch(3, length(get_http_data(Re32))),
|
||||||
|
{ok, Re33} = request_http_rest_list(["$all"], "access=deny"),
|
||||||
|
?assertMatch(4, length(get_http_data(Re33))),
|
||||||
|
{ok, Re34} = request_http_rest_list(["$all"], "action=sub&access=deny"),
|
||||||
|
?assertMatch(2, length(get_http_data(Re34))),
|
||||||
|
|
||||||
{ok, _} = request_http_rest_delete(["$all", "topic", "topic/A"]),
|
{ok, _} = request_http_rest_delete(["$all", "topic", "topic/A"]),
|
||||||
{ok, _} = request_http_rest_delete(["$all", "topic", "topic/B"]),
|
{ok, _} = request_http_rest_delete(["$all", "topic", "topic/B"]),
|
||||||
{ok, _} = request_http_rest_delete(["$all", "topic", "topic/C"]),
|
{ok, _} = request_http_rest_delete(["$all", "topic", "topic/C"]),
|
||||||
|
{ok, _} = request_http_rest_delete(["$all", "topic", "topic/D"]),
|
||||||
{ok, Res3} = request_http_rest_list(["$all"]),
|
{ok, Res3} = request_http_rest_list(["$all"]),
|
||||||
?assertMatch([], get_http_data(Res3)).
|
?assertMatch([], get_http_data(Res3)).
|
||||||
|
|
||||||
|
@ -442,6 +494,9 @@ combined_conflicting_records() ->
|
||||||
request_http_rest_list(Path) ->
|
request_http_rest_list(Path) ->
|
||||||
request_api(get, uri(Path), default_auth_header()).
|
request_api(get, uri(Path), default_auth_header()).
|
||||||
|
|
||||||
|
request_http_rest_list(Path, Qs) ->
|
||||||
|
request_api(get, uri(Path), Qs, default_auth_header()).
|
||||||
|
|
||||||
request_http_rest_lookup(Path) ->
|
request_http_rest_lookup(Path) ->
|
||||||
request_api(get, uri(Path), default_auth_header()).
|
request_api(get, uri(Path), default_auth_header()).
|
||||||
|
|
||||||
|
|
|
@ -286,20 +286,26 @@ t_clientid_rest_api(_Config) ->
|
||||||
|
|
||||||
Params3 = [ #{<<"clientid">> => ?CLIENTID, <<"password">> => ?PASSWORD}
|
Params3 = [ #{<<"clientid">> => ?CLIENTID, <<"password">> => ?PASSWORD}
|
||||||
, #{<<"clientid">> => <<"clientid1">>, <<"password">> => ?PASSWORD}
|
, #{<<"clientid">> => <<"clientid1">>, <<"password">> => ?PASSWORD}
|
||||||
, #{<<"clientid">> => <<"clientid2">>, <<"password">> => ?PASSWORD}
|
, #{<<"clientid">> => <<"client2">>, <<"password">> => ?PASSWORD}
|
||||||
],
|
],
|
||||||
{ok, Result3} = request_http_rest_add(["auth_clientid"], Params3),
|
{ok, Result3} = request_http_rest_add(["auth_clientid"], Params3),
|
||||||
?assertMatch(#{ ?CLIENTID := <<"{error,existed}">>
|
?assertMatch(#{ ?CLIENTID := <<"{error,existed}">>
|
||||||
, <<"clientid1">> := <<"ok">>
|
, <<"clientid1">> := <<"ok">>
|
||||||
, <<"clientid2">> := <<"ok">>
|
, <<"client2">> := <<"ok">>
|
||||||
}, get_http_data(Result3)),
|
}, get_http_data(Result3)),
|
||||||
|
|
||||||
{ok, Result4} = request_http_rest_list(["auth_clientid"]),
|
{ok, Result4} = request_http_rest_list(["auth_clientid"]),
|
||||||
|
|
||||||
?assertEqual(3, length(get_http_data(Result4))),
|
?assertEqual(3, length(get_http_data(Result4))),
|
||||||
|
|
||||||
|
{ok, Result5} = request_http_rest_list(["auth_clientid?_like_clientid=id"]),
|
||||||
|
?assertEqual(2, length(get_http_data(Result5))),
|
||||||
|
{ok, Result6} = request_http_rest_list(["auth_clientid?_like_clientid=x"]),
|
||||||
|
?assertEqual(0, length(get_http_data(Result6))),
|
||||||
|
|
||||||
{ok, _} = request_http_rest_delete(Path),
|
{ok, _} = request_http_rest_delete(Path),
|
||||||
{ok, Result5} = request_http_rest_lookup(Path),
|
{ok, Result7} = request_http_rest_lookup(Path),
|
||||||
?assertMatch(#{}, get_http_data(Result5)).
|
?assertMatch(#{}, get_http_data(Result7)).
|
||||||
|
|
||||||
t_username_rest_api(_Config) ->
|
t_username_rest_api(_Config) ->
|
||||||
clean_all_users(),
|
clean_all_users(),
|
||||||
|
@ -330,9 +336,14 @@ t_username_rest_api(_Config) ->
|
||||||
{ok, Result4} = request_http_rest_list(["auth_username"]),
|
{ok, Result4} = request_http_rest_list(["auth_username"]),
|
||||||
?assertEqual(3, length(get_http_data(Result4))),
|
?assertEqual(3, length(get_http_data(Result4))),
|
||||||
|
|
||||||
|
{ok, Result5} = request_http_rest_list(["auth_username?_like_username=for"]),
|
||||||
|
?assertEqual(1, length(get_http_data(Result5))),
|
||||||
|
{ok, Result6} = request_http_rest_list(["auth_username?_like_username=x"]),
|
||||||
|
?assertEqual(0, length(get_http_data(Result6))),
|
||||||
|
|
||||||
{ok, _} = request_http_rest_delete(Path),
|
{ok, _} = request_http_rest_delete(Path),
|
||||||
{ok, Result5} = request_http_rest_lookup([Path]),
|
{ok, Result7} = request_http_rest_lookup([Path]),
|
||||||
?assertMatch(#{}, get_http_data(Result5)).
|
?assertMatch(#{}, get_http_data(Result7)).
|
||||||
|
|
||||||
t_password_hash(_) ->
|
t_password_hash(_) ->
|
||||||
clean_all_users(),
|
clean_all_users(),
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
%% first_next query APIs
|
%% first_next query APIs
|
||||||
-export([ params2qs/2
|
-export([ params2qs/2
|
||||||
, node_query/4
|
, node_query/4
|
||||||
|
, node_query/5
|
||||||
, cluster_query/3
|
, cluster_query/3
|
||||||
, traverse_table/5
|
, traverse_table/5
|
||||||
, select_table/5
|
, select_table/5
|
||||||
|
@ -82,6 +83,9 @@ limit(Params) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
node_query(Node, Params, {Tab, QsSchema}, QueryFun) ->
|
node_query(Node, Params, {Tab, QsSchema}, QueryFun) ->
|
||||||
|
node_query(Node, Params, {Tab, QsSchema}, QueryFun, undefined).
|
||||||
|
|
||||||
|
node_query(Node, Params, {Tab, QsSchema}, QueryFun, SortFun) ->
|
||||||
{CodCnt, Qs} = params2qs(Params, QsSchema),
|
{CodCnt, Qs} = params2qs(Params, QsSchema),
|
||||||
Limit = limit(Params),
|
Limit = limit(Params),
|
||||||
Page = page(Params),
|
Page = page(Params),
|
||||||
|
@ -94,7 +98,11 @@ node_query(Node, Params, {Tab, QsSchema}, QueryFun) ->
|
||||||
true -> Meta#{count => count(Tab), hasnext => length(Rows) > Limit};
|
true -> Meta#{count => count(Tab), hasnext => length(Rows) > Limit};
|
||||||
_ -> Meta#{count => -1, hasnext => length(Rows) > Limit}
|
_ -> Meta#{count => -1, hasnext => length(Rows) > Limit}
|
||||||
end,
|
end,
|
||||||
#{meta => NMeta, data => lists:sublist(Rows, Limit)}.
|
NRows = case SortFun of
|
||||||
|
undefined -> Rows;
|
||||||
|
_ -> lists:sort(SortFun, Rows)
|
||||||
|
end,
|
||||||
|
#{meta => NMeta, data => lists:sublist(NRows, Limit)}.
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
do_query(Node, Qs, {M,F}, Start, Limit) when Node =:= node() ->
|
do_query(Node, Qs, {M,F}, Start, Limit) when Node =:= node() ->
|
||||||
|
|
Loading…
Reference in New Issue