emqx/apps/emqx_auth_redis/src/emqx_acl_redis.erl

87 lines
3.0 KiB
Erlang

%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_acl_redis).
-include("emqx_auth_redis.hrl").
-include_lib("emqx/include/emqx.hrl").
-include_lib("emqx/include/logger.hrl").
-export([ register_metrics/0
, check_acl/5
, description/0
]).
-spec(register_metrics() -> ok).
register_metrics() ->
lists:foreach(fun emqx_metrics:ensure/1, ?ACL_METRICS).
check_acl(ClientInfo, PubSub, Topic, AclResult, Config) ->
case do_check_acl(ClientInfo, PubSub, Topic, AclResult, Config) of
ok -> emqx_metrics:inc(?ACL_METRICS(ignore)), ok;
{stop, allow} -> emqx_metrics:inc(?ACL_METRICS(allow)), {stop, allow};
{stop, deny} -> emqx_metrics:inc(?ACL_METRICS(deny)), {stop, deny}
end.
do_check_acl(#{username := <<$$, _/binary>>}, _PubSub, _Topic, _AclResult, _Config) ->
ok;
do_check_acl(ClientInfo, PubSub, Topic, _AclResult,
#{acl_cmd := AclCmd, timeout := Timeout, type := Type, pool := Pool}) ->
case emqx_auth_redis_cli:q(Pool, Type, AclCmd, ClientInfo, Timeout) of
{ok, []} -> ok;
{ok, Rules} ->
case match(ClientInfo, PubSub, Topic, Rules) of
allow -> {stop, allow};
nomatch -> {stop, deny}
end;
{error, Reason} ->
?LOG(error, "[Redis] do_check_acl error: ~p", [Reason]),
ok
end.
match(_ClientInfo, _PubSub, _Topic, []) ->
nomatch;
match(ClientInfo, PubSub, Topic, [Filter, Access | Rules]) ->
case {match_topic(Topic, feed_var(ClientInfo, Filter)),
match_access(PubSub, b2i(Access))} of
{true, true} -> allow;
{_, _} -> match(ClientInfo, PubSub, Topic, Rules)
end.
match_topic(Topic, Filter) ->
emqx_topic:match(Topic, Filter).
match_access(subscribe, Access) ->
(1 band Access) > 0;
match_access(publish, Access) ->
(2 band Access) > 0.
feed_var(#{clientid := ClientId, username := Username}, Str) ->
lists:foldl(fun({Var, Val}, Acc) ->
feed_var(Acc, Var, Val)
end, Str, [{"%u", Username}, {"%c", ClientId}]).
feed_var(Str, _Var, undefined) ->
Str;
feed_var(Str, Var, Val) ->
re:replace(Str, Var, Val, [global, {return, binary}]).
b2i(Bin) -> list_to_integer(binary_to_list(Bin)).
description() -> "Redis ACL Module".