feat: mnesia auth/acl support multiple condition queries

This commit is contained in:
zhongwencool 2022-05-17 11:19:29 +08:00
parent 810ca65011
commit 73faf08059
6 changed files with 252 additions and 83 deletions

View File

@ -96,18 +96,24 @@
, 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) ->
Table = emqx_acl_mnesia_db:login_acl_table(clientid),
return({ok, emqx_auth_mnesia_api:paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}).
{_, Params1 = {_Qs, _Fuzzy}} = emqx_mgmt_api:params2qs(Params, ?CLIENTID_SCHEMA),
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) ->
Table = emqx_acl_mnesia_db:login_acl_table(username),
return({ok, emqx_auth_mnesia_api:paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}).
{_, Params1 = {_Qs, _Fuzzy}} = emqx_mgmt_api:params2qs(Params, ?USERNAME_SCHEMA),
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) ->
Table = emqx_acl_mnesia_db:login_acl_table(all),
return({ok, emqx_auth_mnesia_api:paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}).
{_, Params1 = {_Qs, _Fuzzy}} = emqx_mgmt_api:params2qs(Params, ?COMMON_SCHEMA),
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) ->
return({ok, format(emqx_acl_mnesia_db:lookup_acl({clientid, urldecode(Clientid)}))});
@ -170,7 +176,11 @@ delete(#{topic := Topic}, _) ->
%%------------------------------------------------------------------------------
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}) ->
#{clientid => Clientid, topic => Topic, action => Action, access => Access};
@ -222,3 +232,27 @@ format_msg(Message) when is_tuple(Message) ->
urldecode(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.

View File

@ -33,6 +33,7 @@
, remove_acl/2
, merge_acl_records/3
, login_acl_table/1
, login_acl_table/2
, is_migration_started/0
]).
@ -124,7 +125,7 @@ all_acls_export() ->
{atomic, Records} = mnesia:transaction(
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)
end),
Records.
@ -132,9 +133,15 @@ all_acls_export() ->
%% @doc QLC table of logins matching spec
-spec(login_acl_table(acl_target_type()) -> qlc:query_handle()).
login_acl_table(AclTargetType) ->
MatchSpecNew = login_match_spec_new(AclTargetType),
MatchSpecOld = login_match_spec_old(AclTargetType),
acl_table(MatchSpecNew, MatchSpecOld, fun ets:table/2, fun lookup_ets/2).
login_acl_table(AclTargetType, {[], []}).
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
-spec(merge_acl_records(acl_target(), [#?ACL_TABLE{}], [#?ACL_TABLE2{}]) -> #?ACL_TABLE2{}).
@ -223,27 +230,39 @@ comparing({_, _, _, _, 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) ->
Record
end);
login_match_spec_old(Type) when (Type =:= username) or (Type =:= clientid) ->
ets:fun2ms(fun(#?ACL_TABLE{filter = {{RecordType, _}, _}} = Record)
when RecordType =:= Type -> Record
end).
login_match_spec_old(Type, Params) when (Type =:= username) orelse (Type =:= clientid) ->
case maps:get({Type, '=:='}, Params, undefined) of
undefined ->
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) ->
Record
end);
login_match_spec_new(Type) when (Type =:= username) or (Type =:= clientid) ->
ets:fun2ms(fun(#?ACL_TABLE2{who = {RecordType, _}} = Record)
when RecordType =:= Type -> Record
end).
login_match_spec_new(Type, Params) when (Type =:= username) orelse (Type =:= clientid) ->
case maps:get({Type, '=:='}, Params, undefined) of
undefined ->
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 =
fun() ->
CursorNew =
@ -252,7 +271,7 @@ acl_table(MatchSpecNew, MatchSpecOld, TableFun, LookupFun) ->
CursorOld =
qlc:cursor(
TableFun(?ACL_TABLE, [{traverse, {select, MatchSpecOld}}])),
traverse_new(CursorNew, CursorOld, #{}, LookupFun)
traverse_new(CursorNew, CursorOld, Params, #{}, LookupFun)
end,
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
% constant memory.
traverse_new(CursorNew, CursorOld, FoundKeys, LookupFun) ->
traverse_new(CursorNew, CursorOld, Params, FoundKeys, LookupFun) ->
Acls = qlc:next_answers(CursorNew, 1),
case Acls of
[] ->
qlc:delete_cursor(CursorNew),
traverse_old(CursorOld, FoundKeys);
traverse_old(CursorOld, Params, FoundKeys);
[#?ACL_TABLE2{who = Login, rules = Rules} = Acl] ->
Keys = lists:usort([{Login, Topic} || {_, _, Topic, _} <- Rules]),
OldRecs = lists:flatmap(fun(Key) -> LookupFun(?ACL_TABLE, Key) end, Keys),
@ -281,13 +300,41 @@ traverse_new(CursorNew, CursorOld, FoundKeys, LookupFun) ->
OldRecs),
case acl_to_list(MergedAcl) of
[] ->
traverse_new(CursorNew, CursorOld, NewFoundKeys, LookupFun);
traverse_new(CursorNew, CursorOld, Params, NewFoundKeys, LookupFun);
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.
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),
case OldAcls of
[] ->
@ -300,8 +347,10 @@ traverse_old(CursorOld, FoundKeys) ->
not maps:is_key({Login, Topic}, FoundKeys)
],
case Records of
[] -> traverse_old(CursorOld, FoundKeys);
List -> List ++ fun() -> traverse_old(CursorOld, FoundKeys) end
[] -> traverse_old(CursorOld, Params, FoundKeys);
List ->
filter_params(List, Params)
++ fun() -> traverse_old(CursorOld, Params, FoundKeys) end
end
end.

View File

@ -18,18 +18,20 @@
-include_lib("stdlib/include/qlc.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include("emqx_auth_mnesia.hrl").
-define(TABLE, emqx_user).
-import(proplists, [get_value/2]).
-import(minirest, [return/1]).
-export([paginate_qh/5]).
-export([ list_clientid/2
, lookup_clientid/2
, add_clientid/2
, update_clientid/2
, delete_clientid/2
, query_clientid/3
, query_username/3
]).
-rest_api(#{name => list_clientid,
@ -109,13 +111,28 @@
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
%%------------------------------------------------------------------------------
list_clientid(_Bindings, Params) ->
MatchSpec = ets:fun2ms(fun({?TABLE, {clientid, Clientid}, Password, CreatedAt}) -> {?TABLE, {clientid, Clientid}, Password, CreatedAt} end),
return({ok, paginate(?TABLE, MatchSpec, Params, fun emqx_auth_mnesia_cli:comparing/2, fun({?TABLE, {clientid, X}, _, _}) -> #{clientid => X} end)}).
SortFun = fun(#{created_at := C1}, #{created_at := C2}) -> C1 > C2 end,
return({ok, emqx_mgmt_api:node_query(node(), Params, ?CLIENTID_SCHEMA, ?query_clientid, SortFun)}).
lookup_clientid(#{clientid := Clientid}, _Params) ->
return({ok, format(emqx_auth_mnesia_cli:lookup_user({clientid, urldecode(Clientid)}))}).
@ -164,8 +181,8 @@ delete_clientid(#{clientid := Clientid}, _) ->
%%------------------------------------------------------------------------------
list_username(_Bindings, Params) ->
MatchSpec = ets:fun2ms(fun({?TABLE, {username, Username}, Password, CreatedAt}) -> {?TABLE, {username, Username}, Password, CreatedAt} end),
return({ok, paginate(?TABLE, MatchSpec, Params, fun emqx_auth_mnesia_cli:comparing/2, fun({?TABLE, {username, X}, _, _}) -> #{username => X} end)}).
SortFun = fun(#{created_at := C1}, #{created_at := C2}) -> C1 > C2 end,
return({ok, emqx_mgmt_api:node_query(node(), Params, ?USERNAME_SCHEMA, ?query_username, SortFun)}).
lookup_username(#{username := Username}, _Params) ->
return({ok, format(emqx_auth_mnesia_cli:lookup_user({username, urldecode(Username)}))}).
@ -211,57 +228,52 @@ delete_username(#{username := Username}, _) ->
%%------------------------------------------------------------------------------
%% 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) ->
Qh = query_handle(Table, MatchSpec),
Count = count(Table, MatchSpec),
paginate_qh(Qh, Count, Params, ComparingFun, RowFun).
query(Type, {Qs, []}, Start, Limit) ->
Ms = qs2ms(Type, Qs),
emqx_mgmt_api:select_table(?TABLE, Ms, Start, Limit, fun format/1);
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)]}.
query(Type, {Qs, Fuzzy}, Start, Limit) ->
Ms = qs2ms(Type, Qs),
MatchFun = match_fun(Ms, Fuzzy),
emqx_mgmt_api:traverse_table(?TABLE, MatchFun, Start, Limit, fun format/1).
query_handle(Table, MatchSpec) when is_atom(Table) ->
Options = {traverse, {select, MatchSpec}},
qlc:q([R || R <- ets:table(Table, Options)]).
-spec qs2ms(clientid | username, list()) -> ets:match_spec().
qs2ms(Type, Qs) ->
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) ->
[{MatchPattern, Where, _Re}] = MatchSpec,
NMatchSpec = [{MatchPattern, Where, [true]}],
ets:select_count(Table, NMatchSpec).
match_ms({Type, '=:=', Value}, MatchHead) -> MatchHead#?TABLE{login = {Type, Value}};
match_ms(_, MatchHead) -> MatchHead.
page(Params) ->
binary_to_integer(proplists:get_value(<<"_page">>, Params, <<"1">>)).
limit(Params) ->
case proplists:get_value(<<"_limit">>, Params) of
undefined -> 10;
Size -> binary_to_integer(Size)
match_fun(Ms, Fuzzy) ->
MsC = ets:match_spec_compile(Ms),
fun(Rows) ->
Ls = ets:match_spec_run(Rows, MsC),
lists:filter(fun(E) -> run_fuzzy_match(E, Fuzzy) end, Ls)
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
%%------------------------------------------------------------------------------
format([{?TABLE, {clientid, ClientId}, _Password, _InterTime}]) ->
#{clientid => ClientId};
format([{?TABLE, {clientid, ClientId}, _Password, CreatedAt}]) ->
#{clientid => ClientId, created_at => CreatedAt};
format([{?TABLE, {username, Username}, _Password, _InterTime}]) ->
#{username => Username};
format([{?TABLE, {username, Username}, _Password, CreatedAt}]) ->
#{username => Username, created_at => CreatedAt};
format([]) ->
#{}.
#{};
format(User) -> format([User]).
validate([], []) ->
ok;

View File

@ -25,6 +25,7 @@
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
-import(emqx_ct_http, [ request_api/3
, request_api/4
, request_api/5
, get_http_data/1
, create_default_app/0
@ -357,13 +358,31 @@ t_rest_api(_Config) ->
<<"topic">> => <<"topic/C">>,
<<"action">> => <<"pubsub">>,
<<"access">> => <<"deny">>
},
#{<<"clientid">> => <<"good_clientid1">>,
<<"topic">> => <<"topic/D">>,
<<"action">> => <<"pubsub">>,
<<"access">> => <<"deny">>
}],
{ok, _} = request_http_rest_add([], Params1),
{ok, Re1} = request_http_rest_list(["clientid", "test_clientid"]),
?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/B"]),
{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"]),
?assertMatch([], get_http_data(Res1)),
@ -381,13 +400,30 @@ t_rest_api(_Config) ->
<<"topic">> => <<"topic/C">>,
<<"action">> => <<"pubsub">>,
<<"access">> => <<"deny">>
},
#{<<"username">> => <<"good_username">>,
<<"topic">> => <<"topic/D">>,
<<"action">> => <<"pubsub">>,
<<"access">> => <<"deny">>
}],
{ok, _} = request_http_rest_add([], Params2),
{ok, Re2} = request_http_rest_list(["username", "test_username"]),
?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/B"]),
{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"]),
?assertMatch([], get_http_data(Res2)),
@ -402,13 +438,29 @@ t_rest_api(_Config) ->
#{<<"topic">> => <<"topic/C">>,
<<"action">> => <<"pubsub">>,
<<"access">> => <<"deny">>
}],
},
#{<<"topic">> => <<"topic/D">>,
<<"action">> => <<"pubsub">>,
<<"access">> => <<"deny">>
}
],
{ok, _} = request_http_rest_add([], Params3),
{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/B"]),
{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"]),
?assertMatch([], get_http_data(Res3)).
@ -442,6 +494,9 @@ combined_conflicting_records() ->
request_http_rest_list(Path) ->
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_api(get, uri(Path), default_auth_header()).

View File

@ -286,20 +286,26 @@ t_clientid_rest_api(_Config) ->
Params3 = [ #{<<"clientid">> => ?CLIENTID, <<"password">> => ?PASSWORD}
, #{<<"clientid">> => <<"clientid1">>, <<"password">> => ?PASSWORD}
, #{<<"clientid">> => <<"clientid2">>, <<"password">> => ?PASSWORD}
, #{<<"clientid">> => <<"client2">>, <<"password">> => ?PASSWORD}
],
{ok, Result3} = request_http_rest_add(["auth_clientid"], Params3),
?assertMatch(#{ ?CLIENTID := <<"{error,existed}">>
, <<"clientid1">> := <<"ok">>
, <<"clientid2">> := <<"ok">>
, <<"client2">> := <<"ok">>
}, get_http_data(Result3)),
{ok, Result4} = request_http_rest_list(["auth_clientid"]),
?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, Result5} = request_http_rest_lookup(Path),
?assertMatch(#{}, get_http_data(Result5)).
{ok, Result7} = request_http_rest_lookup(Path),
?assertMatch(#{}, get_http_data(Result7)).
t_username_rest_api(_Config) ->
clean_all_users(),
@ -330,9 +336,14 @@ t_username_rest_api(_Config) ->
{ok, Result4} = request_http_rest_list(["auth_username"]),
?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, Result5} = request_http_rest_lookup([Path]),
?assertMatch(#{}, get_http_data(Result5)).
{ok, Result6} = request_http_rest_lookup([Path]),
?assertMatch(#{}, get_http_data(Result6)).
t_password_hash(_) ->
clean_all_users(),

View File

@ -23,6 +23,7 @@
%% first_next query APIs
-export([ params2qs/2
, node_query/4
, node_query/5
, cluster_query/3
, traverse_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, undefined).
node_query(Node, Params, {Tab, QsSchema}, QueryFun, SortFun) ->
{CodCnt, Qs} = params2qs(Params, QsSchema),
Limit = limit(Params),
Page = page(Params),
@ -94,7 +98,11 @@ node_query(Node, Params, {Tab, QsSchema}, QueryFun) ->
true -> Meta#{count => count(Tab), hasnext => length(Rows) > Limit};
_ -> Meta#{count => -1, hasnext => length(Rows) > Limit}
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
do_query(Node, Qs, {M,F}, Start, Limit) when Node =:= node() ->