Merge pull request #7075 from JimMoen/refactor-authz-sources-api

refactor: authz_api_sources swagger spec
This commit is contained in:
JimMoen 2022-02-22 01:24:42 +08:00 committed by GitHub
commit e816e3e4a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 338 additions and 789 deletions

View File

@ -1,5 +1,5 @@
##-------------------------------------------------------------------- ##--------------------------------------------------------------------
## Emq X Rate Limiter ## EMQX Rate Limiter
##-------------------------------------------------------------------- ##--------------------------------------------------------------------
limiter { limiter {

View File

@ -63,7 +63,7 @@ common_fields() ->
[ {mechanism, emqx_authn_schema:mechanism('password-based')} [ {mechanism, emqx_authn_schema:mechanism('password-based')}
, {backend, emqx_authn_schema:backend(http)} , {backend, emqx_authn_schema:backend(http)}
, {url, fun url/1} , {url, fun url/1}
, {body, fun body/1} , {body, map([{fuzzy, term(), binary()}])}
, {request_timeout, fun request_timeout/1} , {request_timeout, fun request_timeout/1}
] ++ emqx_authn_schema:common_fields() ] ++ emqx_authn_schema:common_fields()
++ maps:to_list(maps:without([ base_url ++ maps:to_list(maps:without([ base_url
@ -96,10 +96,6 @@ headers_no_content_type(converter) ->
headers_no_content_type(default) -> default_headers_no_content_type(); headers_no_content_type(default) -> default_headers_no_content_type();
headers_no_content_type(_) -> undefined. headers_no_content_type(_) -> undefined.
body(type) -> map();
body(validator) -> [fun check_body/1];
body(_) -> undefined.
request_timeout(type) -> emqx_schema:duration_ms(); request_timeout(type) -> emqx_schema:duration_ms();
request_timeout(default) -> <<"5s">>; request_timeout(default) -> <<"5s">>;
request_timeout(_) -> undefined. request_timeout(_) -> undefined.
@ -209,11 +205,6 @@ destroy(#{resource_id := ResourceId}) ->
parse_fullpath(RawURL) -> parse_fullpath(RawURL) ->
cow_http:parse_fullpath(to_bin(RawURL)). cow_http:parse_fullpath(to_bin(RawURL)).
check_body(Body) ->
lists:all(
fun erlang:is_binary/1,
maps:values(Body)).
default_headers() -> default_headers() ->
maps:put(<<"content-type">>, maps:put(<<"content-type">>,
<<"application/json">>, <<"application/json">>,
@ -223,7 +214,7 @@ default_headers_no_content_type() ->
#{ <<"accept">> => <<"application/json">> #{ <<"accept">> => <<"application/json">>
, <<"cache-control">> => <<"no-cache">> , <<"cache-control">> => <<"no-cache">>
, <<"connection">> => <<"keep-alive">> , <<"connection">> => <<"keep-alive">>
, <<"keep-alive">> => <<"timeout=5">> , <<"keep-alive">> => <<"timeout=30, max=1000">>
}. }.
transform_header_name(Headers) -> transform_header_name(Headers) ->

View File

@ -16,490 +16,166 @@
-module(emqx_authz_api_schema). -module(emqx_authz_api_schema).
-export([definitions/0]). -include_lib("typerefl/include/types.hrl").
-include_lib("emqx_connector/include/emqx_connector.hrl").
definitions() -> -import(hoconsc, [mk/2, ref/1, ref/2, array/1, enum/1]).
Sources = #{ -import(emqx_schema, [mk_duration/2]).
'oneOf' => [ minirest:ref(<<"http">>)
, minirest:ref(<<"built-in-database">>) -export([fields/1, authz_sources_types/1]).
, minirest:ref(<<"mongo_single">>)
, minirest:ref(<<"mongo_rs">>) fields(http) ->
, minirest:ref(<<"mongo_sharded">>) authz_common_fields(http)
, minirest:ref(<<"mysql">>) ++ [ {url, fun url/1}
, minirest:ref(<<"postgresql">>) , {method, #{ type => enum([get, post])
, minirest:ref(<<"redis_single">>) , default => get}}
, minirest:ref(<<"redis_sentinel">>) , {headers, fun headers/1}
, minirest:ref(<<"redis_cluster">>) , {body, map([{fuzzy, term(), binary()}])}
, minirest:ref(<<"file">>) , {request_timeout, mk_duration("Request timeout", #{default => "30s"})}]
] ++ maps:to_list(maps:without([ base_url
}, , pool_type],
SSL = #{ maps:from_list(emqx_connector_http:fields(config))));
type => object, fields('built-in-database') ->
required => [enable], authz_common_fields('built-in-database');
properties => #{ fields(mongo_single) ->
enable => #{type => boolean, example => true}, authz_mongo_common_fields()
cacertfile => #{type => string}, ++ emqx_connector_mongo:fields(single);
keyfile => #{type => string}, fields(mongo_rs) ->
certfile => #{type => string}, authz_mongo_common_fields()
verify => #{type => boolean, example => false} ++ emqx_connector_mongo:fields(rs);
} fields(mongo_sharded) ->
}, authz_mongo_common_fields()
HTTP = #{ ++ emqx_connector_mongo:fields(sharded);
type => object, fields(mysql) ->
required => [ type authz_common_fields(mysql)
, enable ++ [ {query, #{type => binary()}}]
, method ++ emqx_connector_mysql:fields(config);
, headers fields(postgresql) ->
, request_timeout authz_common_fields(postgresql)
, connect_timeout ++ [ {query, #{type => binary()}}]
, max_retries ++ proplists:delete(named_queries, emqx_connector_pgsql:fields(config));
, retry_interval fields(redis_single) ->
, pool_type authz_redis_common_fields()
, pool_size ++ emqx_connector_redis:fields(single);
, enable_pipelining fields(redis_sentinel) ->
, ssl authz_redis_common_fields()
], ++ emqx_connector_redis:fields(sentinel);
properties => #{ fields(redis_cluster) ->
type => #{ authz_redis_common_fields()
type => string, ++ emqx_connector_redis:fields(cluster);
enum => [<<"http">>], fields(file) ->
example => <<"http">> authz_common_fields(file)
}, ++ [ {rules, #{ type => binary()
enable => #{ , example =>
type => boolean, <<"{allow,{username,\"^dashboard?\"},","subscribe,[\"$SYS/#\"]}.\n",
example => true "{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">>}}
}, %% The path will be deprecated, `acl.conf` will be fixed in subdir of `data`
url => #{ , {path, #{ type => binary()
type => string, , example => <<"acl.conf">>}}];
example => <<"https://emqx.com">> fields(position) ->
}, [ { position
method => #{ , mk( hoconsc:union([binary(), map()])
type => string, , #{ desc => <<"Where to place the source">>
enum => [<<"get">>, <<"post">>], , required => true
example => <<"get">> , in => body
}, , example => #{<<"before">> => <<"file">>}})}].
headers => #{type => object},
body => #{type => object}, %%------------------------------------------------------------------------------
connect_timeout => #{type => string}, %% http type funcs
max_retries => #{type => integer},
retry_interval => #{type => string}, url(type) -> binary();
pool_type => #{ url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")];
type => string, url(nullable) -> false;
enum => [<<"random">>, <<"hash">>], url(_) -> undefined.
example => <<"hash">>
}, headers(type) -> map();
pool_size => #{type => integer}, headers(converter) ->
enable_pipelining => #{type => boolean}, fun(Headers) ->
ssl => minirest:ref(<<"ssl">>) maps:merge(default_headers(), transform_header_name(Headers))
} end;
}, headers(default) -> default_headers();
MongoSingle= #{ headers(_) -> undefined.
type => object,
required => [ type %% headers
, enable default_headers() ->
, collection maps:put(<<"content-type">>,
, selector <<"application/json">>,
, mongo_type default_headers_no_content_type()).
, server
, pool_size default_headers_no_content_type() ->
, username #{ <<"accept">> => <<"application/json">>
, password , <<"cache-control">> => <<"no-cache">>
, auth_source , <<"connection">> => <<"keep-alive">>
, database , <<"keep-alive">> => <<"timeout=30, max=1000">>
, topology }.
, ssl
], transform_header_name(Headers) ->
properties => #{ maps:fold(fun(K0, V, Acc) ->
type => #{ K = list_to_binary(string:to_lower(to_list(K0))),
type => string, maps:put(K, V, Acc)
enum => [<<"mongodb">>], end, #{}, Headers).
example => <<"mongodb">>
}, %%------------------------------------------------------------------------------
enable => #{ %% MonogDB type funcs
type => boolean,
example => true authz_mongo_common_fields() ->
}, authz_common_fields(mongodb) ++
srv_record => #{type => boolean, example => false, default => false}, [ {collection, fun collection/1}
collection => #{type => string}, , {selector, fun selector/1}
selector => #{type => object},
mongo_type => #{type => string,
enum => [<<"single">>],
example => <<"single">>},
server => #{type => string, example => <<"127.0.0.1:27017">>},
pool_size => #{type => integer},
username => #{type => string},
password => #{type => string},
auth_source => #{type => string},
database => #{type => string},
topology => #{type => object,
properties => #{
pool_size => #{type => integer},
max_overflow => #{type => integer},
overflow_ttl => #{type => string},
overflow_check_period => #{type => string},
local_threshold_ms => #{type => integer},
connect_timeout_ms => #{type => integer},
socket_timeout_ms => #{type => integer},
server_selection_timeout_ms => #{type => integer},
wait_queue_timeout_ms => #{type => integer},
heartbeat_frequency_ms => #{type => integer},
min_heartbeat_frequency_ms => #{type => integer}
}
},
ssl => minirest:ref(<<"ssl">>)
}
},
MongoRs= #{
type => object,
required => [ type
, enable
, collection
, selector
, mongo_type
, servers
, replica_set_name
, pool_size
, username
, password
, auth_source
, database
, topology
, ssl
],
properties => #{
type => #{
type => string,
enum => [<<"mongodb">>],
example => <<"mongodb">>
},
enable => #{
type => boolean,
example => true
},
srv_record => #{type => boolean, example => false, default => false},
collection => #{type => string},
selector => #{type => object},
mongo_type => #{type => string,
enum => [<<"rs">>],
example => <<"rs">>},
servers => #{type => string, example => <<"127.0.0.1:27017, 127.0.0.2:27017">>},
replica_set_name => #{type => string},
pool_size => #{type => integer},
username => #{type => string},
password => #{type => string},
auth_source => #{type => string},
database => #{type => string},
topology => #{type => object,
properties => #{
pool_size => #{type => integer},
max_overflow => #{type => integer},
overflow_ttl => #{type => string},
overflow_check_period => #{type => string},
local_threshold_ms => #{type => integer},
connect_timeout_ms => #{type => integer},
socket_timeout_ms => #{type => integer},
server_selection_timeout_ms => #{type => integer},
wait_queue_timeout_ms => #{type => integer},
heartbeat_frequency_ms => #{type => integer},
min_heartbeat_frequency_ms => #{type => integer}
}
},
ssl => minirest:ref(<<"ssl">>)
}
},
MongoSharded = #{
type => object,
required => [ type
, enable
, collection
, selector
, mongo_type
, servers
, pool_size
, username
, password
, auth_source
, database
, topology
, ssl
],
properties => #{
type => #{
type => string,
enum => [<<"mongodb">>],
example => <<"mongodb">>
},
enable => #{
type => boolean,
example => true
},
srv_record => #{type => boolean, example => false, default => false},
collection => #{type => string},
selector => #{type => object},
mongo_type => #{type => string,
enum => [<<"sharded">>],
example => <<"sharded">>},
servers => #{type => string,example => <<"127.0.0.1:27017, 127.0.0.2:27017">>},
pool_size => #{type => integer},
username => #{type => string},
password => #{type => string},
auth_source => #{type => string},
database => #{type => string},
topology => #{type => object,
properties => #{
pool_size => #{type => integer},
max_overflow => #{type => integer},
overflow_ttl => #{type => string},
overflow_check_period => #{type => string},
local_threshold_ms => #{type => integer},
connect_timeout_ms => #{type => integer},
socket_timeout_ms => #{type => integer},
server_selection_timeout_ms => #{type => integer},
wait_queue_timeout_ms => #{type => integer},
heartbeat_frequency_ms => #{type => integer},
min_heartbeat_frequency_ms => #{type => integer}
}
},
ssl => minirest:ref(<<"ssl">>)
}
},
Mysql = #{
type => object,
required => [ type
, enable
, query
, server
, database
, pool_size
, username
, password
, auto_reconnect
, ssl
],
properties => #{
type => #{
type => string,
enum => [<<"mysql">>],
example => <<"mysql">>
},
enable => #{
type => boolean,
example => true
},
query => #{type => string},
server => #{type => string,
example => <<"127.0.0.1:3306">>
},
database => #{type => string},
pool_size => #{type => integer},
username => #{type => string},
password => #{type => string},
auto_reconnect => #{type => boolean,
example => true
},
ssl => minirest:ref(<<"ssl">>)
}
},
Pgsql = #{
type => object,
required => [ type
, enable
, query
, server
, database
, pool_size
, username
, password
, auto_reconnect
, ssl
],
properties => #{
type => #{
type => string,
enum => [<<"postgresql">>],
example => <<"postgresql">>
},
enable => #{
type => boolean,
example => true
},
query => #{type => string},
server => #{type => string,
example => <<"127.0.0.1:5432">>
},
database => #{type => string},
pool_size => #{type => integer},
username => #{type => string},
password => #{type => string},
auto_reconnect => #{type => boolean,
example => true
},
ssl => minirest:ref(<<"ssl">>)
}
},
RedisSingle = #{
type => object,
required => [ type
, enable
, cmd
, server
, redis_type
, pool_size
, auto_reconnect
, ssl
],
properties => #{
type => #{
type => string,
enum => [<<"redis">>],
example => <<"redis">>
},
enable => #{
type => boolean,
example => true
},
cmd => #{
type => string,
example => <<"HGETALL mqtt_authz">>
},
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 => integer},
ssl => minirest:ref(<<"ssl">>)
}
},
RedisSentinel= #{
type => object,
required => [ type
, enable
, cmd
, servers
, redis_type
, sentinel
, pool_size
, auto_reconnect
, ssl
],
properties => #{
type => #{
type => string,
enum => [<<"redis">>],
example => <<"redis">>
},
enable => #{
type => boolean,
example => true
},
cmd => #{
type => string,
example => <<"HGETALL mqtt_authz">>
},
servers => #{type => string, example => <<"127.0.0.1:6379, 127.0.0.2:6379">>},
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 => integer},
ssl => minirest:ref(<<"ssl">>)
}
},
RedisCluster= #{
type => object,
required => [ type
, enable
, cmd
, servers
, redis_type
, pool_size
, auto_reconnect
, ssl],
properties => #{
type => #{
type => string,
enum => [<<"redis">>],
example => <<"redis">>
},
enable => #{
type => boolean,
example => true
},
cmd => #{
type => string,
example => <<"HGETALL mqtt_authz">>
},
servers => #{type => string, example => <<"127.0.0.1:6379, 127.0.0.2:6379">>},
redis_type => #{type => string,
enum => [<<"cluster">>],
example => <<"cluster">>},
pool_size => #{type => integer},
auto_reconnect => #{type => boolean, example => true},
password => #{type => string},
database => #{type => integer},
ssl => minirest:ref(<<"ssl">>)
}
},
Mnesia = #{
type => object,
required => [type, enable],
properties => #{
type => #{
type => string,
enum => [<<"redis">>],
example => <<"redis">>
},
enable => #{
type => boolean,
example => true
}
}
},
File = #{
type => object,
required => [type, enable, rules],
properties => #{
type => #{
type => string,
enum => [<<"redis">>],
example => <<"redis">>
},
enable => #{
type => boolean,
example => true
},
rules => #{
type => array,
items => #{
type => string,
example =>
<<"{allow,{username,\"^dashboard?\"},","subscribe,[\"$SYS/#\"]}.\n",
"{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">>
}
},
path => #{
type => string,
example => <<"/path/to/authorizaiton_rules.conf">>
}
}
},
[ #{<<"sources">> => Sources}
, #{<<"ssl">> => SSL}
, #{<<"http">> => HTTP}
, #{<<"built-in-database">> => Mnesia}
, #{<<"mongo_single">> => MongoSingle}
, #{<<"mongo_rs">> => MongoRs}
, #{<<"mongo_sharded">> => MongoSharded}
, #{<<"mysql">> => Mysql}
, #{<<"postgresql">> => Pgsql}
, #{<<"redis_single">> => RedisSingle}
, #{<<"redis_sentinel">> => RedisSentinel}
, #{<<"redis_cluster">> => RedisCluster}
, #{<<"file">> => File}
]. ].
collection(type) -> binary();
collection(_) -> undefined.
selector(type) -> map();
selector(_) -> undefined.
%%------------------------------------------------------------------------------
%% Redis type funcs
authz_redis_common_fields() ->
authz_common_fields(redis) ++
[ {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
}
}
].
enable(type) -> boolean();
enable(default) -> true;
enable(desc) -> "Set to <code>false</code> to disable this auth provider";
enable(_) -> undefined.
%%------------------------------------------------------------------------------
%% Internal funcs
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].
to_list(A) when is_atom(A) ->
atom_to_list(A);
to_list(B) when is_binary(B) ->
binary_to_list(B).

