chore(authz): improve and clarify types

This commit is contained in:
Ilya Averyanov 2024-04-26 11:45:14 +03:00
parent a0e0a27f87
commit aaf57ecfbc
4 changed files with 107 additions and 33 deletions

View File

@ -29,13 +29,15 @@
compile/4 compile/4
]). ]).
-export_type([action/0, action_precompile/0]).
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl").
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-type permission() :: allow | deny. %%--------------------------------------------------------------------
%% "condition" types describe compiled rules used internally for matching
%%--------------------------------------------------------------------
-type permission_resolution() :: allow | deny.
-type who_condition() :: -type who_condition() ::
ipaddress() ipaddress()
@ -61,7 +63,18 @@
-type topic_condition() :: list(emqx_types:topic() | {eq, emqx_types:topic()}). -type topic_condition() :: list(emqx_types:topic() | {eq, emqx_types:topic()}).
-type rule() :: {permission(), who_condition(), action_condition(), topic_condition()}. -type rule() :: {permission_resolution(), who_condition(), action_condition(), topic_condition()}.
-export_type([
permission_resolution/0,
action_condition/0,
topic_condition/0
]).
%%--------------------------------------------------------------------
%% `action()` type describes client's actions that are mached
%% against the compiled "condition" rules
%%--------------------------------------------------------------------
-type qos() :: emqx_types:qos(). -type qos() :: emqx_types:qos().
-type retain() :: boolean(). -type retain() :: boolean().
@ -69,32 +82,54 @@
#{action_type := subscribe, qos := qos()} #{action_type := subscribe, qos := qos()}
| #{action_type := publish, qos := qos(), retain := retain()}. | #{action_type := publish, qos := qos(), retain := retain()}.
-export_type([ -export_type([action/0, qos/0, retain/0]).
permission/0,
who_condition/0, %%--------------------------------------------------------------------
action_condition/0, %% "precompiled" types describe rule DSL that is used in "acl.conf" file
topic_condition/0 %% to describe rules. Also, rules extracted from external sources
]). %% like database, etc. are preprocessed into these types first
%%--------------------------------------------------------------------
-type permission_resolution_precompile() :: permission_resolution().
-type who_precompile() :: who_condition().
-type subscribe_option_precompile() :: {qos, qos() | [qos()]}.
-type publish_option_precompile() :: {qos, qos() | [qos()]} | {retain, retain_condition()}.
-type action_precompile() :: -type action_precompile() ::
subscribe subscribe
| {subscribe, [subscribe_option_precompile()]}
| publish | publish
| {subscribe, list()} | {publish, [publish_option_precompile()]}
| {publish, list()} | all
| all. | {all, [publish_option_precompile()]}.
-type topic_filter() :: emqx_types:topic(). %% besides exact `topic_condition()` we also accept `<<"eq ...">>` and `"eq ..."`
%% as precompiled topic conditions
-type topic_precompile() :: topic_condition() | binary() | string().
-type rule_precompile() :: {permission(), who_condition(), action_precompile(), [topic_filter()]}. -type rule_precompile() :: {
permission_resolution_precompile(), who_condition(), action_precompile(), [topic_precompile()]
}.
-export_type([
permission_resolution_precompile/0,
action_precompile/0,
topic_precompile/0,
rule_precompile/0
]).
-define(IS_PERMISSION(Permission), (Permission =:= allow orelse Permission =:= deny)). -define(IS_PERMISSION(Permission), (Permission =:= allow orelse Permission =:= deny)).
-define(ALLOWED_VARS, [?VAR_USERNAME, ?VAR_CLIENTID, ?VAR_NS_CLIENT_ATTRS]). -define(ALLOWED_VARS, [?VAR_USERNAME, ?VAR_CLIENTID, ?VAR_NS_CLIENT_ATTRS]).
-spec compile(permission(), who_condition(), action_precompile(), [topic_filter()]) -> rule(). -spec compile(permission_resolution_precompile(), who_precompile(), action_precompile(), [
topic_precompile()
]) -> rule().
compile(Permission, Who, Action, TopicFilters) -> compile(Permission, Who, Action, TopicFilters) ->
compile({Permission, Who, Action, TopicFilters}). compile({Permission, Who, Action, TopicFilters}).
-spec compile({permission(), all} | rule_precompile()) -> rule(). -spec compile({permission_resolution_precompile(), all} | rule_precompile()) -> rule().
compile({Permission, all}) when compile({Permission, all}) when
?IS_PERMISSION(Permission) ?IS_PERMISSION(Permission)
-> ->

View File

