feat: allow client_attr used in authz rules

This commit is contained in:
zmstone 2024-03-19 21:51:21 +01:00
parent e5816f5a13
commit 9ec99fef4a
15 changed files with 81 additions and 12 deletions

View File

@ -36,6 +36,7 @@
-define(VAR_CLIENTID, "clientid").
-define(VAR_USERNAME, "username").
-define(VAR_TOPIC, "topic").
-define(VAR_NS_CLIENT_ATTRS, {var_namespace, "client_attrs"}).
-define(PH_PASSWORD, ?PH(?VAR_PASSWORD)).
-define(PH_CLIENTID, ?PH(?VAR_CLIENTID)).
-define(PH_FROM_CLIENTID, ?PH("from_clientid")).

View File

@ -46,6 +46,7 @@
default_headers_no_content_type/0
]).
%% VAR_NS_CLIENT_ATTRS is not added to this list because client_attrs is to be initialized from authn result
-define(ALLOWED_VARS, [
?VAR_USERNAME,
?VAR_CLIENTID,

View File

@ -223,7 +223,9 @@ compile_topic(<<"eq ", Topic/binary>>) ->
compile_topic({eq, Topic}) ->
{eq, emqx_topic:words(bin(Topic))};
compile_topic(Topic) ->
Template = emqx_authz_utils:parse_str(Topic, [?VAR_USERNAME, ?VAR_CLIENTID]),
Template = emqx_authz_utils:parse_str(Topic, [
?VAR_USERNAME, ?VAR_CLIENTID, ?VAR_NS_CLIENT_ATTRS
]),
case emqx_template:is_const(Template) of
true -> emqx_topic:words(bin(Topic));
false -> {pattern, Template}

View File

@ -74,6 +74,30 @@ t_ok(_Config) ->
emqx_access_control:authorize(ClientInfo, ?AUTHZ_SUBSCRIBE, <<"t">>)
).
t_client_attrs(_Config) ->
ClientInfo0 = emqx_authz_test_lib:base_client_info(),
ClientInfo = ClientInfo0#{client_attrs => #{<<"device_id">> => <<"id1">>}},
ok = setup_config(?RAW_SOURCE#{
<<"rules">> => <<"{allow, all, all, [\"t/${client_attrs.device_id}/#\"]}.">>
}),
?assertEqual(
allow,
emqx_access_control:authorize(ClientInfo, ?AUTHZ_PUBLISH, <<"t/id1/1">>)
),
?assertEqual(
allow,
emqx_access_control:authorize(ClientInfo, ?AUTHZ_SUBSCRIBE, <<"t/id1/#">>)
),
?assertEqual(
deny,
emqx_access_control:authorize(ClientInfo, ?AUTHZ_SUBSCRIBE, <<"t/id2/#">>)
),
ok.
t_rich_actions(_Config) ->
ClientInfo = emqx_authz_test_lib:base_client_info(),

View File

