refactor: authz_api_sources spec
This commit is contained in:
parent
e72c07c31e
commit
000020617c
|
@ -1,5 +1,5 @@
|
|||
##--------------------------------------------------------------------
|
||||
## Emq X Rate Limiter
|
||||
## EMQX Rate Limiter
|
||||
##--------------------------------------------------------------------
|
||||
|
||||
limiter {
|
||||
|
|
|
@ -16,490 +16,184 @@
|
|||
|
||||
-module(emqx_authz_api_schema).
|
||||
|
||||
-export([definitions/0]).
|
||||
-include_lib("typerefl/include/types.hrl").
|
||||
-include_lib("emqx_connector/include/emqx_connector.hrl").
|
||||
|
||||
definitions() ->
|
||||
Sources = #{
|
||||
'oneOf' => [ minirest:ref(<<"http">>)
|
||||
, minirest:ref(<<"built-in-database">>)
|
||||
, minirest:ref(<<"mongo_single">>)
|
||||
, minirest:ref(<<"mongo_rs">>)
|
||||
, minirest:ref(<<"mongo_sharded">>)
|
||||
, minirest:ref(<<"mysql">>)
|
||||
, minirest:ref(<<"postgresql">>)
|
||||
, minirest:ref(<<"redis_single">>)
|
||||
, minirest:ref(<<"redis_sentinel">>)
|
||||
, minirest:ref(<<"redis_cluster">>)
|
||||
, minirest:ref(<<"file">>)
|
||||
]
|
||||
},
|
||||
SSL = #{
|
||||
type => object,
|
||||
required => [enable],
|
||||
properties => #{
|
||||
enable => #{type => boolean, example => true},
|
||||
cacertfile => #{type => string},
|
||||
keyfile => #{type => string},
|
||||
certfile => #{type => string},
|
||||
verify => #{type => boolean, example => false}
|
||||
}
|
||||
},
|
||||
HTTP = #{
|
||||
type => object,
|
||||
required => [ type
|
||||
, enable
|
||||
, method
|
||||
, headers
|
||||
, request_timeout
|
||||
, connect_timeout
|
||||
, max_retries
|
||||
, retry_interval
|
||||
, pool_type
|
||||
, pool_size
|
||||
, enable_pipelining
|
||||
, ssl
|
||||
],
|
||||
properties => #{
|
||||
type => #{
|
||||
type => string,
|
||||
enum => [<<"http">>],
|
||||
example => <<"http">>
|
||||
},
|
||||
enable => #{
|
||||
type => boolean,
|
||||
example => true
|
||||
},
|
||||
url => #{
|
||||
type => string,
|
||||
example => <<"https://emqx.com">>
|
||||
},
|
||||
method => #{
|
||||
type => string,
|
||||
enum => [<<"get">>, <<"post">>],
|
||||
example => <<"get">>
|
||||
},
|
||||
headers => #{type => object},
|
||||
body => #{type => object},
|
||||
connect_timeout => #{type => string},
|
||||
max_retries => #{type => integer},
|
||||
retry_interval => #{type => string},
|
||||
pool_type => #{
|
||||
type => string,
|
||||
enum => [<<"random">>, <<"hash">>],
|
||||
example => <<"hash">>
|
||||
},
|
||||
pool_size => #{type => integer},
|
||||
enable_pipelining => #{type => boolean},
|
||||
ssl => minirest:ref(<<"ssl">>)
|
||||
}
|
||||
},
|
||||
MongoSingle= #{
|
||||
type => object,
|
||||
required => [ type
|
||||
, enable
|
||||
, collection
|
||||
, selector
|
||||
, mongo_type
|
||||
, server
|
||||
, 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 => [<<"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}
|
||||
-import(hoconsc, [mk/2, ref/1, ref/2, array/1, enum/1]).
|
||||
-import(emqx_schema, [mk_duration/2]).
|
||||
|
||||
-export([fields/1, authz_sources_types/1]).
|
||||
|
||||
fields(http) ->
|
||||
authz_common_fields(http)
|
||||
++ [ {url, fun url/1}
|
||||
, {method, #{ type => enum([get, post])
|
||||
, default => get
|
||||
, converter => fun to_bin/1}}
|
||||
, {headers, fun headers/1}
|
||||
, {body, fun body/1}
|
||||
, {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);
|
||||
fields(mongo_rs) ->
|
||||
authz_mongo_common_fields()
|
||||
++ emqx_connector_mongo:fields(rs);
|
||||
fields(mongo_sharded) ->
|
||||
authz_mongo_common_fields()
|
||||
++ emqx_connector_mongo:fields(sharded);
|
||||
fields(mysql) ->
|
||||
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));
|
||||
fields(redis_single) ->
|
||||
authz_redis_common_fields()
|
||||
++ emqx_connector_redis:fields(single);
|
||||
fields(redis_sentinel) ->
|
||||
authz_redis_common_fields()
|
||||
++ emqx_connector_redis:fields(sentinel);
|
||||
fields(redis_cluster) ->
|
||||
authz_redis_common_fields()
|
||||
++ emqx_connector_redis:fields(cluster);
|
||||
fields(file) ->
|
||||
authz_common_fields(file)
|
||||
++ [ {rules, #{ type => binary()
|
||||
, example =>
|
||||
<<"{allow,{username,\"^dashboard?\"},","subscribe,[\"$SYS/#\"]}.\n",
|
||||
"{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">>}}
|
||||
%% The path will be deprecated, `acl.conf` will be fixed in subdir of `data`
|
||||
, {path, #{ type => binary()
|
||||
, example => <<"acl.conf">>}}];
|
||||
fields(position) ->
|
||||
[ { position
|
||||
, mk( hoconsc:union([binary(), map()])
|
||||
, #{ desc => <<"Where to place the source">>
|
||||
, required => true
|
||||
, in => body
|
||||
, example => #{<<"before">> => <<"file">>}})}].
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% http type funcs
|
||||
|
||||
url(type) -> binary();
|
||||
url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")];
|
||||
url(nullable) -> false;
|
||||
url(_) -> undefined.
|
||||
|
||||
headers(type) -> map();
|
||||
headers(converter) ->
|
||||
fun(Headers) ->
|
||||
maps:merge(default_headers(), transform_header_name(Headers))
|
||||
end;
|
||||
headers(default) -> default_headers();
|
||||
headers(_) -> undefined.
|
||||
|
||||
body(type) -> map();
|
||||
body(validator) -> [fun check_body/1];
|
||||
body(_) -> undefined.
|
||||
|
||||
%% headers
|
||||
|
||||
default_headers() ->
|
||||
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=5">>
|
||||
}.
|
||||
|
||||
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).
|
||||
|
||||
%% body
|
||||
|
||||
check_body(Body) ->
|
||||
lists:all(
|
||||
fun erlang:is_binary/1,
|
||||
maps:values(Body)).
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% MonogDB type funcs
|
||||
|
||||
authz_mongo_common_fields() ->
|
||||
authz_common_fields(mongodb) ++
|
||||
[ {collection, fun collection/1}
|
||||
, {selector, fun selector/1}
|
||||
].
|
||||
|
||||
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
|
||||
, converter => fun to_bin/1
|
||||
}
|
||||
}
|
||||
].
|
||||
|
||||
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).
|
||||
|
||||
to_bin(List) when is_list(List) -> list_to_binary(List);
|
||||
to_bin(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8);
|
||||
to_bin(X) -> X.
|
||||
|
|
|
@ -18,9 +18,15 @@
|
|||
|
||||
-behaviour(minirest_api).
|
||||
|
||||
-include_lib("typerefl/include/types.hrl").
|
||||
-include("emqx_authz.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,
|
||||
#{type=> redis,
|
||||
enable => true,
|
||||
|
@ -44,290 +50,117 @@
|
|||
|
||||
-define(IS_TRUE(Val), ((Val =:= true) or (Val =:= <<"true">>))).
|
||||
|
||||
-define(API_SCHEMA_MODULE, emqx_authz_api_schema).
|
||||
|
||||
-export([ get_raw_sources/0
|
||||
, get_raw_source/1
|
||||
]).
|
||||
|
||||
-export([ api_spec/0
|
||||
, sources/2
|
||||
, paths/0
|
||||
, schema/1
|
||||
]).
|
||||
|
||||
-export([ sources/2
|
||||
, source/2
|
||||
, move_source/2
|
||||
]).
|
||||
|
||||
api_spec() ->
|
||||
{[ sources_api()
|
||||
, source_api()
|
||||
, move_source_api()
|
||||
], definitions()}.
|
||||
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
|
||||
|
||||
definitions() -> emqx_authz_api_schema:definitions().
|
||||
paths() ->
|
||||
[ "/authorization/sources"
|
||||
, "/authorization/sources/:type"
|
||||
, "/authorization/sources/:type/move"].
|
||||
|
||||
sources_api() ->
|
||||
Metadata = #{
|
||||
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}.
|
||||
%%--------------------------------------------------------------------
|
||||
%% Schema for each URI
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
source_api() ->
|
||||
Metadata = #{
|
||||
get => #{
|
||||
description => "List authorization sources",
|
||||
parameters => [
|
||||
#{
|
||||
name => type,
|
||||
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">>)
|
||||
schema("/authorization/sources") ->
|
||||
#{ 'operationId' => sources
|
||||
, get =>
|
||||
#{ description => <<"List all authorization sources">>
|
||||
, responses =>
|
||||
#{ 200 => mk( array(hoconsc:union([ref(?API_SCHEMA_MODULE, Type) || Type <- authz_sources_types(detailed)]))
|
||||
, #{desc => <<"Authorization source">>})
|
||||
}
|
||||
}
|
||||
},
|
||||
put => #{
|
||||
description => "Update source",
|
||||
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 => 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()
|
||||
, post =>
|
||||
#{ description => <<"Add a new source">>
|
||||
, 'requestBody' => mk( hoconsc:union([ref(?API_SCHEMA_MODULE, Type) || Type <- authz_sources_types(detailed)])
|
||||
, #{desc => <<"Source config">>})
|
||||
, responses =>
|
||||
#{ 204 => <<"Authorization source created successfully">>
|
||||
, 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>)
|
||||
}
|
||||
}
|
||||
},
|
||||
delete => #{
|
||||
description => "Delete source",
|
||||
parameters => [
|
||||
#{
|
||||
name => type,
|
||||
in => path,
|
||||
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()
|
||||
, put =>
|
||||
#{ description => <<"Update all sources">>
|
||||
, 'requestBody' => mk( array(hoconsc:union([ref(?API_SCHEMA_MODULE, Type) || Type <- authz_sources_types(detailed)]))
|
||||
, #{desc => <<"Sources">>})
|
||||
, responses =>
|
||||
#{ 204 => <<"Authorization source updated successfully">>
|
||||
, 400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad Request">>)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{"/authorization/sources/:type", Metadata, source}.
|
||||
};
|
||||
schema("/authorization/sources/:type") ->
|
||||
#{ '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 => bin(Type)}});
|
||||
sources(get, _) ->
|
||||
Sources = lists:foldl(fun (#{<<"type">> := <<"file">>,
|
||||
<<"enable">> := Enable, <<"path">> := Path}, AccIn) ->
|
||||
|
@ -364,6 +197,9 @@ sources(put, #{body := Body}) when is_list(Body) ->
|
|||
end || Source <- Body],
|
||||
update_config(?CMD_REPLACE, NBody).
|
||||
|
||||
source(Method, #{bindings := #{type := Type} = Bindings } = Req)
|
||||
when is_atom(Type) ->
|
||||
source(Method, Req#{bindings => Bindings#{type => bin(Type)}});
|
||||
source(get, #{bindings := #{type := Type}}) ->
|
||||
case get_raw_source(Type) of
|
||||
[] -> {404, #{message => <<"Not found ", Type/binary>>}};
|
||||
|
@ -390,6 +226,9 @@ source(put, #{bindings := #{type := <<"file">>}, body := #{<<"type">> := <<"file
|
|||
<<"enable">> => Enable,
|
||||
<<"path">> => Filename}) of
|
||||
{ok, _} -> {204};
|
||||
{error, {emqx_conf_schema, _}} ->
|
||||
{400, #{code => <<"BAD_REQUEST">>,
|
||||
message => <<"BAD_SCHEMA">>}};
|
||||
{error, Reason} ->
|
||||
{400, #{code => <<"BAD_REQUEST">>,
|
||||
message => bin(Reason)}}
|
||||
|
@ -399,17 +238,27 @@ source(put, #{bindings := #{type := Type}, body := Body}) when is_map(Body) ->
|
|||
source(delete, #{bindings := #{type := 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 => bin(Type)}});
|
||||
move_source(post, #{bindings := #{type := Type}, body := #{<<"position">> := Position}}) ->
|
||||
case emqx_authz:move(Type, Position) of
|
||||
{ok, _} -> {204};
|
||||
{error, not_found_source} ->
|
||||
{404, #{code => <<"NOT_FOUND">>,
|
||||
message => <<"source ", Type/binary, " not found">>}};
|
||||
{error, {emqx_conf_schema, _}} ->
|
||||
{400, #{code => <<"BAD_REQUEST">>,
|
||||
message => <<"BAD_SCHEMA">>}};
|
||||
{error, Reason} ->
|
||||
{400, #{code => <<"BAD_REQUEST">>,
|
||||
message => bin(Reason)}}
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
get_raw_sources() ->
|
||||
RawSources = emqx:get_raw_config([authorization, sources], []),
|
||||
Schema = #{roots => emqx_authz_schema:fields("authorization"), fields => #{}},
|
||||
|
@ -449,6 +298,10 @@ update_config(Cmd, Sources) ->
|
|||
{error, {post_config_update, emqx_authz, 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">>}};
|
||||
{error, Reason} ->
|
||||
{400, #{code => <<"BAD_REQUEST">>,
|
||||
message => bin(Reason)}}
|
||||
|
@ -489,8 +342,21 @@ do_write_file(Filename, Bytes) ->
|
|||
error(Reason)
|
||||
end.
|
||||
|
||||
bin(Term) ->
|
||||
erlang:iolist_to_binary(io_lib:format("~p", [Term])).
|
||||
bin(List) when is_list(List) -> list_to_binary(List);
|
||||
bin(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8);
|
||||
bin(X) -> X.
|
||||
|
||||
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).
|
||||
|
|
Loading…
Reference in New Issue