feat(authz): authz mnesia rules searching by `clientid` or `username`

This commit is contained in:
JimMoen 2022-03-04 15:43:09 +08:00
parent c01aa3c580
commit 16ec8fe289
2 changed files with 80 additions and 24 deletions

View File

@ -24,8 +24,12 @@
-import(hoconsc, [mk/1, mk/2, ref/1, ref/2, array/1, enum/1]). -import(hoconsc, [mk/1, mk/2, ref/1, ref/2, array/1, enum/1]).
-define(FORMAT_USERNAME_FUN, {?MODULE, format_by_username}). -define(QUERY_USERNAME_FUN, {?MODULE, query_username}).
-define(FORMAT_CLIENTID_FUN, {?MODULE, format_by_clientid}). -define(QUERY_CLIENTID_FUN, {?MODULE, query_clientid}).
-define(ACL_USERNAME_QSCHEMA, [{<<"like_username">>, binary}]).
-define(ACL_CLIENTID_QSCHEMA, [{<<"like_clientid">>, binary}]).
-export([ api_spec/0 -export([ api_spec/0
, paths/0 , paths/0
@ -42,8 +46,11 @@
, purge/2 , purge/2
]). ]).
-export([ format_by_username/1 %% query funs
, format_by_clientid/1]). -export([ query_username/4
, query_clientid/4]).
-export([format_result/1]).
-define(BAD_REQUEST, 'BAD_REQUEST'). -define(BAD_REQUEST, 'BAD_REQUEST').
-define(NOT_FOUND, 'NOT_FOUND'). -define(NOT_FOUND, 'NOT_FOUND').
@ -293,9 +300,10 @@ fields(meta) ->
%% HTTP API %% HTTP API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
users(get, #{query_string := QString}) -> users(get, #{query_string := QueryString}) ->
{Table, MatchSpec} = emqx_authz_mnesia:list_username_rules(), Response = emqx_mgmt_api:node_query(node(), QueryString,
{200, emqx_mgmt_api:paginate(Table, MatchSpec, QString, ?FORMAT_USERNAME_FUN)}; ?ACL_TABLE, ?ACL_USERNAME_QSCHEMA, ?QUERY_USERNAME_FUN),
emqx_mgmt_util:generate_response(Response);
users(post, #{body := Body}) when is_list(Body) -> users(post, #{body := Body}) when is_list(Body) ->
lists:foreach(fun(#{<<"username">> := Username, <<"rules">> := Rules}) -> lists:foreach(fun(#{<<"username">> := Username, <<"rules">> := Rules}) ->
emqx_authz_mnesia:store_rules({username, Username}, format_rules(Rules)) emqx_authz_mnesia:store_rules({username, Username}, format_rules(Rules))
@ -303,8 +311,9 @@ users(post, #{body := Body}) when is_list(Body) ->
{204}. {204}.
clients(get, #{query_string := QueryString}) -> clients(get, #{query_string := QueryString}) ->
{Table, MatchSpec} = emqx_authz_mnesia:list_clientid_rules(), Response = emqx_mgmt_api:node_query(node(), QueryString,
{200, emqx_mgmt_api:paginate(Table, MatchSpec, QueryString, ?FORMAT_CLIENTID_FUN)}; ?ACL_TABLE, ?ACL_CLIENTID_QSCHEMA, ?QUERY_CLIENTID_FUN),
emqx_mgmt_util:generate_response(Response);
clients(post, #{body := Body}) when is_list(Body) -> clients(post, #{body := Body}) when is_list(Body) ->
lists:foreach(fun(#{<<"clientid">> := Clientid, <<"rules">> := Rules}) -> lists:foreach(fun(#{<<"clientid">> := Clientid, <<"rules">> := Rules}) ->
emqx_authz_mnesia:store_rules({clientid, Clientid}, format_rules(Rules)) emqx_authz_mnesia:store_rules({clientid, Clientid}, format_rules(Rules))
@ -379,6 +388,54 @@ purge(delete, _) ->
}} }}
end. end.
%%--------------------------------------------------------------------
%% Query Functions
query_username(Tab, {_QString, []}, Continuation, Limit) ->
Ms = emqx_authz_mnesia:list_username_rules(),
emqx_mgmt_api:select_table_with_count(Tab, Ms, Continuation, Limit,
fun format_result/1);
query_username(Tab, {_QString, FuzzyQString}, Continuation, Limit) ->
Ms = emqx_authz_mnesia:list_username_rules(),
FuzzyFilterFun = fuzzy_filter_fun(FuzzyQString),
emqx_mgmt_api:select_table_with_count(Tab, {Ms, FuzzyFilterFun}, Continuation, Limit,
fun format_result/1).
query_clientid(Tab, {_QString, []}, Continuation, Limit) ->
Ms = emqx_authz_mnesia:list_clientid_rules(),
emqx_mgmt_api:select_table_with_count(Tab, Ms, Continuation, Limit,
fun format_result/1);
query_clientid(Tab, {_QString, FuzzyQString}, Continuation, Limit) ->
Ms = emqx_authz_mnesia:list_clientid_rules(),
FuzzyFilterFun = fuzzy_filter_fun(FuzzyQString),
emqx_mgmt_api:select_table_with_count(Tab, {Ms, FuzzyFilterFun}, Continuation, Limit,
fun format_result/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 = [{username, Username}, _Rule]
, [{username, like, UsernameSubStr} | Fuzzy]) ->
binary:match(Username, UsernameSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy);
run_fuzzy_filter( E = [{clientid, ClientId}, _Rule]
, [{clientid, like, ClientIdSubStr} | Fuzzy]) ->
binary:match(ClientId, ClientIdSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy).
%%--------------------------------------------------------------------
%% format funcs
%% format rule from api
format_rules(Rules) when is_list(Rules) -> format_rules(Rules) when is_list(Rules) ->
lists:foldl(fun(#{<<"topic">> := Topic, lists:foldl(fun(#{<<"topic">> := Topic,
<<"action">> := Action, <<"action">> := Action,
@ -388,14 +445,15 @@ format_rules(Rules) when is_list(Rules) ->
AccIn ++ [{ atom(Permission), atom(Action), Topic }] AccIn ++ [{ atom(Permission), atom(Action), Topic }]
end, [], Rules). end, [], Rules).
format_by_username([{username, Username}, {rules, Rules}]) -> %% format result from mnesia tab
format_result([{username, Username}, {rules, Rules}]) ->
#{username => Username, #{username => Username,
rules => [ #{topic => Topic, rules => [ #{topic => Topic,
action => Action, action => Action,
permission => Permission permission => Permission
} || {Permission, Action, Topic} <- Rules] } || {Permission, Action, Topic} <- Rules]
}. };
format_by_clientid([{clientid, Clientid}, {rules, Rules}]) -> format_result([{clientid, Clientid}, {rules, Rules}]) ->
#{clientid => Clientid, #{clientid => Clientid,
rules => [ #{topic => Topic, rules => [ #{topic => Topic,
action => Action, action => Action,

View File

@ -158,21 +158,19 @@ delete_rules({clientid, Clientid}) ->
delete_rules(all) -> delete_rules(all) ->
mria:dirty_delete(?ACL_TABLE, ?ACL_TABLE_ALL). mria:dirty_delete(?ACL_TABLE, ?ACL_TABLE_ALL).
-spec(list_username_rules() -> {mria:table(), ets:match_spec()}). -spec(list_username_rules() -> ets:match_spec()).
list_username_rules() -> list_username_rules() ->
MatchSpec = ets:fun2ms( ets:fun2ms(
fun(#emqx_acl{who = {?ACL_TABLE_USERNAME, Username}, rules = Rules}) -> fun(#emqx_acl{who = {?ACL_TABLE_USERNAME, Username}, rules = Rules}) ->
[{username, Username}, {rules, Rules}] [{username, Username}, {rules, Rules}]
end), end).
{?ACL_TABLE, MatchSpec}.
-spec(list_clientid_rules() -> {mria:table(), ets:match_spec()}). -spec(list_clientid_rules() -> ets:match_spec()).
list_clientid_rules() -> list_clientid_rules() ->
MatchSpec = ets:fun2ms( ets:fun2ms(
fun(#emqx_acl{who = {?ACL_TABLE_CLIENTID, Clientid}, rules = Rules}) -> fun(#emqx_acl{who = {?ACL_TABLE_CLIENTID, Clientid}, rules = Rules}) ->
[{clientid, Clientid}, {rules, Rules}] [{clientid, Clientid}, {rules, Rules}]
end), end).
{?ACL_TABLE, MatchSpec}.
-spec(record_count() -> non_neg_integer()). -spec(record_count() -> non_neg_integer()).
record_count() -> record_count() ->