@ -47,7 +47,8 @@
?VAR_TOPIC,
?VAR_ACTION,
?VAR_CERT_SUBJECT,
?VAR_CERT_CN_NAME
?VAR_CERT_CN_NAME,
?VAR_NS_CLIENT_ATTRS
]).
-define(ALLOWED_VARS_RICH_ACTIONS, [

View File

@ -1,7 +1,7 @@
%% -*- mode: erlang -*-
{application, emqx_auth_mongodb, [
{description, "EMQX MongoDB Authentication and Authorization"},
{vsn, "0.1.1"},
{vsn, "0.2.0"},
{registered, []},
{mod, {emqx_auth_mongodb_app, []}},
{applications, [

View File

@ -40,7 +40,8 @@
?VAR_CLIENTID,
?VAR_PEERHOST,
?VAR_CERT_CN_NAME,
?VAR_CERT_SUBJECT
?VAR_CERT_SUBJECT,
?VAR_NS_CLIENT_ATTRS
]).
description() ->

View File

@ -1,7 +1,7 @@
%% -*- mode: erlang -*-
{application, emqx_auth_mysql, [
{description, "EMQX MySQL Authentication and Authorization"},
{vsn, "0.1.2"},
{vsn, "0.2.0"},
{registered, []},
{mod, {emqx_auth_mysql_app, []}},
{applications, [

View File

@ -42,7 +42,8 @@
?VAR_CLIENTID,
?VAR_PEERHOST,
?VAR_CERT_CN_NAME,
?VAR_CERT_SUBJECT
?VAR_CERT_SUBJECT,
?VAR_NS_CLIENT_ATTRS
]).
description() ->

View File

@ -1,7 +1,7 @@
%% -*- mode: erlang -*-
{application, emqx_auth_postgresql, [
{description, "EMQX PostgreSQL Authentication and Authorization"},
{vsn, "0.1.1"},
{vsn, "0.2.0"},
{registered, []},
{mod, {emqx_auth_postgresql_app, []}},
{applications, [

View File

@ -42,7 +42,8 @@
?VAR_CLIENTID,
?VAR_PEERHOST,
?VAR_CERT_CN_NAME,
?VAR_CERT_SUBJECT
?VAR_CERT_SUBJECT,
?VAR_NS_CLIENT_ATTRS
]).
description() ->

View File

@ -1,7 +1,7 @@
%% -*- mode: erlang -*-
{application, emqx_auth_redis, [
{description, "EMQX Redis Authentication and Authorization"},
{vsn, "0.1.2"},
{vsn, "0.2.0"},
{registered, []},
{mod, {emqx_auth_redis_app, []}},
{applications, [

View File

@ -40,7 +40,8 @@
?VAR_CERT_SUBJECT,
?VAR_PEERHOST,
?VAR_CLIENTID,
?VAR_USERNAME
?VAR_USERNAME,
?VAR_NS_CLIENT_ATTRS
]).
description() ->

View File

@ -145,18 +145,42 @@ parse_accessor(Var) ->
%% @doc Validate a template against a set of allowed variables.
%% If the given template contains any variable not in the allowed set, an error
%% is returned.
-spec validate([varname()], t()) ->
-spec validate([varname() | {var_namespace, varname()}], t()) ->
ok | {error, [_Error :: {varname(), disallowed}]}.
validate(Allowed, Template) ->
{_, Errors} = render(Template, #{}),
{Used, _} = lists:unzip(Errors),
case lists:usort(Used) -- Allowed of
case find_disallowed(lists:usort(Used), Allowed) of
[] ->
ok;
Disallowed ->
{error, [{Var, disallowed} || Var <- Disallowed]}
end.
find_disallowed([], _Allowed) ->
[];
find_disallowed([Var | Rest], Allowed) ->
case is_allowed(Var, Allowed) of
true ->
find_disallowed(Rest, Allowed);
false ->
[Var | find_disallowed(Rest, Allowed)]
end.
is_allowed(_Var, []) ->
false;
is_allowed(Var, [{var_namespace, VarPrefix} | Allowed]) ->
case lists:prefix(VarPrefix ++ ".", Var) of
true ->
true;
false ->
is_allowed(Var, Allowed)
end;
is_allowed(Var, [Var | _Allowed]) ->
true;
is_allowed(Var, [_ | Allowed]) ->
is_allowed(Var, Allowed).
%% @doc Check if a template is constant with respect to rendering, i.e. does not
%% contain any placeholders.
-spec is_const(t()) ->

View File

@ -337,6 +337,18 @@ t_unparse_tmpl_deep(_) ->
Template = emqx_template:parse_deep(Term),
?assertEqual(Term, emqx_template:unparse(Template)).
t_allow_var_by_namespace(_) ->
Context = #{d => #{d1 => <<"hi">>}},
Template = emqx_template:parse(<<"d.d1:${d.d1}">>),
?assertEqual(
ok,
emqx_template:validate([{var_namespace, "d"}], Template)
),
?assertEqual(
{<<"d.d1:hi">>, []},
render_string(Template, Context)
).
%%
render_string(Template, Context) ->