From b5835099761a40844f08e1b1980ad26cb0137136 Mon Sep 17 00:00:00 2001 From: zhanghongtong Date: Wed, 22 Sep 2021 18:54:24 +0800 Subject: [PATCH] feat(authz): add authorize for mnesia --- apps/emqx_authz/src/emqx_authz.erl | 1 + apps/emqx_authz/src/emqx_authz_mnesia.erl | 30 ++++- apps/emqx_authz/src/emqx_authz_mongodb.erl | 6 +- apps/emqx_authz/src/emqx_authz_mysql.erl | 1 - .../test/emqx_authz_mnesia_SUITE.erl | 109 ++++++++++++++++++ 5 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl diff --git a/apps/emqx_authz/src/emqx_authz.erl b/apps/emqx_authz/src/emqx_authz.erl index 2cfc2c305..13113b060 100644 --- a/apps/emqx_authz/src/emqx_authz.erl +++ b/apps/emqx_authz/src/emqx_authz.erl @@ -407,6 +407,7 @@ create_resource(#{type := DB} = Source) -> {error, Reason} -> {error, Reason} end. +authz_module('built-in-database') ->emqx_authz_mnesia; authz_module(Type) -> list_to_existing_atom("emqx_authz_" ++ atom_to_list(Type)). diff --git a/apps/emqx_authz/src/emqx_authz_mnesia.erl b/apps/emqx_authz/src/emqx_authz_mnesia.erl index 74be86471..b222edfb1 100644 --- a/apps/emqx_authz/src/emqx_authz_mnesia.erl +++ b/apps/emqx_authz/src/emqx_authz_mnesia.erl @@ -48,7 +48,29 @@ mnesia(copy) -> description() -> "AuthZ with Mnesia". -authorize(#{username := _Username, - clientid := _Clientid - } = _Client, _PubSub, _Topic, #{type := mnesia}) -> - ok. +authorize(#{username := Username, + clientid := Clientid + } = Client, PubSub, Topic, #{type := 'built-in-database'}) -> + + Rules = case mnesia:dirty_read(?ACL_TABLE, {clientid, Clientid}) of + [] -> []; + [#emqx_acl{rules = Rules0}] when is_list(Rules0) -> Rules0 + end + ++ case mnesia:dirty_read(?ACL_TABLE, {username, Username}) of + [] -> []; + [#emqx_acl{rules = Rules1}] when is_list(Rules1) -> Rules1 + end + ++ case mnesia:dirty_read(?ACL_TABLE, all) of + [] -> []; + [#emqx_acl{rules = Rules2}] when is_list(Rules2) -> Rules2 + end, + do_authorize(Client, PubSub, Topic, Rules). + +do_authorize(_Client, _PubSub, _Topic, []) -> nomatch; +do_authorize(Client, PubSub, Topic, [ {Permission, Action, TopicFilter} | Tail]) -> + case emqx_authz_rule:match(Client, PubSub, Topic, + emqx_authz_rule:compile({Permission, all, Action, [TopicFilter]}) + ) of + {matched, Permission} -> {matched, Permission}; + nomatch -> do_authorize(Client, PubSub, Topic, Tail) + end. diff --git a/apps/emqx_authz/src/emqx_authz_mongodb.erl b/apps/emqx_authz/src/emqx_authz_mongodb.erl index 6c0fb126a..ca65e6f53 100644 --- a/apps/emqx_authz/src/emqx_authz_mongodb.erl +++ b/apps/emqx_authz/src/emqx_authz_mongodb.erl @@ -58,9 +58,9 @@ do_authorize(Client, PubSub, Topic, [Rule | Tail]) -> end. replvar(Selector, #{clientid := Clientid, - username := Username, - peerhost := IpAddress - }) -> + username := Username, + peerhost := IpAddress + }) -> Fun = fun _Fun(K, V, AccIn) when is_map(V) -> maps:put(K, maps:fold(_Fun, AccIn, V), AccIn); _Fun(K, V, AccIn) when is_list(V) -> diff --git a/apps/emqx_authz/src/emqx_authz_mysql.erl b/apps/emqx_authz/src/emqx_authz_mysql.erl index ac8f04f32..6ad206d90 100644 --- a/apps/emqx_authz/src/emqx_authz_mysql.erl +++ b/apps/emqx_authz/src/emqx_authz_mysql.erl @@ -69,7 +69,6 @@ do_authorize(Client, PubSub, Topic, Columns, [Row | Tail]) -> nomatch -> do_authorize(Client, PubSub, Topic, Columns, Tail) end. - format_result(Columns, Row) -> Permission = lists:nth(index(<<"permission">>, Columns), Row), Action = lists:nth(index(<<"action">>, Columns), Row), diff --git a/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl b/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl new file mode 100644 index 000000000..5dd3fb3a7 --- /dev/null +++ b/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl @@ -0,0 +1,109 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_mnesia_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include("emqx_authz.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-define(CONF_DEFAULT, <<"authorization: {sources: []}">>). + +all() -> + emqx_ct:all(?MODULE). + +groups() -> + []. + +init_per_suite(Config) -> + meck:new(emqx_schema, [non_strict, passthrough, no_history, no_link]), + meck:expect(emqx_schema, fields, fun("authorization") -> + meck:passthrough(["authorization"]) ++ + emqx_authz_schema:fields("authorization"); + (F) -> meck:passthrough([F]) + end), + + ok = emqx_config:init_load(emqx_authz_schema, ?CONF_DEFAULT), + ok = emqx_ct_helpers:start_apps([emqx_authz]), + + {ok, _} = emqx:update_config([authorization, cache, enable], false), + {ok, _} = emqx:update_config([authorization, no_match], deny), + Rules = [#{<<"type">> => <<"built-in-database">>}], + {ok, _} = emqx_authz:update(replace, Rules), + Config. + +end_per_suite(_Config) -> + {ok, _} = emqx_authz:update(replace, []), + emqx_ct_helpers:stop_apps([emqx_authz]), + meck:unload(emqx_schema), + ok. + +init_per_testcase(t_authz, Config) -> + mnesia:transaction(fun mnesia:write/1, [#emqx_acl{who = {username, <<"test_username">>}, + rules = [{allow, publish, <<"test/%u">>}, + {allow, subscribe, <<"eq #">>} + ] + }]), + mnesia:transaction(fun mnesia:write/1, [#emqx_acl{who = {clientid, <<"test_clientid">>}, + rules = [{allow, publish, <<"test/%c">>}, + {deny, subscribe, <<"eq #">>} + ] + }]), + mnesia:transaction(fun mnesia:write/1, [#emqx_acl{who = all, + rules = [{deny, all, <<"#">>}] + }]), + Config; +init_per_testcase(_, Config) -> Config. + +end_per_testcase(t_authz, Config) -> + [ mnesia:dirty_delete(?ACL_TABLE, K) || K <- mnesia:dirty_all_keys(?ACL_TABLE)], + Config; +end_per_testcase(_, Config) -> Config. + +%%------------------------------------------------------------------------------ +%% Testcases +%%------------------------------------------------------------------------------ + +t_authz(_) -> + ClientInfo1 = #{clientid => <<"test">>, + username => <<"test">>, + peerhost => {127,0,0,1}, + listener => {tcp, default} + }, + ClientInfo2 = #{clientid => <<"fake_clientid">>, + username => <<"test_username">>, + peerhost => {127,0,0,1}, + listener => {tcp, default} + }, + ClientInfo3 = #{clientid => <<"test_clientid">>, + username => <<"fake_username">>, + peerhost => {127,0,0,1}, + listener => {tcp, default} + }, + + ?assertEqual(deny, emqx_access_control:authorize(ClientInfo1, subscribe, <<"#">>)), + ?assertEqual(deny, emqx_access_control:authorize(ClientInfo1, publish, <<"#">>)), + + ?assertEqual(allow, emqx_access_control:authorize(ClientInfo2, publish, <<"test/test_username">>)), + ?assertEqual(allow, emqx_access_control:authorize(ClientInfo2, subscribe, <<"#">>)), + + ?assertEqual(allow, emqx_access_control:authorize(ClientInfo3, publish, <<"test/test_clientid">>)), + ?assertEqual(deny, emqx_access_control:authorize(ClientInfo3, subscribe, <<"#">>)), + + ok. +