From c49ac06322a05de7e9dd446476468c3dc7d7da05 Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Mon, 6 Apr 2015 14:11:33 +0800 Subject: [PATCH] acl tests --- apps/emqttd/src/emqttd_access_rule.erl | 2 + apps/emqttd/src/emqttd_acl.erl | 30 ++++--- apps/emqttd/src/emqttd_acl_internal.erl | 12 ++- apps/emqttd/test/emqttd_access_rule_tests.erl | 79 ++++++++++++++++ apps/emqttd/test/emqttd_acl_tests.erl | 90 ++++++++++--------- apps/emqttd/test/test_acl.config | 16 ++++ 6 files changed, 175 insertions(+), 54 deletions(-) create mode 100644 apps/emqttd/test/emqttd_access_rule_tests.erl create mode 100644 apps/emqttd/test/test_acl.config diff --git a/apps/emqttd/src/emqttd_access_rule.erl b/apps/emqttd/src/emqttd_access_rule.erl index 59f17d01c..096c10b8d 100644 --- a/apps/emqttd/src/emqttd_access_rule.erl +++ b/apps/emqttd/src/emqttd_access_rule.erl @@ -112,6 +112,8 @@ match_who(#mqtt_user{clientid = ClientId}, {client, ClientId}) -> true; match_who(#mqtt_user{username = Username}, {user, Username}) -> true; +match_who(#mqtt_user{ipaddr = undefined}, {ipaddr, _Tup}) -> + false; match_who(#mqtt_user{ipaddr = IP}, {ipaddr, {_CDIR, Start, End}}) -> I = esockd_access:atoi(IP), I >= Start andalso I =< End; diff --git a/apps/emqttd/src/emqttd_acl.erl b/apps/emqttd/src/emqttd_acl.erl index 8086f21fc..b2268e9b6 100644 --- a/apps/emqttd/src/emqttd_acl.erl +++ b/apps/emqttd/src/emqttd_acl.erl @@ -35,7 +35,7 @@ -define(SERVER, ?MODULE). %% API Function Exports --export([start_link/1, check/3, reload/0, register_mod/1, unregister_mod/1, all_modules/0]). +-export([start_link/1, check/3, reload/0, register_mod/1, unregister_mod/1, all_modules/0, stop/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -116,7 +116,7 @@ check(User, PubSub, Topic, [Mod|Mods]) -> reload() -> case ets:lookup(?ACL_TABLE, acl_modules) of [] -> {error, "No ACL mod!"}; - [{_, Mods}] -> [M:reload() || M <- Mods] + [{_, Mods}] -> [M:reload_acl() || M <- Mods] end. %%------------------------------------------------------------------------------ @@ -137,7 +137,7 @@ register_mod(Mod) -> %%------------------------------------------------------------------------------ -spec unregister_mod(Mod :: atom()) -> ok | {error, any()}. unregister_mod(Mod) -> - gen_server:call(?SERVER, {unregister_mod, Mod}). + gen_server:cast(?SERVER, {unregister_mod, Mod}). %%------------------------------------------------------------------------------ %% @doc @@ -151,6 +151,9 @@ all_modules() -> [{_, Mods}] -> Mods end. +stop() -> + gen_server:call(?SERVER, stop). + %%%============================================================================= %%% gen_server callbacks. %%%============================================================================= @@ -168,20 +171,23 @@ handle_call({register_mod, Mod}, _From, State) -> {reply, ok, State} end; -handle_call({unregister_mod, Mod}, _From, State) -> - Mods = all_modules(), - case lists:member(Mod, Mods) of - true -> - ets:insert(?ACL_TABLE, {acl_modules, lists:delete(Mod, Mods)}), - {reply, ok, State}; - false -> - {reply, {error, not_found}, State} - end; +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; handle_call(Req, _From, State) -> lager:error("Bad Request: ~p", [Req]), {reply, {error, badreq}, State}. +handle_cast({unregister_mod, Mod}, State) -> + Mods = all_modules(), + case lists:member(Mod, Mods) of + true -> + ets:insert(?ACL_TABLE, {acl_modules, lists:delete(Mod, Mods)}); + false -> + lager:error("unknown acl module: ~s", [Mod]) + end, + {noreply, State}; + handle_cast(_Msg, State) -> {noreply, State}. diff --git a/apps/emqttd/src/emqttd_acl_internal.erl b/apps/emqttd/src/emqttd_acl_internal.erl index bc861b9e1..773c1611c 100644 --- a/apps/emqttd/src/emqttd_acl_internal.erl +++ b/apps/emqttd/src/emqttd_acl_internal.erl @@ -30,7 +30,7 @@ -include("emqttd.hrl"). --export([start_link/1]). +-export([start_link/1, stop/0]). -behaviour(emqttd_acl). @@ -64,6 +64,9 @@ start_link(AclOpts) -> gen_server:start_link({local, ?SERVER}, ?MODULE, [AclOpts], []). +stop() -> + gen_server:call(?SERVER, stop). + %%%============================================================================= %%% ACL callbacks %%%============================================================================= @@ -139,6 +142,9 @@ handle_call(reload, _From, State) -> {reply, {error, Error}, State} end; +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; + handle_call(Req, _From, State) -> lager:error("BadReq: ~p", [Req]), {reply, {error, badreq}, State}. @@ -150,6 +156,7 @@ handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> + emqttd_acl:unregister_mod(?MODULE), ok. code_change(_OldVsn, State, _Extra) -> @@ -170,6 +177,8 @@ load_rules(State = #state{acl_file = AclFile}) -> filter(_PubSub, {allow, all}) -> true; +filter(_PubSub, {deny, all}) -> + true; filter(publish, {_AllowDeny, _Who, publish, _Topics}) -> true; filter(_PubSub, {_AllowDeny, _Who, pubsub, _Topics}) -> @@ -179,3 +188,4 @@ filter(subscribe, {_AllowDeny, _Who, subscribe, _Topics}) -> filter(_PubSub, {_AllowDeny, _Who, _, _Topics}) -> false. + diff --git a/apps/emqttd/test/emqttd_access_rule_tests.erl b/apps/emqttd/test/emqttd_access_rule_tests.erl new file mode 100644 index 000000000..66632c663 --- /dev/null +++ b/apps/emqttd/test/emqttd_access_rule_tests.erl @@ -0,0 +1,79 @@ +%%%----------------------------------------------------------------------------- +%%% @Copyright (C) 2012-2015, Feng Lee +%%% +%%% Permission is hereby granted, free of charge, to any person obtaining a copy +%%% of this software and associated documentation files (the "Software"), to deal +%%% in the Software without restriction, including without limitation the rights +%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%%% copies of the Software, and to permit persons to whom the Software is +%%% furnished to do so, subject to the following conditions: +%%% +%%% The above copyright notice and this permission notice shall be included in all +%%% copies or substantial portions of the Software. +%%% +%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%%% SOFTWARE. +%%%----------------------------------------------------------------------------- +%%% @doc +%%% emqttd_access_rule tests. +%%% +%%% @end +%%%----------------------------------------------------------------------------- +-module(emqttd_access_rule_tests). + +-import(emqttd_access_rule, [compile/1, match/3]). + +-include("emqttd.hrl"). + +-ifdef(TEST). + +-include_lib("eunit/include/eunit.hrl"). + +compile_test() -> + ?assertMatch({allow, {ipaddr, {"127.0.0.1", _I, _I}}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]}, + compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]})), + ?assertMatch({allow, {user, <<"testuser">>}, subscribe, [ [<<"a">>, <<"b">>, <<"c">>], [<<"d">>, <<"e">>, <<"f">>, '#'] ]}, + compile({allow, {user, "testuser"}, subscribe, ["a/b/c", "d/e/f/#"]})), + ?assertEqual({allow, {user, <<"admin">>}, pubsub, [ [<<"d">>, <<"e">>, <<"f">>, '#'] ]}, + compile({allow, {user, "admin"}, pubsub, ["d/e/f/#"]})), + ?assertEqual({allow, {client, <<"testClient">>}, publish, [ [<<"testTopics">>, <<"testClient">>] ]}, + compile({allow, {client, "testClient"}, publish, ["testTopics/testClient"]})), + ?assertEqual({allow, all, pubsub, [{pattern, [<<"clients">>, <<"$c">>]}]}, + compile({allow, all, pubsub, ["clients/$c"]})), + ?assertEqual({allow, all, subscribe, [{pattern, [<<"users">>, <<"$u">>, '#']}]}, + compile({allow, all, subscribe, ["users/$u/#"]})), + ?assertEqual({deny, all, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]}, + compile({deny, all, subscribe, ["$SYS/#", "#"]})), + ?assertEqual({allow, all}, compile({allow, all})), + ?assertEqual({deny, all}, compile({deny, all})). + +match_test() -> + User = #mqtt_user{ipaddr = {127,0,0,1}, clientid = <<"testClient">>, username = <<"TestUser">>}, + User2 = #mqtt_user{ipaddr = {192,168,0,10}, clientid = <<"testClient">>, username = <<"TestUser">>}, + + ?assertEqual({matched, allow}, match(User, <<"Test/Topic">>, {allow, all})), + ?assertEqual({matched, deny}, match(User, <<"Test/Topic">>, {deny, all})), + ?assertMatch({matched, allow}, match(User, <<"Test/Topic">>, + compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]}))), + ?assertMatch({matched, allow}, match(User2, <<"Test/Topic">>, + compile({allow, {ipaddr, "192.168.0.1/24"}, subscribe, ["$SYS/#", "#"]}))), + ?assertMatch({matched, allow}, match(User, <<"d/e/f/x">>, compile({allow, {user, "TestUser"}, subscribe, ["a/b/c", "d/e/f/#"]}))), + ?assertEqual(nomatch, match(User, <<"d/e/f/x">>, compile({allow, {user, "admin"}, pubsub, ["d/e/f/#"]}))), + ?assertMatch({matched, allow}, match(User, <<"testTopics/testClient">>, + compile({allow, {client, "testClient"}, publish, ["testTopics/testClient"]}))), + ?assertMatch({matched, allow}, match(User, <<"clients/testClient">>, + compile({allow, all, pubsub, ["clients/$c"]}))), + ?assertMatch({matched, allow}, match(#mqtt_user{username = <<"user2">>}, <<"users/user2/abc/def">>, + compile({allow, all, subscribe, ["users/$u/#"]}))), + ?assertMatch({matched, deny}, + match(User, <<"d/e/f">>, + compile({deny, all, subscribe, ["$SYS/#", "#"]}))). + +-endif. + + diff --git a/apps/emqttd/test/emqttd_acl_tests.erl b/apps/emqttd/test/emqttd_acl_tests.erl index 8ad832119..438f662e8 100644 --- a/apps/emqttd/test/emqttd_acl_tests.erl +++ b/apps/emqttd/test/emqttd_acl_tests.erl @@ -26,54 +26,62 @@ %%%----------------------------------------------------------------------------- -module(emqttd_acl_tests). --import(emqttd_access_rule, [compile/1, match/3]). - -include("emqttd.hrl"). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -compile_test() -> - ?assertMatch({allow, {ipaddr, {"127.0.0.1", _I, _I}}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]}, - compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]})), - ?assertMatch({allow, {user, <<"testuser">>}, subscribe, [ [<<"a">>, <<"b">>, <<"c">>], [<<"d">>, <<"e">>, <<"f">>, '#'] ]}, - compile({allow, {user, "testuser"}, subscribe, ["a/b/c", "d/e/f/#"]})), - ?assertEqual({allow, {user, <<"admin">>}, pubsub, [ [<<"d">>, <<"e">>, <<"f">>, '#'] ]}, - compile({allow, {user, "admin"}, pubsub, ["d/e/f/#"]})), - ?assertEqual({allow, {client, <<"testClient">>}, publish, [ [<<"testTopics">>, <<"testClient">>] ]}, - compile({allow, {client, "testClient"}, publish, ["testTopics/testClient"]})), - ?assertEqual({allow, all, pubsub, [{pattern, [<<"clients">>, <<"$c">>]}]}, - compile({allow, all, pubsub, ["clients/$c"]})), - ?assertEqual({allow, all, subscribe, [{pattern, [<<"users">>, <<"$u">>, '#']}]}, - compile({allow, all, subscribe, ["users/$u/#"]})), - ?assertEqual({deny, all, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]}, - compile({deny, all, subscribe, ["$SYS/#", "#"]})), - ?assertEqual({allow, all}, compile({allow, all})), - ?assertEqual({deny, all}, compile({deny, all})). +all_modules_test() -> + with_acl( + fun() -> + ?assertEqual([emqttd_acl_internal], emqttd_acl:all_modules()) + end). -match_test() -> - User = #mqtt_user{ipaddr = {127,0,0,1}, clientid = <<"testClient">>, username = <<"TestUser">>}, - User2 = #mqtt_user{ipaddr = {192,168,0,10}, clientid = <<"testClient">>, username = <<"TestUser">>}, - - ?assertEqual({matched, allow}, match(User, <<"Test/Topic">>, {allow, all})), - ?assertEqual({matched, deny}, match(User, <<"Test/Topic">>, {deny, all})), - ?assertMatch({matched, allow}, match(User, <<"Test/Topic">>, - compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]}))), - ?assertMatch({matched, allow}, match(User2, <<"Test/Topic">>, - compile({allow, {ipaddr, "192.168.0.1/24"}, subscribe, ["$SYS/#", "#"]}))), - ?assertMatch({matched, allow}, match(User, <<"d/e/f/x">>, compile({allow, {user, "TestUser"}, subscribe, ["a/b/c", "d/e/f/#"]}))), - ?assertEqual(nomatch, match(User, <<"d/e/f/x">>, compile({allow, {user, "admin"}, pubsub, ["d/e/f/#"]}))), - ?assertMatch({matched, allow}, match(User, <<"testTopics/testClient">>, - compile({allow, {client, "testClient"}, publish, ["testTopics/testClient"]}))), - ?assertMatch({matched, allow}, match(User, <<"clients/testClient">>, - compile({allow, all, pubsub, ["clients/$c"]}))), - ?assertMatch({matched, allow}, match(#mqtt_user{username = <<"user2">>}, <<"users/user2/abc/def">>, - compile({allow, all, subscribe, ["users/$u/#"]}))), - ?assertMatch({matched, deny}, - match(User, <<"d/e/f">>, - compile({deny, all, subscribe, ["$SYS/#", "#"]}))). +reload_test() -> + with_acl( + fun() -> + ?assertEqual([ok], emqttd_acl:reload()) + end). + +register_mod_test() -> + with_acl( + fun() -> + emqttd_acl:register_mod(acl_mysql), + ?assertEqual([acl_mysql, emqttd_acl_internal], emqttd_acl:all_modules()) + end). + +unregister_mod_test() -> + with_acl( + fun() -> + emqttd_acl:register_mod(acl_mysql), + ?assertEqual([acl_mysql, emqttd_acl_internal], emqttd_acl:all_modules()), + emqttd_acl:unregister_mod(acl_mysql), + timer:sleep(5), + ?assertEqual([emqttd_acl_internal], emqttd_acl:all_modules()) + end). + +check_test() -> + with_acl( + fun() -> + User1 = #mqtt_user{clientid = <<"client1">>, username = <<"testuser">>}, + User2 = #mqtt_user{clientid = <<"client2">>, username = <<"xyz">>}, + ?assertEqual({ok, allow}, emqttd_acl:check(User1, subscribe, <<"users/testuser/1">>)), + ?assertEqual({ok, allow}, emqttd_acl:check(User1, subscribe, <<"clients/client1">>)), + ?assertEqual({ok, deny}, emqttd_acl:check(User1, subscribe, <<"clients/client1/x/y">>)), + ?assertEqual({ok, allow}, emqttd_acl:check(User1, publish, <<"users/testuser/1">>)), + ?assertEqual({ok, allow}, emqttd_acl:check(User1, subscribe, <<"a/b/c">>)), + ?assertEqual({ok, deny}, emqttd_acl:check(User2, subscribe, <<"a/b/c">>)) + end). + +with_acl(Fun) -> + process_flag(trap_exit, true), + AclOpts = [{file, "../test/test_acl.config"}], + {ok, _AclSrv} = emqttd_acl:start_link(AclOpts), + {ok, _InternalAcl} = emqttd_acl_internal:start_link(AclOpts), + Fun(), + emqttd_acl_internal:stop(), + emqttd_acl:stop(). -endif. - diff --git a/apps/emqttd/test/test_acl.config b/apps/emqttd/test/test_acl.config new file mode 100644 index 000000000..4a2c6ea44 --- /dev/null +++ b/apps/emqttd/test/test_acl.config @@ -0,0 +1,16 @@ +{allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]}. + +{allow, {user, "testuser"}, subscribe, ["a/b/c", "d/e/f/#"]}. + +{allow, {user, "admin"}, pubsub, ["a/b/c", "d/e/f/#"]}. + +{allow, {client, "testClient"}, subscribe, ["testTopics/testClient"]}. + +{allow, all, subscribe, ["clients/$c"]}. + +{allow, all, pubsub, ["users/$u/#"]}. + +{deny, all, subscribe, ["$SYS/#", "#"]}. + +{deny, all}. +