View File

@ -18,35 +18,45 @@
-behaviour(minirest_api). -behaviour(minirest_api).
-import(hoconsc, [mk/1, ref/2]).
-export([ api_spec/0 -export([ api_spec/0
, settings/2 , paths/0
, schema/1
]). ]).
-export([settings/2]).
-define(BAD_REQUEST, 'BAD_REQUEST').
api_spec() -> api_spec() ->
{[settings_api()], []}. emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
authorization_settings() -> paths() ->
maps:remove(<<"sources">>, emqx:get_raw_config([authorization], #{})). ["/authorization/settings"].
conf_schema() -> %%--------------------------------------------------------------------
emqx_mgmt_api_configs:gen_schema(authorization_settings()). %% Schema for each URI
%%--------------------------------------------------------------------
settings_api() -> schema("/authorization/settings") ->
Metadata = #{ #{ 'operationId' => settings
get => #{ , get =>
description => "Get authorization settings", #{ description => <<"Get authorization settings">>
responses => #{<<"200">> => emqx_mgmt_util:schema(conf_schema())} , responses =>
}, #{200 => ref_authz_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()
} }
} , put =>
}, #{ description => <<"Update authorization settings">>
{"/authorization/settings", Metadata, 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) -> settings(get, _Params) ->
{200, authorization_settings()}; {200, authorization_settings()};
@ -60,3 +70,6 @@ settings(put, #{body := #{<<"no_match">> := NoMatch,
{ok, _} = emqx_authz_utils:update_config([authorization, cache], Cache), {ok, _} = emqx_authz_utils:update_config([authorization, cache], Cache),
ok = emqx_authz_cache:drain_cache(), ok = emqx_authz_cache:drain_cache(),
{200, authorization_settings()}. {200, authorization_settings()}.
authorization_settings() ->
maps:remove(<<"sources">>, emqx:get_raw_config([authorization], #{})).

View File

@ -18,9 +18,15 @@
-behaviour(minirest_api). -behaviour(minirest_api).
-include_lib("typerefl/include/types.hrl").
-include("emqx_authz.hrl"). -include("emqx_authz.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-import(hoconsc, [mk/1, mk/2, ref/1, ref/2, array/1, enum/1]).
-define(BAD_REQUEST, 'BAD_REQUEST').
-define(NOT_FOUND, 'NOT_FOUND').
-define(EXAMPLE_REDIS, -define(EXAMPLE_REDIS,
#{type=> redis, #{type=> redis,
enable => true, enable => true,
@ -44,290 +50,117 @@
-define(IS_TRUE(Val), ((Val =:= true) or (Val =:= <<"true">>))). -define(IS_TRUE(Val), ((Val =:= true) or (Val =:= <<"true">>))).
-define(API_SCHEMA_MODULE, emqx_authz_api_schema).
-export([ get_raw_sources/0 -export([ get_raw_sources/0
, get_raw_source/1 , get_raw_source/1
]). ]).
-export([ api_spec/0 -export([ api_spec/0
, sources/2 , paths/0
, schema/1
]).
-export([ sources/2
, source/2 , source/2
, move_source/2 , move_source/2
]). ]).
api_spec() -> api_spec() ->
{[ sources_api() emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
, source_api()
, move_source_api()
], definitions()}.
definitions() -> emqx_authz_api_schema:definitions(). paths() ->
[ "/authorization/sources"
, "/authorization/sources/:type"
, "/authorization/sources/:type/move"].
sources_api() -> %%--------------------------------------------------------------------
Metadata = #{ %% Schema for each URI
get => #{ %%--------------------------------------------------------------------
description => "List authorization sources",
responses => #{
<<"200">> => #{
description => <<"OK">>,
content => #{
'application/json' => #{
schema => #{
type => object,
required => [sources],
properties => #{sources => #{
type => array,
items => minirest:ref(<<"sources">>)
}
}
},
examples => #{
sources => #{
summary => <<"Sources">>,
value => jsx:encode(?EXAMPLE_RETURNED)
}
}
}
}
}
}
},
post => #{
description => "Add new source",
'requestBody' => #{
content => #{
'application/json' => #{
schema => minirest:ref(<<"sources">>),
examples => #{
redis => #{
summary => <<"Redis">>,
value => jsx:encode(?EXAMPLE_REDIS)
},
file => #{
summary => <<"File">>,
value => jsx:encode(?EXAMPLE_FILE)
}
}
}
}
},
responses => #{
<<"204">> => #{description => <<"Created">>},
<<"400">> => emqx_mgmt_util:bad_request()
}
},
put => #{
description => "Update all sources",
'requestBody' => #{
content => #{
'application/json' => #{
schema => #{
type => array,
items => minirest:ref(<<"sources">>)
},
examples => #{
redis => #{
summary => <<"Redis">>,
value => jsx:encode(?EXAMPLE_REDIS)
},
file => #{
summary => <<"File">>,
value => jsx:encode(?EXAMPLE_FILE)
}
}
}
}
},
responses => #{
<<"204">> => #{description => <<"Created">>},
<<"400">> => emqx_mgmt_util:bad_request()
}
}
},
{"/authorization/sources", Metadata, sources}.
source_api() -> schema("/authorization/sources") ->
Metadata = #{ #{ 'operationId' => sources
get => #{ , get =>
description => "List authorization sources", #{ description => <<"List all authorization sources">>
parameters => [ , responses =>
#{ #{ 200 => mk( array(hoconsc:union([ref(?API_SCHEMA_MODULE, Type) || Type <- authz_sources_types(detailed)]))
name => type, , #{desc => <<"Authorization source">>})
in => path, }
schema => #{
type => string,
enum => [ <<"file">>
, <<"http">>
, <<"mongodb">>
, <<"mysql">>
, <<"postgresql">>
, <<"redis">>
, <<"built-in-database">>
]
},
required => true
}
],
responses => #{
<<"200">> => #{
description => <<"OK">>,
content => #{
'application/json' => #{
schema => minirest:ref(<<"sources">>),
examples => #{
redis => #{
summary => <<"Redis">>,
value => jsx:encode(?EXAMPLE_REDIS)
},
file => #{
summary => <<"File">>,
value => jsx:encode(?EXAMPLE_FILE)
}
}
}
}
},
<<"404">> => emqx_mgmt_util:bad_request(<<"Not Found">>)
} }
}, , post =>
put => #{ #{ description => <<"Add a new source">>
description => "Update source", , 'requestBody' => mk( hoconsc:union([ref(?API_SCHEMA_MODULE, Type) || Type <- authz_sources_types(detailed)])
parameters => [ , #{desc => <<"Source config">>})
#{ , responses =>
name => type, #{ 204 => <<"Authorization source created successfully">>
in => path, , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>)
schema => #{ }
type => string,
enum => [ <<"file">>
, <<"http">>
, <<"mongodb">>
, <<"mysql">>
, <<"postgresql">>
, <<"redis">>
, <<"built-in-database">>
]
},
required => true
}
],
'requestBody' => #{
content => #{
'application/json' => #{
schema => minirest:ref(<<"sources">>),
examples => #{
redis => #{
summary => <<"Redis">>,
value => jsx:encode(?EXAMPLE_REDIS)
},
file => #{
summary => <<"File">>,
value => jsx:encode(?EXAMPLE_FILE)
}
}
}
}
},
responses => #{
<<"204">> => #{description => <<"No Content">>},
<<"404">> => emqx_mgmt_util:bad_request(<<"Not Found">>),
<<"400">> => emqx_mgmt_util:bad_request()
} }
}, , put =>
delete => #{ #{ description => <<"Update all sources">>
description => "Delete source", , 'requestBody' => mk( array(hoconsc:union([ref(?API_SCHEMA_MODULE, Type) || Type <- authz_sources_types(detailed)]))
parameters => [ , #{desc => <<"Sources">>})
#{ , responses =>
name => type, #{ 204 => <<"Authorization source updated successfully">>
in => path, , 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>)
schema => #{ }
type => string,
enum => [ <<"file">>
, <<"http">>
, <<"mongodb">>
, <<"mysql">>
, <<"postgresql">>
, <<"redis">>
, <<"built-in-database">>
]
},
required => true
}
],
responses => #{
<<"204">> => #{description => <<"Deleted">>},
<<"400">> => emqx_mgmt_util:bad_request()
} }
} };
}, schema("/authorization/sources/:type") ->
{"/authorization/sources/:type", Metadata, source}. #{ 'operationId' => source
, get =>
#{ description => <<"Get a authorization source">>
, parameters => parameters_field()
, responses =>
#{ 200 => mk( hoconsc:union([ref(?API_SCHEMA_MODULE, Type) || Type <- authz_sources_types(detailed)])
, #{desc => <<"Authorization source">>})
, 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>)
}
}
, put =>
#{ description => <<"Update source">>
, parameters => parameters_field()
, 'requestBody' => mk( hoconsc:union([ref(?API_SCHEMA_MODULE, Type) || Type <- authz_sources_types(detailed)]))
, responses =>
#{ 204 => <<"Authorization source updated successfully">>
, 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>)
, 404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>)
}
}
, 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/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">>)
}
}
}.
move_source_api() ->
Metadata = #{
post => #{
description => "Change the order of sources",
parameters => [
#{
name => type,
in => path,
schema => #{
type => string,
enum => [ <<"file">>
, <<"http">>
, <<"mongodb">>
, <<"mysql">>
, <<"postgresql">>
, <<"redis">>
, <<"built-in-database">>
]
},
required => true
}
],
'requestBody' => #{
content => #{
'application/json' => #{
schema => #{
type => object,
required => [position],
properties => #{
position => #{
'oneOf' => [
#{type => string,
enum => [<<"top">>, <<"bottom">>]
},
#{type => object,
required => ['after'],
properties => #{
'after' => #{
type => string
}
}
},
#{type => object,
required => ['before'],
properties => #{
'before' => #{
type => string
}
}
}
]
}
}
}
}
}
},
responses => #{
<<"204">> => #{
description => <<"No Content">>
},
<<"404">> => emqx_mgmt_util:bad_request(<<"Not Found">>),
<<"400">> => emqx_mgmt_util:bad_request()
}
}
},
{"/authorization/sources/:type/move", Metadata, move_source}.
%%--------------------------------------------------------------------
%% Operation functions
%%--------------------------------------------------------------------
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(get, _) ->
Sources = lists:foldl(fun (#{<<"type">> := <<"file">>, Sources = lists:foldl(fun (#{<<"type">> := <<"file">>,
<<"enable">> := Enable, <<"path">> := Path}, AccIn) -> <<"enable">> := Enable, <<"path">> := Path}, AccIn) ->
@ -364,6 +197,9 @@ sources(put, #{body := Body}) when is_list(Body) ->
end || Source <- Body], end || Source <- Body],
update_config(?CMD_REPLACE, NBody). update_config(?CMD_REPLACE, NBody).
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}}) -> source(get, #{bindings := #{type := Type}}) ->
case get_raw_source(Type) of case get_raw_source(Type) of
[] -> {404, #{message => <<"Not found ", Type/binary>>}}; [] -> {404, #{message => <<"Not found ", Type/binary>>}};
@ -390,6 +226,9 @@ source(put, #{bindings := #{type := <<"file">>}, body := #{<<"type">> := <<"file
<<"enable">> => Enable, <<"enable">> => Enable,
<<"path">> => Filename}) of <<"path">> => Filename}) of
{ok, _} -> {204}; {ok, _} -> {204};
{error, {emqx_conf_schema, _}} ->
{400, #{code => <<"BAD_REQUEST">>,
message => <<"BAD_SCHEMA">>}};
{error, Reason} -> {error, Reason} ->
{400, #{code => <<"BAD_REQUEST">>, {400, #{code => <<"BAD_REQUEST">>,
message => bin(Reason)}} message => bin(Reason)}}
@ -399,17 +238,27 @@ source(put, #{bindings := #{type := Type}, body := Body}) when is_map(Body) ->
source(delete, #{bindings := #{type := Type}}) -> source(delete, #{bindings := #{type := Type}}) ->
update_config({?CMD_DELETE, Type}, #{}). update_config({?CMD_DELETE, 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}}) -> move_source(post, #{bindings := #{type := Type}, body := #{<<"position">> := Position}}) ->
case emqx_authz:move(Type, Position) of case emqx_authz:move(Type, Position) of
{ok, _} -> {204}; {ok, _} -> {204};
{error, not_found_source} -> {error, not_found_source} ->
{404, #{code => <<"NOT_FOUND">>, {404, #{code => <<"NOT_FOUND">>,
message => <<"source ", Type/binary, " not found">>}}; message => <<"source ", Type/binary, " not found">>}};
{error, {emqx_conf_schema, _}} ->
{400, #{code => <<"BAD_REQUEST">>,
message => <<"BAD_SCHEMA">>}};
{error, Reason} -> {error, Reason} ->
{400, #{code => <<"BAD_REQUEST">>, {400, #{code => <<"BAD_REQUEST">>,
message => bin(Reason)}} message => bin(Reason)}}
end. end.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
get_raw_sources() -> get_raw_sources() ->
RawSources = emqx:get_raw_config([authorization, sources], []), RawSources = emqx:get_raw_config([authorization, sources], []),
Schema = #{roots => emqx_authz_schema:fields("authorization"), fields => #{}}, Schema = #{roots => emqx_authz_schema:fields("authorization"), fields => #{}},
@ -449,6 +298,10 @@ update_config(Cmd, Sources) ->
{error, {post_config_update, emqx_authz, Reason}} -> {error, {post_config_update, emqx_authz, Reason}} ->
{400, #{code => <<"BAD_REQUEST">>, {400, #{code => <<"BAD_REQUEST">>,
message => bin(Reason)}}; 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">>}};
{error, Reason} -> {error, Reason} ->
{400, #{code => <<"BAD_REQUEST">>, {400, #{code => <<"BAD_REQUEST">>,
message => bin(Reason)}} message => bin(Reason)}}
@ -489,8 +342,19 @@ do_write_file(Filename, Bytes) ->
error(Reason) error(Reason)
end. end.
bin(Term) -> bin(Term) -> erlang:iolist_to_binary(io_lib:format("~p", [Term])).
erlang:iolist_to_binary(io_lib:format("~p", [Term])).
acl_conf_file() -> acl_conf_file() ->
emqx_authz:acl_conf_file(). emqx_authz:acl_conf_file().
parameters_field() ->
[ {type, mk( enum(?API_SCHEMA_MODULE:authz_sources_types(simple))
, #{in => path, desc => <<"Authorization type">>})
}
].
position_example() ->
#{<<"position">> => #{<<"before">> => <<"file">>}}.
authz_sources_types(Type) ->
emqx_authz_api_schema:authz_sources_types(Type).

View File

@ -105,6 +105,8 @@ parse_config(#{ url := URL
?PLACEHOLDERS) ?PLACEHOLDERS)
, headers => Headers , headers => Headers
, request_timeout => ReqTimeout , request_timeout => ReqTimeout
%% pool_type default value `random`
, pool_type => random
}. }.
parse_fullpath(RawURL) -> parse_fullpath(RawURL) ->

