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

feat: allow client_attr used in authz rules

zmstone 1 год назад
Родитель
Сommit
9ec99fef4a

+ 1 - 0
apps/emqx/include/emqx_placeholder.hrl

@@ -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")).

+ 1 - 0
apps/emqx_auth/src/emqx_authn/emqx_authn_utils.erl

@@ -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,

+ 3 - 1
apps/emqx_auth/src/emqx_authz/emqx_authz_rule.erl

@@ -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}

+ 24 - 0
apps/emqx_auth/test/emqx_authz/emqx_authz_file_SUITE.erl

@@ -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(),
 

+ 2 - 1
apps/emqx_auth_http/src/emqx_authz_http.erl

@@ -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, [

+ 1 - 1
apps/emqx_auth_mongodb/src/emqx_auth_mongodb.app.src

@@ -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, [

+ 2 - 1
apps/emqx_auth_mongodb/src/emqx_authz_mongodb.erl

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

+ 1 - 1
apps/emqx_auth_mysql/src/emqx_auth_mysql.app.src

@@ -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, [

+ 2 - 1
apps/emqx_auth_mysql/src/emqx_authz_mysql.erl

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

+ 1 - 1
apps/emqx_auth_postgresql/src/emqx_auth_postgresql.app.src

@@ -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, [

+ 2 - 1
apps/emqx_auth_postgresql/src/emqx_authz_postgresql.erl

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

+ 1 - 1
apps/emqx_auth_redis/src/emqx_auth_redis.app.src

@@ -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, [

+ 2 - 1
apps/emqx_auth_redis/src/emqx_authz_redis.erl

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

+ 26 - 2
apps/emqx_utils/src/emqx_template.erl

@@ -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()) ->

+ 12 - 0
apps/emqx_utils/test/emqx_template_SUITE.erl

@@ -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) ->