diff --git a/apps/emqx_authz/src/emqx_authz_api_schema.erl b/apps/emqx_authz/src/emqx_authz_api_schema.erl index 1bc316986..4c17cd0b6 100644 --- a/apps/emqx_authz/src/emqx_authz_api_schema.erl +++ b/apps/emqx_authz/src/emqx_authz_api_schema.erl @@ -19,17 +19,17 @@ -export([definitions/0]). definitions() -> - RetruenedRules = #{ + RetruenedSources = #{ allOf => [ #{type => object, properties => #{ annotations => #{ type => object, - required => [id], + required => [status], properties => #{ - id => #{ - type => string - }, - principal => minirest:ref(<<"principal">>) + status => #{ + type => string, + example => <<"healthy">> + } } } } @@ -37,119 +37,76 @@ definitions() -> , minirest:ref(<<"sources">>) ] }, - Rules = #{ - oneOf => [ minirest:ref(<<"simple_source">>) - % , minirest:ref(<<"connector_redis">>) + Sources = #{ + oneOf => [ minirest:ref(<<"connector_redis">>) ] }, - % ConnectorRedis = #{ - % type => object, - % required => [principal, type, enable, config, cmd] - % properties => #{ - % principal => minirest:ref(<<"principal">>), - % type => #{ - % type => string, - % enum => [<<"redis">>], - % example => <<"redis">> - % }, - % enable => #{ - % type => boolean, - % example => true - % } - % config => #{ - % type => - % } - % } - % } - SimpleRule = #{ + ConnectorRedis= #{ type => object, - required => [principal, permission, action, topics], + required => [type, enable, config, cmd], properties => #{ - action => #{ + type => #{ type => string, - enum => [<<"publish">>, <<"subscribe">>, <<"all">>], - example => <<"publish">> + enum => [<<"redis">>], + example => <<"redis">> }, - permission => #{ + enable => #{ + type => boolean, + example => true + }, + config => #{ + oneOf => [ #{type => object, + required => [server, redis_type, pool_size, auto_reconnect], + properties => #{ + server => #{type => string, example => <<"127.0.0.1:3306">>}, + redis_type => #{type => string, + enum => [<<"single">>], + example => <<"single">>}, + pool_size => #{type => integer}, + auto_reconnect => #{type => boolean, example => true}, + password => #{type => string}, + database => #{type => string, example => mqtt} + } + } + , #{type => object, + required => [servers, redis_type, sentinel, pool_size, auto_reconnect], + properties => #{ + servers => #{type => array, + items => #{type => string,example => <<"127.0.0.1:3306">>}}, + redis_type => #{type => string, + enum => [<<"sentinel">>], + example => <<"sentinel">>}, + sentinel => #{type => string}, + pool_size => #{type => integer}, + auto_reconnect => #{type => boolean, example => true}, + password => #{type => string}, + database => #{type => string, example => mqtt} + } + } + , #{type => object, + required => [servers, redis_type, pool_size, auto_reconnect], + properties => #{ + servers => #{type => array, + items => #{type => string, example => <<"127.0.0.1:3306">>}}, + redis_type => #{type => string, + enum => [<<"cluster">>], + example => <<"cluster">>}, + pool_size => #{type => integer}, + auto_reconnect => #{type => boolean, example => true}, + password => #{type => string}, + database => #{type => string, example => mqtt} + } + } + ], + type => object + }, + cmd => #{ type => string, - enum => [<<"allow">>, <<"deny">>], - example => <<"allow">> - }, - topics => #{ - type => array, - items => #{ - oneOf => [ #{type => string, example => <<"#">>} - , #{type => object, - required => [eq], - properties => #{ - eq => #{type => string} - }, - example => #{eq => <<"#">>} - } - ] - } - }, - principal => minirest:ref(<<"principal">>) - } - }, - Principal = #{ - oneOf => [ minirest:ref(<<"principal_username">>) - , minirest:ref(<<"principal_clientid">>) - , minirest:ref(<<"principal_ipaddress">>) - , #{type => string, enum=>[<<"all">>], example => <<"all">>} - , #{type => object, - required => ['and'], - properties => #{'and' => #{type => array, - items => #{oneOf => [ minirest:ref(<<"principal_username">>) - , minirest:ref(<<"principal_clientid">>) - , minirest:ref(<<"principal_ipaddress">>) - ]}}}, - example => #{'and' => [#{username => <<"emqx">>}, #{clientid => <<"emqx">>}]} - } - , #{type => object, - required => ['or'], - properties => #{'and' => #{type => array, - items => #{oneOf => [ minirest:ref(<<"principal_username">>) - , minirest:ref(<<"principal_clientid">>) - , minirest:ref(<<"principal_ipaddress">>) - ]}}}, - example => #{'or' => [#{username => <<"emqx">>}, #{clientid => <<"emqx">>}]} - } - ] - }, - PrincipalUsername = #{type => object, - required => [username], - properties => #{username => #{type => string}}, - example => #{username => <<"emqx">>} - }, - PrincipalClientid = #{type => object, - required => [clientid], - properties => #{clientid => #{type => string}}, - example => #{clientid => <<"emqx">>} - }, - PrincipalIpaddress = #{type => object, - required => [ipaddress], - properties => #{ipaddress => #{type => string}}, - example => #{ipaddress => <<"127.0.0.1">>} - }, - ErrorDef = #{ - type => object, - properties => #{ - code => #{ - type => string, - example => <<"BAD_REQUEST">> - }, - message => #{ - type => string + example => <<"HGETALL mqtt_authz">> } } }, - [ #{<<"returned_sources">> => RetruenedRules} - , #{<<"sources">> => Rules} - , #{<<"simple_source">> => SimpleRule} - , #{<<"principal">> => Principal} - , #{<<"principal_username">> => PrincipalUsername} - , #{<<"principal_clientid">> => PrincipalClientid} - , #{<<"principal_ipaddress">> => PrincipalIpaddress} - , #{<<"error">> => ErrorDef} + [ #{<<"returned_sources">> => RetruenedSources} + , #{<<"sources">> => Sources} + , #{<<"connector_redis">> => ConnectorRedis} ]. diff --git a/apps/emqx_authz/src/emqx_authz_api_settings.erl b/apps/emqx_authz/src/emqx_authz_api_settings.erl new file mode 100644 index 000000000..ac48fafd9 --- /dev/null +++ b/apps/emqx_authz/src/emqx_authz_api_settings.erl @@ -0,0 +1,61 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_api_settings). + +-behavior(minirest_api). + +-export([ api_spec/0 + , settings/2 + ]). + +api_spec() -> + {[settings_api()], []}. + +authorization_settings() -> + maps:remove(<<"sources">>, emqx:get_raw_config([authorization], #{})). + +conf_schema() -> + emqx_mgmt_api_configs:gen_schema(authorization_settings()). + +settings_api() -> + Metadata = #{ + get => #{ + description => "Get authorization settings", + responses => #{<<"200">> => emqx_mgmt_util:schema(conf_schema())} + }, + put => #{ + description => "Update authorization settings", + requestBody => emqx_mgmt_util:schema(conf_schema()), + responses => #{ + <<"200">> => emqx_mgmt_util:schema(conf_schema()), + <<"400">> => emqx_mgmt_util:bad_request() + } + } + }, + {"/authorization/settings", Metadata, settings}. + +settings(get, _Params) -> + {200, authorization_settings()}; + +settings(put, #{body := #{<<"no_match">> := NoMatch, + <<"deny_action">> := DenyAction, + <<"cache">> := Cache}}) -> + {ok, _} = emqx:update_config([authorization, no_match], NoMatch), + {ok, _} = emqx:update_config([authorization, deny_action], DenyAction), + {ok, _} = emqx:update_config([authorization, cache], Cache), + ok = emqx_authz_cache:drain_cache(), + {200, authorization_settings()}. diff --git a/apps/emqx_authz/src/emqx_authz_api.erl b/apps/emqx_authz/src/emqx_authz_api_sources.erl similarity index 66% rename from apps/emqx_authz/src/emqx_authz_api.erl rename to apps/emqx_authz/src/emqx_authz_api_sources.erl index dc8694c3f..2ad5db1da 100644 --- a/apps/emqx_authz/src/emqx_authz_api.erl +++ b/apps/emqx_authz/src/emqx_authz_api_sources.erl @@ -14,31 +14,29 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_authz_api). +-module(emqx_authz_api_sources). -behavior(minirest_api). -include("emqx_authz.hrl"). --define(EXAMPLE_RETURNED_RULE1, - #{principal => <<"all">>, - permission => <<"allow">>, - action => <<"all">>, - topics => [<<"#">>], - annotations => #{id => 1} - }). - +-define(EXAMPLE_REDIS, + #{type=> redis, + config => #{server => <<"127.0.0.1:3306">>, + redis_type => single, + pool_size => 1, + auto_reconnect => true + }, + cmd => <<"HGETALL mqtt_authz">>}). +-define(EXAMPLE_RETURNED_REDIS, + maps:put(annotations, #{status => healthy}, ?EXAMPLE_REDIS) + ). -define(EXAMPLE_RETURNED_RULES, - #{sources => [?EXAMPLE_RETURNED_RULE1 - ] + #{sources => [?EXAMPLE_RETURNED_REDIS + ] }). --define(EXAMPLE_RULE1, #{principal => <<"all">>, - permission => <<"allow">>, - action => <<"all">>, - topics => [<<"#">>]}). - -export([ api_spec/0 , sources/2 , source/2 @@ -107,9 +105,9 @@ sources_api() -> 'application/json' => #{ schema => minirest:ref(<<"sources">>), examples => #{ - simple_source => #{ - summary => <<"Sources">>, - value => jsx:encode(?EXAMPLE_RULE1) + redis => #{ + summary => <<"Redis">>, + value => jsx:encode(?EXAMPLE_REDIS) } } } @@ -117,23 +115,7 @@ sources_api() -> }, responses => #{ <<"204">> => #{description => <<"Created">>}, - <<"400">> => #{ - description => <<"Bad Request">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>), - examples => #{ - example1 => #{ - summary => <<"Bad Request">>, - value => #{ - code => <<"BAD_REQUEST">>, - message => <<"Bad Request">> - } - } - } - } - } - } + <<"400">> => emqx_mgmt_util:bad_request() } }, put => #{ @@ -146,9 +128,9 @@ sources_api() -> items => minirest:ref(<<"returned_sources">>) }, examples => #{ - sources => #{ - summary => <<"Sources">>, - value => jsx:encode([?EXAMPLE_RULE1]) + redis => #{ + summary => <<"Redis">>, + value => jsx:encode([?EXAMPLE_REDIS]) } } } @@ -156,23 +138,7 @@ sources_api() -> }, responses => #{ <<"204">> => #{description => <<"Created">>}, - <<"400">> => #{ - description => <<"Bad Request">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>), - examples => #{ - example1 => #{ - summary => <<"Bad Request">>, - value => #{ - code => <<"BAD_REQUEST">>, - message => <<"Bad Request">> - } - } - } - } - } - } + <<"400">> => emqx_mgmt_util:bad_request() } } }, @@ -201,29 +167,13 @@ source_api() -> examples => #{ sources => #{ summary => <<"Sources">>, - value => jsx:encode(?EXAMPLE_RETURNED_RULE1) + value => jsx:encode(?EXAMPLE_RETURNED_REDIS) } } } } }, - <<"404">> => #{ - description => <<"Bad Request">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>), - examples => #{ - example1 => #{ - summary => <<"Not Found">>, - value => #{ - code => <<"NOT_FOUND">>, - message => <<"source xxx not found">> - } - } - } - } - } - } + <<"404">> => emqx_mgmt_util:bad_request(<<"Not Found">>) } }, put => #{ @@ -243,9 +193,9 @@ source_api() -> 'application/json' => #{ schema => minirest:ref(<<"sources">>), examples => #{ - simple_source => #{ - summary => <<"Sources">>, - value => jsx:encode(?EXAMPLE_RULE1) + redis => #{ + summary => <<"Redis">>, + value => jsx:encode(?EXAMPLE_REDIS) } } } @@ -253,47 +203,15 @@ source_api() -> }, responses => #{ <<"204">> => #{description => <<"No Content">>}, - <<"404">> => #{ - description => <<"Bad Request">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>), - examples => #{ - example1 => #{ - summary => <<"Not Found">>, - value => #{ - code => <<"NOT_FOUND">>, - message => <<"source xxx not found">> - } - } - } - } - } - }, - <<"400">> => #{ - description => <<"Bad Request">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>), - examples => #{ - example1 => #{ - summary => <<"Bad Request">>, - value => #{ - code => <<"BAD_REQUEST">>, - message => <<"Bad Request">> - } - } - } - } - } - } + <<"404">> => emqx_mgmt_util:bad_request(<<"Not Found">>), + <<"400">> => emqx_mgmt_util:bad_request() } }, delete => #{ description => "Delete source", parameters => [ #{ - name => id, + name => type, in => path, schema => #{ type => string @@ -303,23 +221,7 @@ source_api() -> ], responses => #{ <<"204">> => #{description => <<"No Content">>}, - <<"400">> => #{ - description => <<"Bad Request">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>), - examples => #{ - example1 => #{ - summary => <<"Bad Request">>, - value => #{ - code => <<"BAD_REQUEST">>, - message => <<"Bad Request">> - } - } - } - } - } - } + <<"400">> => emqx_mgmt_util:bad_request() } } }, @@ -378,38 +280,8 @@ move_source_api() -> <<"204">> => #{ description => <<"No Content">> }, - <<"404">> => #{ - description => <<"Bad Request">>, - content => #{ 'application/json' => #{ schema => minirest:ref(<<"error">>), - examples => #{ - example1 => #{ - summary => <<"Not Found">>, - value => #{ - code => <<"NOT_FOUND">>, - message => <<"source xxx not found">> - } - } - } - } - } - }, - <<"400">> => #{ - description => <<"Bad Request">>, - content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"error">>), - examples => #{ - example1 => #{ - summary => <<"Bad Request">>, - value => #{ - code => <<"BAD_REQUEST">>, - message => <<"Bad Request">> - } - } - } - } - } - } + <<"404">> => emqx_mgmt_util:bad_request(<<"Not Found">>), + <<"400">> => emqx_mgmt_util:bad_request() } } }, @@ -519,6 +391,3 @@ move_source(post, #{bindings := #{type := Type}, body := #{<<"position">> := Pos {400, #{code => <<"BAD_REQUEST">>, messgae => atom_to_binary(Reason)}} end. - - - diff --git a/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl b/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl new file mode 100644 index 000000000..1db9fff2b --- /dev/null +++ b/apps/emqx_authz/test/emqx_authz_api_settings_SUITE.erl @@ -0,0 +1,135 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_authz_api_settings_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include("emqx_authz.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-define(CONF_DEFAULT, <<"authorization: {sources: []}">>). + +-import(emqx_ct_http, [ request_api/3 + , request_api/5 + , get_http_data/1 + , create_default_app/0 + , delete_default_app/0 + , default_auth_header/0 + , auth_header/2 + ]). + +-define(HOST, "http://127.0.0.1:18083/"). +-define(API_VERSION, "v5"). +-define(BASE_PATH, "api"). + +all() -> + emqx_ct:all(?MODULE). + +groups() -> + []. + +init_per_suite(Config) -> + ok = emqx_ct_helpers:start_apps([emqx_authz, emqx_dashboard], fun set_special_configs/1), + {ok, _} = emqx:update_config([authorization, cache, enable], false), + {ok, _} = emqx:update_config([authorization, no_match], deny), + + Config. + +end_per_suite(_Config) -> + emqx_ct_helpers:stop_apps([emqx_resource, emqx_authz, emqx_dashboard]), + ok. + +set_special_configs(emqx_dashboard) -> + Config = #{ + default_username => <<"admin">>, + default_password => <<"public">>, + listeners => [#{ + protocol => http, + port => 18083 + }] + }, + emqx_config:put([emqx_dashboard], Config), + ok; +set_special_configs(_App) -> + ok. + +%%------------------------------------------------------------------------------ +%% Testcases +%%------------------------------------------------------------------------------ + +t_api(_) -> + 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 + } + }, + + {ok, 200, Result2} = request(put, uri(["authorization", "settings"]), Settings2), + {ok, 200, Result2} = request(get, uri(["authorization", "settings"]), []), + ?assertEqual(Settings2, jsx:decode(Result2)), + + ok. + +%%-------------------------------------------------------------------- +%% HTTP Request +%%-------------------------------------------------------------------- + +request(Method, Url, Body) -> + Request = case Body of + [] -> {Url, [auth_header_()]}; + _ -> {Url, [auth_header_()], "application/json", jsx:encode(Body)} + end, + ct:pal("Method: ~p, Request: ~p", [Method, Request]), + case httpc:request(Method, Request, [], [{body_format, binary}]) of + {error, socket_closed_remotely} -> + {error, socket_closed_remotely}; + {ok, {{"HTTP/1.1", Code, _}, _Headers, Return} } -> + {ok, Code, Return}; + {ok, {Reason, _, _}} -> + {error, Reason} + end. + +uri() -> uri([]). +uri(Parts) when is_list(Parts) -> + NParts = [E || E <- Parts], + ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION | NParts]). + +get_sources(Result) -> + maps:get(<<"sources">>, jsx:decode(Result), []). + +auth_header_() -> + Username = <<"admin">>, + Password = <<"public">>, + {ok, Token} = emqx_dashboard_admin:sign_token(Username, Password), + {"Authorization", "Bearer " ++ binary_to_list(Token)}. diff --git a/apps/emqx_authz/test/emqx_authz_api_SUITE.erl b/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl similarity index 99% rename from apps/emqx_authz/test/emqx_authz_api_SUITE.erl rename to apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl index 946b1a30b..55185de78 100644 --- a/apps/emqx_authz/test/emqx_authz_api_SUITE.erl +++ b/apps/emqx_authz/test/emqx_authz_api_sources_SUITE.erl @@ -13,7 +13,7 @@ %% limitations under the License. %%-------------------------------------------------------------------- --module(emqx_authz_api_SUITE). +-module(emqx_authz_api_sources_SUITE). -compile(nowarn_export_all). -compile(export_all).