View File

@ -146,7 +146,9 @@ http_common_fields() ->
[ {url, fun url/1} [ {url, fun url/1}
, {request_timeout, mk_duration("Request timeout", #{default => "30s"})} , {request_timeout, mk_duration("Request timeout", #{default => "30s"})}
, {body, #{type => map(), nullable => true}} , {body, #{type => map(), nullable => true}}
] ++ proplists:delete(base_url, connector_fields(http)). ] ++ maps:to_list(maps:without([ base_url
, pool_type],
maps:from_list(connector_fields(http)))).
mongo_common_fields() -> mongo_common_fields() ->
[ {collection, #{type => atom()}} [ {collection, #{type => atom()}}
@ -195,7 +197,7 @@ default_headers_no_content_type() ->
#{ <<"accept">> => <<"application/json">> #{ <<"accept">> => <<"application/json">>
, <<"cache-control">> => <<"no-cache">> , <<"cache-control">> => <<"no-cache">>
, <<"connection">> => <<"keep-alive">> , <<"connection">> => <<"keep-alive">>
, <<"keep-alive">> => <<"timeout=5">> , <<"keep-alive">> => <<"timeout=30, max=1000">>
}. }.
transform_header_name(Headers) -> transform_header_name(Headers) ->

View File

@ -159,7 +159,7 @@ on_start(InstId, #{base_url := #{scheme := Scheme,
, {connect_timeout, ConnectTimeout} , {connect_timeout, ConnectTimeout}
, {retry, MaxRetries} , {retry, MaxRetries}
, {retry_timeout, RetryInterval} , {retry_timeout, RetryInterval}
, {keepalive, 5000} , {keepalive, 30000}
, {pool_type, PoolType} , {pool_type, PoolType}
, {pool_size, PoolSize} , {pool_size, PoolSize}
, {transport, Transport} , {transport, Transport}

View File

@ -459,6 +459,7 @@ typename_to_spec("timeout()", _Mod) -> #{<<"oneOf">> => [#{type => string, examp
typename_to_spec("bytesize()", _Mod) -> #{type => string, example => <<"32MB">>}; typename_to_spec("bytesize()", _Mod) -> #{type => string, example => <<"32MB">>};
typename_to_spec("wordsize()", _Mod) -> #{type => string, example => <<"1024KB">>}; typename_to_spec("wordsize()", _Mod) -> #{type => string, example => <<"1024KB">>};
typename_to_spec("map()", _Mod) -> #{type => object, example => #{}}; typename_to_spec("map()", _Mod) -> #{type => object, example => #{}};
typename_to_spec("#{" ++ _, Mod) -> typename_to_spec("map()", Mod);
typename_to_spec("qos()", _Mod) -> #{type => string, enum => [0, 1, 2], example => 0}; typename_to_spec("qos()", _Mod) -> #{type => string, enum => [0, 1, 2], example => 0};
typename_to_spec("{binary(), binary()}", _Mod) -> #{type => object, example => #{}}; typename_to_spec("{binary(), binary()}", _Mod) -> #{type => object, example => #{}};
typename_to_spec("comma_separated_list()", _Mod) -> typename_to_spec("comma_separated_list()", _Mod) ->