From 82559b9b089c6cfadf54b7ce5143156dd403876f Mon Sep 17 00:00:00 2001 From: JimMoen Date: Fri, 1 Apr 2022 02:11:51 +0800 Subject: [PATCH] style: erlfmt apps/emqx_authz --- apps/emqx_authz/include/emqx_authz.hrl | 128 +-- apps/emqx_authz/rebar.config | 11 +- apps/emqx_authz/src/emqx_authz.app.src | 32 +- apps/emqx_authz/src/emqx_authz.erl | 184 ++-- apps/emqx_authz/src/emqx_authz_api_mnesia.erl | 792 +++++++++++------- apps/emqx_authz/src/emqx_authz_api_schema.erl | 204 +++-- .../src/emqx_authz_api_settings.erl | 54 +- .../emqx_authz/src/emqx_authz_api_sources.erl | 643 ++++++++------ apps/emqx_authz/src/emqx_authz_file.erl | 38 +- apps/emqx_authz/src/emqx_authz_http.erl | 148 ++-- apps/emqx_authz/src/emqx_authz_mnesia.erl | 137 +-- apps/emqx_authz/src/emqx_authz_mongodb.erl | 87 +- apps/emqx_authz/src/emqx_authz_mysql.erl | 91 +- apps/emqx_authz/src/emqx_authz_postgresql.erl | 96 ++- apps/emqx_authz/src/emqx_authz_redis.erl | 71 +- apps/emqx_authz/src/emqx_authz_rule.erl | 147 ++-- apps/emqx_authz/src/emqx_authz_schema.erl | 337 ++++---- apps/emqx_authz/src/emqx_authz_sup.erl | 8 +- apps/emqx_authz/src/emqx_authz_utils.erl | 74 +- apps/emqx_authz/test/emqx_authz_SUITE.erl | 301 ++++--- .../test/emqx_authz_api_mnesia_SUITE.erl | 314 ++++--- .../test/emqx_authz_api_settings_SUITE.erl | 50 +- .../test/emqx_authz_api_sources_SUITE.erl | 509 ++++++----- .../emqx_authz/test/emqx_authz_file_SUITE.erl | 91 +- .../emqx_authz/test/emqx_authz_http_SUITE.erl | 406 +++++---- .../test/emqx_authz_http_test_server.erl | 32 +- .../test/emqx_authz_mnesia_SUITE.erl | 109 +-- .../test/emqx_authz_mongodb_SUITE.erl | 223 +++-- .../test/emqx_authz_mysql_SUITE.erl | 325 ++++--- .../test/emqx_authz_postgresql_SUITE.erl | 331 +++++--- .../test/emqx_authz_redis_SUITE.erl | 204 +++-- .../emqx_authz/test/emqx_authz_rule_SUITE.erl | 359 +++++--- apps/emqx_authz/test/emqx_authz_test_lib.erl | 169 ++-- 33 files changed, 4042 insertions(+), 2663 deletions(-) diff --git a/apps/emqx_authz/include/emqx_authz.hrl b/apps/emqx_authz/include/emqx_authz.hrl index 13c100ba8..a3fe2d1f9 100644 --- a/apps/emqx_authz/include/emqx_authz.hrl +++ b/apps/emqx_authz/include/emqx_authz.hrl @@ -16,13 +16,15 @@ -define(APP, emqx_authz). --define(ALLOW_DENY(A), ((A =:= allow) orelse (A =:= <<"allow">>) orelse - (A =:= deny) orelse (A =:= <<"deny">>) - )). --define(PUBSUB(A), ((A =:= subscribe) orelse (A =:= <<"subscribe">>) orelse - (A =:= publish) orelse (A =:= <<"publish">>) orelse - (A =:= all) orelse (A =:= <<"all">>) - )). +-define(ALLOW_DENY(A), + ((A =:= allow) orelse (A =:= <<"allow">>) orelse + (A =:= deny) orelse (A =:= <<"deny">>)) +). +-define(PUBSUB(A), + ((A =:= subscribe) orelse (A =:= <<"subscribe">>) orelse + (A =:= publish) orelse (A =:= <<"publish">>) orelse + (A =:= all) orelse (A =:= <<"all">>)) +). %% authz_mnesia -define(ACL_TABLE, emqx_acl). @@ -44,53 +46,69 @@ -define(RE_PLACEHOLDER, "\\$\\{[a-z0-9_]+\\}"). %% API examples --define(USERNAME_RULES_EXAMPLE, #{username => user1, - rules => [ #{topic => <<"test/toopic/1">>, - permission => <<"allow">>, - action => <<"publish">> - } - , #{topic => <<"test/toopic/2">>, - permission => <<"allow">>, - action => <<"subscribe">> - } - , #{topic => <<"eq test/#">>, - permission => <<"deny">>, - action => <<"all">> - } - ] - }). --define(CLIENTID_RULES_EXAMPLE, #{clientid => client1, - rules => [ #{topic => <<"test/toopic/1">>, - permission => <<"allow">>, - action => <<"publish">> - } - , #{topic => <<"test/toopic/2">>, - permission => <<"allow">>, - action => <<"subscribe">> - } - , #{topic => <<"eq test/#">>, - permission => <<"deny">>, - action => <<"all">> - } - ] - }). --define(ALL_RULES_EXAMPLE, #{rules => [ #{topic => <<"test/toopic/1">>, - permission => <<"allow">>, - action => <<"publish">> - } - , #{topic => <<"test/toopic/2">>, - permission => <<"allow">>, - action => <<"subscribe">> - } - , #{topic => <<"eq test/#">>, - permission => <<"deny">>, - action => <<"all">> - } - ] - }). --define(META_EXAMPLE, #{ page => 1 - , limit => 100 - , count => 1 - }). +-define(USERNAME_RULES_EXAMPLE, #{ + username => user1, + rules => [ + #{ + topic => <<"test/toopic/1">>, + permission => <<"allow">>, + action => <<"publish">> + }, + #{ + topic => <<"test/toopic/2">>, + permission => <<"allow">>, + action => <<"subscribe">> + }, + #{ + topic => <<"eq test/#">>, + permission => <<"deny">>, + action => <<"all">> + } + ] +}). +-define(CLIENTID_RULES_EXAMPLE, #{ + clientid => client1, + rules => [ + #{ + topic => <<"test/toopic/1">>, + permission => <<"allow">>, + action => <<"publish">> + }, + #{ + topic => <<"test/toopic/2">>, + permission => <<"allow">>, + action => <<"subscribe">> + }, + #{ + topic => <<"eq test/#">>, + permission => <<"deny">>, + action => <<"all">> + } + ] +}). +-define(ALL_RULES_EXAMPLE, #{ + rules => [ + #{ + topic => <<"test/toopic/1">>, + permission => <<"allow">>, + action => <<"publish">> + }, + #{ + topic => <<"test/toopic/2">>, + permission => <<"allow">>, + action => <<"subscribe">> + }, + #{ + topic => <<"eq test/#">>, + permission => <<"deny">>, + action => <<"all">> + } + ] +}). +-define(META_EXAMPLE, #{ + page => 1, + limit => 100, + count => 1 +}). -define(RESOURCE_GROUP, <<"emqx_authz">>). diff --git a/apps/emqx_authz/rebar.config b/apps/emqx_authz/rebar.config index 2e33f41d5..da2fa7807 100644 --- a/apps/emqx_authz/rebar.config +++ b/apps/emqx_authz/rebar.config @@ -1,11 +1,14 @@ %% -*- mode: erlang -*- {erl_opts, [debug_info, nowarn_unused_import]}. -{deps, [ {emqx, {path, "../emqx"}} - , {emqx_connector, {path, "../emqx_connector"}} - ]}. +{deps, [ + {emqx, {path, "../emqx"}}, + {emqx_connector, {path, "../emqx_connector"}} +]}. {shell, [ - % {config, "config/sys.config"}, + % {config, "config/sys.config"}, {apps, [emqx_authz]} ]}. + +{project_plugins, [erlfmt]}. diff --git a/apps/emqx_authz/src/emqx_authz.app.src b/apps/emqx_authz/src/emqx_authz.app.src index ff43b3536..a8e781df9 100644 --- a/apps/emqx_authz/src/emqx_authz.app.src +++ b/apps/emqx_authz/src/emqx_authz.app.src @@ -1,18 +1,18 @@ %% -*- mode: erlang -*- -{application, emqx_authz, - [{description, "An OTP application"}, - {vsn, "0.1.1"}, - {registered, []}, - {mod, {emqx_authz_app, []}}, - {applications, - [kernel, - stdlib, - crypto, - emqx_connector - ]}, - {env,[]}, - {modules, []}, +{application, emqx_authz, [ + {description, "An OTP application"}, + {vsn, "0.1.1"}, + {registered, []}, + {mod, {emqx_authz_app, []}}, + {applications, [ + kernel, + stdlib, + crypto, + emqx_connector + ]}, + {env, []}, + {modules, []}, - {licenses, ["Apache 2.0"]}, - {links, []} - ]}. + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_authz/src/emqx_authz.erl b/apps/emqx_authz/src/emqx_authz.erl index 3e5a53649..40c987e1f 100644 --- a/apps/emqx_authz/src/emqx_authz.erl +++ b/apps/emqx_authz/src/emqx_authz.erl @@ -25,29 +25,30 @@ -compile(nowarn_export_all). -endif. --export([ register_metrics/0 - , init/0 - , deinit/0 - , lookup/0 - , lookup/1 - , move/2 - , update/2 - , authorize/5 - ]). +-export([ + register_metrics/0, + init/0, + deinit/0, + lookup/0, + lookup/1, + move/2, + update/2, + authorize/5 +]). -export([post_config_update/5, pre_config_update/3]). -export([acl_conf_file/0]). --type(source() :: map()). +-type source() :: map(). --type(match_result() :: {matched, allow} | {matched, deny} | nomatch). +-type match_result() :: {matched, allow} | {matched, deny} | nomatch. --type(default_result() :: allow | deny). +-type default_result() :: allow | deny. --type(authz_result() :: {stop, allow} | {ok, deny}). +-type authz_result() :: {stop, allow} | {ok, deny}. --type(sources() :: [source()]). +-type sources() :: [source()]. -define(METRIC_ALLOW, 'client.authorize.allow'). -define(METRIC_DENY, 'client.authorize.deny'). @@ -60,24 +61,25 @@ %% Initialize authz backend. %% Populate the passed configuration map with necessary data, %% like `ResourceID`s --callback(init(source()) -> source()). +-callback init(source()) -> source(). %% Get authz text description. --callback(description() -> string()). +-callback description() -> string(). %% Destroy authz backend. %% Make cleanup of all allocated data. %% An authz backend will not be used after `destroy`. --callback(destroy(source()) -> ok). +-callback destroy(source()) -> ok. %% Authorize client action. --callback(authorize( - emqx_types:clientinfo(), - emqx_types:pubsub(), - emqx_types:topic(), - source()) -> match_result()). +-callback authorize( + emqx_types:clientinfo(), + emqx_types:pubsub(), + emqx_types:topic(), + source() +) -> match_result(). --spec(register_metrics() -> ok). +-spec register_metrics() -> ok. register_metrics() -> lists:foreach(fun emqx_metrics:ensure/1, ?METRICS). @@ -104,13 +106,16 @@ lookup(Type) -> move(Type, ?CMD_MOVE_BEFORE(Before)) -> emqx_authz_utils:update_config( - ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), ?CMD_MOVE_BEFORE(type(Before))}); + ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), ?CMD_MOVE_BEFORE(type(Before))} + ); move(Type, ?CMD_MOVE_AFTER(After)) -> emqx_authz_utils:update_config( - ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), ?CMD_MOVE_AFTER(type(After))}); + ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), ?CMD_MOVE_AFTER(type(After))} + ); move(Type, Position) -> emqx_authz_utils:update_config( - ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), Position}). + ?CONF_KEY_PATH, {?CMD_MOVE, type(Type), Position} + ). update({?CMD_REPLACE, Type}, Sources) -> emqx_authz_utils:update_config(?CONF_KEY_PATH, {{?CMD_REPLACE, type(Type)}, Sources}); @@ -205,7 +210,8 @@ do_move({?CMD_MOVE, Type, ?CMD_MOVE_AFTER(After)}, Sources) -> {S2, Front2, Rear2} = take(After, Front1 ++ Rear1), Front2 ++ [S2, S1] ++ Rear2. -ensure_resource_deleted(#{enable := false}) -> ok; +ensure_resource_deleted(#{enable := false}) -> + ok; ensure_resource_deleted(#{type := Type} = Source) -> Module = authz_module(Type), Module:destroy(Source). @@ -213,17 +219,19 @@ ensure_resource_deleted(#{type := Type} = Source) -> check_dup_types(Sources) -> check_dup_types(Sources, []). -check_dup_types([], _Checked) -> ok; +check_dup_types([], _Checked) -> + ok; check_dup_types([Source | Sources], Checked) -> %% the input might be raw or type-checked result, so lookup both 'type' and <<"type">> %% TODO: check: really? - Type = case maps:get(<<"type">>, Source, maps:get(type, Source, undefined)) of - undefined -> - %% this should never happen if the value is type checked by honcon schema - throw({bad_source_input, Source}); - Type0 -> - type(Type0) - end, + Type = + case maps:get(<<"type">>, Source, maps:get(type, Source, undefined)) of + undefined -> + %% this should never happen if the value is type checked by honcon schema + throw({bad_source_input, Source}); + Type0 -> + type(Type0) + end, case lists:member(Type, Checked) of true -> %% we have made it clear not to support more than one authz instance for each type @@ -240,7 +248,8 @@ init_sources(Sources) -> end, lists:map(fun init_source/1, Sources). -init_source(#{enable := false} = Source) -> Source; +init_source(#{enable := false} = Source) -> + Source; init_source(#{type := Type} = Source) -> Module = authz_module(Type), Module:init(Source). @@ -250,42 +259,63 @@ init_source(#{type := Type} = Source) -> %%-------------------------------------------------------------------- %% @doc Check AuthZ --spec(authorize( emqx_types:clientinfo() - , emqx_types:pubsub() - , emqx_types:topic() - , default_result() - , sources()) - -> authz_result()). -authorize(#{username := Username, - peerhost := IpAddress - } = Client, PubSub, Topic, DefaultResult, Sources) -> +-spec authorize( + emqx_types:clientinfo(), + emqx_types:pubsub(), + emqx_types:topic(), + default_result(), + sources() +) -> + authz_result(). +authorize( + #{ + username := Username, + peerhost := IpAddress + } = Client, + PubSub, + Topic, + DefaultResult, + Sources +) -> case do_authorize(Client, PubSub, Topic, Sources) of - {{matched, allow}, AuthzSource}-> - emqx:run_hook('client.check_authz_complete', - [Client, PubSub, Topic, allow, AuthzSource]), - ?SLOG(info, #{msg => "authorization_permission_allowed", - username => Username, - ipaddr => IpAddress, - topic => Topic}), + {{matched, allow}, AuthzSource} -> + emqx:run_hook( + 'client.check_authz_complete', + [Client, PubSub, Topic, allow, AuthzSource] + ), + ?SLOG(info, #{ + msg => "authorization_permission_allowed", + username => Username, + ipaddr => IpAddress, + topic => Topic + }), emqx_metrics:inc(?METRIC_ALLOW), {stop, allow}; - {{matched, deny}, AuthzSource}-> - emqx:run_hook('client.check_authz_complete', - [Client, PubSub, Topic, deny, AuthzSource]), - ?SLOG(info, #{msg => "authorization_permission_denied", - username => Username, - ipaddr => IpAddress, - topic => Topic}), + {{matched, deny}, AuthzSource} -> + emqx:run_hook( + 'client.check_authz_complete', + [Client, PubSub, Topic, deny, AuthzSource] + ), + ?SLOG(info, #{ + msg => "authorization_permission_denied", + username => Username, + ipaddr => IpAddress, + topic => Topic + }), emqx_metrics:inc(?METRIC_DENY), {stop, deny}; nomatch -> - emqx:run_hook('client.check_authz_complete', - [Client, PubSub, Topic, DefaultResult, default]), - ?SLOG(info, #{msg => "authorization_failed_nomatch", - username => Username, - ipaddr => IpAddress, - topic => Topic, - reason => "no-match rule"}), + emqx:run_hook( + 'client.check_authz_complete', + [Client, PubSub, Topic, DefaultResult, default] + ), + ?SLOG(info, #{ + msg => "authorization_failed_nomatch", + username => Username, + ipaddr => IpAddress, + topic => Topic, + reason => "no-match rule" + }), emqx_metrics:inc(?METRIC_NOMATCH), {stop, DefaultResult} end. @@ -294,8 +324,12 @@ do_authorize(_Client, _PubSub, _Topic, []) -> nomatch; do_authorize(Client, PubSub, Topic, [#{enable := false} | Rest]) -> do_authorize(Client, PubSub, Topic, Rest); -do_authorize(Client, PubSub, Topic, - [Connector = #{type := Type} | Tail] ) -> +do_authorize( + Client, + PubSub, + Topic, + [Connector = #{type := Type} | Tail] +) -> Module = authz_module(Type), case Module:authorize(Client, PubSub, Topic, Connector) of nomatch -> do_authorize(Client, PubSub, Topic, Tail); @@ -311,7 +345,7 @@ take(Type) -> take(Type, lookup()). %% Take the source of give type, the sources list is split into two parts %% front part and rear part. take(Type, Sources) -> - {Front, Rear} = lists:splitwith(fun(T) -> type(T) =/= type(Type) end, Sources), + {Front, Rear} = lists:splitwith(fun(T) -> type(T) =/= type(Type) end, Sources), case Rear =:= [] of true -> throw({not_found_source, Type}); @@ -321,7 +355,7 @@ take(Type, Sources) -> find_action_in_hooks() -> Callbacks = emqx_hooks:lookup('client.authorize'), - [Action] = [Action || {callback,{?MODULE, authorize, _} = Action, _, _} <- Callbacks ], + [Action] = [Action || {callback, {?MODULE, authorize, _} = Action, _, _} <- Callbacks], Action. authz_module('built_in_database') -> @@ -364,8 +398,11 @@ acl_conf_file() -> filename:join([emqx:data_dir(), "authz", "acl.conf"]). maybe_write_certs(#{<<"type">> := Type} = Source) -> - case emqx_tls_lib:ensure_ssl_files( - ssl_file_path(Type), maps:get(<<"ssl">>, Source, undefined)) of + case + emqx_tls_lib:ensure_ssl_files( + ssl_file_path(Type), maps:get(<<"ssl">>, Source, undefined) + ) + of {ok, SSL} -> new_ssl_source(Source, SSL); {error, Reason} -> @@ -380,7 +417,8 @@ clear_certs(OldSource) -> write_file(Filename, Bytes) -> ok = filelib:ensure_dir(Filename), case file:write_file(Filename, Bytes) of - ok -> {ok, iolist_to_binary(Filename)}; + ok -> + {ok, iolist_to_binary(Filename)}; {error, Reason} -> ?SLOG(error, #{filename => Filename, msg => "write_file_error", reason => Reason}), throw(Reason) diff --git a/apps/emqx_authz/src/emqx_authz_api_mnesia.erl b/apps/emqx_authz/src/emqx_authz_api_mnesia.erl index 12459d684..2f06ccdbc 100644 --- a/apps/emqx_authz/src/emqx_authz_api_mnesia.erl +++ b/apps/emqx_authz/src/emqx_authz_api_mnesia.erl @@ -30,25 +30,28 @@ -define(ACL_USERNAME_QSCHEMA, [{<<"like_username">>, binary}]). -define(ACL_CLIENTID_QSCHEMA, [{<<"like_clientid">>, binary}]). - --export([ api_spec/0 - , paths/0 - , schema/1 - , fields/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1, + fields/1 +]). %% operation funs --export([ users/2 - , clients/2 - , user/2 - , client/2 - , all/2 - , purge/2 - ]). +-export([ + users/2, + clients/2, + user/2, + client/2, + all/2, + purge/2 +]). %% query funs --export([ query_username/4 - , query_clientid/4]). +-export([ + query_username/4, + query_clientid/4 +]). -export([format_result/1]). @@ -65,279 +68,399 @@ api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). paths() -> - [ "/authorization/sources/built_in_database/username" - , "/authorization/sources/built_in_database/clientid" - , "/authorization/sources/built_in_database/username/:username" - , "/authorization/sources/built_in_database/clientid/:clientid" - , "/authorization/sources/built_in_database/all" - , "/authorization/sources/built_in_database/purge-all"]. + [ + "/authorization/sources/built_in_database/username", + "/authorization/sources/built_in_database/clientid", + "/authorization/sources/built_in_database/username/:username", + "/authorization/sources/built_in_database/clientid/:clientid", + "/authorization/sources/built_in_database/all", + "/authorization/sources/built_in_database/purge-all" + ]. %%-------------------------------------------------------------------- %% Schema for each URI %%-------------------------------------------------------------------- schema("/authorization/sources/built_in_database/username") -> - #{ 'operationId' => users - , get => - #{ tags => [<<"authorization">>] - , description => <<"Show the list of record for username">> - , parameters => - [ ref(emqx_dashboard_swagger, page) - , ref(emqx_dashboard_swagger, limit) - , { like_username - , mk( binary(), #{ in => query - , required => false - , desc => <<"Fuzzy search `username` as substring">>})} - ] - , responses => - #{ 200 => swagger_with_example( {username_response_data, ?TYPE_REF} - , {username, ?PAGE_QUERY_EXAMPLE}) + #{ + 'operationId' => users, + get => + #{ + tags => [<<"authorization">>], + description => <<"Show the list of record for username">>, + parameters => + [ + ref(emqx_dashboard_swagger, page), + ref(emqx_dashboard_swagger, limit), + {like_username, + mk(binary(), #{ + in => query, + required => false, + desc => <<"Fuzzy search `username` as substring">> + })} + ], + responses => + #{ + 200 => swagger_with_example( + {username_response_data, ?TYPE_REF}, + {username, ?PAGE_QUERY_EXAMPLE} + ) + } + }, + post => + #{ + tags => [<<"authorization">>], + description => <<"Add new records for username">>, + 'requestBody' => swagger_with_example( + {rules_for_username, ?TYPE_ARRAY}, + {username, ?POST_ARRAY_EXAMPLE} + ), + responses => + #{ + 204 => <<"Created">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad username or bad rule schema">> + ) + } } - } - , post => - #{ tags => [<<"authorization">>] - , description => <<"Add new records for username">> - , 'requestBody' => swagger_with_example( {rules_for_username, ?TYPE_ARRAY} - , {username, ?POST_ARRAY_EXAMPLE}) - , responses => - #{ 204 => <<"Created">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad username or bad rule schema">>) - } - } }; schema("/authorization/sources/built_in_database/clientid") -> - #{ 'operationId' => clients - , get => - #{ tags => [<<"authorization">>] - , description => <<"Show the list of record for clientid">> - , parameters => - [ ref(emqx_dashboard_swagger, page) - , ref(emqx_dashboard_swagger, limit) - , { like_clientid - , mk( binary() - , #{ in => query - , required => false - , desc => <<"Fuzzy search `clientid` as substring">>}) + #{ + 'operationId' => clients, + get => + #{ + tags => [<<"authorization">>], + description => <<"Show the list of record for clientid">>, + parameters => + [ + ref(emqx_dashboard_swagger, page), + ref(emqx_dashboard_swagger, limit), + {like_clientid, + mk( + binary(), + #{ + in => query, + required => false, + desc => <<"Fuzzy search `clientid` as substring">> + } + )} + ], + responses => + #{ + 200 => swagger_with_example( + {clientid_response_data, ?TYPE_REF}, + {clientid, ?PAGE_QUERY_EXAMPLE} + ) } - ] - , responses => - #{ 200 => swagger_with_example( {clientid_response_data, ?TYPE_REF} - , {clientid, ?PAGE_QUERY_EXAMPLE}) - } - } - , post => - #{ tags => [<<"authorization">>] - , description => <<"Add new records for clientid">> - , 'requestBody' => swagger_with_example( {rules_for_clientid, ?TYPE_ARRAY} - , {clientid, ?POST_ARRAY_EXAMPLE}) - , responses => - #{ 204 => <<"Created">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad clientid or bad rule schema">>) - } - } - }; -schema("/authorization/sources/built_in_database/username/:username") -> - #{ 'operationId' => user - , get => - #{ tags => [<<"authorization">>] - , description => <<"Get record info for username">> - , parameters => [ref(username)] - , responses => - #{ 200 => swagger_with_example( {rules_for_username, ?TYPE_REF} - , {username, ?PUT_MAP_EXAMPLE}) - , 404 => emqx_dashboard_swagger:error_codes( - [?NOT_FOUND], <<"Not Found">>) - } - } - , put => - #{ tags => [<<"authorization">>] - , description => <<"Set record for username">> - , parameters => [ref(username)] - , 'requestBody' => swagger_with_example( {rules_for_username, ?TYPE_REF} - , {username, ?PUT_MAP_EXAMPLE}) - , responses => - #{ 204 => <<"Updated">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad username or bad rule schema">>) - } - } - , delete => - #{ tags => [<<"authorization">>] - , description => <<"Delete one record for username">> - , parameters => [ref(username)] - , responses => - #{ 204 => <<"Deleted">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad username">>) - , 404 => emqx_dashboard_swagger:error_codes( - [?NOT_FOUND], <<"Username Not Found">>) - } - } - }; -schema("/authorization/sources/built_in_database/clientid/:clientid") -> - #{ 'operationId' => client - , get => - #{ tags => [<<"authorization">>] - , description => <<"Get record info for clientid">> - , parameters => [ref(clientid)] - , responses => - #{ 200 => swagger_with_example( {rules_for_clientid, ?TYPE_REF} - , {clientid, ?PUT_MAP_EXAMPLE}) - , 404 => emqx_dashboard_swagger:error_codes( - [?NOT_FOUND], <<"Not Found">>) - } }, - put => - #{ tags => [<<"authorization">>] - , description => <<"Set record for clientid">> - , parameters => [ref(clientid)] - , 'requestBody' => swagger_with_example( {rules_for_clientid, ?TYPE_REF} - , {clientid, ?PUT_MAP_EXAMPLE}) - , responses => - #{ 204 => <<"Updated">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad clientid or bad rule schema">>) - } + post => + #{ + tags => [<<"authorization">>], + description => <<"Add new records for clientid">>, + 'requestBody' => swagger_with_example( + {rules_for_clientid, ?TYPE_ARRAY}, + {clientid, ?POST_ARRAY_EXAMPLE} + ), + responses => + #{ + 204 => <<"Created">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad clientid or bad rule schema">> + ) + } } - , delete => - #{ tags => [<<"authorization">>] - , description => <<"Delete one record for clientid">> - , parameters => [ref(clientid)] - , responses => - #{ 204 => <<"Deleted">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad clientid">>) - , 404 => emqx_dashboard_swagger:error_codes( - [?NOT_FOUND], <<"ClientID Not Found">>) - } + }; +schema("/authorization/sources/built_in_database/username/:username") -> + #{ + 'operationId' => user, + get => + #{ + tags => [<<"authorization">>], + description => <<"Get record info for username">>, + parameters => [ref(username)], + responses => + #{ + 200 => swagger_with_example( + {rules_for_username, ?TYPE_REF}, + {username, ?PUT_MAP_EXAMPLE} + ), + 404 => emqx_dashboard_swagger:error_codes( + [?NOT_FOUND], <<"Not Found">> + ) + } + }, + put => + #{ + tags => [<<"authorization">>], + description => <<"Set record for username">>, + parameters => [ref(username)], + 'requestBody' => swagger_with_example( + {rules_for_username, ?TYPE_REF}, + {username, ?PUT_MAP_EXAMPLE} + ), + responses => + #{ + 204 => <<"Updated">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad username or bad rule schema">> + ) + } + }, + delete => + #{ + tags => [<<"authorization">>], + description => <<"Delete one record for username">>, + parameters => [ref(username)], + responses => + #{ + 204 => <<"Deleted">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad username">> + ), + 404 => emqx_dashboard_swagger:error_codes( + [?NOT_FOUND], <<"Username Not Found">> + ) + } } - }; + }; +schema("/authorization/sources/built_in_database/clientid/:clientid") -> + #{ + 'operationId' => client, + get => + #{ + tags => [<<"authorization">>], + description => <<"Get record info for clientid">>, + parameters => [ref(clientid)], + responses => + #{ + 200 => swagger_with_example( + {rules_for_clientid, ?TYPE_REF}, + {clientid, ?PUT_MAP_EXAMPLE} + ), + 404 => emqx_dashboard_swagger:error_codes( + [?NOT_FOUND], <<"Not Found">> + ) + } + }, + put => + #{ + tags => [<<"authorization">>], + description => <<"Set record for clientid">>, + parameters => [ref(clientid)], + 'requestBody' => swagger_with_example( + {rules_for_clientid, ?TYPE_REF}, + {clientid, ?PUT_MAP_EXAMPLE} + ), + responses => + #{ + 204 => <<"Updated">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad clientid or bad rule schema">> + ) + } + }, + delete => + #{ + tags => [<<"authorization">>], + description => <<"Delete one record for clientid">>, + parameters => [ref(clientid)], + responses => + #{ + 204 => <<"Deleted">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad clientid">> + ), + 404 => emqx_dashboard_swagger:error_codes( + [?NOT_FOUND], <<"ClientID Not Found">> + ) + } + } + }; schema("/authorization/sources/built_in_database/all") -> - #{ 'operationId' => all - , get => - #{ tags => [<<"authorization">>] - , description => <<"Show the list of rules for all">> - , responses => - #{200 => swagger_with_example({rules, ?TYPE_REF}, {all, ?PUT_MAP_EXAMPLE})} + #{ + 'operationId' => all, + get => + #{ + tags => [<<"authorization">>], + description => <<"Show the list of rules for all">>, + responses => + #{200 => swagger_with_example({rules, ?TYPE_REF}, {all, ?PUT_MAP_EXAMPLE})} + }, + post => + #{ + tags => [<<"authorization">>], + description => << + "Create/Update the list of rules for all. " + "Set a empty list to clean up rules" + >>, + 'requestBody' => + swagger_with_example({rules, ?TYPE_REF}, {all, ?PUT_MAP_EXAMPLE}), + responses => + #{ + 204 => <<"Updated">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad rule schema">> + ) + } } - , post => - #{ tags => [<<"authorization">>] - , description => <<"Create/Update the list of rules for all. " - "Set a empty list to clean up rules">> - , 'requestBody' => - swagger_with_example({rules, ?TYPE_REF}, {all, ?PUT_MAP_EXAMPLE}) - , responses => - #{ 204 => <<"Updated">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad rule schema">>) - } - } - }; + }; schema("/authorization/sources/built_in_database/purge-all") -> - #{ 'operationId' => purge - , delete => - #{ tags => [<<"authorization">>] - , description => <<"Purge all records">> - , responses => - #{ 204 => <<"Deleted">> - , 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad Request">>) - } + #{ + 'operationId' => purge, + delete => + #{ + tags => [<<"authorization">>], + description => <<"Purge all records">>, + responses => + #{ + 204 => <<"Deleted">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad Request">> + ) + } } - }. + }. fields(rule_item) -> - [ {topic, mk(string(), - #{ required => true - , desc => <<"Rule on specific topic">> - , example => <<"test/topic/1">> - })} - , {permission, mk(enum([allow, deny]), - #{ desc => <<"Permission">> - , required => true - , example => allow - })} - , {action, mk(enum([publish, subscribe, all]), - #{ required => true - , example => publish - , desc => <<"Authorized action">> - })} + [ + {topic, + mk( + string(), + #{ + required => true, + desc => <<"Rule on specific topic">>, + example => <<"test/topic/1">> + } + )}, + {permission, + mk( + enum([allow, deny]), + #{ + desc => <<"Permission">>, + required => true, + example => allow + } + )}, + {action, + mk( + enum([publish, subscribe, all]), + #{ + required => true, + example => publish, + desc => <<"Authorized action">> + } + )} ]; fields(clientid) -> - [ {clientid, mk(binary(), - #{ in => path - , required => true - , desc => <<"ClientID">> - , example => <<"client1">> - })} + [ + {clientid, + mk( + binary(), + #{ + in => path, + required => true, + desc => <<"ClientID">>, + example => <<"client1">> + } + )} ]; fields(username) -> - [ {username, mk(binary(), - #{ in => path - , required => true - , desc => <<"Username">> - , example => <<"user1">>})} + [ + {username, + mk( + binary(), + #{ + in => path, + required => true, + desc => <<"Username">>, + example => <<"user1">> + } + )} ]; fields(rules_for_username) -> - fields(rules) - ++ fields(username); + fields(rules) ++ + fields(username); fields(username_response_data) -> - [ {data, mk(array(ref(rules_for_username)), #{})} - , {meta, ref(meta)} + [ + {data, mk(array(ref(rules_for_username)), #{})}, + {meta, ref(meta)} ]; fields(rules_for_clientid) -> - fields(rules) - ++ fields(clientid); + fields(rules) ++ + fields(clientid); fields(clientid_response_data) -> - [ {data, mk(array(ref(rules_for_clientid)), #{})} - , {meta, ref(meta)} + [ + {data, mk(array(ref(rules_for_clientid)), #{})}, + {meta, ref(meta)} ]; fields(rules) -> [{rules, mk(array(ref(rule_item)))}]; fields(meta) -> - emqx_dashboard_swagger:fields(page) - ++ emqx_dashboard_swagger:fields(limit) - ++ [{count, mk(integer(), #{example => 1})}]. + emqx_dashboard_swagger:fields(page) ++ + emqx_dashboard_swagger:fields(limit) ++ + [{count, mk(integer(), #{example => 1})}]. %%-------------------------------------------------------------------- %% HTTP API %%-------------------------------------------------------------------- users(get, #{query_string := QueryString}) -> - Response = emqx_mgmt_api:node_query(node(), QueryString, - ?ACL_TABLE, ?ACL_USERNAME_QSCHEMA, ?QUERY_USERNAME_FUN), + Response = emqx_mgmt_api:node_query( + node(), + QueryString, + ?ACL_TABLE, + ?ACL_USERNAME_QSCHEMA, + ?QUERY_USERNAME_FUN + ), emqx_mgmt_util:generate_response(Response); users(post, #{body := Body}) when is_list(Body) -> - lists:foreach(fun(#{<<"username">> := Username, <<"rules">> := Rules}) -> - emqx_authz_mnesia:store_rules({username, Username}, format_rules(Rules)) - end, Body), + lists:foreach( + fun(#{<<"username">> := Username, <<"rules">> := Rules}) -> + emqx_authz_mnesia:store_rules({username, Username}, format_rules(Rules)) + end, + Body + ), {204}. clients(get, #{query_string := QueryString}) -> - Response = emqx_mgmt_api:node_query(node(), QueryString, - ?ACL_TABLE, ?ACL_CLIENTID_QSCHEMA, ?QUERY_CLIENTID_FUN), + Response = emqx_mgmt_api:node_query( + node(), + QueryString, + ?ACL_TABLE, + ?ACL_CLIENTID_QSCHEMA, + ?QUERY_CLIENTID_FUN + ), emqx_mgmt_util:generate_response(Response); clients(post, #{body := Body}) when is_list(Body) -> - lists:foreach(fun(#{<<"clientid">> := ClientID, <<"rules">> := Rules}) -> - emqx_authz_mnesia:store_rules({clientid, ClientID}, format_rules(Rules)) - end, Body), + lists:foreach( + fun(#{<<"clientid">> := ClientID, <<"rules">> := Rules}) -> + emqx_authz_mnesia:store_rules({clientid, ClientID}, format_rules(Rules)) + end, + Body + ), {204}. user(get, #{bindings := #{username := Username}}) -> case emqx_authz_mnesia:get_rules({username, Username}) of - not_found -> {404, #{code => <<"NOT_FOUND">>, message => <<"Not Found">>}}; + not_found -> + {404, #{code => <<"NOT_FOUND">>, message => <<"Not Found">>}}; {ok, Rules} -> - {200, #{username => Username, - rules => [ #{topic => Topic, - action => Action, - permission => Permission - } || {Permission, Action, Topic} <- Rules]} - } + {200, #{ + username => Username, + rules => [ + #{ + topic => Topic, + action => Action, + permission => Permission + } + || {Permission, Action, Topic} <- Rules + ] + }} end; -user(put, #{bindings := #{username := Username}, - body := #{<<"username">> := Username, <<"rules">> := Rules}}) -> +user(put, #{ + bindings := #{username := Username}, + body := #{<<"username">> := Username, <<"rules">> := Rules} +}) -> emqx_authz_mnesia:store_rules({username, Username}, format_rules(Rules)), {204}; user(delete, #{bindings := #{username := Username}}) -> @@ -351,17 +474,25 @@ user(delete, #{bindings := #{username := Username}}) -> client(get, #{bindings := #{clientid := ClientID}}) -> case emqx_authz_mnesia:get_rules({clientid, ClientID}) of - not_found -> {404, #{code => <<"NOT_FOUND">>, message => <<"Not Found">>}}; + not_found -> + {404, #{code => <<"NOT_FOUND">>, message => <<"Not Found">>}}; {ok, Rules} -> - {200, #{clientid => ClientID, - rules => [ #{topic => Topic, - action => Action, - permission => Permission - } || {Permission, Action, Topic} <- Rules]} - } + {200, #{ + clientid => ClientID, + rules => [ + #{ + topic => Topic, + action => Action, + permission => Permission + } + || {Permission, Action, Topic} <- Rules + ] + }} end; -client(put, #{bindings := #{clientid := ClientID}, - body := #{<<"clientid">> := ClientID, <<"rules">> := Rules}}) -> +client(put, #{ + bindings := #{clientid := ClientID}, + body := #{<<"clientid">> := ClientID, <<"rules">> := Rules} +}) -> emqx_authz_mnesia:store_rules({clientid, ClientID}, format_rules(Rules)), {204}; client(delete, #{bindings := #{clientid := ClientID}}) -> @@ -378,11 +509,16 @@ all(get, _) -> not_found -> {200, #{rules => []}}; {ok, Rules} -> - {200, #{rules => [ #{topic => Topic, - action => Action, - permission => Permission - } || {Permission, Action, Topic} <- Rules]} - } + {200, #{ + rules => [ + #{ + topic => Topic, + action => Action, + permission => Permission + } + || {Permission, Action, Topic} <- Rules + ] + }} end; all(post, #{body := #{<<"rules">> := Rules}}) -> emqx_authz_mnesia:store_rules(all, format_rules(Rules)), @@ -394,13 +530,16 @@ purge(delete, _) -> ok = emqx_authz_mnesia:purge_rules(), {204}; [#{<<"enable">> := true}] -> - {400, #{code => <<"BAD_REQUEST">>, - message => - <<"'built_in_database' type source must be disabled before purge.">>}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => + <<"'built_in_database' type source must be disabled before purge.">> + }}; [] -> - {404, #{code => <<"BAD_REQUEST">>, - message => <<"'built_in_database' type source is not found.">> - }} + {404, #{ + code => <<"BAD_REQUEST">>, + message => <<"'built_in_database' type source is not found.">> + }} end. %%-------------------------------------------------------------------- @@ -408,25 +547,43 @@ purge(delete, _) -> query_username(Tab, {_QString, []}, Continuation, Limit) -> Ms = emqx_authz_mnesia:list_username_rules(), - emqx_mgmt_api:select_table_with_count(Tab, Ms, Continuation, Limit, - fun format_result/1); - + emqx_mgmt_api:select_table_with_count( + Tab, + Ms, + Continuation, + Limit, + fun format_result/1 + ); query_username(Tab, {_QString, FuzzyQString}, Continuation, Limit) -> Ms = emqx_authz_mnesia:list_username_rules(), FuzzyFilterFun = fuzzy_filter_fun(FuzzyQString), - emqx_mgmt_api:select_table_with_count(Tab, {Ms, FuzzyFilterFun}, Continuation, Limit, - fun format_result/1). + emqx_mgmt_api:select_table_with_count( + Tab, + {Ms, FuzzyFilterFun}, + Continuation, + Limit, + fun format_result/1 + ). query_clientid(Tab, {_QString, []}, Continuation, Limit) -> Ms = emqx_authz_mnesia:list_clientid_rules(), - emqx_mgmt_api:select_table_with_count(Tab, Ms, Continuation, Limit, - fun format_result/1); - + emqx_mgmt_api:select_table_with_count( + Tab, + Ms, + Continuation, + Limit, + fun format_result/1 + ); query_clientid(Tab, {_QString, FuzzyQString}, Continuation, Limit) -> Ms = emqx_authz_mnesia:list_clientid_rules(), FuzzyFilterFun = fuzzy_filter_fun(FuzzyQString), - emqx_mgmt_api:select_table_with_count(Tab, {Ms, FuzzyFilterFun}, Continuation, Limit, - fun format_result/1). + emqx_mgmt_api:select_table_with_count( + Tab, + {Ms, FuzzyFilterFun}, + Continuation, + Limit, + fun format_result/1 + ). %%-------------------------------------------------------------------- %% Match funcs @@ -434,17 +591,23 @@ query_clientid(Tab, {_QString, FuzzyQString}, Continuation, Limit) -> %% Fuzzy username funcs fuzzy_filter_fun(Fuzzy) -> fun(MsRaws) when is_list(MsRaws) -> - lists:filter( fun(E) -> run_fuzzy_filter(E, Fuzzy) end - , MsRaws) + lists:filter( + fun(E) -> run_fuzzy_filter(E, Fuzzy) end, + MsRaws + ) end. run_fuzzy_filter(_, []) -> true; -run_fuzzy_filter( E = [{username, Username}, _Rule] - , [{username, like, UsernameSubStr} | Fuzzy]) -> +run_fuzzy_filter( + E = [{username, Username}, _Rule], + [{username, like, UsernameSubStr} | Fuzzy] +) -> binary:match(Username, UsernameSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy); -run_fuzzy_filter( E = [{clientid, ClientId}, _Rule] - , [{clientid, like, ClientIdSubStr} | Fuzzy]) -> +run_fuzzy_filter( + E = [{clientid, ClientId}, _Rule], + [{clientid, like, ClientIdSubStr} | Fuzzy] +) -> binary:match(ClientId, ClientIdSubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy). %%-------------------------------------------------------------------- @@ -452,31 +615,52 @@ run_fuzzy_filter( E = [{clientid, ClientId}, _Rule] %% format rule from api format_rules(Rules) when is_list(Rules) -> - lists:foldl(fun(#{<<"topic">> := Topic, - <<"action">> := Action, - <<"permission">> := Permission - }, AccIn) when ?PUBSUB(Action) - andalso ?ALLOW_DENY(Permission) -> - AccIn ++ [{ atom(Permission), atom(Action), Topic }] - end, [], Rules). + lists:foldl( + fun( + #{ + <<"topic">> := Topic, + <<"action">> := Action, + <<"permission">> := Permission + }, + AccIn + ) when + ?PUBSUB(Action) andalso + ?ALLOW_DENY(Permission) + -> + AccIn ++ [{atom(Permission), atom(Action), Topic}] + end, + [], + Rules + ). %% format result from mnesia tab format_result([{username, Username}, {rules, Rules}]) -> - #{username => Username, - rules => [ #{topic => Topic, - action => Action, - permission => Permission - } || {Permission, Action, Topic} <- Rules] - }; + #{ + username => Username, + rules => [ + #{ + topic => Topic, + action => Action, + permission => Permission + } + || {Permission, Action, Topic} <- Rules + ] + }; format_result([{clientid, ClientID}, {rules, Rules}]) -> - #{clientid => ClientID, - rules => [ #{topic => Topic, - action => Action, - permission => Permission - } || {Permission, Action, Topic} <- Rules] - }. + #{ + clientid => ClientID, + rules => [ + #{ + topic => Topic, + action => Action, + permission => Permission + } + || {Permission, Action, Topic} <- Rules + ] + }. atom(B) when is_binary(B) -> - try binary_to_existing_atom(B, utf8) + try + binary_to_existing_atom(B, utf8) catch _Error:_Expection -> binary_to_atom(B) end; @@ -488,25 +672,27 @@ atom(A) when is_atom(A) -> A. swagger_with_example({Ref, TypeP}, {_Name, _Type} = Example) -> emqx_dashboard_swagger:schema_with_examples( - case TypeP of - ?TYPE_REF -> ref(?MODULE, Ref); - ?TYPE_ARRAY -> array(ref(?MODULE, Ref)) - end, - rules_example(Example)). + case TypeP of + ?TYPE_REF -> ref(?MODULE, Ref); + ?TYPE_ARRAY -> array(ref(?MODULE, Ref)) + end, + rules_example(Example) + ). rules_example({ExampleName, ExampleType}) -> {Summary, Example} = case ExampleName of username -> {<<"Username">>, ?USERNAME_RULES_EXAMPLE}; clientid -> {<<"ClientID">>, ?CLIENTID_RULES_EXAMPLE}; - all -> {<<"All">>, ?ALL_RULES_EXAMPLE} + all -> {<<"All">>, ?ALL_RULES_EXAMPLE} end, Value = case ExampleType of - ?PAGE_QUERY_EXAMPLE -> #{ - data => [Example], - meta => ?META_EXAMPLE - }; + ?PAGE_QUERY_EXAMPLE -> + #{ + data => [Example], + meta => ?META_EXAMPLE + }; ?PUT_MAP_EXAMPLE -> Example; ?POST_ARRAY_EXAMPLE -> @@ -515,6 +701,6 @@ rules_example({ExampleName, ExampleType}) -> #{ 'password_based:built_in_database' => #{ summary => Summary, - value => Value + value => Value } }. diff --git a/apps/emqx_authz/src/emqx_authz_api_schema.erl b/apps/emqx_authz/src/emqx_authz_api_schema.erl index 35822bd52..78c8539ae 100644 --- a/apps/emqx_authz/src/emqx_authz_api_schema.erl +++ b/apps/emqx_authz/src/emqx_authz_api_schema.erl @@ -25,58 +25,77 @@ -export([fields/1, authz_sources_types/1]). fields(http) -> - authz_common_fields(http) - ++ [ {url, fun url/1} - , {method, #{ type => enum([get, post]) - , default => get}} - , {headers, fun headers/1} - , {body, map([{fuzzy, term(), binary()}])} - , {request_timeout, mk_duration("Request timeout", #{default => "30s"})}] - ++ maps:to_list(maps:without([ base_url - , pool_type], - maps:from_list(emqx_connector_http:fields(config)))); + authz_common_fields(http) ++ + [ + {url, fun url/1}, + {method, #{ + type => enum([get, post]), + default => get + }}, + {headers, fun headers/1}, + {body, map([{fuzzy, term(), binary()}])}, + {request_timeout, mk_duration("Request timeout", #{default => "30s"})} + ] ++ + maps:to_list( + maps:without( + [ + base_url, + pool_type + ], + maps:from_list(emqx_connector_http:fields(config)) + ) + ); fields('built_in_database') -> authz_common_fields('built_in_database'); fields(mongo_single) -> - authz_mongo_common_fields() - ++ emqx_connector_mongo:fields(single); + authz_mongo_common_fields() ++ + emqx_connector_mongo:fields(single); fields(mongo_rs) -> - authz_mongo_common_fields() - ++ emqx_connector_mongo:fields(rs); + authz_mongo_common_fields() ++ + emqx_connector_mongo:fields(rs); fields(mongo_sharded) -> - authz_mongo_common_fields() - ++ emqx_connector_mongo:fields(sharded); + authz_mongo_common_fields() ++ + emqx_connector_mongo:fields(sharded); fields(mysql) -> - authz_common_fields(mysql) - ++ [ {query, #{type => binary()}}] - ++ emqx_connector_mysql:fields(config); + authz_common_fields(mysql) ++ + [{query, #{type => binary()}}] ++ + emqx_connector_mysql:fields(config); fields(postgresql) -> - authz_common_fields(postgresql) - ++ [ {query, #{type => binary()}}] - ++ proplists:delete(named_queries, emqx_connector_pgsql:fields(config)); + authz_common_fields(postgresql) ++ + [{query, #{type => binary()}}] ++ + proplists:delete(named_queries, emqx_connector_pgsql:fields(config)); fields(redis_single) -> - authz_redis_common_fields() - ++ emqx_connector_redis:fields(single); + authz_redis_common_fields() ++ + emqx_connector_redis:fields(single); fields(redis_sentinel) -> - authz_redis_common_fields() - ++ emqx_connector_redis:fields(sentinel); + authz_redis_common_fields() ++ + emqx_connector_redis:fields(sentinel); fields(redis_cluster) -> - authz_redis_common_fields() - ++ emqx_connector_redis:fields(cluster); + authz_redis_common_fields() ++ + emqx_connector_redis:fields(cluster); fields(file) -> - authz_common_fields(file) - ++ [ { rules, #{ type => binary() - , required => true - , example => - <<"{allow,{username,\"^dashboard?\"},","subscribe,[\"$SYS/#\"]}.\n", - "{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">>}} - ]; + authz_common_fields(file) ++ + [ + {rules, #{ + type => binary(), + required => true, + example => + <<"{allow,{username,\"^dashboard?\"},", "subscribe,[\"$SYS/#\"]}.\n", + "{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">> + }} + ]; fields(position) -> - [ { position - , mk( string() - , #{ desc => <<"Where to place the source">> - , required => true - , in => body})}]. + [ + {position, + mk( + string(), + #{ + desc => <<"Where to place the source">>, + required => true, + in => body + } + )} + ]. %%------------------------------------------------------------------------------ %% http type funcs @@ -86,41 +105,52 @@ url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")]; url(required) -> true; url(_) -> undefined. -headers(type) -> map(); +headers(type) -> + map(); headers(converter) -> fun(Headers) -> - maps:merge(default_headers(), transform_header_name(Headers)) + maps:merge(default_headers(), transform_header_name(Headers)) end; -headers(default) -> default_headers(); -headers(_) -> undefined. +headers(default) -> + default_headers(); +headers(_) -> + undefined. %% headers default_headers() -> - maps:put(<<"content-type">>, - <<"application/json">>, - default_headers_no_content_type()). + maps:put( + <<"content-type">>, + <<"application/json">>, + default_headers_no_content_type() + ). default_headers_no_content_type() -> - #{ <<"accept">> => <<"application/json">> - , <<"cache-control">> => <<"no-cache">> - , <<"connection">> => <<"keep-alive">> - , <<"keep-alive">> => <<"timeout=30, max=1000">> - }. + #{ + <<"accept">> => <<"application/json">>, + <<"cache-control">> => <<"no-cache">>, + <<"connection">> => <<"keep-alive">>, + <<"keep-alive">> => <<"timeout=30, max=1000">> + }. transform_header_name(Headers) -> - maps:fold(fun(K0, V, Acc) -> - K = list_to_binary(string:to_lower(to_list(K0))), - maps:put(K, V, Acc) - end, #{}, Headers). + maps:fold( + fun(K0, V, Acc) -> + K = list_to_binary(string:to_lower(to_list(K0))), + maps:put(K, V, Acc) + end, + #{}, + Headers + ). %%------------------------------------------------------------------------------ %% MonogDB type funcs authz_mongo_common_fields() -> authz_common_fields(mongodb) ++ - [ {collection, fun collection/1} - , {selector, fun selector/1} - ]. + [ + {collection, fun collection/1}, + {selector, fun selector/1} + ]. collection(type) -> binary(); collection(_) -> undefined. @@ -133,19 +163,24 @@ selector(_) -> undefined. authz_redis_common_fields() -> authz_common_fields(redis) ++ - [ {cmd, #{ type => binary() - , example => <<"HGETALL mqtt_authz">>}}]. + [ + {cmd, #{ + type => binary(), + example => <<"HGETALL mqtt_authz">> + }} + ]. %%------------------------------------------------------------------------------ %% Authz api type funcs -authz_common_fields(Type) when is_atom(Type)-> - [ {enable, fun enable/1} - , {type, #{ type => enum([Type]) - , default => Type - , in => body - } - } +authz_common_fields(Type) when is_atom(Type) -> + [ + {enable, fun enable/1}, + {type, #{ + type => enum([Type]), + default => Type, + in => body + }} ]. enable(type) -> boolean(); @@ -158,20 +193,25 @@ enable(_) -> undefined. authz_sources_types(Type) -> case Type of - simple -> [mongodb, redis]; - detailed -> [ mongo_single - , mongo_rs - , mongo_sharded - , redis_single - , redis_sentinel - , redis_cluster] - end - ++ - [ http - , 'built_in_database' - , mysql - , postgresql - , file]. + simple -> + [mongodb, redis]; + detailed -> + [ + mongo_single, + mongo_rs, + mongo_sharded, + redis_single, + redis_sentinel, + redis_cluster + ] + end ++ + [ + http, + 'built_in_database', + mysql, + postgresql, + file + ]. to_list(A) when is_atom(A) -> atom_to_list(A); diff --git a/apps/emqx_authz/src/emqx_authz_api_settings.erl b/apps/emqx_authz/src/emqx_authz_api_settings.erl index b4b2ab5cc..cd00d131e 100644 --- a/apps/emqx_authz/src/emqx_authz_api_settings.erl +++ b/apps/emqx_authz/src/emqx_authz_api_settings.erl @@ -20,10 +20,11 @@ -import(hoconsc, [mk/1, ref/2]). --export([ api_spec/0 - , paths/0 - , schema/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1 +]). -export([settings/2]). @@ -40,33 +41,42 @@ paths() -> %%-------------------------------------------------------------------- schema("/authorization/settings") -> - #{ 'operationId' => settings - , get => - #{ description => <<"Get authorization settings">> - , responses => - #{200 => ref_authz_schema()} + #{ + 'operationId' => settings, + get => + #{ + description => <<"Get authorization settings">>, + responses => + #{200 => ref_authz_schema()} + }, + put => + #{ + description => <<"Update authorization settings">>, + 'requestBody' => ref_authz_schema(), + responses => + #{ + 200 => ref_authz_schema(), + 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) + } } - , put => - #{ description => <<"Update authorization settings">> - , 'requestBody' => ref_authz_schema() - , responses => - #{ 200 => ref_authz_schema() - , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>)} - } - }. + }. ref_authz_schema() -> proplists:delete(sources, emqx_conf_schema:fields("authorization")). settings(get, _Params) -> {200, authorization_settings()}; - -settings(put, #{body := #{<<"no_match">> := NoMatch, - <<"deny_action">> := DenyAction, - <<"cache">> := Cache}}) -> +settings(put, #{ + body := #{ + <<"no_match">> := NoMatch, + <<"deny_action">> := DenyAction, + <<"cache">> := Cache + } +}) -> {ok, _} = emqx_authz_utils:update_config([authorization, no_match], NoMatch), {ok, _} = emqx_authz_utils:update_config( - [authorization, deny_action], DenyAction), + [authorization, deny_action], DenyAction + ), {ok, _} = emqx_authz_utils:update_config([authorization, cache], Cache), ok = emqx_authz_cache:drain_cache(), {200, authorization_settings()}. diff --git a/apps/emqx_authz/src/emqx_authz_api_sources.erl b/apps/emqx_authz/src/emqx_authz_api_sources.erl index 22a4f0f86..759fd92f8 100644 --- a/apps/emqx_authz/src/emqx_authz_api_sources.erl +++ b/apps/emqx_authz/src/emqx_authz_api_sources.erl @@ -29,167 +29,226 @@ -define(API_SCHEMA_MODULE, emqx_authz_api_schema). --export([ get_raw_sources/0 - , get_raw_source/1 - , source_status/2 - , lookup_from_local_node/1 - , lookup_from_all_nodes/1 - ]). +-export([ + get_raw_sources/0, + get_raw_source/1, + source_status/2, + lookup_from_local_node/1, + lookup_from_all_nodes/1 +]). --export([ api_spec/0 - , paths/0 - , schema/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1 +]). --export([ sources/2 - , source/2 - , move_source/2 - ]). +-export([ + sources/2, + source/2, + move_source/2 +]). api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). paths() -> - [ "/authorization/sources" - , "/authorization/sources/:type" - , "/authorization/sources/:type/status" - , "/authorization/sources/:type/move"]. + [ + "/authorization/sources", + "/authorization/sources/:type", + "/authorization/sources/:type/status", + "/authorization/sources/:type/move" + ]. %%-------------------------------------------------------------------- %% Schema for each URI %%-------------------------------------------------------------------- schema("/authorization/sources") -> - #{ 'operationId' => sources - , get => - #{ description => <<"List all authorization sources">> - , responses => - #{ 200 => mk( array(hoconsc:union(authz_sources_type_refs())) - , #{desc => <<"Authorization source">>}) - } + #{ + 'operationId' => sources, + get => + #{ + description => <<"List all authorization sources">>, + responses => + #{ + 200 => mk( + array(hoconsc:union(authz_sources_type_refs())), + #{desc => <<"Authorization source">>} + ) + } + }, + post => + #{ + description => <<"Add a new source">>, + 'requestBody' => mk( + hoconsc:union(authz_sources_type_refs()), + #{desc => <<"Source config">>} + ), + responses => + #{ + 204 => <<"Authorization source created successfully">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], + <<"Bad Request">> + ) + } } - , post => - #{ description => <<"Add a new source">> - , 'requestBody' => mk( hoconsc:union(authz_sources_type_refs()) - , #{desc => <<"Source config">>}) - , responses => - #{ 204 => <<"Authorization source created successfully">> - , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], - <<"Bad Request">>) - } - } - }; + }; schema("/authorization/sources/:type") -> - #{ 'operationId' => source - , get => - #{ description => <<"Get a authorization source">> - , parameters => parameters_field() - , responses => - #{ 200 => mk( hoconsc:union(authz_sources_type_refs()) - , #{desc => <<"Authorization source">>}) - , 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) - } + #{ + 'operationId' => source, + get => + #{ + description => <<"Get a authorization source">>, + parameters => parameters_field(), + responses => + #{ + 200 => mk( + hoconsc:union(authz_sources_type_refs()), + #{desc => <<"Authorization source">>} + ), + 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) + } + }, + put => + #{ + description => <<"Update source">>, + parameters => parameters_field(), + 'requestBody' => mk(hoconsc:union(authz_sources_type_refs())), + responses => + #{ + 204 => <<"Authorization source updated successfully">>, + 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) + } + }, + delete => + #{ + description => <<"Delete source">>, + parameters => parameters_field(), + responses => + #{ + 204 => <<"Deleted successfully">>, + 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) + } } - , put => - #{ description => <<"Update source">> - , parameters => parameters_field() - , 'requestBody' => mk(hoconsc:union(authz_sources_type_refs())) - , responses => - #{ 204 => <<"Authorization source updated successfully">> - , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) - } - } - , delete => - #{ description => <<"Delete source">> - , parameters => parameters_field() - , responses => - #{ 204 => <<"Deleted successfully">> - , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) - } - } - }; + }; schema("/authorization/sources/:type/status") -> - #{ 'operationId' => source_status - , get => - #{ description => <<"Get a authorization source">> - , parameters => parameters_field() - , responses => - #{ 200 => emqx_dashboard_swagger:schema_with_examples( - hoconsc:ref(emqx_authn_schema, "metrics_status_fields"), - status_metrics_example()) - , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad request">>) - , 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) - } + #{ + 'operationId' => source_status, + get => + #{ + description => <<"Get a authorization source">>, + parameters => parameters_field(), + responses => + #{ + 200 => emqx_dashboard_swagger:schema_with_examples( + hoconsc:ref(emqx_authn_schema, "metrics_status_fields"), + status_metrics_example() + ), + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad request">> + ), + 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) + } } - }; + }; schema("/authorization/sources/:type/move") -> - #{ 'operationId' => move_source - , post => - #{ description => <<"Change the order of sources">> - , parameters => parameters_field() - , 'requestBody' => - emqx_dashboard_swagger:schema_with_examples( - ref(?API_SCHEMA_MODULE, position), - position_example()) - , responses => - #{ 204 => <<"No Content">> - , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>) - , 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) - } + #{ + 'operationId' => move_source, + post => + #{ + description => <<"Change the order of sources">>, + parameters => parameters_field(), + 'requestBody' => + emqx_dashboard_swagger:schema_with_examples( + ref(?API_SCHEMA_MODULE, position), + position_example() + ), + responses => + #{ + 204 => <<"No Content">>, + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad Request">> + ), + 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>) + } } - }. - + }. %%-------------------------------------------------------------------- %% Operation functions %%-------------------------------------------------------------------- -sources(Method, #{bindings := #{type := Type} = Bindings } = Req) - when is_atom(Type) -> +sources(Method, #{bindings := #{type := Type} = Bindings} = Req) when + is_atom(Type) +-> sources(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}}); sources(get, _) -> - Sources = lists:foldl(fun (#{<<"type">> := <<"file">>, - <<"enable">> := Enable, <<"path">> := Path}, AccIn) -> - case file:read_file(Path) of - {ok, Rules} -> - lists:append(AccIn, [#{type => file, - enable => Enable, - rules => Rules - }]); - {error, _} -> - lists:append(AccIn, [#{type => file, - enable => Enable, - rules => <<"">> - }]) - end; - (Source, AccIn) -> - lists:append(AccIn, [read_certs(Source)]) - end, [], get_raw_sources()), + Sources = lists:foldl( + fun + ( + #{ + <<"type">> := <<"file">>, + <<"enable">> := Enable, + <<"path">> := Path + }, + AccIn + ) -> + case file:read_file(Path) of + {ok, Rules} -> + lists:append(AccIn, [ + #{ + type => file, + enable => Enable, + rules => Rules + } + ]); + {error, _} -> + lists:append(AccIn, [ + #{ + type => file, + enable => Enable, + rules => <<"">> + } + ]) + end; + (Source, AccIn) -> + lists:append(AccIn, [read_certs(Source)]) + end, + [], + get_raw_sources() + ), {200, #{sources => Sources}}; sources(post, #{body := #{<<"type">> := <<"file">>} = Body}) -> create_authz_file(Body); sources(post, #{body := Body}) -> update_config(?CMD_PREPEND, Body). -source(Method, #{bindings := #{type := Type} = Bindings } = Req) - when is_atom(Type) -> +source(Method, #{bindings := #{type := Type} = Bindings} = Req) when + is_atom(Type) +-> source(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}}); source(get, #{bindings := #{type := Type}}) -> case get_raw_source(Type) of - [] -> {404, #{message => <<"Not found ", Type/binary>>}}; + [] -> + {404, #{message => <<"Not found ", Type/binary>>}}; [#{<<"type">> := <<"file">>, <<"enable">> := Enable, <<"path">> := Path}] -> case file:read_file(Path) of {ok, Rules} -> - {200, #{type => file, - enable => Enable, - rules => Rules - } - }; + {200, #{ + type => file, + enable => Enable, + rules => Rules + }}; {error, Reason} -> - {500, #{code => <<"INTERNAL_ERROR">>, - message => bin(Reason)}} + {500, #{ + code => <<"INTERNAL_ERROR">>, + message => bin(Reason) + }} end; - [Source] -> {200, read_certs(Source)} + [Source] -> + {200, read_certs(Source)} end; source(put, #{bindings := #{type := <<"file">>}, body := #{<<"type">> := <<"file">>} = Body}) -> update_authz_file(Body); @@ -201,44 +260,61 @@ source(delete, #{bindings := #{type := Type}}) -> source_status(get, #{bindings := #{type := Type}}) -> BinType = atom_to_binary(Type, utf8), case get_raw_source(BinType) of - [] -> {404, #{code => <<"NOT_FOUND">>, - message => <<"Not found", BinType/binary>>}}; + [] -> + {404, #{ + code => <<"NOT_FOUND">>, + message => <<"Not found", BinType/binary>> + }}; [#{<<"type">> := <<"file">>}] -> - {400, #{code => <<"BAD_REQUEST">>, - message => <<"Not Support Status">>}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"Not Support Status">> + }}; [_] -> case emqx_authz:lookup(Type) of - #{annotations := #{id := ResourceId }} -> lookup_from_all_nodes(ResourceId); + #{annotations := #{id := ResourceId}} -> lookup_from_all_nodes(ResourceId); _ -> {400, #{code => <<"BAD_REQUEST">>, message => <<"Resource Disable">>}} end end. -move_source(Method, #{bindings := #{type := Type} = Bindings } = Req) - when is_atom(Type) -> +move_source(Method, #{bindings := #{type := Type} = Bindings} = Req) when + is_atom(Type) +-> move_source(Method, Req#{bindings => Bindings#{type => atom_to_binary(Type, utf8)}}); move_source(post, #{bindings := #{type := Type}, body := #{<<"position">> := Position}}) -> - case parse_position(Position) of + case parse_position(Position) of {ok, NPosition} -> try emqx_authz:move(Type, NPosition) of - {ok, _} -> {204}; + {ok, _} -> + {204}; {error, {not_found_source, _Type}} -> - {404, #{code => <<"NOT_FOUND">>, - message => <<"source ", Type/binary, " not found">>}}; + {404, #{ + code => <<"NOT_FOUND">>, + message => <<"source ", Type/binary, " not found">> + }}; {error, {emqx_conf_schema, _}} -> - {400, #{code => <<"BAD_REQUEST">>, - message => <<"BAD_SCHEMA">>}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"BAD_SCHEMA">> + }}; {error, Reason} -> - {400, #{code => <<"BAD_REQUEST">>, - message => bin(Reason)}} + {400, #{ + code => <<"BAD_REQUEST">>, + message => bin(Reason) + }} catch - error : {unknown_authz_source_type, Unknown} -> + error:{unknown_authz_source_type, Unknown} -> NUnknown = bin(Unknown), - {400, #{code => <<"BAD_REQUEST">>, - message => <<"Unknown authz Source Type: ", NUnknown/binary>>}} + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"Unknown authz Source Type: ", NUnknown/binary>> + }} end; {error, Reason} -> - {400, #{code => <<"BAD_REQUEST">>, - message => bin(Reason)}} + {400, #{ + code => <<"BAD_REQUEST">>, + message => bin(Reason) + }} end. %%-------------------------------------------------------------------- @@ -249,46 +325,53 @@ lookup_from_local_node(ResourceId) -> NodeId = node(self()), case emqx_resource:get_instance(ResourceId) of {error, not_found} -> {error, {NodeId, not_found_resource}}; - {ok, _, #{ status := Status, metrics := Metrics }} -> - {ok, {NodeId, Status, Metrics}} + {ok, _, #{status := Status, metrics := Metrics}} -> {ok, {NodeId, Status, Metrics}} end. lookup_from_all_nodes(ResourceId) -> Nodes = mria_mnesia:running_nodes(), case is_ok(emqx_authz_proto_v1:lookup_from_all_nodes(Nodes, ResourceId)) of {ok, ResList} -> - {StatusMap, MetricsMap, _} = make_result_map(ResList), - AggregateStatus = aggregate_status(maps:values(StatusMap)), - AggregateMetrics = aggregate_metrics(maps:values(MetricsMap)), - Fun = fun (_, V1) -> restructure_map(V1) end, - MKMap = fun (Name) -> fun ({Key, Val}) -> #{ node => Key, Name => Val } end end, - HelpFun = fun (M, Name) -> lists:map(MKMap(Name), maps:to_list(M)) end, - case AggregateStatus of - empty_metrics_and_status -> {400, #{code => <<"BAD_REQUEST">>, - message => <<"Resource Not Support Status">>}}; - _ -> {200, #{node_status => HelpFun(StatusMap, status), - node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics), - status => AggregateStatus, - metrics => restructure_map(AggregateMetrics) - } - } - end; + {StatusMap, MetricsMap, _} = make_result_map(ResList), + AggregateStatus = aggregate_status(maps:values(StatusMap)), + AggregateMetrics = aggregate_metrics(maps:values(MetricsMap)), + Fun = fun(_, V1) -> restructure_map(V1) end, + MKMap = fun(Name) -> fun({Key, Val}) -> #{node => Key, Name => Val} end end, + HelpFun = fun(M, Name) -> lists:map(MKMap(Name), maps:to_list(M)) end, + case AggregateStatus of + empty_metrics_and_status -> + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"Resource Not Support Status">> + }}; + _ -> + {200, #{ + node_status => HelpFun(StatusMap, status), + node_metrics => HelpFun(maps:map(Fun, MetricsMap), metrics), + status => AggregateStatus, + metrics => restructure_map(AggregateMetrics) + }} + end; {error, ErrL} -> - {500, #{code => <<"INTERNAL_ERROR">>, - message => bin_t(io_lib:format("~p", [ErrL]))}} + {500, #{ + code => <<"INTERNAL_ERROR">>, + message => bin_t(io_lib:format("~p", [ErrL])) + }} end. -aggregate_status([]) -> empty_metrics_and_status; +aggregate_status([]) -> + empty_metrics_and_status; aggregate_status(AllStatus) -> - Head = fun ([A | _]) -> A end, + Head = fun([A | _]) -> A end, HeadVal = Head(AllStatus), - AllRes = lists:all(fun (Val) -> Val == HeadVal end, AllStatus), + AllRes = lists:all(fun(Val) -> Val == HeadVal end, AllStatus), case AllRes of true -> HeadVal; false -> inconsistent end. -aggregate_metrics([]) -> empty_metrics_and_status; +aggregate_metrics([]) -> + empty_metrics_and_status; aggregate_metrics([HeadMetrics | AllMetrics]) -> CombinerFun = fun ComFun(Val1, Val2) -> @@ -297,8 +380,9 @@ aggregate_metrics([HeadMetrics | AllMetrics]) -> false -> Val1 + Val2 end end, - Fun = fun (ElemMap, AccMap) -> - emqx_map_lib:merge_with(CombinerFun, ElemMap, AccMap) end, + Fun = fun(ElemMap, AccMap) -> + emqx_map_lib:merge_with(CombinerFun, ElemMap, AccMap) + end, lists:foldl(Fun, HeadMetrics, AllMetrics). make_result_map(ResList) -> @@ -306,39 +390,45 @@ make_result_map(ResList) -> fun(Elem, {StatusMap, MetricsMap, ErrorMap}) -> case Elem of {ok, {NodeId, Status, Metrics}} -> - {maps:put(NodeId, Status, StatusMap), - maps:put(NodeId, Metrics, MetricsMap), - ErrorMap + { + maps:put(NodeId, Status, StatusMap), + maps:put(NodeId, Metrics, MetricsMap), + ErrorMap }; {error, {NodeId, Reason}} -> - {StatusMap, - MetricsMap, - maps:put(NodeId, Reason, ErrorMap) - } + {StatusMap, MetricsMap, maps:put(NodeId, Reason, ErrorMap)} end end, lists:foldl(Fun, {maps:new(), maps:new(), maps:new()}, ResList). -restructure_map(#{counters := #{failed := Failed, matched := Match, success := Succ}, - rate := #{matched := #{current := Rate, last5m := Rate5m, max := RateMax} - } - } - ) -> - #{matched => Match, - success => Succ, - failed => Failed, - rate => Rate, - rate_last5m => Rate5m, - rate_max => RateMax - }; +restructure_map(#{ + counters := #{failed := Failed, matched := Match, success := Succ}, + rate := #{matched := #{current := Rate, last5m := Rate5m, max := RateMax}} +}) -> + #{ + matched => Match, + success => Succ, + failed => Failed, + rate => Rate, + rate_last5m => Rate5m, + rate_max => RateMax + }; restructure_map(Error) -> - Error. + Error. bin_t(S) when is_list(S) -> list_to_binary(S). is_ok(ResL) -> - case lists:filter(fun({ok, _}) -> false; (_) -> true end, ResL) of + case + lists:filter( + fun + ({ok, _}) -> false; + (_) -> true + end, + ResL + ) + of [] -> {ok, [Res || {ok, Res} <- ResL]}; ErrL -> {error, ErrL} end. @@ -352,43 +442,60 @@ get_raw_sources() -> merge_default_headers(Sources). merge_default_headers(Sources) -> - lists:map(fun(Source) -> - case maps:find(<<"headers">>, Source) of - {ok, Headers} -> - NewHeaders = - case Source of - #{<<"method">> := <<"get">>} -> - (emqx_authz_schema:headers_no_content_type(converter))(Headers); - #{<<"method">> := <<"post">>} -> - (emqx_authz_schema:headers(converter))(Headers); - _ -> Headers - end, - Source#{<<"headers">> => NewHeaders}; - error -> Source - end - end, Sources). + lists:map( + fun(Source) -> + case maps:find(<<"headers">>, Source) of + {ok, Headers} -> + NewHeaders = + case Source of + #{<<"method">> := <<"get">>} -> + (emqx_authz_schema:headers_no_content_type(converter))(Headers); + #{<<"method">> := <<"post">>} -> + (emqx_authz_schema:headers(converter))(Headers); + _ -> + Headers + end, + Source#{<<"headers">> => NewHeaders}; + error -> + Source + end + end, + Sources + ). get_raw_source(Type) -> - lists:filter(fun (#{<<"type">> := T}) -> - T =:= Type - end, get_raw_sources()). + lists:filter( + fun(#{<<"type">> := T}) -> + T =:= Type + end, + get_raw_sources() + ). update_config(Cmd, Sources) -> case emqx_authz:update(Cmd, Sources) of - {ok, _} -> {204}; + {ok, _} -> + {204}; {error, {pre_config_update, emqx_authz, Reason}} -> - {400, #{code => <<"BAD_REQUEST">>, - message => bin(Reason)}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => bin(Reason) + }}; {error, {post_config_update, emqx_authz, Reason}} -> - {400, #{code => <<"BAD_REQUEST">>, - message => bin(Reason)}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => bin(Reason) + }}; %% TODO: The `Reason` may cann't be trans to json term. (i.e. ecpool start failed) {error, {emqx_conf_schema, _}} -> - {400, #{code => <<"BAD_REQUEST">>, - message => <<"BAD_SCHEMA">>}}; + {400, #{ + code => <<"BAD_REQUEST">>, + message => <<"BAD_SCHEMA">> + }}; {error, Reason} -> - {400, #{code => <<"BAD_REQUEST">>, - message => bin(Reason)}} + {400, #{ + code => <<"BAD_REQUEST">>, + message => bin(Reason) + }} end. read_certs(#{<<"ssl">> := SSL} = Source) -> @@ -399,12 +506,16 @@ read_certs(#{<<"ssl">> := SSL} = Source) -> {ok, NewSSL} -> Source#{<<"ssl">> => NewSSL} end; -read_certs(Source) -> Source. +read_certs(Source) -> + Source. parameters_field() -> - [ {type, mk( enum(?API_SCHEMA_MODULE:authz_sources_types(simple)) - , #{in => path, desc => <<"Authorization type">>}) - } + [ + {type, + mk( + enum(?API_SCHEMA_MODULE:authz_sources_types(simple)), + #{in => path, desc => <<"Authorization type">>} + )} ]. parse_position(<<"front">>) -> @@ -423,50 +534,68 @@ parse_position(_) -> {error, <<"Invalid parameter. Unknow position">>}. position_example() -> - #{ front => - #{ summary => <<"front example">> - , value => #{<<"position">> => <<"front">>}} - , rear => - #{ summary => <<"rear example">> - , value => #{<<"position">> => <<"rear">>}} - , relative_before => - #{ summary => <<"relative example">> - , value => #{<<"position">> => <<"before:file">>}} - , relative_after => - #{ summary => <<"relative example">> - , value => #{<<"position">> => <<"after:file">>}} - }. + #{ + front => + #{ + summary => <<"front example">>, + value => #{<<"position">> => <<"front">>} + }, + rear => + #{ + summary => <<"rear example">>, + value => #{<<"position">> => <<"rear">>} + }, + relative_before => + #{ + summary => <<"relative example">>, + value => #{<<"position">> => <<"before:file">>} + }, + relative_after => + #{ + summary => <<"relative example">>, + value => #{<<"position">> => <<"after:file">>} + } + }. authz_sources_type_refs() -> - [ref(?API_SCHEMA_MODULE, Type) - || Type <- emqx_authz_api_schema:authz_sources_types(detailed)]. + [ + ref(?API_SCHEMA_MODULE, Type) + || Type <- emqx_authz_api_schema:authz_sources_types(detailed) + ]. bin(Term) -> erlang:iolist_to_binary(io_lib:format("~p", [Term])). status_metrics_example() -> - #{ metrics => #{ matched => 0, - success => 0, - failed => 0, - rate => 0.0, - rate_last5m => 0.0, - rate_max => 0.0 - }, - node_metrics => [ #{node => node(), - metrics => #{ matched => 0, - success => 0, - failed => 0, - rate => 0.0, - rate_last5m => 0.0, - rate_max => 0.0 - } - } - ], - status => connected, - node_status => [ #{node => node(), - status => connected - } - ] - }. + #{ + metrics => #{ + matched => 0, + success => 0, + failed => 0, + rate => 0.0, + rate_last5m => 0.0, + rate_max => 0.0 + }, + node_metrics => [ + #{ + node => node(), + metrics => #{ + matched => 0, + success => 0, + failed => 0, + rate => 0.0, + rate_last5m => 0.0, + rate_max => 0.0 + } + } + ], + status => connected, + node_status => [ + #{ + node => node(), + status => connected + } + ] + }. create_authz_file(Body) -> do_update_authz_file(?CMD_PREPEND, Body). @@ -476,4 +605,4 @@ update_authz_file(Body) -> do_update_authz_file(Cmd, Body) -> %% API update will placed in `authz` subdirectory inside EMQX's `data_dir` - update_config(Cmd, Body). + update_config(Cmd, Body). diff --git a/apps/emqx_authz/src/emqx_authz_file.erl b/apps/emqx_authz/src/emqx_authz_file.erl index 729646714..38ff8447a 100644 --- a/apps/emqx_authz/src/emqx_authz_file.erl +++ b/apps/emqx_authz/src/emqx_authz_file.erl @@ -27,28 +27,32 @@ -endif. %% APIs --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4 +]). description() -> "AuthZ with static rules". init(#{path := Path} = Source) -> - Rules = case file:consult(Path) of - {ok, Terms} -> - [emqx_authz_rule:compile(Term) || Term <- Terms]; - {error, Reason} when is_atom(Reason) -> - ?SLOG(alert, #{msg => failed_to_read_acl_file, - path => Path, - explain => emqx_misc:explain_posix(Reason)}), - throw(failed_to_read_acl_file); - {error, Reason} -> - ?SLOG(alert, #{msg => bad_acl_file_content, path => Path, reason => Reason}), - throw(bad_acl_file_content) - end, + Rules = + case file:consult(Path) of + {ok, Terms} -> + [emqx_authz_rule:compile(Term) || Term <- Terms]; + {error, Reason} when is_atom(Reason) -> + ?SLOG(alert, #{ + msg => failed_to_read_acl_file, + path => Path, + explain => emqx_misc:explain_posix(Reason) + }), + throw(failed_to_read_acl_file); + {error, Reason} -> + ?SLOG(alert, #{msg => bad_acl_file_content, path => Path, reason => Reason}), + throw(bad_acl_file_content) + end, Source#{annotations => #{rules => Rules}}. destroy(_Source) -> ok. diff --git a/apps/emqx_authz/src/emqx_authz_http.erl b/apps/emqx_authz/src/emqx_authz_http.erl index 592c59b03..94dfcecf3 100644 --- a/apps/emqx_authz/src/emqx_authz_http.erl +++ b/apps/emqx_authz/src/emqx_authz_http.erl @@ -24,25 +24,28 @@ -behaviour(emqx_authz). %% AuthZ Callbacks --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - , parse_url/1 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4, + parse_url/1 +]). -ifdef(TEST). -compile(export_all). -compile(nowarn_export_all). -endif. --define(PLACEHOLDERS, [?PH_USERNAME, - ?PH_CLIENTID, - ?PH_PEERHOST, - ?PH_PROTONAME, - ?PH_MOUNTPOINT, - ?PH_TOPIC, - ?PH_ACTION]). +-define(PLACEHOLDERS, [ + ?PH_USERNAME, + ?PH_CLIENTID, + ?PH_PEERHOST, + ?PH_PROTONAME, + ?PH_MOUNTPOINT, + ?PH_TOPIC, + ?PH_ACTION +]). description() -> "AuthZ with http". @@ -57,14 +60,17 @@ init(Config) -> destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -authorize( Client - , PubSub - , Topic - , #{ type := http - , annotations := #{id := ResourceID} - , method := Method - , request_timeout := RequestTimeout - } = Config) -> +authorize( + Client, + PubSub, + Topic, + #{ + type := http, + annotations := #{id := ResourceID}, + method := Method, + request_timeout := RequestTimeout + } = Config +) -> Request = generate_request(PubSub, Topic, Client, Config), case emqx_resource:query(ResourceID, {Method, Request, RequestTimeout}) of {ok, 200, _Headers} -> @@ -78,38 +84,47 @@ authorize( Client {ok, _Status, _Headers, _Body} -> nomatch; {error, Reason} -> - ?SLOG(error, #{msg => "http_server_query_failed", - resource => ResourceID, - reason => Reason}), + ?SLOG(error, #{ + msg => "http_server_query_failed", + resource => ResourceID, + reason => Reason + }), ignore end. -parse_config(#{ url := URL - , method := Method - , headers := Headers - , request_timeout := ReqTimeout - } = Conf) -> +parse_config( + #{ + url := URL, + method := Method, + headers := Headers, + request_timeout := ReqTimeout + } = Conf +) -> {BaseURLWithPath, Query} = parse_fullpath(URL), BaseURLMap = parse_url(BaseURLWithPath), - Conf#{ method => Method - , base_url => maps:remove(query, BaseURLMap) - , base_query_template => emqx_authz_utils:parse_deep( - cow_qs:parse_qs(bin(Query)), - ?PLACEHOLDERS) - , body_template => emqx_authz_utils:parse_deep( - maps:to_list(maps:get(body, Conf, #{})), - ?PLACEHOLDERS) - , headers => Headers - , request_timeout => ReqTimeout - %% pool_type default value `random` - , pool_type => random - }. + Conf#{ + method => Method, + base_url => maps:remove(query, BaseURLMap), + base_query_template => emqx_authz_utils:parse_deep( + cow_qs:parse_qs(bin(Query)), + ?PLACEHOLDERS + ), + body_template => emqx_authz_utils:parse_deep( + maps:to_list(maps:get(body, Conf, #{})), + ?PLACEHOLDERS + ), + headers => Headers, + request_timeout => ReqTimeout, + %% pool_type default value `random` + pool_type => random + }. parse_fullpath(RawURL) -> cow_http:parse_fullpath(bin(RawURL)). -parse_url(URL) - when URL =:= undefined -> +parse_url(URL) when + URL =:= undefined +-> #{}; parse_url(URL) -> {ok, URIMap} = emqx_http_lib:uri_parse(URL), @@ -120,28 +135,31 @@ parse_url(URL) -> URIMap end. -generate_request( PubSub - , Topic - , Client - , #{ method := Method - , base_url := #{path := Path} - , base_query_template := BaseQueryTemplate - , headers := Headers - , body_template := BodyTemplate - }) -> +generate_request( + PubSub, + Topic, + Client, + #{ + method := Method, + base_url := #{path := Path}, + base_query_template := BaseQueryTemplate, + headers := Headers, + body_template := BodyTemplate + } +) -> Values = client_vars(Client, PubSub, Topic), Body = emqx_authz_utils:render_deep(BodyTemplate, Values), NBaseQuery = emqx_authz_utils:render_deep(BaseQueryTemplate, Values), case Method of - get -> + get -> NPath = append_query(Path, NBaseQuery ++ Body), {NPath, Headers}; _ -> NPath = append_query(Path, NBaseQuery), NBody = serialize_body( - proplists:get_value(<<"Accept">>, Headers, <<"application/json">>), - Body - ), + proplists:get_value(<<"Accept">>, Headers, <<"application/json">>), + Body + ), {NPath, Headers, NBody} end. @@ -161,9 +179,13 @@ query_string([], Acc) -> <<>> end; query_string([{K, V} | More], Acc) -> - query_string( More - , [ ["&", emqx_http_lib:uri_encode(K), "=", emqx_http_lib:uri_encode(V)] - | Acc]). + query_string( + More, + [ + ["&", emqx_http_lib:uri_encode(K), "=", emqx_http_lib:uri_encode(V)] + | Acc + ] + ). serialize_body(<<"application/json">>, Body) -> jsx:encode(Body); @@ -172,9 +194,9 @@ serialize_body(<<"application/x-www-form-urlencoded">>, Body) -> client_vars(Client, PubSub, Topic) -> Client#{ - action => PubSub, - topic => Topic - }. + action => PubSub, + topic => Topic + }. bin(A) when is_atom(A) -> atom_to_binary(A, utf8); bin(B) when is_binary(B) -> B; diff --git a/apps/emqx_authz/src/emqx_authz_mnesia.erl b/apps/emqx_authz/src/emqx_authz_mnesia.erl index f02419b0c..0bb85b96a 100644 --- a/apps/emqx_authz/src/emqx_authz_mnesia.erl +++ b/apps/emqx_authz/src/emqx_authz_mnesia.erl @@ -29,38 +29,40 @@ -define(ACL_TABLE_USERNAME, 1). -define(ACL_TABLE_CLIENTID, 2). --type(username() :: {username, binary()}). --type(clientid() :: {clientid, binary()}). --type(who() :: username() | clientid() | all). +-type username() :: {username, binary()}. +-type clientid() :: {clientid, binary()}. +-type who() :: username() | clientid() | all. --type(rule() :: {emqx_authz_rule:permission(), emqx_authz_rule:action(), emqx_topic:topic()}). --type(rules() :: [rule()]). +-type rule() :: {emqx_authz_rule:permission(), emqx_authz_rule:action(), emqx_topic:topic()}. +-type rules() :: [rule()]. -record(emqx_acl, { - who :: ?ACL_TABLE_ALL | {?ACL_TABLE_USERNAME, binary()} | {?ACL_TABLE_CLIENTID, binary()}, - rules :: rules() - }). + who :: ?ACL_TABLE_ALL | {?ACL_TABLE_USERNAME, binary()} | {?ACL_TABLE_CLIENTID, binary()}, + rules :: rules() +}). -behaviour(emqx_authz). %% AuthZ Callbacks --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4 +]). %% Management API --export([ mnesia/1 - , init_tables/0 - , store_rules/2 - , purge_rules/0 - , get_rules/1 - , delete_rules/1 - , list_clientid_rules/0 - , list_username_rules/0 - , record_count/0 - ]). +-export([ + mnesia/1, + init_tables/0, + store_rules/2, + purge_rules/0, + get_rules/1, + delete_rules/1, + list_clientid_rules/0, + list_username_rules/0, + record_count/0 +]). -ifdef(TEST). -compile(export_all). @@ -69,14 +71,15 @@ -boot_mnesia({mnesia, [boot]}). --spec(mnesia(boot | copy) -> ok). +-spec mnesia(boot | copy) -> ok. mnesia(boot) -> ok = mria:create_table(?ACL_TABLE, [ - {type, ordered_set}, - {rlog_shard, ?ACL_SHARDED}, - {storage, disc_copies}, - {attributes, record_info(fields, ?ACL_TABLE)}, - {storage_properties, [{ets, [{read_concurrency, true}]}]}]). + {type, ordered_set}, + {rlog_shard, ?ACL_SHARDED}, + {storage, disc_copies}, + {attributes, record_info(fields, ?ACL_TABLE)}, + {storage_properties, [{ets, [{read_concurrency, true}]}]} + ]). %%-------------------------------------------------------------------- %% emqx_authz callbacks @@ -89,19 +92,25 @@ init(Source) -> Source. destroy(_Source) -> ok. -authorize(#{username := Username, - clientid := Clientid - } = Client, PubSub, Topic, #{type := 'built_in_database'}) -> - - Rules = case mnesia:dirty_read(?ACL_TABLE, {?ACL_TABLE_CLIENTID, Clientid}) of - [] -> []; - [#emqx_acl{rules = Rules0}] when is_list(Rules0) -> Rules0 - end - ++ case mnesia:dirty_read(?ACL_TABLE, {?ACL_TABLE_USERNAME, Username}) of +authorize( + #{ + username := Username, + clientid := Clientid + } = Client, + PubSub, + Topic, + #{type := 'built_in_database'} +) -> + Rules = + case mnesia:dirty_read(?ACL_TABLE, {?ACL_TABLE_CLIENTID, Clientid}) of + [] -> []; + [#emqx_acl{rules = Rules0}] when is_list(Rules0) -> Rules0 + end ++ + case mnesia:dirty_read(?ACL_TABLE, {?ACL_TABLE_USERNAME, Username}) of [] -> []; [#emqx_acl{rules = Rules1}] when is_list(Rules1) -> Rules1 - end - ++ case mnesia:dirty_read(?ACL_TABLE, ?ACL_TABLE_ALL) of + end ++ + case mnesia:dirty_read(?ACL_TABLE, ?ACL_TABLE_ALL) of [] -> []; [#emqx_acl{rules = Rules2}] when is_list(Rules2) -> Rules2 end, @@ -112,12 +121,12 @@ authorize(#{username := Username, %%-------------------------------------------------------------------- %% Init --spec(init_tables() -> ok). +-spec init_tables() -> ok. init_tables() -> ok = mria_rlog:wait_for_shards([?ACL_SHARDED], infinity). %% @doc Update authz rules --spec(store_rules(who(), rules()) -> ok). +-spec store_rules(who(), rules()) -> ok. store_rules({username, Username}, Rules) -> Record = #emqx_acl{who = {?ACL_TABLE_USERNAME, Username}, rules = normalize_rules(Rules)}, mria:dirty_write(Record); @@ -129,16 +138,17 @@ store_rules(all, Rules) -> mria:dirty_write(Record). %% @doc Clean all authz rules for (username & clientid & all) --spec(purge_rules() -> ok). +-spec purge_rules() -> ok. purge_rules() -> ok = lists:foreach( - fun(Key) -> - ok = mria:dirty_delete(?ACL_TABLE, Key) - end, - mnesia:dirty_all_keys(?ACL_TABLE)). + fun(Key) -> + ok = mria:dirty_delete(?ACL_TABLE, Key) + end, + mnesia:dirty_all_keys(?ACL_TABLE) + ). %% @doc Get one record --spec(get_rules(who()) -> {ok, rules()} | not_found). +-spec get_rules(who()) -> {ok, rules()} | not_found. get_rules({username, Username}) -> do_get_rules({?ACL_TABLE_USERNAME, Username}); get_rules({clientid, Clientid}) -> @@ -147,7 +157,7 @@ get_rules(all) -> do_get_rules(?ACL_TABLE_ALL). %% @doc Delete one record --spec(delete_rules(who()) -> ok). +-spec delete_rules(who()) -> ok. delete_rules({username, Username}) -> mria:dirty_delete(?ACL_TABLE, {?ACL_TABLE_USERNAME, Username}); delete_rules({clientid, Clientid}) -> @@ -155,21 +165,23 @@ delete_rules({clientid, Clientid}) -> delete_rules(all) -> mria:dirty_delete(?ACL_TABLE, ?ACL_TABLE_ALL). --spec(list_username_rules() -> ets:match_spec()). +-spec list_username_rules() -> ets:match_spec(). list_username_rules() -> ets:fun2ms( - fun(#emqx_acl{who = {?ACL_TABLE_USERNAME, Username}, rules = Rules}) -> - [{username, Username}, {rules, Rules}] - end). + fun(#emqx_acl{who = {?ACL_TABLE_USERNAME, Username}, rules = Rules}) -> + [{username, Username}, {rules, Rules}] + end + ). --spec(list_clientid_rules() -> ets:match_spec()). +-spec list_clientid_rules() -> ets:match_spec(). list_clientid_rules() -> ets:fun2ms( - fun(#emqx_acl{who = {?ACL_TABLE_CLIENTID, Clientid}, rules = Rules}) -> - [{clientid, Clientid}, {rules, Rules}] - end). + fun(#emqx_acl{who = {?ACL_TABLE_CLIENTID, Clientid}, rules = Rules}) -> + [{clientid, Clientid}, {rules, Rules}] + end + ). --spec(record_count() -> non_neg_integer()). +-spec record_count() -> non_neg_integer(). record_count() -> mnesia:table_info(?ACL_TABLE, size). @@ -181,9 +193,7 @@ normalize_rules(Rules) -> lists:map(fun normalize_rule/1, Rules). normalize_rule({Permission, Action, Topic}) -> - {normalize_permission(Permission), - normalize_action(Action), - normalize_topic(Topic)}; + {normalize_permission(Permission), normalize_action(Action), normalize_topic(Topic)}; normalize_rule(Rule) -> error({invalid_rule, Rule}). @@ -206,8 +216,9 @@ do_get_rules(Key) -> [] -> not_found end. -do_authorize(_Client, _PubSub, _Topic, []) -> nomatch; -do_authorize(Client, PubSub, Topic, [ {Permission, Action, TopicFilter} | Tail]) -> +do_authorize(_Client, _PubSub, _Topic, []) -> + nomatch; +do_authorize(Client, PubSub, Topic, [{Permission, Action, TopicFilter} | Tail]) -> Rule = emqx_authz_rule:compile({Permission, all, Action, [TopicFilter]}), case emqx_authz_rule:match(Client, PubSub, Topic, Rule) of {matched, Permission} -> {matched, Permission}; diff --git a/apps/emqx_authz/src/emqx_authz_mongodb.erl b/apps/emqx_authz/src/emqx_authz_mongodb.erl index 158bff152..2bac33003 100644 --- a/apps/emqx_authz/src/emqx_authz_mongodb.erl +++ b/apps/emqx_authz/src/emqx_authz_mongodb.erl @@ -24,62 +24,83 @@ -behaviour(emqx_authz). %% AuthZ Callbacks --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4 +]). -ifdef(TEST). -compile(export_all). -compile(nowarn_export_all). -endif. --define(PLACEHOLDERS, [?PH_USERNAME, - ?PH_CLIENTID, - ?PH_PEERHOST]). +-define(PLACEHOLDERS, [ + ?PH_USERNAME, + ?PH_CLIENTID, + ?PH_PEERHOST +]). description() -> "AuthZ with MongoDB". init(#{selector := Selector} = Source) -> case emqx_authz_utils:create_resource(emqx_connector_mongo, Source) of - {error, Reason} -> error({load_config_error, Reason}); - {ok, Id} -> Source#{annotations => #{id => Id}, - selector_template => emqx_authz_utils:parse_deep( - Selector, - ?PLACEHOLDERS)} + {error, Reason} -> + error({load_config_error, Reason}); + {ok, Id} -> + Source#{ + annotations => #{id => Id}, + selector_template => emqx_authz_utils:parse_deep( + Selector, + ?PLACEHOLDERS + ) + } end. destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -authorize(Client, PubSub, Topic, - #{collection := Collection, - selector_template := SelectorTemplate, - annotations := #{id := ResourceID} - }) -> +authorize( + Client, + PubSub, + Topic, + #{ + collection := Collection, + selector_template := SelectorTemplate, + annotations := #{id := ResourceID} + } +) -> RenderedSelector = emqx_authz_utils:render_deep(SelectorTemplate, Client), - Result = try - emqx_resource:query(ResourceID, {find, Collection, RenderedSelector, #{}}) - catch - error:Error -> {error, Error} - end, + Result = + try + emqx_resource:query(ResourceID, {find, Collection, RenderedSelector, #{}}) + catch + error:Error -> {error, Error} + end, case Result of {error, Reason} -> - ?SLOG(error, #{msg => "query_mongo_error", - reason => Reason, - collection => Collection, - selector => RenderedSelector, - resource_id => ResourceID}), + ?SLOG(error, #{ + msg => "query_mongo_error", + reason => Reason, + collection => Collection, + selector => RenderedSelector, + resource_id => ResourceID + }), + nomatch; + [] -> nomatch; - [] -> nomatch; Rows -> - Rules = [ emqx_authz_rule:compile({Permission, all, Action, Topics}) - || #{<<"topics">> := Topics, - <<"permission">> := Permission, - <<"action">> := Action} <- Rows], + Rules = [ + emqx_authz_rule:compile({Permission, all, Action, Topics}) + || #{ + <<"topics">> := Topics, + <<"permission">> := Permission, + <<"action">> := Action + } <- Rows + ], do_authorize(Client, PubSub, Topic, Rules) end. diff --git a/apps/emqx_authz/src/emqx_authz_mysql.erl b/apps/emqx_authz/src/emqx_authz_mysql.erl index 31e8dcd98..d88273e14 100644 --- a/apps/emqx_authz/src/emqx_authz_mysql.erl +++ b/apps/emqx_authz/src/emqx_authz_mysql.erl @@ -24,66 +24,89 @@ -behaviour(emqx_authz). %% AuthZ Callbacks --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4 +]). -ifdef(TEST). -compile(export_all). -compile(nowarn_export_all). -endif. --define(PLACEHOLDERS, [?PH_USERNAME, - ?PH_CLIENTID, - ?PH_PEERHOST, - ?PH_CERT_CN_NAME, - ?PH_CERT_SUBJECT]). +-define(PLACEHOLDERS, [ + ?PH_USERNAME, + ?PH_CLIENTID, + ?PH_PEERHOST, + ?PH_CERT_CN_NAME, + ?PH_CERT_SUBJECT +]). description() -> "AuthZ with Mysql". init(#{query := SQL} = Source) -> case emqx_authz_utils:create_resource(emqx_connector_mysql, Source) of - {error, Reason} -> error({load_config_error, Reason}); - {ok, Id} -> Source#{annotations => - #{id => Id, - query => emqx_authz_utils:parse_sql( - SQL, - '?', - ?PLACEHOLDERS)}} + {error, Reason} -> + error({load_config_error, Reason}); + {ok, Id} -> + Source#{ + annotations => + #{ + id => Id, + query => emqx_authz_utils:parse_sql( + SQL, + '?', + ?PLACEHOLDERS + ) + } + } end. destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -authorize(Client, PubSub, Topic, - #{annotations := #{id := ResourceID, - query := {Query, Params} - } - }) -> +authorize( + Client, + PubSub, + Topic, + #{ + annotations := #{ + id := ResourceID, + query := {Query, Params} + } + } +) -> RenderParams = emqx_authz_utils:render_sql_params(Params, Client), case emqx_resource:query(ResourceID, {sql, Query, RenderParams}) of - {ok, _Columns, []} -> nomatch; + {ok, _Columns, []} -> + nomatch; {ok, Columns, Rows} -> do_authorize(Client, PubSub, Topic, Columns, Rows); {error, Reason} -> - ?SLOG(error, #{ msg => "query_mysql_error" - , reason => Reason - , query => Query - , params => RenderParams - , resource_id => ResourceID}), + ?SLOG(error, #{ + msg => "query_mysql_error", + reason => Reason, + query => Query, + params => RenderParams, + resource_id => ResourceID + }), nomatch end. - do_authorize(_Client, _PubSub, _Topic, _Columns, []) -> nomatch; do_authorize(Client, PubSub, Topic, Columns, [Row | Tail]) -> - case emqx_authz_rule:match(Client, PubSub, Topic, - emqx_authz_rule:compile(format_result(Columns, Row)) - ) of + case + emqx_authz_rule:match( + Client, + PubSub, + Topic, + emqx_authz_rule:compile(format_result(Columns, Row)) + ) + of {matched, Permission} -> {matched, Permission}; nomatch -> do_authorize(Client, PubSub, Topic, Columns, Tail) end. @@ -97,5 +120,5 @@ format_result(Columns, Row) -> index(Elem, List) -> index(Elem, List, 1). index(_Elem, [], _Index) -> {error, not_found}; -index(Elem, [ Elem | _List], Index) -> Index; -index(Elem, [ _ | List], Index) -> index(Elem, List, Index + 1). +index(Elem, [Elem | _List], Index) -> Index; +index(Elem, [_ | List], Index) -> index(Elem, List, Index + 1). diff --git a/apps/emqx_authz/src/emqx_authz_postgresql.erl b/apps/emqx_authz/src/emqx_authz_postgresql.erl index 275b39f2b..4305addb7 100644 --- a/apps/emqx_authz/src/emqx_authz_postgresql.erl +++ b/apps/emqx_authz/src/emqx_authz_postgresql.erl @@ -24,42 +24,53 @@ -behaviour(emqx_authz). %% AuthZ Callbacks --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4 +]). -ifdef(TEST). -compile(export_all). -compile(nowarn_export_all). -endif. --define(PLACEHOLDERS, [?PH_USERNAME, - ?PH_CLIENTID, - ?PH_PEERHOST, - ?PH_CERT_CN_NAME, - ?PH_CERT_SUBJECT]). +-define(PLACEHOLDERS, [ + ?PH_USERNAME, + ?PH_CLIENTID, + ?PH_PEERHOST, + ?PH_CERT_CN_NAME, + ?PH_CERT_SUBJECT +]). description() -> "AuthZ with PostgreSQL". init(#{query := SQL0} = Source) -> {SQL, PlaceHolders} = emqx_authz_utils:parse_sql( - SQL0, - '$n', - ?PLACEHOLDERS), + SQL0, + '$n', + ?PLACEHOLDERS + ), ResourceID = emqx_authz_utils:make_resource_id(emqx_connector_pgsql), - case emqx_resource:create_local( + case + emqx_resource:create_local( ResourceID, ?RESOURCE_GROUP, emqx_connector_pgsql, Source#{named_queries => #{ResourceID => SQL}}, - #{}) of + #{} + ) + of {ok, _} -> - Source#{annotations => - #{id => ResourceID, - placeholders => PlaceHolders}}; + Source#{ + annotations => + #{ + id => ResourceID, + placeholders => PlaceHolders + } + }; {error, Reason} -> error({load_config_error, Reason}) end. @@ -67,30 +78,44 @@ init(#{query := SQL0} = Source) -> destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -authorize(Client, PubSub, Topic, - #{annotations := #{id := ResourceID, - placeholders := Placeholders - } - }) -> +authorize( + Client, + PubSub, + Topic, + #{ + annotations := #{ + id := ResourceID, + placeholders := Placeholders + } + } +) -> RenderedParams = emqx_authz_utils:render_sql_params(Placeholders, Client), case emqx_resource:query(ResourceID, {prepared_query, ResourceID, RenderedParams}) of - {ok, _Columns, []} -> nomatch; + {ok, _Columns, []} -> + nomatch; {ok, Columns, Rows} -> do_authorize(Client, PubSub, Topic, Columns, Rows); {error, Reason} -> - ?SLOG(error, #{ msg => "query_postgresql_error" - , reason => Reason - , params => RenderedParams - , resource_id => ResourceID}), + ?SLOG(error, #{ + msg => "query_postgresql_error", + reason => Reason, + params => RenderedParams, + resource_id => ResourceID + }), nomatch end. do_authorize(_Client, _PubSub, _Topic, _Columns, []) -> nomatch; do_authorize(Client, PubSub, Topic, Columns, [Row | Tail]) -> - case emqx_authz_rule:match(Client, PubSub, Topic, - emqx_authz_rule:compile(format_result(Columns, Row)) - ) of + case + emqx_authz_rule:match( + Client, + PubSub, + Topic, + emqx_authz_rule:compile(format_result(Columns, Row)) + ) + of {matched, Permission} -> {matched, Permission}; nomatch -> do_authorize(Client, PubSub, Topic, Columns, Tail) end. @@ -104,6 +129,9 @@ format_result(Columns, Row) -> index(Key, N, TupleList) when is_integer(N) -> Tuple = lists:keyfind(Key, N, TupleList), index(Tuple, TupleList, 1); -index(_Tuple, [], _Index) -> {error, not_found}; -index(Tuple, [Tuple | _TupleList], Index) -> Index; -index(Tuple, [_ | TupleList], Index) -> index(Tuple, TupleList, Index + 1). +index(_Tuple, [], _Index) -> + {error, not_found}; +index(Tuple, [Tuple | _TupleList], Index) -> + Index; +index(Tuple, [_ | TupleList], Index) -> + index(Tuple, TupleList, Index + 1). diff --git a/apps/emqx_authz/src/emqx_authz_redis.erl b/apps/emqx_authz/src/emqx_authz_redis.erl index 7e9ea8325..54ec16475 100644 --- a/apps/emqx_authz/src/emqx_authz_redis.erl +++ b/apps/emqx_authz/src/emqx_authz_redis.erl @@ -24,22 +24,25 @@ -behaviour(emqx_authz). %% AuthZ Callbacks --export([ description/0 - , init/1 - , destroy/1 - , authorize/4 - ]). +-export([ + description/0, + init/1, + destroy/1, + authorize/4 +]). -ifdef(TEST). -compile(export_all). -compile(nowarn_export_all). -endif. --define(PLACEHOLDERS, [?PH_CERT_CN_NAME, - ?PH_CERT_SUBJECT, - ?PH_PEERHOST, - ?PH_CLIENTID, - ?PH_USERNAME]). +-define(PLACEHOLDERS, [ + ?PH_CERT_CN_NAME, + ?PH_CERT_SUBJECT, + ?PH_PEERHOST, + ?PH_CLIENTID, + ?PH_USERNAME +]). description() -> "AuthZ with Redis". @@ -48,38 +51,54 @@ init(#{cmd := CmdStr} = Source) -> Cmd = tokens(CmdStr), CmdTemplate = emqx_authz_utils:parse_deep(Cmd, ?PLACEHOLDERS), case emqx_authz_utils:create_resource(emqx_connector_redis, Source) of - {error, Reason} -> error({load_config_error, Reason}); - {ok, Id} -> Source#{annotations => #{id => Id}, - cmd_template => CmdTemplate} + {error, Reason} -> + error({load_config_error, Reason}); + {ok, Id} -> + Source#{ + annotations => #{id => Id}, + cmd_template => CmdTemplate + } end. destroy(#{annotations := #{id := Id}}) -> ok = emqx_resource:remove_local(Id). -authorize(Client, PubSub, Topic, - #{cmd_template := CmdTemplate, - annotations := #{id := ResourceID} - }) -> +authorize( + Client, + PubSub, + Topic, + #{ + cmd_template := CmdTemplate, + annotations := #{id := ResourceID} + } +) -> Cmd = emqx_authz_utils:render_deep(CmdTemplate, Client), case emqx_resource:query(ResourceID, {cmd, Cmd}) of - {ok, []} -> nomatch; + {ok, []} -> + nomatch; {ok, Rows} -> do_authorize(Client, PubSub, Topic, Rows); {error, Reason} -> - ?SLOG(error, #{ msg => "query_redis_error" - , reason => Reason - , cmd => Cmd - , resource_id => ResourceID}), + ?SLOG(error, #{ + msg => "query_redis_error", + reason => Reason, + cmd => Cmd, + resource_id => ResourceID + }), nomatch end. do_authorize(_Client, _PubSub, _Topic, []) -> nomatch; do_authorize(Client, PubSub, Topic, [TopicFilter, Action | Tail]) -> - case emqx_authz_rule:match( - Client, PubSub, Topic, - emqx_authz_rule:compile({allow, all, Action, [TopicFilter]}) - ) of + case + emqx_authz_rule:match( + Client, + PubSub, + Topic, + emqx_authz_rule:compile({allow, 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_rule.erl b/apps/emqx_authz/src/emqx_authz_rule.erl index 4333cf0e7..c5ad0b657 100644 --- a/apps/emqx_authz/src/emqx_authz_rule.erl +++ b/apps/emqx_authz/src/emqx_authz_rule.erl @@ -26,50 +26,66 @@ -endif. %% APIs --export([ match/4 - , matches/4 - , compile/1 - ]). +-export([ + match/4, + matches/4, + compile/1 +]). --type(ipaddress() :: {ipaddr, esockd_cidr:cidr_string()} | - {ipaddrs, list(esockd_cidr:cidr_string())}). +-type ipaddress() :: + {ipaddr, esockd_cidr:cidr_string()} + | {ipaddrs, list(esockd_cidr:cidr_string())}. --type(username() :: {username, binary()}). +-type username() :: {username, binary()}. --type(clientid() :: {clientid, binary()}). +-type clientid() :: {clientid, binary()}. --type(who() :: ipaddress() | username() | clientid() | - {'and', [ipaddress() | username() | clientid()]} | - {'or', [ipaddress() | username() | clientid()]} | - all). +-type who() :: + ipaddress() + | username() + | clientid() + | {'and', [ipaddress() | username() | clientid()]} + | {'or', [ipaddress() | username() | clientid()]} + | all. --type(action() :: subscribe | publish | all). --type(permission() :: allow | deny). +-type action() :: subscribe | publish | all. +-type permission() :: allow | deny. --type(rule() :: {permission(), who(), action(), list(emqx_types:topic())}). +-type rule() :: {permission(), who(), action(), list(emqx_types:topic())}. --export_type([ action/0 - , permission/0 - ]). +-export_type([ + action/0, + permission/0 +]). -compile({Permission, all}) - when ?ALLOW_DENY(Permission) -> {Permission, all, all, [compile_topic(<<"#">>)]}; -compile({Permission, Who, Action, TopicFilters}) - when ?ALLOW_DENY(Permission), ?PUBSUB(Action), is_list(TopicFilters) -> - { atom(Permission), compile_who(Who), atom(Action) - , [compile_topic(Topic) || Topic <- TopicFilters]}. +compile({Permission, all}) when + ?ALLOW_DENY(Permission) +-> + {Permission, all, all, [compile_topic(<<"#">>)]}; +compile({Permission, Who, Action, TopicFilters}) when + ?ALLOW_DENY(Permission), ?PUBSUB(Action), is_list(TopicFilters) +-> + {atom(Permission), compile_who(Who), atom(Action), [ + compile_topic(Topic) + || Topic <- TopicFilters + ]}. -compile_who(all) -> all; -compile_who({user, Username}) -> compile_who({username, Username}); +compile_who(all) -> + all; +compile_who({user, Username}) -> + compile_who({username, Username}); compile_who({username, {re, Username}}) -> {ok, MP} = re:compile(bin(Username)), {username, MP}; -compile_who({username, Username}) -> {username, {eq, bin(Username)}}; -compile_who({client, Clientid}) -> compile_who({clientid, Clientid}); +compile_who({username, Username}) -> + {username, {eq, bin(Username)}}; +compile_who({client, Clientid}) -> + compile_who({clientid, Clientid}); compile_who({clientid, {re, Clientid}}) -> {ok, MP} = re:compile(bin(Clientid)), {clientid, MP}; -compile_who({clientid, Clientid}) -> {clientid, {eq, bin(Clientid)}}; +compile_who({clientid, Clientid}) -> + {clientid, {eq, bin(Clientid)}}; compile_who({ipaddr, CIDR}) -> {ipaddr, esockd_cidr:parse(CIDR, true)}; compile_who({ipaddrs, CIDRs}) -> @@ -86,7 +102,7 @@ compile_topic({eq, Topic}) -> compile_topic(Topic) -> Words = emqx_topic:words(bin(Topic)), case pattern(Words) of - true -> {pattern, Words}; + true -> {pattern, Words}; false -> Words end. @@ -94,7 +110,8 @@ pattern(Words) -> lists:member(?PH_USERNAME, Words) orelse lists:member(?PH_CLIENTID, Words). atom(B) when is_binary(B) -> - try binary_to_existing_atom(B, utf8) + try + binary_to_existing_atom(B, utf8) catch _E:_S -> binary_to_atom(B) end; @@ -105,21 +122,24 @@ bin(L) when is_list(L) -> bin(B) when is_binary(B) -> B. --spec(matches(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic(), [rule()]) - -> {matched, allow} | {matched, deny} | nomatch). -matches(_Client, _PubSub, _Topic, []) -> nomatch; +-spec matches(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic(), [rule()]) -> + {matched, allow} | {matched, deny} | nomatch. +matches(_Client, _PubSub, _Topic, []) -> + nomatch; matches(Client, PubSub, Topic, [{Permission, Who, Action, TopicFilters} | Tail]) -> case match(Client, PubSub, Topic, {Permission, Who, Action, TopicFilters}) of nomatch -> matches(Client, PubSub, Topic, Tail); Matched -> Matched end. --spec(match(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic(), rule()) - -> {matched, allow} | {matched, deny} | nomatch). +-spec match(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic(), rule()) -> + {matched, allow} | {matched, deny} | nomatch. match(Client, PubSub, Topic, {Permission, Who, Action, TopicFilters}) -> - case match_action(PubSub, Action) andalso - match_who(Client, Who) andalso - match_topics(Client, Topic, TopicFilters) of + case + match_action(PubSub, Action) andalso + match_who(Client, Who) andalso + match_topics(Client, Topic, TopicFilters) + of true -> {matched, Permission}; _ -> nomatch end. @@ -129,16 +149,19 @@ match_action(subscribe, subscribe) -> true; match_action(_, all) -> true; match_action(_, _) -> false. -match_who(_, all) -> true; +match_who(_, all) -> + true; match_who(#{username := undefined}, {username, _}) -> false; -match_who(#{username := Username}, {username, {eq, Username}}) -> true; +match_who(#{username := Username}, {username, {eq, Username}}) -> + true; match_who(#{username := Username}, {username, {re_pattern, _, _, _, _} = MP}) -> case re:run(Username, MP) of {match, _} -> true; _ -> false end; -match_who(#{clientid := Clientid}, {clientid, {eq, Clientid}}) -> true; +match_who(#{clientid := Clientid}, {clientid, {eq, Clientid}}) -> + true; match_who(#{clientid := Clientid}, {clientid, {re_pattern, _, _, _, _} = MP}) -> case re:run(Clientid, MP) of {match, _} -> true; @@ -151,28 +174,40 @@ match_who(#{peerhost := IpAddress}, {ipaddr, CIDR}) -> match_who(#{peerhost := undefined}, {ipaddrs, _CIDR}) -> false; match_who(#{peerhost := IpAddress}, {ipaddrs, CIDRs}) -> - lists:any(fun(CIDR) -> - esockd_cidr:match(IpAddress, CIDR) - end, CIDRs); + lists:any( + fun(CIDR) -> + esockd_cidr:match(IpAddress, CIDR) + end, + CIDRs + ); match_who(ClientInfo, {'and', Principals}) when is_list(Principals) -> - lists:foldl(fun(Principal, Permission) -> - match_who(ClientInfo, Principal) andalso Permission - end, true, Principals); + lists:foldl( + fun(Principal, Permission) -> + match_who(ClientInfo, Principal) andalso Permission + end, + true, + Principals + ); match_who(ClientInfo, {'or', Principals}) when is_list(Principals) -> - lists:foldl(fun(Principal, Permission) -> - match_who(ClientInfo, Principal) orelse Permission - end, false, Principals); -match_who(_, _) -> false. + lists:foldl( + fun(Principal, Permission) -> + match_who(ClientInfo, Principal) orelse Permission + end, + false, + Principals + ); +match_who(_, _) -> + false. match_topics(_ClientInfo, _Topic, []) -> false; match_topics(ClientInfo, Topic, [{pattern, PatternFilter} | Filters]) -> TopicFilter = feed_var(ClientInfo, PatternFilter), - match_topic(emqx_topic:words(Topic), TopicFilter) - orelse match_topics(ClientInfo, Topic, Filters); + match_topic(emqx_topic:words(Topic), TopicFilter) orelse + match_topics(ClientInfo, Topic, Filters); match_topics(ClientInfo, Topic, [TopicFilter | Filters]) -> - match_topic(emqx_topic:words(Topic), TopicFilter) - orelse match_topics(ClientInfo, Topic, Filters). + match_topic(emqx_topic:words(Topic), TopicFilter) orelse + match_topics(ClientInfo, Topic, Filters). match_topic(Topic, {'eq', TopicFilter}) -> Topic =:= TopicFilter; diff --git a/apps/emqx_authz/src/emqx_authz_schema.erl b/apps/emqx_authz/src/emqx_authz_schema.erl index d787da82d..1e34a2620 100644 --- a/apps/emqx_authz/src/emqx_authz_schema.erl +++ b/apps/emqx_authz/src/emqx_authz_schema.erl @@ -19,22 +19,25 @@ -include_lib("typerefl/include/types.hrl"). -include_lib("emqx_connector/include/emqx_connector.hrl"). --reflect_type([ permission/0 - , action/0 - ]). +-reflect_type([ + permission/0, + action/0 +]). -type action() :: publish | subscribe | all. -type permission() :: allow | deny. --export([ namespace/0 - , roots/0 - , fields/1 - , validations/0 - ]). +-export([ + namespace/0, + roots/0, + fields/1, + validations/0 +]). --export([ headers_no_content_type/1 - , headers/1 - ]). +-export([ + headers_no_content_type/1, + headers/1 +]). -import(emqx_schema, [mk_duration/2]). -include_lib("hocon/include/hoconsc.hrl"). @@ -50,73 +53,84 @@ namespace() -> authz. roots() -> []. fields("authorization") -> - [ {sources, #{type => union_array( - [ hoconsc:ref(?MODULE, file) - , hoconsc:ref(?MODULE, http_get) - , hoconsc:ref(?MODULE, http_post) - , hoconsc:ref(?MODULE, mnesia) - , hoconsc:ref(?MODULE, mongo_single) - , hoconsc:ref(?MODULE, mongo_rs) - , hoconsc:ref(?MODULE, mongo_sharded) - , hoconsc:ref(?MODULE, mysql) - , hoconsc:ref(?MODULE, postgresql) - , hoconsc:ref(?MODULE, redis_single) - , hoconsc:ref(?MODULE, redis_sentinel) - , hoconsc:ref(?MODULE, redis_cluster) - ]), - default => [], - desc => -" -Authorization data sources.
-An array of authorization (ACL) data providers. -It is designed as an array, not a hash-map, so the sources can be -ordered to form a chain of access controls.
- -When authorizing a 'publish' or 'subscribe' action, the configured -sources are checked in order. When checking an ACL source, -in case the client (identified by username or client ID) is not found, -it moves on to the next source. And it stops immediately -once an 'allow' or 'deny' decision is returned.
- -If the client is not found in any of the sources, -the default action configured in 'authorization.no_match' is applied.
- -NOTE: -The source elements are identified by their 'type'. -It is NOT allowed to configure two or more sources of the same type. -" - } - } + [ + {sources, #{ + type => union_array( + [ + hoconsc:ref(?MODULE, file), + hoconsc:ref(?MODULE, http_get), + hoconsc:ref(?MODULE, http_post), + hoconsc:ref(?MODULE, mnesia), + hoconsc:ref(?MODULE, mongo_single), + hoconsc:ref(?MODULE, mongo_rs), + hoconsc:ref(?MODULE, mongo_sharded), + hoconsc:ref(?MODULE, mysql), + hoconsc:ref(?MODULE, postgresql), + hoconsc:ref(?MODULE, redis_single), + hoconsc:ref(?MODULE, redis_sentinel), + hoconsc:ref(?MODULE, redis_cluster) + ] + ), + default => [], + desc => + "\n" + "Authorization data sources.
\n" + "An array of authorization (ACL) data providers.\n" + "It is designed as an array, not a hash-map, so the sources can be\n" + "ordered to form a chain of access controls.
\n" + "\n" + "When authorizing a 'publish' or 'subscribe' action, the configured\n" + "sources are checked in order. When checking an ACL source,\n" + "in case the client (identified by username or client ID) is not found,\n" + "it moves on to the next source. And it stops immediately\n" + "once an 'allow' or 'deny' decision is returned.
\n" + "\n" + "If the client is not found in any of the sources,\n" + "the default action configured in 'authorization.no_match' is applied.
\n" + "\n" + "NOTE:\n" + "The source elements are identified by their 'type'.\n" + "It is NOT allowed to configure two or more sources of the same type.\n" + }} ]; fields(file) -> - [ {type, #{type => file}} - , {enable, #{type => boolean(), - default => true}} - , {path, #{type => string(), - required => true, - desc => " -Path to the file which contains the ACL rules.
-If the file provisioned before starting EMQX node, -it can be placed anywhere as long as EMQX has read access to it. - -In case the rule-set is created from EMQX dashboard or management API, -the file will be placed in `authz` subdirectory inside EMQX's `data_dir`, -and the new rules will override all rules from the old config file. -" - }} + [ + {type, #{type => file}}, + {enable, #{ + type => boolean(), + default => true + }}, + {path, #{ + type => string(), + required => true, + desc => + "\n" + "Path to the file which contains the ACL rules.
\n" + "If the file provisioned before starting EMQX node,\n" + "it can be placed anywhere as long as EMQX has read access to it.\n" + "\n" + "In case the rule-set is created from EMQX dashboard or management API,\n" + "the file will be placed in `authz` subdirectory inside EMQX's `data_dir`,\n" + "and the new rules will override all rules from the old config file.\n" + }} ]; fields(http_get) -> - [ {method, #{type => get, default => post}} - , {headers, fun headers_no_content_type/1} + [ + {method, #{type => get, default => post}}, + {headers, fun headers_no_content_type/1} ] ++ http_common_fields(); fields(http_post) -> - [ {method, #{type => post, default => post}} - , {headers, fun headers/1} + [ + {method, #{type => post, default => post}}, + {headers, fun headers/1} ] ++ http_common_fields(); fields(mnesia) -> - [ {type, #{type => 'built_in_database'}} - , {enable, #{type => boolean(), - default => true}} + [ + {type, #{type => 'built_in_database'}}, + {enable, #{ + type => boolean(), + default => true + }} ]; fields(mongo_single) -> mongo_common_fields() ++ emqx_connector_mongo:fields(single); @@ -126,62 +140,88 @@ fields(mongo_sharded) -> mongo_common_fields() ++ emqx_connector_mongo:fields(sharded); fields(mysql) -> connector_fields(mysql) ++ - [ {query, query()} ]; + [{query, query()}]; fields(postgresql) -> - [ {query, query()} - , {type, #{type => postgresql}} - , {enable, #{type => boolean(), - default => true}} + [ + {query, query()}, + {type, #{type => postgresql}}, + {enable, #{ + type => boolean(), + default => true + }} ] ++ emqx_connector_pgsql:fields(config); fields(redis_single) -> connector_fields(redis, single) ++ - [ {cmd, query()} ]; + [{cmd, query()}]; fields(redis_sentinel) -> connector_fields(redis, sentinel) ++ - [ {cmd, query()} ]; + [{cmd, query()}]; fields(redis_cluster) -> connector_fields(redis, cluster) ++ - [ {cmd, query()} ]. + [{cmd, query()}]. http_common_fields() -> - [ {url, fun url/1} - , {request_timeout, mk_duration("Request timeout", #{default => "30s", desc => "Request timeout."})} - , {body, #{type => map(), required => false, desc => "HTTP request body."}} - ] ++ maps:to_list(maps:without([ base_url - , pool_type], - maps:from_list(connector_fields(http)))). + [ + {url, fun url/1}, + {request_timeout, + mk_duration("Request timeout", #{default => "30s", desc => "Request timeout."})}, + {body, #{type => map(), required => false, desc => "HTTP request body."}} + ] ++ + maps:to_list( + maps:without( + [ + base_url, + pool_type + ], + maps:from_list(connector_fields(http)) + ) + ). mongo_common_fields() -> - [ {collection, #{type => atom(), desc => "`MongoDB` collection containing the authorization data."}} - , {selector, #{type => map(), desc => "MQL query used to select the authorization record."}} - , {type, #{type => mongodb, desc => "Database backend."}} - , {enable, #{type => boolean(), - default => true, - desc => "Enable or disable the backend."}} + [ + {collection, #{ + type => atom(), desc => "`MongoDB` collection containing the authorization data." + }}, + {selector, #{type => map(), desc => "MQL query used to select the authorization record."}}, + {type, #{type => mongodb, desc => "Database backend."}}, + {enable, #{ + type => boolean(), + default => true, + desc => "Enable or disable the backend." + }} ]. validations() -> - [ {check_ssl_opts, fun check_ssl_opts/1} - , {check_headers, fun check_headers/1} + [ + {check_ssl_opts, fun check_ssl_opts/1}, + {check_headers, fun check_headers/1} ]. -headers(type) -> list({binary(), binary()}); -headers(desc) -> "List of HTTP headers."; +headers(type) -> + list({binary(), binary()}); +headers(desc) -> + "List of HTTP headers."; headers(converter) -> fun(Headers) -> maps:to_list(maps:merge(default_headers(), transform_header_name(Headers))) end; -headers(default) -> default_headers(); -headers(_) -> undefined. +headers(default) -> + default_headers(); +headers(_) -> + undefined. -headers_no_content_type(type) -> list({binary(), binary()}); -headers_no_content_type(desc) -> "List of HTTP headers."; +headers_no_content_type(type) -> + list({binary(), binary()}); +headers_no_content_type(desc) -> + "List of HTTP headers."; headers_no_content_type(converter) -> fun(Headers) -> - maps:to_list(maps:merge(default_headers_no_content_type(), transform_header_name(Headers))) + maps:to_list(maps:merge(default_headers_no_content_type(), transform_header_name(Headers))) end; -headers_no_content_type(default) -> default_headers_no_content_type(); -headers_no_content_type(_) -> undefined. +headers_no_content_type(default) -> + default_headers_no_content_type(); +headers_no_content_type(_) -> + undefined. url(type) -> binary(); url(desc) -> "URL of the auth server."; @@ -194,26 +234,34 @@ url(_) -> undefined. %%-------------------------------------------------------------------- default_headers() -> - maps:put(<<"content-type">>, - <<"application/json">>, - default_headers_no_content_type()). + maps:put( + <<"content-type">>, + <<"application/json">>, + default_headers_no_content_type() + ). default_headers_no_content_type() -> - #{ <<"accept">> => <<"application/json">> - , <<"cache-control">> => <<"no-cache">> - , <<"connection">> => <<"keep-alive">> - , <<"keep-alive">> => <<"timeout=30, max=1000">> - }. + #{ + <<"accept">> => <<"application/json">>, + <<"cache-control">> => <<"no-cache">>, + <<"connection">> => <<"keep-alive">>, + <<"keep-alive">> => <<"timeout=30, max=1000">> + }. transform_header_name(Headers) -> - maps:fold(fun(K0, V, Acc) -> - K = list_to_binary(string:to_lower(to_list(K0))), - maps:put(K, V, Acc) - end, #{}, Headers). + maps:fold( + fun(K0, V, Acc) -> + K = list_to_binary(string:to_lower(to_list(K0))), + maps:put(K, V, Acc) + end, + #{}, + Headers + ). check_ssl_opts(Conf) -> case hocon_maps:get("config.url", Conf) of - undefined -> true; + undefined -> + true; Url -> case emqx_authz_http:parse_url(Url) of #{scheme := https} -> @@ -221,19 +269,23 @@ check_ssl_opts(Conf) -> true -> true; _ -> {error, ssl_not_enable} end; - #{scheme := http} -> true; - Bad -> {bad_scheme, Url, Bad} + #{scheme := http} -> + true; + Bad -> + {bad_scheme, Url, Bad} end end. check_headers(Conf) -> case hocon_maps:get("config.method", Conf) of - undefined -> true; + undefined -> + true; Method0 -> Method = to_bin(Method0), Headers = hocon_maps:get("config.headers", Conf), case Method of - <<"post">> -> true; + <<"post">> -> + true; _ when Headers =:= undefined -> true; _ when is_list(Headers) -> case lists:member(<<"content-type">>, Headers) of @@ -247,32 +299,37 @@ union_array(Item) when is_list(Item) -> hoconsc:array(hoconsc:union(Item)). query() -> - #{type => binary(), - desc => "", - validator => fun(S) -> - case size(S) > 0 of - true -> ok; - _ -> {error, "Request query"} - end - end - }. + #{ + type => binary(), + desc => "", + validator => fun(S) -> + case size(S) > 0 of + true -> ok; + _ -> {error, "Request query"} + end + end + }. connector_fields(DB) -> connector_fields(DB, config). connector_fields(DB, Fields) -> - Mod0 = io_lib:format("~ts_~ts",[emqx_connector, DB]), - Mod = try - list_to_existing_atom(Mod0) - catch - error:badarg -> - list_to_atom(Mod0); - error:Reason -> - erlang:error(Reason) - end, - [ {type, #{type => DB, desc => "Database backend."}} - , {enable, #{type => boolean(), - default => true, - desc => "Enable or disable the backend."}} + Mod0 = io_lib:format("~ts_~ts", [emqx_connector, DB]), + Mod = + try + list_to_existing_atom(Mod0) + catch + error:badarg -> + list_to_atom(Mod0); + error:Reason -> + erlang:error(Reason) + end, + [ + {type, #{type => DB, desc => "Database backend."}}, + {enable, #{ + type => boolean(), + default => true, + desc => "Enable or disable the backend." + }} ] ++ erlang:apply(Mod, fields, [Fields]). to_list(A) when is_atom(A) -> diff --git a/apps/emqx_authz/src/emqx_authz_sup.erl b/apps/emqx_authz/src/emqx_authz_sup.erl index ca50f5a83..47a639201 100644 --- a/apps/emqx_authz/src/emqx_authz_sup.erl +++ b/apps/emqx_authz/src/emqx_authz_sup.erl @@ -33,8 +33,10 @@ start_link() -> supervisor:start_link({local, ?SERVER}, ?MODULE, []). init([]) -> - SupFlags = #{strategy => one_for_all, - intensity => 0, - period => 1}, + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, ChildSpecs = [], {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/emqx_authz/src/emqx_authz_utils.erl b/apps/emqx_authz/src/emqx_authz_utils.erl index 4a0e447e9..c153dbc1f 100644 --- a/apps/emqx_authz/src/emqx_authz_utils.erl +++ b/apps/emqx_authz/src/emqx_authz_utils.erl @@ -19,15 +19,16 @@ -include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("emqx_authz.hrl"). --export([ cleanup_resources/0 - , make_resource_id/1 - , create_resource/2 - , update_config/2 - , parse_deep/2 - , parse_sql/3 - , render_deep/2 - , render_sql_params/2 - ]). +-export([ + cleanup_resources/0, + make_resource_id/1, + create_resource/2, + update_config/2, + parse_deep/2, + parse_sql/3, + render_deep/2, + render_sql_params/2 +]). %%------------------------------------------------------------------------------ %% APIs @@ -35,10 +36,15 @@ create_resource(Module, Config) -> ResourceID = make_resource_id(Module), - case emqx_resource:create_local(ResourceID, - ?RESOURCE_GROUP, - Module, Config, - #{}) of + case + emqx_resource:create_local( + ResourceID, + ?RESOURCE_GROUP, + Module, + Config, + #{} + ) + of {ok, already_created} -> {ok, ResourceID}; {ok, _} -> {ok, ResourceID}; {error, Reason} -> {error, Reason} @@ -46,37 +52,45 @@ create_resource(Module, Config) -> cleanup_resources() -> lists:foreach( - fun emqx_resource:remove_local/1, - emqx_resource:list_group_instances(?RESOURCE_GROUP)). + fun emqx_resource:remove_local/1, + emqx_resource:list_group_instances(?RESOURCE_GROUP) + ). make_resource_id(Name) -> NameBin = bin(Name), emqx_resource:generate_id(NameBin). update_config(Path, ConfigRequest) -> - emqx_conf:update(Path, ConfigRequest, #{rawconf_with_defaults => true, - override_to => cluster}). + emqx_conf:update(Path, ConfigRequest, #{ + rawconf_with_defaults => true, + override_to => cluster + }). parse_deep(Template, PlaceHolders) -> emqx_placeholder:preproc_tmpl_deep(Template, #{placeholders => PlaceHolders}). parse_sql(Template, ReplaceWith, PlaceHolders) -> emqx_placeholder:preproc_sql( - Template, - #{replace_with => ReplaceWith, - placeholders => PlaceHolders}). + Template, + #{ + replace_with => ReplaceWith, + placeholders => PlaceHolders + } + ). render_deep(Template, Values) -> emqx_placeholder:proc_tmpl_deep( - Template, - client_vars(Values), - #{return => full_binary, var_trans => fun handle_var/2}). + Template, + client_vars(Values), + #{return => full_binary, var_trans => fun handle_var/2} + ). render_sql_params(ParamList, Values) -> emqx_placeholder:proc_tmpl( - ParamList, - client_vars(Values), - #{return => rawlist, var_trans => fun handle_sql_var/2}). + ParamList, + client_vars(Values), + #{return => rawlist, var_trans => fun handle_sql_var/2} + ). %%------------------------------------------------------------------------------ %% Internal functions @@ -84,9 +98,11 @@ render_sql_params(ParamList, Values) -> client_vars(ClientInfo) -> maps:from_list( - lists:map( - fun convert_client_var/1, - maps:to_list(ClientInfo))). + lists:map( + fun convert_client_var/1, + maps:to_list(ClientInfo) + ) + ). convert_client_var({cn, CN}) -> {cert_common_name, CN}; convert_client_var({dn, DN}) -> {cert_subject, DN}; diff --git a/apps/emqx_authz/test/emqx_authz_SUITE.erl b/apps/emqx_authz/test/emqx_authz_SUITE.erl index 2d2648913..40f918727 100644 --- a/apps/emqx_authz/test/emqx_authz_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_SUITE.erl @@ -33,22 +33,29 @@ init_per_suite(Config) -> meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_resource, create_local, fun(_, _, _, _) -> {ok, meck_data} end), meck:expect(emqx_resource, remove_local, fun(_) -> ok end), - meck:expect(emqx_authz, acl_conf_file, - fun() -> - emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") - end), + meck:expect( + emqx_authz, + acl_conf_file, + fun() -> + emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") + end + ), ok = emqx_common_test_helpers:start_apps( - [emqx_connector, emqx_conf, emqx_authz], - fun set_special_configs/1), + [emqx_connector, emqx_conf, emqx_authz], + fun set_special_configs/1 + ), Config. end_per_suite(_Config) -> {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => <<"allow">>, - <<"cache">> => #{<<"enable">> => <<"true">>}, - <<"sources">> => []}), + [authorization], + #{ + <<"no_match">> => <<"allow">>, + <<"cache">> => #{<<"enable">> => <<"true">>}, + <<"sources">> => [] + } + ), ok = stop_apps([emqx_resource]), emqx_common_test_helpers:stop_apps([emqx_authz, emqx_conf]), meck:unload(emqx_resource), @@ -66,64 +73,71 @@ set_special_configs(emqx_authz) -> set_special_configs(_App) -> ok. --define(SOURCE1, #{<<"type">> => <<"http">>, - <<"enable">> => true, - <<"url">> => <<"https://example.com:443/a/b?c=d">>, - <<"headers">> => #{}, - <<"method">> => <<"get">>, - <<"request_timeout">> => 5000 - }). --define(SOURCE2, #{<<"type">> => <<"mongodb">>, - <<"enable">> => true, - <<"mongo_type">> => <<"single">>, - <<"server">> => <<"127.0.0.1:27017">>, - <<"w_mode">> => <<"unsafe">>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"ssl">> => #{<<"enable">> => false}, - <<"collection">> => <<"authz">>, - <<"selector">> => #{<<"a">> => <<"b">>} - }). --define(SOURCE3, #{<<"type">> => <<"mysql">>, - <<"enable">> => true, - <<"server">> => <<"127.0.0.1:27017">>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"username">> => <<"xx">>, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"query">> => <<"abcb">> - }). --define(SOURCE4, #{<<"type">> => <<"postgresql">>, - <<"enable">> => true, - <<"server">> => <<"127.0.0.1:27017">>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"username">> => <<"xx">>, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"query">> => <<"abcb">> - }). --define(SOURCE5, #{<<"type">> => <<"redis">>, - <<"redis_type">> => <<"single">>, - <<"enable">> => true, - <<"server">> => <<"127.0.0.1:27017">>, - <<"pool_size">> => 1, - <<"database">> => 0, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"cmd">> => <<"HGETALL mqtt_authz:", ?PH_USERNAME/binary>> - }). --define(SOURCE6, #{<<"type">> => <<"file">>, - <<"enable">> => true, - <<"rules">> => -<<"{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}." - "\n{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">> - }). - +-define(SOURCE1, #{ + <<"type">> => <<"http">>, + <<"enable">> => true, + <<"url">> => <<"https://example.com:443/a/b?c=d">>, + <<"headers">> => #{}, + <<"method">> => <<"get">>, + <<"request_timeout">> => 5000 +}). +-define(SOURCE2, #{ + <<"type">> => <<"mongodb">>, + <<"enable">> => true, + <<"mongo_type">> => <<"single">>, + <<"server">> => <<"127.0.0.1:27017">>, + <<"w_mode">> => <<"unsafe">>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"ssl">> => #{<<"enable">> => false}, + <<"collection">> => <<"authz">>, + <<"selector">> => #{<<"a">> => <<"b">>} +}). +-define(SOURCE3, #{ + <<"type">> => <<"mysql">>, + <<"enable">> => true, + <<"server">> => <<"127.0.0.1:27017">>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"username">> => <<"xx">>, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false}, + <<"query">> => <<"abcb">> +}). +-define(SOURCE4, #{ + <<"type">> => <<"postgresql">>, + <<"enable">> => true, + <<"server">> => <<"127.0.0.1:27017">>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"username">> => <<"xx">>, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false}, + <<"query">> => <<"abcb">> +}). +-define(SOURCE5, #{ + <<"type">> => <<"redis">>, + <<"redis_type">> => <<"single">>, + <<"enable">> => true, + <<"server">> => <<"127.0.0.1:27017">>, + <<"pool_size">> => 1, + <<"database">> => 0, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false}, + <<"cmd">> => <<"HGETALL mqtt_authz:", ?PH_USERNAME/binary>> +}). +-define(SOURCE6, #{ + <<"type">> => <<"file">>, + <<"enable">> => true, + <<"rules">> => + << + "{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}." + "\n{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}." + >> +}). %%------------------------------------------------------------------------------ %% Testcases @@ -138,95 +152,130 @@ t_update_source(_) -> {ok, _} = emqx_authz:update(?CMD_APPEND, ?SOURCE5), {ok, _} = emqx_authz:update(?CMD_APPEND, ?SOURCE6), - ?assertMatch([ #{type := http, enable := true} - , #{type := mongodb, enable := true} - , #{type := mysql, enable := true} - , #{type := postgresql, enable := true} - , #{type := redis, enable := true} - , #{type := file, enable := true} - ], emqx_conf:get([authorization, sources], [])), + ?assertMatch( + [ + #{type := http, enable := true}, + #{type := mongodb, enable := true}, + #{type := mysql, enable := true}, + #{type := postgresql, enable := true}, + #{type := redis, enable := true}, + #{type := file, enable := true} + ], + emqx_conf:get([authorization, sources], []) + ), - {ok, _} = emqx_authz:update({?CMD_REPLACE, http}, ?SOURCE1#{<<"enable">> := true}), + {ok, _} = emqx_authz:update({?CMD_REPLACE, http}, ?SOURCE1#{<<"enable">> := true}), {ok, _} = emqx_authz:update({?CMD_REPLACE, mongodb}, ?SOURCE2#{<<"enable">> := true}), {ok, _} = emqx_authz:update({?CMD_REPLACE, mysql}, ?SOURCE3#{<<"enable">> := true}), {ok, _} = emqx_authz:update({?CMD_REPLACE, postgresql}, ?SOURCE4#{<<"enable">> := true}), {ok, _} = emqx_authz:update({?CMD_REPLACE, redis}, ?SOURCE5#{<<"enable">> := true}), - {ok, _} = emqx_authz:update({?CMD_REPLACE, file}, ?SOURCE6#{<<"enable">> := true}), + {ok, _} = emqx_authz:update({?CMD_REPLACE, file}, ?SOURCE6#{<<"enable">> := true}), - {ok, _} = emqx_authz:update({?CMD_REPLACE, http}, ?SOURCE1#{<<"enable">> := false}), + {ok, _} = emqx_authz:update({?CMD_REPLACE, http}, ?SOURCE1#{<<"enable">> := false}), {ok, _} = emqx_authz:update({?CMD_REPLACE, mongodb}, ?SOURCE2#{<<"enable">> := false}), {ok, _} = emqx_authz:update({?CMD_REPLACE, mysql}, ?SOURCE3#{<<"enable">> := false}), {ok, _} = emqx_authz:update({?CMD_REPLACE, postgresql}, ?SOURCE4#{<<"enable">> := false}), {ok, _} = emqx_authz:update({?CMD_REPLACE, redis}, ?SOURCE5#{<<"enable">> := false}), - {ok, _} = emqx_authz:update({?CMD_REPLACE, file}, ?SOURCE6#{<<"enable">> := false}), + {ok, _} = emqx_authz:update({?CMD_REPLACE, file}, ?SOURCE6#{<<"enable">> := false}), - ?assertMatch([ #{type := http, enable := false} - , #{type := mongodb, enable := false} - , #{type := mysql, enable := false} - , #{type := postgresql, enable := false} - , #{type := redis, enable := false} - , #{type := file, enable := false} - ], emqx_conf:get([authorization, sources], [])), + ?assertMatch( + [ + #{type := http, enable := false}, + #{type := mongodb, enable := false}, + #{type := mysql, enable := false}, + #{type := postgresql, enable := false}, + #{type := redis, enable := false}, + #{type := file, enable := false} + ], + emqx_conf:get([authorization, sources], []) + ), {ok, _} = emqx_authz:update(?CMD_REPLACE, []). t_delete_source(_) -> {ok, _} = emqx_authz:update(?CMD_REPLACE, [?SOURCE1]), - ?assertMatch([ #{type := http, enable := true} - ], emqx_conf:get([authorization, sources], [])), + ?assertMatch([#{type := http, enable := true}], emqx_conf:get([authorization, sources], [])), {ok, _} = emqx_authz:update({?CMD_DELETE, http}, #{}), ?assertMatch([], emqx_conf:get([authorization, sources], [])). t_move_source(_) -> - {ok, _} = emqx_authz:update(?CMD_REPLACE, - [?SOURCE1, ?SOURCE2, ?SOURCE3, - ?SOURCE4, ?SOURCE5, ?SOURCE6]), - ?assertMatch([ #{type := http} - , #{type := mongodb} - , #{type := mysql} - , #{type := postgresql} - , #{type := redis} - , #{type := file} - ], emqx_authz:lookup()), + {ok, _} = emqx_authz:update( + ?CMD_REPLACE, + [ + ?SOURCE1, + ?SOURCE2, + ?SOURCE3, + ?SOURCE4, + ?SOURCE5, + ?SOURCE6 + ] + ), + ?assertMatch( + [ + #{type := http}, + #{type := mongodb}, + #{type := mysql}, + #{type := postgresql}, + #{type := redis}, + #{type := file} + ], + emqx_authz:lookup() + ), {ok, _} = emqx_authz:move(postgresql, ?CMD_MOVE_FRONT), - ?assertMatch([ #{type := postgresql} - , #{type := http} - , #{type := mongodb} - , #{type := mysql} - , #{type := redis} - , #{type := file} - ], emqx_authz:lookup()), + ?assertMatch( + [ + #{type := postgresql}, + #{type := http}, + #{type := mongodb}, + #{type := mysql}, + #{type := redis}, + #{type := file} + ], + emqx_authz:lookup() + ), {ok, _} = emqx_authz:move(http, ?CMD_MOVE_REAR), - ?assertMatch([ #{type := postgresql} - , #{type := mongodb} - , #{type := mysql} - , #{type := redis} - , #{type := file} - , #{type := http} - ], emqx_authz:lookup()), + ?assertMatch( + [ + #{type := postgresql}, + #{type := mongodb}, + #{type := mysql}, + #{type := redis}, + #{type := file}, + #{type := http} + ], + emqx_authz:lookup() + ), {ok, _} = emqx_authz:move(mysql, ?CMD_MOVE_BEFORE(postgresql)), - ?assertMatch([ #{type := mysql} - , #{type := postgresql} - , #{type := mongodb} - , #{type := redis} - , #{type := file} - , #{type := http} - ], emqx_authz:lookup()), + ?assertMatch( + [ + #{type := mysql}, + #{type := postgresql}, + #{type := mongodb}, + #{type := redis}, + #{type := file}, + #{type := http} + ], + emqx_authz:lookup() + ), {ok, _} = emqx_authz:move(mongodb, ?CMD_MOVE_AFTER(http)), - ?assertMatch([ #{type := mysql} - , #{type := postgresql} - , #{type := redis} - , #{type := file} - , #{type := http} - , #{type := mongodb} - ], emqx_authz:lookup()), + ?assertMatch( + [ + #{type := mysql}, + #{type := postgresql}, + #{type := redis}, + #{type := file}, + #{type := http}, + #{type := mongodb} + ], + emqx_authz:lookup() + ), ok. diff --git a/apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl b/apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl index ee7f68c14..385a10eb9 100644 --- a/apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_api_mnesia_SUITE.erl @@ -32,16 +32,20 @@ groups() -> init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz, emqx_dashboard], - fun set_special_configs/1), + [emqx_conf, emqx_authz, emqx_dashboard], + fun set_special_configs/1 + ), Config. end_per_suite(_Config) -> {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => <<"allow">>, - <<"cache">> => #{<<"enable">> => <<"true">>}, - <<"sources">> => []}), + [authorization], + #{ + <<"no_match">> => <<"allow">>, + <<"cache">> => #{<<"enable">> => <<"true">>}, + <<"sources">> => [] + } + ), emqx_common_test_helpers:stop_apps([emqx_dashboard, emqx_authz, emqx_conf]), ok. @@ -50,8 +54,10 @@ set_special_configs(emqx_dashboard) -> set_special_configs(emqx_authz) -> {ok, _} = emqx:update_config([authorization, cache, enable], false), {ok, _} = emqx:update_config([authorization, no_match], deny), - {ok, _} = emqx:update_config([authorization, sources], - [#{<<"type">> => <<"built_in_database">>}]), + {ok, _} = emqx:update_config( + [authorization, sources], + [#{<<"type">> => <<"built_in_database">>}] + ), ok; set_special_configs(_App) -> ok. @@ -62,176 +68,248 @@ set_special_configs(_App) -> t_api(_) -> {ok, 204, _} = - request( post - , uri(["authorization", "sources", "built_in_database", "username"]) - , [?USERNAME_RULES_EXAMPLE]), + request( + post, + uri(["authorization", "sources", "built_in_database", "username"]), + [?USERNAME_RULES_EXAMPLE] + ), {ok, 200, Request1} = - request( get - , uri(["authorization", "sources", "built_in_database", "username"]) - , []), - #{<<"data">> := [#{<<"username">> := <<"user1">>, <<"rules">> := Rules1}], - <<"meta">> := #{<<"count">> := 1, - <<"limit">> := 100, - <<"page">> := 1}} = jsx:decode(Request1), + request( + get, + uri(["authorization", "sources", "built_in_database", "username"]), + [] + ), + #{ + <<"data">> := [#{<<"username">> := <<"user1">>, <<"rules">> := Rules1}], + <<"meta">> := #{ + <<"count">> := 1, + <<"limit">> := 100, + <<"page">> := 1 + } + } = jsx:decode(Request1), ?assertEqual(3, length(Rules1)), {ok, 200, Request1_1} = - request( get - , uri([ "authorization" - , "sources" - , "built_in_database" - , "username?page=1&limit=20&like_username=noexist"]) - , []), - #{<<"data">> := [], - <<"meta">> := #{<<"count">> := 0, - <<"limit">> := 20, - <<"page">> := 1}} = jsx:decode(Request1_1), - + request( + get, + uri([ + "authorization", + "sources", + "built_in_database", + "username?page=1&limit=20&like_username=noexist" + ]), + [] + ), + #{ + <<"data">> := [], + <<"meta">> := #{ + <<"count">> := 0, + <<"limit">> := 20, + <<"page">> := 1 + } + } = jsx:decode(Request1_1), {ok, 200, Request2} = - request( get - , uri(["authorization", "sources", "built_in_database", "username", "user1"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "username", "user1"]), + [] + ), #{<<"username">> := <<"user1">>, <<"rules">> := Rules1} = jsx:decode(Request2), - {ok, 204, _} = - request( put - , uri(["authorization", "sources", "built_in_database", "username", "user1"]) - , ?USERNAME_RULES_EXAMPLE#{rules => []}), + request( + put, + uri(["authorization", "sources", "built_in_database", "username", "user1"]), + ?USERNAME_RULES_EXAMPLE#{rules => []} + ), {ok, 200, Request3} = - request( get - , uri(["authorization", "sources", "built_in_database", "username", "user1"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "username", "user1"]), + [] + ), #{<<"username">> := <<"user1">>, <<"rules">> := Rules2} = jsx:decode(Request3), ?assertEqual(0, length(Rules2)), {ok, 204, _} = - request( delete - , uri(["authorization", "sources", "built_in_database", "username", "user1"]) - , []), + request( + delete, + uri(["authorization", "sources", "built_in_database", "username", "user1"]), + [] + ), {ok, 404, _} = - request( get - , uri(["authorization", "sources", "built_in_database", "username", "user1"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "username", "user1"]), + [] + ), {ok, 404, _} = - request( delete - , uri(["authorization", "sources", "built_in_database", "username", "user1"]) - , []), - + request( + delete, + uri(["authorization", "sources", "built_in_database", "username", "user1"]), + [] + ), {ok, 204, _} = - request( post - , uri(["authorization", "sources", "built_in_database", "clientid"]) - , [?CLIENTID_RULES_EXAMPLE]), + request( + post, + uri(["authorization", "sources", "built_in_database", "clientid"]), + [?CLIENTID_RULES_EXAMPLE] + ), {ok, 200, Request4} = - request( get - , uri(["authorization", "sources", "built_in_database", "clientid"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "clientid"]), + [] + ), {ok, 200, Request5} = - request( get - , uri(["authorization", "sources", "built_in_database", "clientid", "client1"]) - , []), - #{<<"data">> := [#{<<"clientid">> := <<"client1">>, <<"rules">> := Rules3}], - <<"meta">> := #{<<"count">> := 1, <<"limit">> := 100, <<"page">> := 1}} - = jsx:decode(Request4), + request( + get, + uri(["authorization", "sources", "built_in_database", "clientid", "client1"]), + [] + ), + #{ + <<"data">> := [#{<<"clientid">> := <<"client1">>, <<"rules">> := Rules3}], + <<"meta">> := #{<<"count">> := 1, <<"limit">> := 100, <<"page">> := 1} + } = + jsx:decode(Request4), #{<<"clientid">> := <<"client1">>, <<"rules">> := Rules3} = jsx:decode(Request5), ?assertEqual(3, length(Rules3)), {ok, 204, _} = - request( put - , uri(["authorization", "sources", "built_in_database", "clientid", "client1"]) - , ?CLIENTID_RULES_EXAMPLE#{rules => []}), + request( + put, + uri(["authorization", "sources", "built_in_database", "clientid", "client1"]), + ?CLIENTID_RULES_EXAMPLE#{rules => []} + ), {ok, 200, Request6} = - request( get - , uri(["authorization", "sources", "built_in_database", "clientid", "client1"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "clientid", "client1"]), + [] + ), #{<<"clientid">> := <<"client1">>, <<"rules">> := Rules4} = jsx:decode(Request6), ?assertEqual(0, length(Rules4)), {ok, 204, _} = - request( delete - , uri(["authorization", "sources", "built_in_database", "clientid", "client1"]) - , []), + request( + delete, + uri(["authorization", "sources", "built_in_database", "clientid", "client1"]), + [] + ), {ok, 404, _} = - request( get - , uri(["authorization", "sources", "built_in_database", "clientid", "client1"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "clientid", "client1"]), + [] + ), {ok, 404, _} = - request( delete - , uri(["authorization", "sources", "built_in_database", "clientid", "client1"]) - , []), - + request( + delete, + uri(["authorization", "sources", "built_in_database", "clientid", "client1"]), + [] + ), {ok, 204, _} = - request( post - , uri(["authorization", "sources", "built_in_database", "all"]) - , ?ALL_RULES_EXAMPLE), + request( + post, + uri(["authorization", "sources", "built_in_database", "all"]), + ?ALL_RULES_EXAMPLE + ), {ok, 200, Request7} = - request( get - , uri(["authorization", "sources", "built_in_database", "all"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "all"]), + [] + ), #{<<"rules">> := Rules5} = jsx:decode(Request7), ?assertEqual(3, length(Rules5)), {ok, 204, _} = - request( post - , uri(["authorization", "sources", "built_in_database", "all"]) + request( + post, + uri(["authorization", "sources", "built_in_database", "all"]), - , ?ALL_RULES_EXAMPLE#{rules => []}), + ?ALL_RULES_EXAMPLE#{rules => []} + ), {ok, 200, Request8} = - request( get - , uri(["authorization", "sources", "built_in_database", "all"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "all"]), + [] + ), #{<<"rules">> := Rules6} = jsx:decode(Request8), ?assertEqual(0, length(Rules6)), {ok, 204, _} = - request( post - , uri(["authorization", "sources", "built_in_database", "username"]) - , [ #{username => erlang:integer_to_binary(N), rules => []} - || N <- lists:seq(1, 20) ]), + request( + post, + uri(["authorization", "sources", "built_in_database", "username"]), + [ + #{username => erlang:integer_to_binary(N), rules => []} + || N <- lists:seq(1, 20) + ] + ), {ok, 200, Request9} = - request( get - , uri(["authorization", "sources", "built_in_database", "username?page=2&limit=5"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "username?page=2&limit=5"]), + [] + ), #{<<"data">> := Data1} = jsx:decode(Request9), ?assertEqual(5, length(Data1)), {ok, 204, _} = - request( post - , uri(["authorization", "sources", "built_in_database", "clientid"]) - , [ #{clientid => erlang:integer_to_binary(N), rules => []} - || N <- lists:seq(1, 20) ]), + request( + post, + uri(["authorization", "sources", "built_in_database", "clientid"]), + [ + #{clientid => erlang:integer_to_binary(N), rules => []} + || N <- lists:seq(1, 20) + ] + ), {ok, 200, Request10} = - request( get - , uri(["authorization", "sources", "built_in_database", "clientid?limit=5"]) - , []), + request( + get, + uri(["authorization", "sources", "built_in_database", "clientid?limit=5"]), + [] + ), #{<<"data">> := Data2} = jsx:decode(Request10), ?assertEqual(5, length(Data2)), {ok, 400, Msg1} = - request( delete - , uri(["authorization", "sources", "built_in_database", "purge-all"]) - , []), + request( + delete, + uri(["authorization", "sources", "built_in_database", "purge-all"]), + [] + ), ?assertMatch({match, _}, re:run(Msg1, "must\sbe\sdisabled\sbefore")), {ok, 204, _} = - request( put - , uri(["authorization", "sources", "built_in_database"]) - , #{<<"enable">> => true, <<"type">> => <<"built_in_database">>}), + request( + put, + uri(["authorization", "sources", "built_in_database"]), + #{<<"enable">> => true, <<"type">> => <<"built_in_database">>} + ), %% test idempotence {ok, 204, _} = - request( put - , uri(["authorization", "sources", "built_in_database"]) - , #{<<"enable">> => true, <<"type">> => <<"built_in_database">>}), + request( + put, + uri(["authorization", "sources", "built_in_database"]), + #{<<"enable">> => true, <<"type">> => <<"built_in_database">>} + ), {ok, 204, _} = - request( put - , uri(["authorization", "sources", "built_in_database"]) - , #{<<"enable">> => false, <<"type">> => <<"built_in_database">>}), + request( + put, + uri(["authorization", "sources", "built_in_database"]), + #{<<"enable">> => false, <<"type">> => <<"built_in_database">>} + ), {ok, 204, _} = - request( delete - , uri(["authorization", "sources", "built_in_database", "purge-all"]) - , []), + request( + delete, + uri(["authorization", "sources", "built_in_database", "purge-all"]), + [] + ), ?assertEqual(0, emqx_authz_mnesia:record_count()), ok. diff --git a/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl b/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl index a14535018..b53b7aa1b 100644 --- a/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl @@ -31,16 +31,20 @@ groups() -> init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz, emqx_dashboard], - fun set_special_configs/1), + [emqx_conf, emqx_authz, emqx_dashboard], + fun set_special_configs/1 + ), Config. end_per_suite(_Config) -> {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => <<"allow">>, - <<"cache">> => #{<<"enable">> => <<"true">>}, - <<"sources">> => []}), + [authorization], + #{ + <<"no_match">> => <<"allow">>, + <<"cache">> => #{<<"enable">> => <<"true">>}, + <<"sources">> => [] + } + ), ok = stop_apps([emqx_resource, emqx_connector]), emqx_common_test_helpers:stop_apps([emqx_dashboard, emqx_authz, emqx_conf]), ok. @@ -60,27 +64,29 @@ set_special_configs(_App) -> %%------------------------------------------------------------------------------ t_api(_) -> - Settings1 = #{<<"no_match">> => <<"deny">>, - <<"deny_action">> => <<"disconnect">>, - <<"cache">> => #{ - <<"enable">> => false, - <<"max_size">> => 32, - <<"ttl">> => 60000 - } - }, + Settings1 = #{ + <<"no_match">> => <<"deny">>, + <<"deny_action">> => <<"disconnect">>, + <<"cache">> => #{ + <<"enable">> => false, + <<"max_size">> => 32, + <<"ttl">> => 60000 + } + }, {ok, 200, Result1} = request(put, uri(["authorization", "settings"]), Settings1), {ok, 200, Result1} = request(get, uri(["authorization", "settings"]), []), ?assertEqual(Settings1, jsx:decode(Result1)), - Settings2 = #{<<"no_match">> => <<"allow">>, - <<"deny_action">> => <<"ignore">>, - <<"cache">> => #{ - <<"enable">> => true, - <<"max_size">> => 32, - <<"ttl">> => 60000 - } - }, + Settings2 = #{ + <<"no_match">> => <<"allow">>, + <<"deny_action">> => <<"ignore">>, + <<"cache">> => #{ + <<"enable">> => true, + <<"max_size">> => 32, + <<"ttl">> => 60000 + } + }, {ok, 200, Result2} = request(put, uri(["authorization", "settings"]), Settings2), {ok, 200, Result2} = request(get, uri(["authorization", "settings"]), []), diff --git a/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl b/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl index 737091585..fe445fbf0 100644 --- a/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl @@ -29,62 +29,70 @@ -define(PGSQL_HOST, "pgsql"). -define(REDIS_SINGLE_HOST, "redis"). --define(SOURCE1, #{<<"type">> => <<"http">>, - <<"enable">> => true, - <<"url">> => <<"https://fake.com:443/acl?username=", ?PH_USERNAME/binary>>, - <<"headers">> => #{}, - <<"method">> => <<"get">>, - <<"request_timeout">> => <<"5s">> - }). --define(SOURCE2, #{<<"type">> => <<"mongodb">>, - <<"enable">> => true, - <<"mongo_type">> => <<"single">>, - <<"server">> => <>, - <<"w_mode">> => <<"unsafe">>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"ssl">> => #{<<"enable">> => false}, - <<"collection">> => <<"fake">>, - <<"selector">> => #{<<"a">> => <<"b">>} - }). --define(SOURCE3, #{<<"type">> => <<"mysql">>, - <<"enable">> => true, - <<"server">> => <>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"username">> => <<"xx">>, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"query">> => <<"abcb">> - }). --define(SOURCE4, #{<<"type">> => <<"postgresql">>, - <<"enable">> => true, - <<"server">> => <>, - <<"pool_size">> => 1, - <<"database">> => <<"mqtt">>, - <<"username">> => <<"xx">>, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"query">> => <<"abcb">> - }). --define(SOURCE5, #{<<"type">> => <<"redis">>, - <<"enable">> => true, - <<"servers">> => <>, - <<"pool_size">> => 1, - <<"database">> => 0, - <<"password">> => <<"ee">>, - <<"auto_reconnect">> => true, - <<"ssl">> => #{<<"enable">> => false}, - <<"cmd">> => <<"HGETALL mqtt_authz:", ?PH_USERNAME/binary>> - }). --define(SOURCE6, #{<<"type">> => <<"file">>, - <<"enable">> => true, - <<"rules">> => -<<"{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}." - "\n{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">> - }). +-define(SOURCE1, #{ + <<"type">> => <<"http">>, + <<"enable">> => true, + <<"url">> => <<"https://fake.com:443/acl?username=", ?PH_USERNAME/binary>>, + <<"headers">> => #{}, + <<"method">> => <<"get">>, + <<"request_timeout">> => <<"5s">> +}). +-define(SOURCE2, #{ + <<"type">> => <<"mongodb">>, + <<"enable">> => true, + <<"mongo_type">> => <<"single">>, + <<"server">> => <>, + <<"w_mode">> => <<"unsafe">>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"ssl">> => #{<<"enable">> => false}, + <<"collection">> => <<"fake">>, + <<"selector">> => #{<<"a">> => <<"b">>} +}). +-define(SOURCE3, #{ + <<"type">> => <<"mysql">>, + <<"enable">> => true, + <<"server">> => <>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"username">> => <<"xx">>, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false}, + <<"query">> => <<"abcb">> +}). +-define(SOURCE4, #{ + <<"type">> => <<"postgresql">>, + <<"enable">> => true, + <<"server">> => <>, + <<"pool_size">> => 1, + <<"database">> => <<"mqtt">>, + <<"username">> => <<"xx">>, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false}, + <<"query">> => <<"abcb">> +}). +-define(SOURCE5, #{ + <<"type">> => <<"redis">>, + <<"enable">> => true, + <<"servers">> => <>, + <<"pool_size">> => 1, + <<"database">> => 0, + <<"password">> => <<"ee">>, + <<"auto_reconnect">> => true, + <<"ssl">> => #{<<"enable">> => false}, + <<"cmd">> => <<"HGETALL mqtt_authz:", ?PH_USERNAME/binary>> +}). +-define(SOURCE6, #{ + <<"type">> => <<"file">>, + <<"enable">> => true, + <<"rules">> => + << + "{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}." + "\n{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}." + >> +}). -define(MATCH_RSA_KEY, <<"-----BEGIN RSA PRIVATE KEY", _/binary>>). -define(MATCH_CERT, <<"-----BEGIN CERTIFICATE", _/binary>>). @@ -100,24 +108,31 @@ init_per_suite(Config) -> meck:new(emqx_resource, [non_strict, passthrough, no_history, no_link]), meck:expect(emqx_resource, create_local, fun(_, _, _, _) -> {ok, meck_data} end), meck:expect(emqx_resource, health_check, fun(St) -> {ok, St} end), - meck:expect(emqx_resource, remove_local, fun(_) -> ok end ), - meck:expect(emqx_authz, acl_conf_file, - fun() -> - emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") - end), + meck:expect(emqx_resource, remove_local, fun(_) -> ok end), + meck:expect( + emqx_authz, + acl_conf_file, + fun() -> + emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") + end + ), ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz, emqx_dashboard], - fun set_special_configs/1), + [emqx_conf, emqx_authz, emqx_dashboard], + fun set_special_configs/1 + ), ok = start_apps([emqx_resource, emqx_connector]), Config. end_per_suite(_Config) -> {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => <<"allow">>, - <<"cache">> => #{<<"enable">> => <<"true">>}, - <<"sources">> => []}), + [authorization], + #{ + <<"no_match">> => <<"allow">>, + <<"cache">> => #{<<"enable">> => <<"true">>}, + <<"sources">> => [] + } + ), %% resource and connector should be stop first, %% or authz_[mysql|pgsql|redis..]_SUITE would be failed ok = stop_apps([emqx_resource, emqx_connector]), @@ -140,19 +155,24 @@ init_per_testcase(t_api, Config) -> meck:expect(emqx_misc, gen_id, fun() -> "fake" end), meck:new(emqx, [non_strict, passthrough, no_history, no_link]), - meck:expect(emqx, data_dir, - fun() -> - {data_dir, Data} = lists:keyfind(data_dir, 1, Config), - Data - end), + meck:expect( + emqx, + data_dir, + fun() -> + {data_dir, Data} = lists:keyfind(data_dir, 1, Config), + Data + end + ), Config; -init_per_testcase(_, Config) -> Config. +init_per_testcase(_, Config) -> + Config. end_per_testcase(t_api, _Config) -> meck:unload(emqx_misc), meck:unload(emqx), ok; -end_per_testcase(_, _Config) -> ok. +end_per_testcase(_, _Config) -> + ok. %%------------------------------------------------------------------------------ %% Testcases @@ -162,139 +182,190 @@ t_api(_) -> {ok, 200, Result1} = request(get, uri(["authorization", "sources"]), []), ?assertEqual([], get_sources(Result1)), - [ begin {ok, 204, _} = request(post, uri(["authorization", "sources"]), Source) end - || Source <- lists:reverse([?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5, ?SOURCE6])], + [ + begin + {ok, 204, _} = request(post, uri(["authorization", "sources"]), Source) + end + || Source <- lists:reverse([?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5, ?SOURCE6]) + ], {ok, 204, _} = request(post, uri(["authorization", "sources"]), ?SOURCE1), - Snd = fun ({_, Val}) -> Val end, + Snd = fun({_, Val}) -> Val end, LookupVal = fun LookupV(List, RestJson) -> - case List of - [Name] -> Snd(lists:keyfind(Name, 1, RestJson)); - [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson))) - end - end, - EqualFun = fun (RList) -> - fun ({M, V}) -> - ?assertEqual(V, - LookupVal([<<"metrics">>, M], - RList) - ) - end - end, + case List of + [Name] -> Snd(lists:keyfind(Name, 1, RestJson)); + [Name | NS] -> LookupV(NS, Snd(lists:keyfind(Name, 1, RestJson))) + end + end, + EqualFun = fun(RList) -> + fun({M, V}) -> + ?assertEqual( + V, + LookupVal( + [<<"metrics">>, M], + RList + ) + ) + end + end, AssertFun = - fun (ResultJson) -> + fun(ResultJson) -> {ok, RList} = emqx_json:safe_decode(ResultJson), - MetricsList = [{<<"failed">>, 0}, - {<<"matched">>, 0}, - {<<"rate">>, 0.0}, - {<<"rate_last5m">>, 0.0}, - {<<"rate_max">>, 0.0}, - {<<"success">>, 0}], + MetricsList = [ + {<<"failed">>, 0}, + {<<"matched">>, 0}, + {<<"rate">>, 0.0}, + {<<"rate_last5m">>, 0.0}, + {<<"rate_max">>, 0.0}, + {<<"success">>, 0} + ], lists:map(EqualFun(RList), MetricsList) end, {ok, 200, Result2} = request(get, uri(["authorization", "sources"]), []), Sources = get_sources(Result2), - ?assertMatch([ #{<<"type">> := <<"http">>} - , #{<<"type">> := <<"mongodb">>} - , #{<<"type">> := <<"mysql">>} - , #{<<"type">> := <<"postgresql">>} - , #{<<"type">> := <<"redis">>} - , #{<<"type">> := <<"file">>} - ], Sources), + ?assertMatch( + [ + #{<<"type">> := <<"http">>}, + #{<<"type">> := <<"mongodb">>}, + #{<<"type">> := <<"mysql">>}, + #{<<"type">> := <<"postgresql">>}, + #{<<"type">> := <<"redis">>}, + #{<<"type">> := <<"file">>} + ], + Sources + ), ?assert(filelib:is_file(emqx_authz:acl_conf_file())), - {ok, 204, _} = request(put, uri(["authorization", "sources", "http"]), - ?SOURCE1#{<<"enable">> := false}), + {ok, 204, _} = request( + put, + uri(["authorization", "sources", "http"]), + ?SOURCE1#{<<"enable">> := false} + ), {ok, 200, Result3} = request(get, uri(["authorization", "sources", "http"]), []), ?assertMatch(#{<<"type">> := <<"http">>, <<"enable">> := false}, jsx:decode(Result3)), Keyfile = emqx_common_test_helpers:app_path( - emqx, - filename:join(["etc", "certs", "key.pem"])), + emqx, + filename:join(["etc", "certs", "key.pem"]) + ), Certfile = emqx_common_test_helpers:app_path( - emqx, - filename:join(["etc", "certs", "cert.pem"])), + emqx, + filename:join(["etc", "certs", "cert.pem"]) + ), Cacertfile = emqx_common_test_helpers:app_path( - emqx, - filename:join(["etc", "certs", "cacert.pem"])), + emqx, + filename:join(["etc", "certs", "cacert.pem"]) + ), - {ok, 204, _} = request(put, uri(["authorization", "sources", "mongodb"]), - ?SOURCE2#{<<"ssl">> => #{ - <<"enable">> => <<"true">>, - <<"cacertfile">> => Cacertfile, - <<"certfile">> => Certfile, - <<"keyfile">> => Keyfile, - <<"verify">> => <<"verify_none">> - }}), + {ok, 204, _} = request( + put, + uri(["authorization", "sources", "mongodb"]), + ?SOURCE2#{ + <<"ssl">> => #{ + <<"enable">> => <<"true">>, + <<"cacertfile">> => Cacertfile, + <<"certfile">> => Certfile, + <<"keyfile">> => Keyfile, + <<"verify">> => <<"verify_none">> + } + } + ), {ok, 200, Result4} = request(get, uri(["authorization", "sources", "mongodb"]), []), {ok, 200, Status4} = request(get, uri(["authorization", "sources", "mongodb", "status"]), []), AssertFun(Status4), - ?assertMatch(#{<<"type">> := <<"mongodb">>, - <<"ssl">> := #{<<"enable">> := <<"true">>, - <<"cacertfile">> := ?MATCH_CERT, - <<"certfile">> := ?MATCH_CERT, - <<"keyfile">> := ?MATCH_RSA_KEY, - <<"verify">> := <<"verify_none">> - } - }, jsx:decode(Result4)), + ?assertMatch( + #{ + <<"type">> := <<"mongodb">>, + <<"ssl">> := #{ + <<"enable">> := <<"true">>, + <<"cacertfile">> := ?MATCH_CERT, + <<"certfile">> := ?MATCH_CERT, + <<"keyfile">> := ?MATCH_RSA_KEY, + <<"verify">> := <<"verify_none">> + } + }, + jsx:decode(Result4) + ), {ok, Cacert} = file:read_file(Cacertfile), {ok, Cert} = file:read_file(Certfile), {ok, Key} = file:read_file(Keyfile), - {ok, 204, _} = request(put, uri(["authorization", "sources", "mongodb"]), - ?SOURCE2#{<<"ssl">> => #{ - <<"enable">> => <<"true">>, - <<"cacertfile">> => Cacert, - <<"certfile">> => Cert, - <<"keyfile">> => Key, - <<"verify">> => <<"verify_none">> - }}), + {ok, 204, _} = request( + put, + uri(["authorization", "sources", "mongodb"]), + ?SOURCE2#{ + <<"ssl">> => #{ + <<"enable">> => <<"true">>, + <<"cacertfile">> => Cacert, + <<"certfile">> => Cert, + <<"keyfile">> => Key, + <<"verify">> => <<"verify_none">> + } + } + ), {ok, 200, Result5} = request(get, uri(["authorization", "sources", "mongodb"]), []), {ok, 200, Status5} = request(get, uri(["authorization", "sources", "mongodb", "status"]), []), AssertFun(Status5), - ?assertMatch(#{<<"type">> := <<"mongodb">>, - <<"ssl">> := #{<<"enable">> := <<"true">>, - <<"cacertfile">> := ?MATCH_CERT, - <<"certfile">> := ?MATCH_CERT, - <<"keyfile">> := ?MATCH_RSA_KEY, - <<"verify">> := <<"verify_none">> - } - }, jsx:decode(Result5)), + ?assertMatch( + #{ + <<"type">> := <<"mongodb">>, + <<"ssl">> := #{ + <<"enable">> := <<"true">>, + <<"cacertfile">> := ?MATCH_CERT, + <<"certfile">> := ?MATCH_CERT, + <<"keyfile">> := ?MATCH_RSA_KEY, + <<"verify">> := <<"verify_none">> + } + }, + jsx:decode(Result5) + ), - - #{ssl := #{cacertfile := SavedCacertfile, - certfile := SavedCertfile, - keyfile := SavedKeyfile - }} = emqx_authz:lookup(mongodb), + #{ + ssl := #{ + cacertfile := SavedCacertfile, + certfile := SavedCertfile, + keyfile := SavedKeyfile + } + } = emqx_authz:lookup(mongodb), ?assert(filelib:is_file(SavedCacertfile)), ?assert(filelib:is_file(SavedCertfile)), ?assert(filelib:is_file(SavedKeyfile)), {ok, 204, _} = request( - put, - uri(["authorization", "sources", "mysql"]), - ?SOURCE3#{<<"server">> := <<"192.168.1.100:3306">>}), + put, + uri(["authorization", "sources", "mysql"]), + ?SOURCE3#{<<"server">> := <<"192.168.1.100:3306">>} + ), {ok, 204, _} = request( - put, - uri(["authorization", "sources", "postgresql"]), - ?SOURCE4#{<<"server">> := <<"fake">>}), + put, + uri(["authorization", "sources", "postgresql"]), + ?SOURCE4#{<<"server">> := <<"fake">>} + ), {ok, 204, _} = request( - put, - uri(["authorization", "sources", "redis"]), - ?SOURCE5#{<<"servers">> := [<<"192.168.1.100:6379">>, - <<"192.168.1.100:6380">>]}), + put, + uri(["authorization", "sources", "redis"]), + ?SOURCE5#{ + <<"servers">> := [ + <<"192.168.1.100:6379">>, + <<"192.168.1.100:6380">> + ] + } + ), lists:foreach( - fun(#{<<"type">> := Type}) -> - {ok, 204, _} = request( - delete, - uri(["authorization", "sources", binary_to_list(Type)]), - []) - end, Sources), + fun(#{<<"type">> := Type}) -> + {ok, 204, _} = request( + delete, + uri(["authorization", "sources", binary_to_list(Type)]), + [] + ) + end, + Sources + ), {ok, 200, Result6} = request(get, uri(["authorization", "sources"]), []), ?assertEqual([], get_sources(Result6)), ?assertEqual([], emqx:get_config([authorization, sources])), @@ -302,48 +373,80 @@ t_api(_) -> t_move_source(_) -> {ok, _} = emqx_authz:update(replace, [?SOURCE1, ?SOURCE2, ?SOURCE3, ?SOURCE4, ?SOURCE5]), - ?assertMatch([ #{type := http} - , #{type := mongodb} - , #{type := mysql} - , #{type := postgresql} - , #{type := redis} - ], emqx_authz:lookup()), + ?assertMatch( + [ + #{type := http}, + #{type := mongodb}, + #{type := mysql}, + #{type := postgresql}, + #{type := redis} + ], + emqx_authz:lookup() + ), - {ok, 204, _} = request(post, uri(["authorization", "sources", "postgresql", "move"]), - #{<<"position">> => <<"front">>}), - ?assertMatch([ #{type := postgresql} - , #{type := http} - , #{type := mongodb} - , #{type := mysql} - , #{type := redis} - ], emqx_authz:lookup()), + {ok, 204, _} = request( + post, + uri(["authorization", "sources", "postgresql", "move"]), + #{<<"position">> => <<"front">>} + ), + ?assertMatch( + [ + #{type := postgresql}, + #{type := http}, + #{type := mongodb}, + #{type := mysql}, + #{type := redis} + ], + emqx_authz:lookup() + ), - {ok, 204, _} = request(post, uri(["authorization", "sources", "http", "move"]), - #{<<"position">> => <<"rear">>}), - ?assertMatch([ #{type := postgresql} - , #{type := mongodb} - , #{type := mysql} - , #{type := redis} - , #{type := http} - ], emqx_authz:lookup()), + {ok, 204, _} = request( + post, + uri(["authorization", "sources", "http", "move"]), + #{<<"position">> => <<"rear">>} + ), + ?assertMatch( + [ + #{type := postgresql}, + #{type := mongodb}, + #{type := mysql}, + #{type := redis}, + #{type := http} + ], + emqx_authz:lookup() + ), - {ok, 204, _} = request(post, uri(["authorization", "sources", "mysql", "move"]), - #{<<"position">> => <<"before:postgresql">>}), - ?assertMatch([ #{type := mysql} - , #{type := postgresql} - , #{type := mongodb} - , #{type := redis} - , #{type := http} - ], emqx_authz:lookup()), + {ok, 204, _} = request( + post, + uri(["authorization", "sources", "mysql", "move"]), + #{<<"position">> => <<"before:postgresql">>} + ), + ?assertMatch( + [ + #{type := mysql}, + #{type := postgresql}, + #{type := mongodb}, + #{type := redis}, + #{type := http} + ], + emqx_authz:lookup() + ), - {ok, 204, _} = request(post, uri(["authorization", "sources", "mongodb", "move"]), - #{<<"position">> => <<"after:http">>}), - ?assertMatch([ #{type := mysql} - , #{type := postgresql} - , #{type := redis} - , #{type := http} - , #{type := mongodb} - ], emqx_authz:lookup()), + {ok, 204, _} = request( + post, + uri(["authorization", "sources", "mongodb", "move"]), + #{<<"position">> => <<"after:http">>} + ), + ?assertMatch( + [ + #{type := mysql}, + #{type := postgresql}, + #{type := redis}, + #{type := http}, + #{type := mongodb} + ], + emqx_authz:lookup() + ), ok. diff --git a/apps/emqx_authz/test/emqx_authz_file_SUITE.erl b/apps/emqx_authz/test/emqx_authz_file_SUITE.erl index fb9849a26..9e7767d1e 100644 --- a/apps/emqx_authz/test/emqx_authz_file_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_file_SUITE.erl @@ -22,12 +22,15 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). --define(RAW_SOURCE, #{<<"type">> => <<"file">>, - <<"enable">> => true, - <<"rules">> => -<<"{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}." - "\n{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">> - }). +-define(RAW_SOURCE, #{ + <<"type">> => <<"file">>, + <<"enable">> => true, + <<"rules">> => + << + "{allow,{username,\"^dashboard?\"},subscribe,[\"$SYS/#\"]}." + "\n{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}." + >> +}). all() -> emqx_common_test_helpers:all(?MODULE). @@ -37,13 +40,17 @@ groups() -> init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), %% meck after authz started - meck:expect(emqx_authz, acl_conf_file, - fun() -> - emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") - end), + meck:expect( + emqx_authz, + acl_conf_file, + fun() -> + emqx_common_test_helpers:deps_path(emqx_authz, "etc/acl.conf") + end + ), Config. end_per_suite(_Config) -> @@ -57,7 +64,6 @@ init_per_testcase(_TestCase, Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. @@ -66,43 +72,55 @@ set_special_configs(_) -> %%------------------------------------------------------------------------------ t_ok(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, - ok = setup_config(?RAW_SOURCE#{<<"rules">> => <<"{allow, {user, \"username\"}, publish, [\"t\"]}.">>}), + ok = setup_config(?RAW_SOURCE#{ + <<"rules">> => <<"{allow, {user, \"username\"}, publish, [\"t\"]}.">> + }), io:format("~p", [emqx_authz:acl_conf_file()]), ?assertEqual( - allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), + allow, + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ), ?assertEqual( - deny, - emqx_access_control:authorize(ClientInfo, subscribe, <<"t">>)). + deny, + emqx_access_control:authorize(ClientInfo, subscribe, <<"t">>) + ). t_invalid_file(_Config) -> ?assertMatch( - {error, bad_acl_file_content}, - emqx_authz:update(?CMD_REPLACE, [?RAW_SOURCE#{<<"rules">> => <<"{{invalid term">>}])). + {error, bad_acl_file_content}, + emqx_authz:update(?CMD_REPLACE, [?RAW_SOURCE#{<<"rules">> => <<"{{invalid term">>}]) + ). t_update(_Config) -> - ok = setup_config(?RAW_SOURCE#{<<"rules">> => <<"{allow, {user, \"username\"}, publish, [\"t\"]}.">>}), + ok = setup_config(?RAW_SOURCE#{ + <<"rules">> => <<"{allow, {user, \"username\"}, publish, [\"t\"]}.">> + }), ?assertMatch( - {error, _}, - emqx_authz:update( - {?CMD_REPLACE, file}, - ?RAW_SOURCE#{<<"rules">> => <<"{{invalid term">>})), + {error, _}, + emqx_authz:update( + {?CMD_REPLACE, file}, + ?RAW_SOURCE#{<<"rules">> => <<"{{invalid term">>} + ) + ), ?assertMatch( - {ok, _}, - emqx_authz:update( - {?CMD_REPLACE, file}, ?RAW_SOURCE)). + {ok, _}, + emqx_authz:update( + {?CMD_REPLACE, file}, ?RAW_SOURCE + ) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -110,8 +128,9 @@ t_update(_Config) -> setup_config(SpecialParams) -> emqx_authz_test_lib:setup_config( - ?RAW_SOURCE, - SpecialParams). + ?RAW_SOURCE, + SpecialParams + ). stop_apps(Apps) -> lists:foreach(fun application:stop/1, Apps). diff --git a/apps/emqx_authz/test/emqx_authz_http_SUITE.erl b/apps/emqx_authz/test/emqx_authz_http_SUITE.erl index e6aeb703c..03016f7e2 100644 --- a/apps/emqx_authz/test/emqx_authz_http_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_http_SUITE.erl @@ -32,9 +32,9 @@ all() -> init_per_suite(Config) -> ok = stop_apps([emqx_resource, emqx_connector, cowboy]), ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 - ), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), ok = start_apps([emqx_resource, emqx_connector, cowboy]), Config. @@ -45,7 +45,6 @@ end_per_suite(_Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. @@ -62,251 +61,301 @@ end_per_testcase(_Case, _Config) -> %%------------------------------------------------------------------------------ t_response_handling(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, %% OK, get, no body ok = setup_handler_and_config( - fun(Req0, State) -> - Req = cowboy_req:reply(200, Req0), - {ok, Req, State} - end, - #{}), + fun(Req0, State) -> + Req = cowboy_req:reply(200, Req0), + {ok, Req, State} + end, + #{} + ), allow = emqx_access_control:authorize(ClientInfo, publish, <<"t">>), %% OK, get, body & headers ok = setup_handler_and_config( - fun(Req0, State) -> - Req = cowboy_req:reply( - 200, - #{<<"content-type">> => <<"text/plain">>}, - "Response body", - Req0), - {ok, Req, State} - end, - #{}), + fun(Req0, State) -> + Req = cowboy_req:reply( + 200, + #{<<"content-type">> => <<"text/plain">>}, + "Response body", + Req0 + ), + {ok, Req, State} + end, + #{} + ), ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ), %% OK, get, 204 ok = setup_handler_and_config( - fun(Req0, State) -> - Req = cowboy_req:reply(204, Req0), - {ok, Req, State} - end, - #{}), + fun(Req0, State) -> + Req = cowboy_req:reply(204, Req0), + {ok, Req, State} + end, + #{} + ), ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ), %% Not OK, get, 400 ok = setup_handler_and_config( - fun(Req0, State) -> - Req = cowboy_req:reply(400, Req0), - {ok, Req, State} - end, - #{}), + fun(Req0, State) -> + Req = cowboy_req:reply(400, Req0), + {ok, Req, State} + end, + #{} + ), ?assertEqual( deny, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ), %% Not OK, get, 400 + body & headers ok = setup_handler_and_config( - fun(Req0, State) -> - Req = cowboy_req:reply( - 400, - #{<<"content-type">> => <<"text/plain">>}, - "Response body", - Req0), - {ok, Req, State} - end, - #{}), + fun(Req0, State) -> + Req = cowboy_req:reply( + 400, + #{<<"content-type">> => <<"text/plain">>}, + "Response body", + Req0 + ), + {ok, Req, State} + end, + #{} + ), ?assertEqual( deny, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)). + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ). t_query_params(_Config) -> ok = setup_handler_and_config( - fun(Req0, State) -> - #{username := <<"user name">>, - clientid := <<"client id">>, - peerhost := <<"127.0.0.1">>, - proto_name := <<"MQTT">>, - mountpoint := <<"MOUNTPOINT">>, - topic := <<"t">>, - action := <<"publish">> - } = cowboy_req:match_qs( - [username, - clientid, - peerhost, - proto_name, - mountpoint, - topic, - action], - Req0), - Req = cowboy_req:reply(200, Req0), - {ok, Req, State} - end, - #{<<"url">> => <<"http://127.0.0.1:33333/authz/users/?" - "username=${username}&" - "clientid=${clientid}&" - "peerhost=${peerhost}&" - "proto_name=${proto_name}&" - "mountpoint=${mountpoint}&" - "topic=${topic}&" - "action=${action}">> - }), + fun(Req0, State) -> + #{ + username := <<"user name">>, + clientid := <<"client id">>, + peerhost := <<"127.0.0.1">>, + proto_name := <<"MQTT">>, + mountpoint := <<"MOUNTPOINT">>, + topic := <<"t">>, + action := <<"publish">> + } = cowboy_req:match_qs( + [ + username, + clientid, + peerhost, + proto_name, + mountpoint, + topic, + action + ], + Req0 + ), + Req = cowboy_req:reply(200, Req0), + {ok, Req, State} + end, + #{ + <<"url">> => << + "http://127.0.0.1:33333/authz/users/?" + "username=${username}&" + "clientid=${clientid}&" + "peerhost=${peerhost}&" + "proto_name=${proto_name}&" + "mountpoint=${mountpoint}&" + "topic=${topic}&" + "action=${action}" + >> + } + ), - ClientInfo = #{clientid => <<"client id">>, - username => <<"user name">>, - peerhost => {127,0,0,1}, - protocol => <<"MQTT">>, - mountpoint => <<"MOUNTPOINT">>, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"client id">>, + username => <<"user name">>, + peerhost => {127, 0, 0, 1}, + protocol => <<"MQTT">>, + mountpoint => <<"MOUNTPOINT">>, + zone => default, + listener => {tcp, default} + }, ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)). + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ). t_json_body(_Config) -> ok = setup_handler_and_config( - fun(Req0, State) -> - ?assertEqual( - <<"/authz/users/">>, - cowboy_req:path(Req0)), + fun(Req0, State) -> + ?assertEqual( + <<"/authz/users/">>, + cowboy_req:path(Req0) + ), - {ok, RawBody, Req1} = cowboy_req:read_body(Req0), + {ok, RawBody, Req1} = cowboy_req:read_body(Req0), - ?assertMatch( - #{<<"username">> := <<"user name">>, - <<"CLIENT">> := <<"client id">>, - <<"peerhost">> := <<"127.0.0.1">>, - <<"proto_name">> := <<"MQTT">>, - <<"mountpoint">> := <<"MOUNTPOINT">>, - <<"topic">> := <<"t">>, - <<"action">> := <<"publish">>}, - jiffy:decode(RawBody, [return_maps])), + ?assertMatch( + #{ + <<"username">> := <<"user name">>, + <<"CLIENT">> := <<"client id">>, + <<"peerhost">> := <<"127.0.0.1">>, + <<"proto_name">> := <<"MQTT">>, + <<"mountpoint">> := <<"MOUNTPOINT">>, + <<"topic">> := <<"t">>, + <<"action">> := <<"publish">> + }, + jiffy:decode(RawBody, [return_maps]) + ), - Req = cowboy_req:reply(200, Req1), - {ok, Req, State} - end, - #{<<"method">> => <<"post">>, - <<"body">> => #{<<"username">> => <<"${username}">>, - <<"CLIENT">> => <<"${clientid}">>, - <<"peerhost">> => <<"${peerhost}">>, - <<"proto_name">> => <<"${proto_name}">>, - <<"mountpoint">> => <<"${mountpoint}">>, - <<"topic">> => <<"${topic}">>, - <<"action">> => <<"${action}">>} - }), + Req = cowboy_req:reply(200, Req1), + {ok, Req, State} + end, + #{ + <<"method">> => <<"post">>, + <<"body">> => #{ + <<"username">> => <<"${username}">>, + <<"CLIENT">> => <<"${clientid}">>, + <<"peerhost">> => <<"${peerhost}">>, + <<"proto_name">> => <<"${proto_name}">>, + <<"mountpoint">> => <<"${mountpoint}">>, + <<"topic">> => <<"${topic}">>, + <<"action">> => <<"${action}">> + } + } + ), - ClientInfo = #{clientid => <<"client id">>, - username => <<"user name">>, - peerhost => {127,0,0,1}, - protocol => <<"MQTT">>, - mountpoint => <<"MOUNTPOINT">>, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"client id">>, + username => <<"user name">>, + peerhost => {127, 0, 0, 1}, + protocol => <<"MQTT">>, + mountpoint => <<"MOUNTPOINT">>, + zone => default, + listener => {tcp, default} + }, ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)). - + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ). t_form_body(_Config) -> ok = setup_handler_and_config( - fun(Req0, State) -> - ?assertEqual( - <<"/authz/users/">>, - cowboy_req:path(Req0)), + fun(Req0, State) -> + ?assertEqual( + <<"/authz/users/">>, + cowboy_req:path(Req0) + ), - {ok, [{PostVars, true}], Req1} = cowboy_req:read_urlencoded_body(Req0), + {ok, [{PostVars, true}], Req1} = cowboy_req:read_urlencoded_body(Req0), - ?assertMatch( - #{<<"username">> := <<"user name">>, - <<"clientid">> := <<"client id">>, - <<"peerhost">> := <<"127.0.0.1">>, - <<"proto_name">> := <<"MQTT">>, - <<"mountpoint">> := <<"MOUNTPOINT">>, - <<"topic">> := <<"t">>, - <<"action">> := <<"publish">>}, - jiffy:decode(PostVars, [return_maps])), + ?assertMatch( + #{ + <<"username">> := <<"user name">>, + <<"clientid">> := <<"client id">>, + <<"peerhost">> := <<"127.0.0.1">>, + <<"proto_name">> := <<"MQTT">>, + <<"mountpoint">> := <<"MOUNTPOINT">>, + <<"topic">> := <<"t">>, + <<"action">> := <<"publish">> + }, + jiffy:decode(PostVars, [return_maps]) + ), - Req = cowboy_req:reply(200, Req1), - {ok, Req, State} - end, - #{<<"method">> => <<"post">>, - <<"body">> => #{<<"username">> => <<"${username}">>, - <<"clientid">> => <<"${clientid}">>, - <<"peerhost">> => <<"${peerhost}">>, - <<"proto_name">> => <<"${proto_name}">>, - <<"mountpoint">> => <<"${mountpoint}">>, - <<"topic">> => <<"${topic}">>, - <<"action">> => <<"${action}">>}, - <<"headers">> => #{<<"content-type">> => <<"application/x-www-form-urlencoded">>} - }), + Req = cowboy_req:reply(200, Req1), + {ok, Req, State} + end, + #{ + <<"method">> => <<"post">>, + <<"body">> => #{ + <<"username">> => <<"${username}">>, + <<"clientid">> => <<"${clientid}">>, + <<"peerhost">> => <<"${peerhost}">>, + <<"proto_name">> => <<"${proto_name}">>, + <<"mountpoint">> => <<"${mountpoint}">>, + <<"topic">> => <<"${topic}">>, + <<"action">> => <<"${action}">> + }, + <<"headers">> => #{<<"content-type">> => <<"application/x-www-form-urlencoded">>} + } + ), - ClientInfo = #{clientid => <<"client id">>, - username => <<"user name">>, - peerhost => {127,0,0,1}, - protocol => <<"MQTT">>, - mountpoint => <<"MOUNTPOINT">>, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"client id">>, + username => <<"user name">>, + peerhost => {127, 0, 0, 1}, + protocol => <<"MQTT">>, + mountpoint => <<"MOUNTPOINT">>, + zone => default, + listener => {tcp, default} + }, ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)). - + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ). t_create_replace(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, %% Create with valid URL ok = setup_handler_and_config( - fun(Req0, State) -> - Req = cowboy_req:reply(200, Req0), - {ok, Req, State} - end, - #{<<"url">> => - <<"http://127.0.0.1:33333/authz/users/?topic=${topic}&action=${action}">>}), + fun(Req0, State) -> + Req = cowboy_req:reply(200, Req0), + {ok, Req, State} + end, + #{ + <<"url">> => + <<"http://127.0.0.1:33333/authz/users/?topic=${topic}&action=${action}">> + } + ), ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ), %% Changing to valid config OkConfig = maps:merge( - raw_http_authz_config(), - #{<<"url">> => - <<"http://127.0.0.1:33333/authz/users/?topic=${topic}&action=${action}">>}), + raw_http_authz_config(), + #{ + <<"url">> => + <<"http://127.0.0.1:33333/authz/users/?topic=${topic}&action=${action}">> + } + ), ?assertMatch( {ok, _}, - emqx_authz:update({?CMD_REPLACE, http}, OkConfig)), + emqx_authz:update({?CMD_REPLACE, http}, OkConfig) + ), ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)). + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -324,8 +373,9 @@ raw_http_authz_config() -> setup_handler_and_config(Handler, Config) -> ok = emqx_authz_http_test_server:set_handler(Handler), ok = emqx_authz_test_lib:setup_config( - raw_http_authz_config(), - Config). + raw_http_authz_config(), + Config + ). start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authz/test/emqx_authz_http_test_server.erl b/apps/emqx_authz/test/emqx_authz_http_test_server.erl index d1c3e2959..ea67f363b 100644 --- a/apps/emqx_authz/test/emqx_authz_http_test_server.erl +++ b/apps/emqx_authz/test/emqx_authz_http_test_server.erl @@ -26,10 +26,11 @@ -export([init/1]). % API --export([start_link/2, - stop/0, - set_handler/1 - ]). +-export([ + start_link/2, + stop/0, + set_handler/1 +]). %%------------------------------------------------------------------------------ %% API @@ -51,11 +52,14 @@ set_handler(F) when is_function(F, 2) -> init([Port, Path]) -> Dispatch = cowboy_router:compile( - [ - {'_', [{Path, ?MODULE, []}]} - ]), - TransOpts = #{socket_opts => [{port, Port}], - connection_type => supervisor}, + [ + {'_', [{Path, ?MODULE, []}]} + ] + ), + TransOpts = #{ + socket_opts => [{port, Port}], + connection_type => supervisor + }, ProtoOpts = #{env => #{dispatch => Dispatch}}, Tab = ets:new(?MODULE, [set, named_table, public]), @@ -78,9 +82,9 @@ init(Req, State) -> default_handler(Req0, State) -> Req = cowboy_req:reply( - 400, - #{<<"content-type">> => <<"text/plain">>}, - <<"">>, - Req0), + 400, + #{<<"content-type">> => <<"text/plain">>}, + <<"">>, + Req0 + ), {ok, Req, State}. - diff --git a/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl b/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl index 0f926f1bd..8c0f282e8 100644 --- a/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_mnesia_SUITE.erl @@ -29,8 +29,9 @@ groups() -> init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), Config. end_per_suite(_Config) -> @@ -47,7 +48,6 @@ end_per_testcase(_TestCase, _Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. @@ -64,16 +64,17 @@ t_all_topic_rules(_Config) -> ok = test_topic_rules(all). test_topic_rules(Key) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, SetupSamples = fun(CInfo, Samples) -> - setup_client_samples(CInfo, Samples, Key) - end, + setup_client_samples(CInfo, Samples, Key) + end, ok = emqx_authz_test_lib:test_no_topic_rules(ClientInfo, SetupSamples), @@ -82,41 +83,50 @@ test_topic_rules(Key) -> ok = emqx_authz_test_lib:test_deny_topic_rules(ClientInfo, SetupSamples). t_normalize_rules(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = emqx_authz_mnesia:store_rules( - {username, <<"username">>}, - [{allow, publish, "t"}]), + {username, <<"username">>}, + [{allow, publish, "t"}] + ), ?assertEqual( allow, - emqx_access_control:authorize(ClientInfo, publish, <<"t">>)), + emqx_access_control:authorize(ClientInfo, publish, <<"t">>) + ), ?assertException( - error, - {invalid_rule, _}, - emqx_authz_mnesia:store_rules( - {username, <<"username">>}, - [[allow, publish, <<"t">>]])), + error, + {invalid_rule, _}, + emqx_authz_mnesia:store_rules( + {username, <<"username">>}, + [[allow, publish, <<"t">>]] + ) + ), ?assertException( - error, - {invalid_rule_action, _}, - emqx_authz_mnesia:store_rules( - {username, <<"username">>}, - [{allow, pub, <<"t">>}])), + error, + {invalid_rule_action, _}, + emqx_authz_mnesia:store_rules( + {username, <<"username">>}, + [{allow, pub, <<"t">>}] + ) + ), ?assertException( - error, - {invalid_rule_permission, _}, - emqx_authz_mnesia:store_rules( - {username, <<"username">>}, - [{accept, publish, <<"t">>}])). + error, + {invalid_rule_permission, _}, + emqx_authz_mnesia:store_rules( + {username, <<"username">>}, + [{accept, publish, <<"t">>}] + ) + ). %%------------------------------------------------------------------------------ %% Helpers @@ -131,20 +141,23 @@ raw_mnesia_authz_config() -> setup_client_samples(ClientInfo, Samples, Key) -> ok = emqx_authz_mnesia:purge_rules(), Rules = lists:flatmap( - fun(#{topics := Topics, permission := Permission, action := Action}) -> - lists:map( - fun(Topic) -> - {binary_to_atom(Permission), binary_to_atom(Action), Topic} - end, - Topics) - end, - Samples), + fun(#{topics := Topics, permission := Permission, action := Action}) -> + lists:map( + fun(Topic) -> + {binary_to_atom(Permission), binary_to_atom(Action), Topic} + end, + Topics + ) + end, + Samples + ), #{username := Username, clientid := ClientId} = ClientInfo, - Who = case Key of - username -> {username, Username}; - clientid -> {clientid, ClientId}; - all -> all - end, + Who = + case Key of + username -> {username, Username}; + clientid -> {clientid, ClientId}; + all -> all + end, ok = emqx_authz_mnesia:store_rules(Who, Rules). setup_config() -> diff --git a/apps/emqx_authz/test/emqx_authz_mongodb_SUITE.erl b/apps/emqx_authz/test/emqx_authz_mongodb_SUITE.erl index 26eaf5a45..e0220488f 100644 --- a/apps/emqx_authz/test/emqx_authz_mongodb_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_mongodb_SUITE.erl @@ -38,9 +38,9 @@ init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?MONGO_HOST, ?MONGO_DEFAULT_PORT) of true -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 - ), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), ok = start_apps([emqx_resource, emqx_connector]), Config; false -> @@ -54,7 +54,6 @@ end_per_suite(_Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. @@ -72,12 +71,13 @@ end_per_testcase(_TestCase, _Config) -> %%------------------------------------------------------------------------------ t_topic_rules(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = emqx_authz_test_lib:test_no_topic_rules(ClientInfo, fun setup_client_samples/2), @@ -87,104 +87,137 @@ t_topic_rules(_Config) -> t_complex_selector(_) -> %% atom and string values also supported - ClientInfo = #{clientid => clientid, - username => "username", - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => clientid, + username => "username", + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, - Samples = [#{<<"x">> => #{<<"u">> => <<"username">>, - <<"c">> => [#{<<"c">> => <<"clientid">>}], - <<"y">> => 1}, - <<"permission">> => <<"allow">>, - <<"action">> => <<"publish">>, - <<"topics">> => [<<"t">>] - }], + Samples = [ + #{ + <<"x">> => #{ + <<"u">> => <<"username">>, + <<"c">> => [#{<<"c">> => <<"clientid">>}], + <<"y">> => 1 + }, + <<"permission">> => <<"allow">>, + <<"action">> => <<"publish">>, + <<"topics">> => [<<"t">>] + } + ], ok = setup_samples(Samples), ok = setup_config( - #{<<"selector">> => #{<<"x">> => #{<<"u">> => <<"${username}">>, - <<"c">> => [#{<<"c">> => <<"${clientid}">>}], - <<"y">> => 1} - } - }), + #{ + <<"selector">> => #{ + <<"x">> => #{ + <<"u">> => <<"${username}">>, + <<"c">> => [#{<<"c">> => <<"${clientid}">>}], + <<"y">> => 1 + } + } + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, publish, <<"t">>}]). + ClientInfo, + [{allow, publish, <<"t">>}] + ). t_mongo_error(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = setup_samples([]), ok = setup_config( - #{<<"selector">> => #{<<"$badoperator">> => <<"$badoperator">>}}), + #{<<"selector">> => #{<<"$badoperator">> => <<"$badoperator">>}} + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{deny, publish, <<"t">>}]). + ClientInfo, + [{deny, publish, <<"t">>}] + ). t_lookups(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - cn => <<"cn">>, - dn => <<"dn">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + cn => <<"cn">>, + dn => <<"dn">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, - ByClientid = #{<<"clientid">> => <<"clientid">>, - <<"topics">> => [<<"a">>], - <<"action">> => <<"all">>, - <<"permission">> => <<"allow">>}, + ByClientid = #{ + <<"clientid">> => <<"clientid">>, + <<"topics">> => [<<"a">>], + <<"action">> => <<"all">>, + <<"permission">> => <<"allow">> + }, ok = setup_samples([ByClientid]), ok = setup_config( - #{<<"selector">> => #{<<"clientid">> => <<"${clientid}">>}}), + #{<<"selector">> => #{<<"clientid">> => <<"${clientid}">>}} + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), - ByPeerhost = #{<<"peerhost">> => <<"127.0.0.1">>, - <<"topics">> => [<<"a">>], - <<"action">> => <<"all">>, - <<"permission">> => <<"allow">>}, + ByPeerhost = #{ + <<"peerhost">> => <<"127.0.0.1">>, + <<"topics">> => [<<"a">>], + <<"action">> => <<"all">>, + <<"permission">> => <<"allow">> + }, ok = setup_samples([ByPeerhost]), ok = setup_config( - #{<<"selector">> => #{<<"peerhost">> => <<"${peerhost}">>}}), + #{<<"selector">> => #{<<"peerhost">> => <<"${peerhost}">>}} + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). t_bad_selector(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - cn => <<"cn">>, - dn => <<"dn">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + cn => <<"cn">>, + dn => <<"dn">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = setup_config( - #{<<"selector">> => #{<<"$in">> => #{<<"a">> => 1}}}), + #{<<"selector">> => #{<<"$in">> => #{<<"a">> => 1}}} + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{deny, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {deny, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). %%------------------------------------------------------------------------------ %% Helpers @@ -201,17 +234,22 @@ setup_samples(AclRecords) -> setup_client_samples(ClientInfo, Samples) -> #{username := Username} = ClientInfo, Records = lists:map( - fun(Sample) -> - #{topics := Topics, - permission := Permission, - action := Action} = Sample, + fun(Sample) -> + #{ + topics := Topics, + permission := Permission, + action := Action + } = Sample, - #{<<"topics">> => Topics, - <<"permission">> => Permission, - <<"action">> => Action, - <<"username">> => Username} - end, - Samples), + #{ + <<"topics">> => Topics, + <<"permission">> => Permission, + <<"action">> => Action, + <<"username">> => Username + } + end, + Samples + ), setup_samples(Records), setup_config(#{<<"selector">> => #{<<"username">> => <<"${username}">>}}). @@ -221,8 +259,9 @@ reset_samples() -> setup_config(SpecialParams) -> emqx_authz_test_lib:setup_config( - raw_mongo_authz_config(), - SpecialParams). + raw_mongo_authz_config(), + SpecialParams + ). raw_mongo_authz_config() -> #{ @@ -238,14 +277,14 @@ raw_mongo_authz_config() -> }. mongo_server() -> - iolist_to_binary(io_lib:format("~s",[?MONGO_HOST])). + iolist_to_binary(io_lib:format("~s", [?MONGO_HOST])). mongo_config() -> [ - {database, <<"mqtt">>}, - {host, ?MONGO_HOST}, - {port, ?MONGO_DEFAULT_PORT}, - {register, ?MONGO_CLIENT} + {database, <<"mqtt">>}, + {host, ?MONGO_HOST}, + {port, ?MONGO_DEFAULT_PORT}, + {register, ?MONGO_CLIENT} ]. start_apps(Apps) -> diff --git a/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl b/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl index 193ed80fa..ce8d03984 100644 --- a/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_mysql_SUITE.erl @@ -37,16 +37,17 @@ init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?MYSQL_HOST, ?MYSQL_DEFAULT_PORT) of true -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 - ), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), ok = start_apps([emqx_resource, emqx_connector]), {ok, _} = emqx_resource:create_local( - ?MYSQL_RESOURCE, - ?RESOURCE_GROUP, - emqx_connector_mysql, - mysql_config(), - #{}), + ?MYSQL_RESOURCE, + ?RESOURCE_GROUP, + emqx_connector_mysql, + mysql_config(), + #{} + ), Config; false -> {skip, no_mysql} @@ -64,7 +65,6 @@ init_per_testcase(_TestCase, Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. @@ -73,12 +73,13 @@ set_special_configs(_) -> %%------------------------------------------------------------------------------ t_topic_rules(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = emqx_authz_test_lib:test_no_topic_rules(ClientInfo, fun setup_client_samples/2), @@ -86,127 +87,190 @@ t_topic_rules(_Config) -> ok = emqx_authz_test_lib:test_deny_topic_rules(ClientInfo, fun setup_client_samples/2). - t_lookups(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - cn => <<"cn">>, - dn => <<"dn">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + cn => <<"cn">>, + dn => <<"dn">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, %% by clientid ok = init_table(), - ok = q(<<"INSERT INTO acl(clientid, topic, permission, action)" - "VALUES(?, ?, ?, ?)">>, - [<<"clientid">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = q( + << + "INSERT INTO acl(clientid, topic, permission, action)" + "VALUES(?, ?, ?, ?)" + >>, + [<<"clientid">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE clientid = ${clientid}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE clientid = ${clientid}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), %% by peerhost ok = init_table(), - ok = q(<<"INSERT INTO acl(peerhost, topic, permission, action)" - "VALUES(?, ?, ?, ?)">>, - [<<"127.0.0.1">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = q( + << + "INSERT INTO acl(peerhost, topic, permission, action)" + "VALUES(?, ?, ?, ?)" + >>, + [<<"127.0.0.1">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE peerhost = ${peerhost}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE peerhost = ${peerhost}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), %% by cn ok = init_table(), - ok = q(<<"INSERT INTO acl(cn, topic, permission, action)" - "VALUES(?, ?, ?, ?)">>, - [<<"cn">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = q( + << + "INSERT INTO acl(cn, topic, permission, action)" + "VALUES(?, ?, ?, ?)" + >>, + [<<"cn">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE cn = ${cert_common_name}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE cn = ${cert_common_name}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), %% by dn ok = init_table(), - ok = q(<<"INSERT INTO acl(dn, topic, permission, action)" - "VALUES(?, ?, ?, ?)">>, - [<<"dn">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = q( + << + "INSERT INTO acl(dn, topic, permission, action)" + "VALUES(?, ?, ?, ?)" + >>, + [<<"dn">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE dn = ${cert_subject}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE dn = ${cert_subject}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). t_mysql_error(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = setup_config( - #{<<"query">> => <<"SOME INVALID STATEMENT">>}), + #{<<"query">> => <<"SOME INVALID STATEMENT">>} + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{deny, subscribe, <<"a">>}]). - + ClientInfo, + [{deny, subscribe, <<"a">>}] + ). t_create_invalid(_Config) -> BadConfig = maps:merge( - raw_mysql_authz_config(), - #{<<"server">> => <<"255.255.255.255:33333">>}), + raw_mysql_authz_config(), + #{<<"server">> => <<"255.255.255.255:33333">>} + ), {ok, _} = emqx_authz:update(?CMD_REPLACE, [BadConfig]), [_] = emqx_authz:lookup(). t_nonbinary_values(_Config) -> - ClientInfo = #{clientid => clientid, - username => "username", - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, - + ClientInfo = #{ + clientid => clientid, + username => "username", + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = init_table(), - ok = q(<<"INSERT INTO acl(clientid, username, topic, permission, action)" - "VALUES(?, ?, ?, ?, ?)">>, - [<<"clientid">>, <<"username">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = q( + << + "INSERT INTO acl(clientid, username, topic, permission, action)" + "VALUES(?, ?, ?, ?, ?)" + >>, + [<<"clientid">>, <<"username">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE clientid = ${clientid} AND username = ${username}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE clientid = ${clientid} AND username = ${username}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). %%------------------------------------------------------------------------------ %% Helpers @@ -221,33 +285,39 @@ raw_mysql_authz_config() -> <<"username">> => <<"root">>, <<"password">> => <<"public">>, - <<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE username = ${username}">>, + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE username = ${username}" + >>, <<"server">> => mysql_server() }. q(Sql) -> emqx_resource:query( - ?MYSQL_RESOURCE, - {sql, Sql}). + ?MYSQL_RESOURCE, + {sql, Sql} + ). q(Sql, Params) -> emqx_resource:query( - ?MYSQL_RESOURCE, - {sql, Sql, Params}). + ?MYSQL_RESOURCE, + {sql, Sql, Params} + ). init_table() -> ok = drop_table(), - ok = q("CREATE TABLE acl( - username VARCHAR(255), - clientid VARCHAR(255), - peerhost VARCHAR(255), - cn VARCHAR(255), - dn VARCHAR(255), - topic VARCHAR(255), - permission VARCHAR(255), - action VARCHAR(255))"). + ok = q( + "CREATE TABLE acl(\n" + " username VARCHAR(255),\n" + " clientid VARCHAR(255),\n" + " peerhost VARCHAR(255),\n" + " cn VARCHAR(255),\n" + " dn VARCHAR(255),\n" + " topic VARCHAR(255),\n" + " permission VARCHAR(255),\n" + " action VARCHAR(255))" + ). drop_table() -> ok = q("DROP TABLE IF EXISTS acl"). @@ -256,37 +326,50 @@ setup_client_samples(ClientInfo, Samples) -> #{username := Username} = ClientInfo, ok = init_table(), ok = lists:foreach( - fun(#{topics := Topics, permission := Permission, action := Action}) -> - lists:foreach( - fun(Topic) -> - q(<<"INSERT INTO acl(username, topic, permission, action)" - "VALUES(?, ?, ?, ?)">>, - [Username, Topic, Permission, Action]) - end, - Topics) - end, - Samples), + fun(#{topics := Topics, permission := Permission, action := Action}) -> + lists:foreach( + fun(Topic) -> + q( + << + "INSERT INTO acl(username, topic, permission, action)" + "VALUES(?, ?, ?, ?)" + >>, + [Username, Topic, Permission, Action] + ) + end, + Topics + ) + end, + Samples + ), setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE username = ${username}">>}). + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE username = ${username}" + >> + } + ). setup_config(SpecialParams) -> emqx_authz_test_lib:setup_config( - raw_mysql_authz_config(), - SpecialParams). + raw_mysql_authz_config(), + SpecialParams + ). mysql_server() -> - iolist_to_binary(io_lib:format("~s",[?MYSQL_HOST])). + iolist_to_binary(io_lib:format("~s", [?MYSQL_HOST])). mysql_config() -> - #{auto_reconnect => true, - database => <<"mqtt">>, - username => <<"root">>, - password => <<"public">>, - pool_size => 8, - server => {?MYSQL_HOST, ?MYSQL_DEFAULT_PORT}, - ssl => #{enable => false} - }. + #{ + auto_reconnect => true, + database => <<"mqtt">>, + username => <<"root">>, + password => <<"public">>, + pool_size => 8, + server => {?MYSQL_HOST, ?MYSQL_DEFAULT_PORT}, + ssl => #{enable => false} + }. start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl b/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl index 1785656aa..d4aaf7077 100644 --- a/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_postgresql_SUITE.erl @@ -37,16 +37,17 @@ init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?PGSQL_HOST, ?PGSQL_DEFAULT_PORT) of true -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 - ), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), ok = start_apps([emqx_resource, emqx_connector]), {ok, _} = emqx_resource:create_local( - ?PGSQL_RESOURCE, - ?RESOURCE_GROUP, - emqx_connector_pgsql, - pgsql_config(), - #{}), + ?PGSQL_RESOURCE, + ?RESOURCE_GROUP, + emqx_connector_pgsql, + pgsql_config(), + #{} + ), Config; false -> {skip, no_pgsql} @@ -64,7 +65,6 @@ init_per_testcase(_TestCase, Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. @@ -73,12 +73,13 @@ set_special_configs(_) -> %%------------------------------------------------------------------------------ t_topic_rules(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = emqx_authz_test_lib:test_no_topic_rules(ClientInfo, fun setup_client_samples/2), @@ -86,128 +87,195 @@ t_topic_rules(_Config) -> ok = emqx_authz_test_lib:test_deny_topic_rules(ClientInfo, fun setup_client_samples/2). - t_lookups(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - cn => <<"cn">>, - dn => <<"dn">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + cn => <<"cn">>, + dn => <<"dn">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, %% by clientid ok = init_table(), - ok = insert(<<"INSERT INTO acl(clientid, topic, permission, action)" - "VALUES($1, $2, $3, $4)">>, - [<<"clientid">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = insert( + << + "INSERT INTO acl(clientid, topic, permission, action)" + "VALUES($1, $2, $3, $4)" + >>, + [<<"clientid">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE clientid = ${clientid}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE clientid = ${clientid}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), %% by peerhost ok = init_table(), - ok = insert(<<"INSERT INTO acl(peerhost, topic, permission, action)" - "VALUES($1, $2, $3, $4)">>, - [<<"127.0.0.1">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = insert( + << + "INSERT INTO acl(peerhost, topic, permission, action)" + "VALUES($1, $2, $3, $4)" + >>, + [<<"127.0.0.1">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE peerhost = ${peerhost}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE peerhost = ${peerhost}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), %% by cn ok = init_table(), - ok = insert(<<"INSERT INTO acl(cn, topic, permission, action)" - "VALUES($1, $2, $3, $4)">>, - [<<"cn">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = insert( + << + "INSERT INTO acl(cn, topic, permission, action)" + "VALUES($1, $2, $3, $4)" + >>, + [<<"cn">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE cn = ${cert_common_name}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE cn = ${cert_common_name}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), %% by dn ok = init_table(), - ok = insert(<<"INSERT INTO acl(dn, topic, permission, action)" - "VALUES($1, $2, $3, $4)">>, - [<<"dn">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = insert( + << + "INSERT INTO acl(dn, topic, permission, action)" + "VALUES($1, $2, $3, $4)" + >>, + [<<"dn">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE dn = ${cert_subject}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE dn = ${cert_subject}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). t_pgsql_error(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE clientid = ${username}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE clientid = ${username}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{deny, subscribe, <<"a">>}]). - + ClientInfo, + [{deny, subscribe, <<"a">>}] + ). t_create_invalid(_Config) -> BadConfig = maps:merge( - raw_pgsql_authz_config(), - #{<<"server">> => <<"255.255.255.255:33333">>}), + raw_pgsql_authz_config(), + #{<<"server">> => <<"255.255.255.255:33333">>} + ), {ok, _} = emqx_authz:update(?CMD_REPLACE, [BadConfig]), [_] = emqx_authz:lookup(). t_nonbinary_values(_Config) -> - ClientInfo = #{clientid => clientid, - username => "username", - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, - + ClientInfo = #{ + clientid => clientid, + username => "username", + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = init_table(), - ok = insert(<<"INSERT INTO acl(clientid, username, topic, permission, action)" - "VALUES($1, $2, $3, $4, $5)">>, - [<<"clientid">>, <<"username">>, <<"a">>, <<"allow">>, <<"subscribe">>]), + ok = insert( + << + "INSERT INTO acl(clientid, username, topic, permission, action)" + "VALUES($1, $2, $3, $4, $5)" + >>, + [<<"clientid">>, <<"username">>, <<"a">>, <<"allow">>, <<"subscribe">>] + ), ok = setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE clientid = ${clientid} AND username = ${username}">>}), + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE clientid = ${clientid} AND username = ${username}" + >> + } + ), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). %%------------------------------------------------------------------------------ %% Helpers @@ -222,34 +290,40 @@ raw_pgsql_authz_config() -> <<"username">> => <<"root">>, <<"password">> => <<"public">>, - <<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE username = ${username}">>, + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE username = ${username}" + >>, <<"server">> => pgsql_server() }. q(Sql) -> emqx_resource:query( - ?PGSQL_RESOURCE, - {query, Sql}). + ?PGSQL_RESOURCE, + {query, Sql} + ). insert(Sql, Params) -> {ok, _} = emqx_resource:query( - ?PGSQL_RESOURCE, - {query, Sql, Params}), + ?PGSQL_RESOURCE, + {query, Sql, Params} + ), ok. init_table() -> ok = drop_table(), - {ok, _, _} = q("CREATE TABLE acl( - username VARCHAR(255), - clientid VARCHAR(255), - peerhost VARCHAR(255), - cn VARCHAR(255), - dn VARCHAR(255), - topic VARCHAR(255), - permission VARCHAR(255), - action VARCHAR(255))"), + {ok, _, _} = q( + "CREATE TABLE acl(\n" + " username VARCHAR(255),\n" + " clientid VARCHAR(255),\n" + " peerhost VARCHAR(255),\n" + " cn VARCHAR(255),\n" + " dn VARCHAR(255),\n" + " topic VARCHAR(255),\n" + " permission VARCHAR(255),\n" + " action VARCHAR(255))" + ), ok. drop_table() -> @@ -260,37 +334,50 @@ setup_client_samples(ClientInfo, Samples) -> #{username := Username} = ClientInfo, ok = init_table(), ok = lists:foreach( - fun(#{topics := Topics, permission := Permission, action := Action}) -> - lists:foreach( - fun(Topic) -> - insert(<<"INSERT INTO acl(username, topic, permission, action)" - "VALUES($1, $2, $3, $4)">>, - [Username, Topic, Permission, Action]) - end, - Topics) - end, - Samples), + fun(#{topics := Topics, permission := Permission, action := Action}) -> + lists:foreach( + fun(Topic) -> + insert( + << + "INSERT INTO acl(username, topic, permission, action)" + "VALUES($1, $2, $3, $4)" + >>, + [Username, Topic, Permission, Action] + ) + end, + Topics + ) + end, + Samples + ), setup_config( - #{<<"query">> => <<"SELECT permission, action, topic " - "FROM acl WHERE username = ${username}">>}). + #{ + <<"query">> => << + "SELECT permission, action, topic " + "FROM acl WHERE username = ${username}" + >> + } + ). setup_config(SpecialParams) -> emqx_authz_test_lib:setup_config( - raw_pgsql_authz_config(), - SpecialParams). + raw_pgsql_authz_config(), + SpecialParams + ). pgsql_server() -> - iolist_to_binary(io_lib:format("~s",[?PGSQL_HOST])). + iolist_to_binary(io_lib:format("~s", [?PGSQL_HOST])). pgsql_config() -> - #{auto_reconnect => true, - database => <<"mqtt">>, - username => <<"root">>, - password => <<"public">>, - pool_size => 8, - server => {?PGSQL_HOST, ?PGSQL_DEFAULT_PORT}, - ssl => #{enable => false} - }. + #{ + auto_reconnect => true, + database => <<"mqtt">>, + username => <<"root">>, + password => <<"public">>, + pool_size => 8, + server => {?PGSQL_HOST, ?PGSQL_DEFAULT_PORT}, + ssl => #{enable => false} + }. start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl b/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl index c0cdef2c9..286dbd286 100644 --- a/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_redis_SUITE.erl @@ -38,16 +38,17 @@ init_per_suite(Config) -> case emqx_common_test_helpers:is_tcp_server_available(?REDIS_HOST, ?REDIS_DEFAULT_PORT) of true -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1 - ), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), ok = start_apps([emqx_resource, emqx_connector]), {ok, _} = emqx_resource:create_local( - ?REDIS_RESOURCE, - ?RESOURCE_GROUP, - emqx_connector_redis, - redis_config(), - #{}), + ?REDIS_RESOURCE, + ?RESOURCE_GROUP, + emqx_connector_redis, + redis_config(), + #{} + ), Config; false -> {skip, no_redis} @@ -65,109 +66,130 @@ init_per_testcase(_TestCase, Config) -> set_special_configs(emqx_authz) -> ok = emqx_authz_test_lib:reset_authorizers(); - set_special_configs(_) -> ok. - %%------------------------------------------------------------------------------ %% Tests %%------------------------------------------------------------------------------ t_topic_rules(_Config) -> - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, ok = emqx_authz_test_lib:test_no_topic_rules(ClientInfo, fun setup_client_samples/2), ok = emqx_authz_test_lib:test_allow_topic_rules(ClientInfo, fun setup_client_samples/2). - t_lookups(_Config) -> - ClientInfo = #{clientid => <<"client id">>, - cn => <<"cn">>, - dn => <<"dn">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"client id">>, + cn => <<"cn">>, + dn => <<"dn">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, - ByClientid = #{<<"mqtt_user:client id">> => - #{<<"a">> => <<"all">>}}, + ByClientid = #{ + <<"mqtt_user:client id">> => + #{<<"a">> => <<"all">>} + }, ok = setup_sample(ByClientid), ok = setup_config(#{<<"cmd">> => <<"HGETALL mqtt_user:${clientid}">>}), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), - ByPeerhost = #{<<"mqtt_user:127.0.0.1">> => - #{<<"a">> => <<"all">>}}, + ByPeerhost = #{ + <<"mqtt_user:127.0.0.1">> => + #{<<"a">> => <<"all">>} + }, ok = setup_sample(ByPeerhost), ok = setup_config(#{<<"cmd">> => <<"HGETALL mqtt_user:${peerhost}">>}), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), - ByCN = #{<<"mqtt_user:cn">> => - #{<<"a">> => <<"all">>}}, + ByCN = #{ + <<"mqtt_user:cn">> => + #{<<"a">> => <<"all">>} + }, ok = setup_sample(ByCN), ok = setup_config(#{<<"cmd">> => <<"HGETALL mqtt_user:${cert_common_name}">>}), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]), + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ), - - ByDN = #{<<"mqtt_user:dn">> => - #{<<"a">> => <<"all">>}}, + ByDN = #{ + <<"mqtt_user:dn">> => + #{<<"a">> => <<"all">>} + }, ok = setup_sample(ByDN), ok = setup_config(#{<<"cmd">> => <<"HGETALL mqtt_user:${cert_subject}">>}), ok = emqx_authz_test_lib:test_samples( - ClientInfo, - [{allow, subscribe, <<"a">>}, - {deny, subscribe, <<"b">>}]). + ClientInfo, + [ + {allow, subscribe, <<"a">>}, + {deny, subscribe, <<"b">>} + ] + ). t_create_invalid(_Config) -> AuthzConfig = raw_redis_authz_config(), InvalidConfigs = - [maps:without([<<"server">>], AuthzConfig), - AuthzConfig#{<<"server">> => <<"unknownhost:3333">>}, - AuthzConfig#{<<"password">> => <<"wrongpass">>}, - AuthzConfig#{<<"database">> => <<"5678">>}], + [ + maps:without([<<"server">>], AuthzConfig), + AuthzConfig#{<<"server">> => <<"unknownhost:3333">>}, + AuthzConfig#{<<"password">> => <<"wrongpass">>}, + AuthzConfig#{<<"database">> => <<"5678">>} + ], lists:foreach( - fun(Config) -> + fun(Config) -> {ok, _} = emqx_authz:update(?CMD_REPLACE, [Config]), [_] = emqx_authz:lookup() - - end, - InvalidConfigs). + end, + InvalidConfigs + ). t_redis_error(_Config) -> ok = setup_config(#{<<"cmd">> => <<"INVALID COMMAND">>}), - ClientInfo = #{clientid => <<"clientid">>, - username => <<"username">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo = #{ + clientid => <<"clientid">>, + username => <<"username">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, deny = emqx_access_control:authorize(ClientInfo, subscribe, <<"a">>). @@ -178,30 +200,36 @@ t_redis_error(_Config) -> setup_sample(AuthzData) -> {ok, _} = q(["FLUSHDB"]), ok = lists:foreach( - fun({Key, Values}) -> - lists:foreach( - fun({TopicFilter, Action}) -> - q(["HSET", Key, TopicFilter, Action]) - end, - maps:to_list(Values)) - end, - maps:to_list(AuthzData)). + fun({Key, Values}) -> + lists:foreach( + fun({TopicFilter, Action}) -> + q(["HSET", Key, TopicFilter, Action]) + end, + maps:to_list(Values) + ) + end, + maps:to_list(AuthzData) + ). setup_client_samples(ClientInfo, Samples) -> #{username := Username} = ClientInfo, Key = <<"mqtt_user:", Username/binary>>, lists:foreach( - fun(Sample) -> - #{topics := Topics, + fun(Sample) -> + #{ + topics := Topics, permission := <<"allow">>, - action := Action} = Sample, - lists:foreach( + action := Action + } = Sample, + lists:foreach( fun(Topic) -> - q(["HSET", Key, Topic, Action]) + q(["HSET", Key, Topic, Action]) end, - Topics) - end, - Samples), + Topics + ) + end, + Samples + ), setup_config(#{}). setup_config(SpecialParams) -> @@ -221,22 +249,24 @@ raw_redis_authz_config() -> }. redis_server() -> - iolist_to_binary(io_lib:format("~s",[?REDIS_HOST])). + iolist_to_binary(io_lib:format("~s", [?REDIS_HOST])). q(Command) -> emqx_resource:query( - ?REDIS_RESOURCE, - {cmd, Command}). + ?REDIS_RESOURCE, + {cmd, Command} + ). redis_config() -> - #{auto_reconnect => true, - database => 1, - pool_size => 8, - redis_type => single, - password => "public", - server => {?REDIS_HOST, ?REDIS_DEFAULT_PORT}, - ssl => #{enable => false} - }. + #{ + auto_reconnect => true, + database => 1, + pool_size => 8, + redis_type => single, + password => "public", + server => {?REDIS_HOST, ?REDIS_DEFAULT_PORT}, + ssl => #{enable => false} + }. start_apps(Apps) -> lists:foreach(fun application:ensure_all_started/1, Apps). diff --git a/apps/emqx_authz/test/emqx_authz_rule_SUITE.erl b/apps/emqx_authz/test/emqx_authz_rule_SUITE.erl index 62299621a..eb16c4e50 100644 --- a/apps/emqx_authz/test/emqx_authz_rule_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_rule_SUITE.erl @@ -23,30 +23,38 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl"). --define(SOURCE1, {deny, all}). --define(SOURCE2, {allow, {ipaddr, "127.0.0.1"}, all, [{eq, "#"}, {eq, "+"}]}). +-define(SOURCE1, {deny, all}). +-define(SOURCE2, {allow, {ipaddr, "127.0.0.1"}, all, [{eq, "#"}, {eq, "+"}]}). -define(SOURCE3, {allow, {ipaddrs, ["127.0.0.1", "192.168.1.0/24"]}, subscribe, [?PH_S_CLIENTID]}). -define(SOURCE4, {allow, {'and', [{client, "test"}, {user, "test"}]}, publish, ["topic/test"]}). --define(SOURCE5, {allow, {'or', - [{username, {re, "^test"}}, - {clientid, {re, "test?"}}]}, - publish, [?PH_S_USERNAME, ?PH_S_CLIENTID]}). +-define(SOURCE5, + {allow, + {'or', [ + {username, {re, "^test"}}, + {clientid, {re, "test?"}} + ]}, + publish, [?PH_S_USERNAME, ?PH_S_CLIENTID]} +). all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Config) -> ok = emqx_common_test_helpers:start_apps( - [emqx_conf, emqx_authz], - fun set_special_configs/1), + [emqx_conf, emqx_authz], + fun set_special_configs/1 + ), Config. end_per_suite(_Config) -> {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => <<"allow">>, - <<"cache">> => #{<<"enable">> => <<"true">>}, - <<"sources">> => []}), + [authorization], + #{ + <<"no_match">> => <<"allow">>, + <<"cache">> => #{<<"enable">> => <<"true">>}, + <<"sources">> => [] + } + ), emqx_common_test_helpers:stop_apps([emqx_authz, emqx_conf]), ok. @@ -61,115 +69,242 @@ set_special_configs(_App) -> t_compile(_) -> ?assertEqual({deny, all, all, [['#']]}, emqx_authz_rule:compile(?SOURCE1)), - ?assertEqual({allow, {ipaddr, {{127,0,0,1}, {127,0,0,1}, 32}}, - all, [{eq, ['#']}, {eq, ['+']}]}, emqx_authz_rule:compile(?SOURCE2)), + ?assertEqual( + {allow, {ipaddr, {{127, 0, 0, 1}, {127, 0, 0, 1}, 32}}, all, [{eq, ['#']}, {eq, ['+']}]}, + emqx_authz_rule:compile(?SOURCE2) + ), - ?assertEqual({allow, - {ipaddrs,[{{127,0,0,1},{127,0,0,1},32}, - {{192,168,1,0},{192,168,1,255},24}]}, - subscribe, - [{pattern,[?PH_CLIENTID]}] - }, emqx_authz_rule:compile(?SOURCE3)), + ?assertEqual( + {allow, + {ipaddrs, [ + {{127, 0, 0, 1}, {127, 0, 0, 1}, 32}, + {{192, 168, 1, 0}, {192, 168, 1, 255}, 24} + ]}, + subscribe, [{pattern, [?PH_CLIENTID]}]}, + emqx_authz_rule:compile(?SOURCE3) + ), - ?assertMatch({allow, - {'and', [{clientid, {eq, <<"test">>}}, {username, {eq, <<"test">>}}]}, - publish, - [[<<"topic">>, <<"test">>]] - }, emqx_authz_rule:compile(?SOURCE4)), + ?assertMatch( + {allow, {'and', [{clientid, {eq, <<"test">>}}, {username, {eq, <<"test">>}}]}, publish, [ + [<<"topic">>, <<"test">>] + ]}, + emqx_authz_rule:compile(?SOURCE4) + ), - ?assertMatch({allow, - {'or', [{username, {re_pattern, _, _, _, _}}, - {clientid, {re_pattern, _, _, _, _}}]}, - publish, [{pattern, [?PH_USERNAME]}, {pattern, [?PH_CLIENTID]}] - }, emqx_authz_rule:compile(?SOURCE5)), + ?assertMatch( + {allow, + {'or', [ + {username, {re_pattern, _, _, _, _}}, + {clientid, {re_pattern, _, _, _, _}} + ]}, + publish, [{pattern, [?PH_USERNAME]}, {pattern, [?PH_CLIENTID]}]}, + emqx_authz_rule:compile(?SOURCE5) + ), ok. - t_match(_) -> - ClientInfo1 = #{clientid => <<"test">>, - username => <<"test">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, - ClientInfo2 = #{clientid => <<"test">>, - username => <<"test">>, - peerhost => {192,168,1,10}, - zone => default, - listener => {tcp, default} - }, - ClientInfo3 = #{clientid => <<"test">>, - username => <<"fake">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, - ClientInfo4 = #{clientid => <<"fake">>, - username => <<"test">>, - peerhost => {127,0,0,1}, - zone => default, - listener => {tcp, default} - }, + ClientInfo1 = #{ + clientid => <<"test">>, + username => <<"test">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, + ClientInfo2 = #{ + clientid => <<"test">>, + username => <<"test">>, + peerhost => {192, 168, 1, 10}, + zone => default, + listener => {tcp, default} + }, + ClientInfo3 = #{ + clientid => <<"test">>, + username => <<"fake">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, + ClientInfo4 = #{ + clientid => <<"fake">>, + username => <<"test">>, + peerhost => {127, 0, 0, 1}, + zone => default, + listener => {tcp, default} + }, - ?assertEqual({matched, deny}, - emqx_authz_rule:match(ClientInfo1, subscribe, <<"#">>, - emqx_authz_rule:compile(?SOURCE1))), - ?assertEqual({matched, deny}, - emqx_authz_rule:match(ClientInfo2, subscribe, <<"+">>, - emqx_authz_rule:compile(?SOURCE1))), - ?assertEqual({matched, deny}, - emqx_authz_rule:match(ClientInfo3, subscribe, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE1))), + ?assertEqual( + {matched, deny}, + emqx_authz_rule:match( + ClientInfo1, + subscribe, + <<"#">>, + emqx_authz_rule:compile(?SOURCE1) + ) + ), + ?assertEqual( + {matched, deny}, + emqx_authz_rule:match( + ClientInfo2, + subscribe, + <<"+">>, + emqx_authz_rule:compile(?SOURCE1) + ) + ), + ?assertEqual( + {matched, deny}, + emqx_authz_rule:match( + ClientInfo3, + subscribe, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE1) + ) + ), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo1, subscribe, <<"#">>, - emqx_authz_rule:compile(?SOURCE2))), - ?assertEqual(nomatch, - emqx_authz_rule:match(ClientInfo1, subscribe, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE2))), - ?assertEqual(nomatch, - emqx_authz_rule:match(ClientInfo2, subscribe, <<"#">>, - emqx_authz_rule:compile(?SOURCE2))), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo1, + subscribe, + <<"#">>, + emqx_authz_rule:compile(?SOURCE2) + ) + ), + ?assertEqual( + nomatch, + emqx_authz_rule:match( + ClientInfo1, + subscribe, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE2) + ) + ), + ?assertEqual( + nomatch, + emqx_authz_rule:match( + ClientInfo2, + subscribe, + <<"#">>, + emqx_authz_rule:compile(?SOURCE2) + ) + ), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo1, subscribe, <<"test">>, - emqx_authz_rule:compile(?SOURCE3))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo2, subscribe, <<"test">>, - emqx_authz_rule:compile(?SOURCE3))), - ?assertEqual(nomatch, - emqx_authz_rule:match(ClientInfo2, subscribe, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE3))), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo1, + subscribe, + <<"test">>, + emqx_authz_rule:compile(?SOURCE3) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo2, + subscribe, + <<"test">>, + emqx_authz_rule:compile(?SOURCE3) + ) + ), + ?assertEqual( + nomatch, + emqx_authz_rule:match( + ClientInfo2, + subscribe, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE3) + ) + ), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo1, publish, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE4))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo2, publish, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE4))), - ?assertEqual(nomatch, - emqx_authz_rule:match(ClientInfo3, publish, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE4))), - ?assertEqual(nomatch, - emqx_authz_rule:match(ClientInfo4, publish, <<"topic/test">>, - emqx_authz_rule:compile(?SOURCE4))), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo1, + publish, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE4) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo2, + publish, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE4) + ) + ), + ?assertEqual( + nomatch, + emqx_authz_rule:match( + ClientInfo3, + publish, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE4) + ) + ), + ?assertEqual( + nomatch, + emqx_authz_rule:match( + ClientInfo4, + publish, + <<"topic/test">>, + emqx_authz_rule:compile(?SOURCE4) + ) + ), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo1, publish, <<"test">>, - emqx_authz_rule:compile(?SOURCE5))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo2, publish, <<"test">>, - emqx_authz_rule:compile(?SOURCE5))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo3, publish, <<"test">>, - emqx_authz_rule:compile(?SOURCE5))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo3, publish, <<"fake">>, - emqx_authz_rule:compile(?SOURCE5))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo4, publish, <<"test">>, - emqx_authz_rule:compile(?SOURCE5))), - ?assertEqual({matched, allow}, - emqx_authz_rule:match(ClientInfo4, publish, <<"fake">>, - emqx_authz_rule:compile(?SOURCE5))), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo1, + publish, + <<"test">>, + emqx_authz_rule:compile(?SOURCE5) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo2, + publish, + <<"test">>, + emqx_authz_rule:compile(?SOURCE5) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo3, + publish, + <<"test">>, + emqx_authz_rule:compile(?SOURCE5) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo3, + publish, + <<"fake">>, + emqx_authz_rule:compile(?SOURCE5) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo4, + publish, + <<"test">>, + emqx_authz_rule:compile(?SOURCE5) + ) + ), + ?assertEqual( + {matched, allow}, + emqx_authz_rule:match( + ClientInfo4, + publish, + <<"fake">>, + emqx_authz_rule:compile(?SOURCE5) + ) + ), ok. diff --git a/apps/emqx_authz/test/emqx_authz_test_lib.erl b/apps/emqx_authz/test/emqx_authz_test_lib.erl index 6225d3a83..a2cc39b13 100644 --- a/apps/emqx_authz/test/emqx_authz_test_lib.erl +++ b/apps/emqx_authz/test/emqx_authz_test_lib.erl @@ -32,33 +32,40 @@ restore_authorizers() -> reset_authorizers(Nomatch, ChacheEnabled) -> {ok, _} = emqx:update_config( - [authorization], - #{<<"no_match">> => atom_to_binary(Nomatch), - <<"cache">> => #{<<"enable">> => atom_to_binary(ChacheEnabled)}, - <<"sources">> => []}), + [authorization], + #{ + <<"no_match">> => atom_to_binary(Nomatch), + <<"cache">> => #{<<"enable">> => atom_to_binary(ChacheEnabled)}, + <<"sources">> => [] + } + ), ok. setup_config(BaseConfig, SpecialParams) -> Config = maps:merge(BaseConfig, SpecialParams), case emqx_authz:update(?CMD_REPLACE, [Config]) of - {ok, _} -> ok; - {error, Reason} -> {error, Reason} + {ok, _} -> ok; + {error, Reason} -> {error, Reason} end. test_samples(ClientInfo, Samples) -> lists:foreach( - fun({Expected, Action, Topic}) -> - ct:pal( + fun({Expected, Action, Topic}) -> + ct:pal( "client_info: ~p, action: ~p, topic: ~p, expected: ~p", - [ClientInfo, Action, Topic, Expected]), - ?assertEqual( - Expected, - emqx_access_control:authorize( - ClientInfo, - Action, - Topic)) - end, - Samples). + [ClientInfo, Action, Topic, Expected] + ), + ?assertEqual( + Expected, + emqx_access_control:authorize( + ClientInfo, + Action, + Topic + ) + ) + end, + Samples + ). test_no_topic_rules(ClientInfo, SetupSamples) -> %% No rules @@ -67,43 +74,52 @@ test_no_topic_rules(ClientInfo, SetupSamples) -> ok = SetupSamples(ClientInfo, []), ok = test_samples( - ClientInfo, - [{deny, subscribe, <<"#">>}, + ClientInfo, + [ + {deny, subscribe, <<"#">>}, {deny, subscribe, <<"subs">>}, - {deny, publish, <<"pub">>}]). + {deny, publish, <<"pub">>} + ] + ). test_allow_topic_rules(ClientInfo, SetupSamples) -> - Samples = [#{ - topics => [<<"eq testpub1/${username}">>, - <<"testpub2/${clientid}">>, - <<"testpub3/#">>], - permission => <<"allow">>, - action => <<"publish">> - }, - #{ - topics => [<<"eq testsub1/${username}">>, - <<"testsub2/${clientid}">>, - <<"testsub3/#">>], - permission => <<"allow">>, - action => <<"subscribe">> - }, + Samples = [ + #{ + topics => [ + <<"eq testpub1/${username}">>, + <<"testpub2/${clientid}">>, + <<"testpub3/#">> + ], + permission => <<"allow">>, + action => <<"publish">> + }, + #{ + topics => [ + <<"eq testsub1/${username}">>, + <<"testsub2/${clientid}">>, + <<"testsub3/#">> + ], + permission => <<"allow">>, + action => <<"subscribe">> + }, - #{ - topics => [<<"eq testall1/${username}">>, - <<"testall2/${clientid}">>, - <<"testall3/#">>], - permission => <<"allow">>, - action => <<"all">> - } - ], + #{ + topics => [ + <<"eq testall1/${username}">>, + <<"testall2/${clientid}">>, + <<"testall3/#">> + ], + permission => <<"allow">>, + action => <<"all">> + } + ], ok = reset_authorizers(deny, false), ok = SetupSamples(ClientInfo, Samples), ok = test_samples( - ClientInfo, - [ - + ClientInfo, + [ %% Publish rules {deny, publish, <<"testpub1/username">>}, @@ -114,7 +130,6 @@ test_allow_topic_rules(ClientInfo, SetupSamples) -> {deny, publish, <<"testpub2/username">>}, {deny, publish, <<"testpub1/clientid">>}, - {deny, subscribe, <<"testpub1/username">>}, {deny, subscribe, <<"testpub2/clientid">>}, {deny, subscribe, <<"testpub3/foobar">>}, @@ -154,41 +169,47 @@ test_allow_topic_rules(ClientInfo, SetupSamples) -> {deny, publish, <<"testall2/username">>}, {deny, publish, <<"testall1/clientid">>}, {deny, publish, <<"testall4/foobar">>} - ]). + ] + ). test_deny_topic_rules(ClientInfo, SetupSamples) -> Samples = [ - #{ - topics => [<<"eq testpub1/${username}">>, - <<"testpub2/${clientid}">>, - <<"testpub3/#">>], - permission => <<"deny">>, - action => <<"publish">> - }, - #{ - topics => [<<"eq testsub1/${username}">>, - <<"testsub2/${clientid}">>, - <<"testsub3/#">>], - permission => <<"deny">>, - action => <<"subscribe">> - }, + #{ + topics => [ + <<"eq testpub1/${username}">>, + <<"testpub2/${clientid}">>, + <<"testpub3/#">> + ], + permission => <<"deny">>, + action => <<"publish">> + }, + #{ + topics => [ + <<"eq testsub1/${username}">>, + <<"testsub2/${clientid}">>, + <<"testsub3/#">> + ], + permission => <<"deny">>, + action => <<"subscribe">> + }, - #{ - topics => [<<"eq testall1/${username}">>, - <<"testall2/${clientid}">>, - <<"testall3/#">>], - permission => <<"deny">>, - action => <<"all">> - } - ], + #{ + topics => [ + <<"eq testall1/${username}">>, + <<"testall2/${clientid}">>, + <<"testall3/#">> + ], + permission => <<"deny">>, + action => <<"all">> + } + ], ok = reset_authorizers(allow, false), ok = SetupSamples(ClientInfo, Samples), ok = test_samples( - ClientInfo, - [ - + ClientInfo, + [ %% Publish rules {allow, publish, <<"testpub1/username">>}, @@ -199,7 +220,6 @@ test_deny_topic_rules(ClientInfo, SetupSamples) -> {allow, publish, <<"testpub2/username">>}, {allow, publish, <<"testpub1/clientid">>}, - {allow, subscribe, <<"testpub1/username">>}, {allow, subscribe, <<"testpub2/clientid">>}, {allow, subscribe, <<"testpub3/foobar">>}, @@ -239,4 +259,5 @@ test_deny_topic_rules(ClientInfo, SetupSamples) -> {allow, publish, <<"testall2/username">>}, {allow, publish, <<"testall1/clientid">>}, {allow, publish, <<"testall4/foobar">>} - ]). + ] + ).