From aaf57ecfbc4a3b7319c294a586ad9faa6378c495 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Fri, 26 Apr 2024 11:45:14 +0300 Subject: [PATCH] chore(authz): improve and clarify types --- .../src/emqx_authz/emqx_authz_rule.erl | 69 ++++++++++++++----- .../src/emqx_authz/emqx_authz_rule_raw.erl | 36 ++++++++-- .../sources/emqx_authz_client_info.erl | 31 ++++++--- .../src/emqx_authz_mnesia.erl | 4 +- 4 files changed, 107 insertions(+), 33 deletions(-) diff --git a/apps/emqx_auth/src/emqx_authz/emqx_authz_rule.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_rule.erl index 9a877b5c8..2fb6fb8f5 100644 --- a/apps/emqx_auth/src/emqx_authz/emqx_authz_rule.erl +++ b/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) -> diff --git a/apps/emqx_auth/src/emqx_authz/emqx_authz_rule_raw.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_rule_raw.erl index 0c56be2d1..35eae8a11 100644 --- a/apps/emqx_auth/src/emqx_authz/emqx_authz_rule_raw.erl +++ b/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( diff --git a/apps/emqx_auth/src/emqx_authz/sources/emqx_authz_client_info.erl b/apps/emqx_auth/src/emqx_authz/sources/emqx_authz_client_info.erl index fd8193262..4451b9e03 100644 --- a/apps/emqx_auth/src/emqx_authz/sources/emqx_authz_client_info.erl +++ b/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) -> diff --git a/apps/emqx_auth_mnesia/src/emqx_authz_mnesia.erl b/apps/emqx_auth_mnesia/src/emqx_authz_mnesia.erl index 2707fc6ea..ea77a10ab 100644 --- a/apps/emqx_auth_mnesia/src/emqx_authz_mnesia.erl +++ b/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()].