Просмотр исходного кода

Merge pull request #12936 from savonarola/0426-improve-authz-rule-typespecs

chore(authz): improve and clarify types
Ilia Averianov 1 год назад
Родитель
Сommit
002dc8541b

+ 52 - 17
apps/emqx_auth/src/emqx_authz/emqx_authz_rule.erl

@@ -29,13 +29,15 @@
     compile/4
 ]).
 
--export_type([action/0, action_precompile/0]).
-
 -include_lib("emqx/include/logger.hrl").
 -include_lib("emqx/include/emqx_placeholder.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() ::
     ipaddress()
@@ -61,7 +63,18 @@
 
 -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 retain() :: boolean().
@@ -69,32 +82,54 @@
     #{action_type := subscribe, qos := qos()}
     | #{action_type := publish, qos := qos(), retain := retain()}.
 
--export_type([
-    permission/0,
-    who_condition/0,
-    action_condition/0,
-    topic_condition/0
-]).
+-export_type([action/0, qos/0, retain/0]).
+
+%%--------------------------------------------------------------------
+%% "precompiled" types describe rule DSL that is used in "acl.conf" file
+%% 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() ::
     subscribe
+    | {subscribe, [subscribe_option_precompile()]}
     | publish
-    | {subscribe, list()}
-    | {publish, list()}
-    | all.
+    | {publish, [publish_option_precompile()]}
+    | 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(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}).
 
--spec compile({permission(), all} | rule_precompile()) -> rule().
+-spec compile({permission_resolution_precompile(), all} | rule_precompile()) -> rule().
 compile({Permission, all}) when
     ?IS_PERMISSION(Permission)
 ->

+ 30 - 6
apps/emqx_auth/src/emqx_authz/emqx_authz_rule_raw.erl

@@ -25,6 +25,30 @@
 
 -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()]}.
 
 %%--------------------------------------------------------------------
@@ -33,9 +57,9 @@
 
 -spec parse_rule(rule_raw()) ->
     {ok, {
-        emqx_authz_rule:permission(),
-        emqx_authz_rule:action_condition(),
-        emqx_authz_rule:topic_condition()
+        emqx_authz_rule:permission_resolution_precompile(),
+        emqx_authz_rule:action_precompile(),
+        emqx_authz_rule:topic_precompile()
     }}
     | {error, map()}.
 parse_rule(
@@ -65,9 +89,9 @@ parse_rule(RuleRaw) ->
     }}.
 
 -spec format_rule({
-    emqx_authz_rule:permission(),
-    emqx_authz_rule:action_condition(),
-    emqx_authz_rule:topic_condition()
+    emqx_authz_rule:permission_resolution_precompile(),
+    emqx_authz_rule:action_precompile(),
+    emqx_authz_rule:topic_precompile()
 }) -> map().
 format_rule({Permission, Action, Topics}) when is_list(Topics) ->
     maps:merge(

+ 22 - 9
apps/emqx_auth/src/emqx_authz/sources/emqx_authz_client_info.erl

@@ -75,22 +75,35 @@ destroy(_Source) -> ok.
 %% 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(),
 %%        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
-%%            action := pub | sub | all
-%%            topic => TopicFilter,
-%%            topics => [TopicFilter] %% when 'topic' is not provided
-%%            qos => 0 | 1 | 2 | [0, 1, 2]
-%%            retain => true | false | all %% only for pub action
-%%        }
+%%            %% <<"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
+%%        },
+%%        ...
 %%    ]
 %%
 authorize(#{acl := Acl} = Client, PubSub, Topic, _Source) ->

+ 3 - 1
apps/emqx_auth_mnesia/src/emqx_authz_mnesia.erl

@@ -33,7 +33,9 @@
 -type who() :: username() | clientid() | all.
 
 -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()].