feat(authz api): support '/authorization/settings' api and update swagger schema

Signed-off-by: zhanghongtong <rory-z@outlook.com>
This commit is contained in:
zhanghongtong 2021-08-31 16:43:03 +08:00 committed by Rory Z
parent cfe4e37d50
commit ef1b617624
5 changed files with 298 additions and 276 deletions

View File

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

View File

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

View File

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

View File

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

View File

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