@ -25,6 +25,30 @@
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
%% Raw rules have the following format:
%% [
%% #{
%% %% <<"allow">> | <"deny">>,
%% <<"permission">> => <<"allow">>,
%%
%% %% <<"pub">> | <<"sub">> | <<"all">>
%% <<"action">> => <<"pub">>,
%%
%% %% <<"a/$#">>, <<"eq a/b/+">>, ...
%% <<"topic">> => TopicFilter,
%%
%% %% when 'topic' is not provided
%% <<"topics">> => [TopicFilter],
%%
%% %% 0 | 1 | 2 | [0, 1, 2] | <<"0">> | <<"1">> | ...
%% <<"qos">> => 0,
%%
%% %% true | false | all | 0 | 1 | <<"true">> | ...
%% %% only for pub action
%% <<"retain">> => true
%% },
%% ...
%% ],
-type rule_raw() :: #{binary() => binary() | [binary()]}. -type rule_raw() :: #{binary() => binary() | [binary()]}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -33,9 +57,9 @@
-spec parse_rule(rule_raw()) -> -spec parse_rule(rule_raw()) ->
{ok, { {ok, {
emqx_authz_rule:permission(), emqx_authz_rule:permission_resolution_precompile(),
emqx_authz_rule:action_condition(), emqx_authz_rule:action_precompile(),
emqx_authz_rule:topic_condition() emqx_authz_rule:topic_precompile()
}} }}
| {error, map()}. | {error, map()}.
parse_rule( parse_rule(
@ -65,9 +89,9 @@ parse_rule(RuleRaw) ->
}}. }}.
-spec format_rule({ -spec format_rule({
emqx_authz_rule:permission(), emqx_authz_rule:permission_resolution_precompile(),
emqx_authz_rule:action_condition(), emqx_authz_rule:action_precompile(),
emqx_authz_rule:topic_condition() emqx_authz_rule:topic_precompile()
}) -> map(). }) -> map().
format_rule({Permission, Action, Topics}) when is_list(Topics) -> format_rule({Permission, Action, Topics}) when is_list(Topics) ->
maps:merge( maps:merge(

View File

@ -75,22 +75,35 @@ destroy(_Source) -> ok.
%% v2: (rules are checked in sequence, passthrough when no match) %% v2: (rules are checked in sequence, passthrough when no match)
%% %%
%% [{ %% [{
%% Permission :: emqx_authz_rule:permission(), %% Permission :: emqx_authz_rule:permission_resolution(),
%% Action :: emqx_authz_rule:action_condition(), %% Action :: emqx_authz_rule:action_condition(),
%% Topics :: emqx_authz_rule:topic_condition() %% Topics :: emqx_authz_rule:topic_condition()
%% }] %% }]
%% %%
%% which is compiled from raw rules like below by emqx_authz_rule_raw %% which is compiled from raw rule maps like below by `emqx_authz_rule_raw`
%% %%
%% [ %% [
%% #{ %% #{
%% permission := allow | deny %% %% <<"allow">> | <"deny">>,
%% action := pub | sub | all %% <<"permission">> => <<"allow">>,
%% topic => TopicFilter, %%
%% topics => [TopicFilter] %% when 'topic' is not provided %% %% <<"pub">> | <<"sub">> | <<"all">>
%% qos => 0 | 1 | 2 | [0, 1, 2] %% <<"action">> => <<"pub">>,
%% retain => true | false | all %% only for pub action %%
%% } %% %% <<"a/$#">>, <<"eq a/b/+">>, ...
%% <<"topic">> => TopicFilter,
%%
%% %% when 'topic' is not provided
%% <<"topics">> => [TopicFilter],
%%
%% %% 0 | 1 | 2 | [0, 1, 2] | <<"0">> | <<"1">> | ...
%% <<"qos">> => 0,
%%
%% %% true | false | all | 0 | 1 | <<"true">> | ...
%% %% only for pub action
%% <<"retain">> => true
%% },
%% ...
%% ] %% ]
%% %%
authorize(#{acl := Acl} = Client, PubSub, Topic, _Source) -> authorize(#{acl := Acl} = Client, PubSub, Topic, _Source) ->

View File

@ -33,7 +33,9 @@
-type who() :: username() | clientid() | all. -type who() :: username() | clientid() | all.
-type rule() :: { -type rule() :: {
emqx_authz_rule:permission(), emqx_authz_rule:action_precompile(), emqx_types:topic() emqx_authz_rule:permission_resolution_precompile(),
emqx_authz_rule:action_precompile(),
emqx_authz_rule:topic_precompile()
}. }.
-type rules() :: [rule()]. -type rules() :: [rule()].