diff --git a/apps/emqx/include/emqx_access_control.hrl b/apps/emqx/include/emqx_access_control.hrl
new file mode 100644
index 000000000..4d5ba8179
--- /dev/null
+++ b/apps/emqx/include/emqx_access_control.hrl
@@ -0,0 +1,20 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2022 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.
+%%--------------------------------------------------------------------
+
+%% config root name all auth providers have to agree on.
+-define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME, "authorization").
+-define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_ATOM, authorization).
+-define(EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_BINARY, <<"authorization">>).
diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl
index 1079443f3..04f20cf0d 100644
--- a/apps/emqx/src/emqx_schema.erl
+++ b/apps/emqx/src/emqx_schema.erl
@@ -24,6 +24,7 @@
-elvis([{elvis_style, invalid_dynamic_call, disable}]).
-include("emqx_authentication.hrl").
+-include("emqx_access_control.hrl").
-include_lib("typerefl/include/types.hrl").
-type duration() :: integer().
@@ -159,9 +160,9 @@ roots(high) ->
)},
%% NOTE: authorization schema here is only to keep emqx app prue
%% the full schema for EMQX node is injected in emqx_conf_schema.
- {"authorization",
+ {?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME,
sc(
- ref("authorization"),
+ ref(?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME),
#{}
)}
];
@@ -315,6 +316,7 @@ fields("authorization") ->
hoconsc:enum([allow, deny]),
#{
default => allow,
+ required => true,
%% TODO: make sources a reference link
desc =>
"Default access control action if the user or client matches no ACL rules,\n"
@@ -328,6 +330,7 @@ fields("authorization") ->
hoconsc:enum([ignore, disconnect]),
#{
default => ignore,
+ required => true,
desc => "The action when the authorization check rejects an operation."
}
)},
diff --git a/apps/emqx_authn/src/emqx_authn.erl b/apps/emqx_authn/src/emqx_authn.erl
index 70e662cc3..79d269a55 100644
--- a/apps/emqx_authn/src/emqx_authn.erl
+++ b/apps/emqx_authn/src/emqx_authn.erl
@@ -29,14 +29,14 @@
providers() ->
[
- {{'password_based', 'built_in_database'}, emqx_authn_mnesia},
- {{'password_based', mysql}, emqx_authn_mysql},
- {{'password_based', postgresql}, emqx_authn_pgsql},
- {{'password_based', mongodb}, emqx_authn_mongodb},
- {{'password_based', redis}, emqx_authn_redis},
- {{'password_based', 'http'}, emqx_authn_http},
+ {{password_based, built_in_database}, emqx_authn_mnesia},
+ {{password_based, mysql}, emqx_authn_mysql},
+ {{password_based, postgresql}, emqx_authn_pgsql},
+ {{password_based, mongodb}, emqx_authn_mongodb},
+ {{password_based, redis}, emqx_authn_redis},
+ {{password_based, http}, emqx_authn_http},
{jwt, emqx_authn_jwt},
- {{scram, 'built_in_database'}, emqx_enhanced_authn_scram_mnesia}
+ {{scram, built_in_database}, emqx_enhanced_authn_scram_mnesia}
].
check_configs(C) when is_map(C) ->
diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl
index 878e879ed..c785bf0f6 100644
--- a/apps/emqx_authn/src/emqx_authn_api.erl
+++ b/apps/emqx_authn/src/emqx_authn_api.erl
@@ -24,7 +24,7 @@
-include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_authentication.hrl").
--import(hoconsc, [mk/2, ref/1]).
+-import(hoconsc, [mk/2, ref/1, ref/2]).
-import(emqx_dashboard_swagger, [error_codes/2]).
-define(BAD_REQUEST, 'BAD_REQUEST').
@@ -128,31 +128,26 @@ roots() ->
fields(request_user_create) ->
[
- {user_id, binary()}
+ {user_id, mk(binary(), #{required => true})}
| fields(request_user_update)
];
fields(request_user_update) ->
[
- {password, binary()},
+ {password, mk(binary(), #{required => true})},
{is_superuser, mk(boolean(), #{default => false, required => false})}
];
fields(request_move) ->
- [{position, binary()}];
+ [{position, mk(binary(), #{required => true})}];
fields(request_import_users) ->
- [{filename, binary()}];
+ %% TODO: add file update
+ [{filename, mk(binary(), #{required => true})}];
fields(response_user) ->
[
- {user_id, binary()},
+ {user_id, mk(binary(), #{required => true})},
{is_superuser, mk(boolean(), #{default => false, required => false})}
];
fields(response_users) ->
- paginated_list_type(ref(response_user));
-fields(pagination_meta) ->
- [
- {page, pos_integer()},
- {limit, pos_integer()},
- {count, non_neg_integer()}
- ].
+ paginated_list_type(ref(response_user)).
schema("/authentication") ->
#{
@@ -431,10 +426,8 @@ schema("/authentication/:id/users") ->
description => <<"List users in authenticator in global authentication chain">>,
parameters => [
param_auth_id(),
- {page,
- mk(pos_integer(), #{in => query, desc => <<"Page Index">>, required => false})},
- {limit,
- mk(pos_integer(), #{in => query, desc => <<"Page Limit">>, required => false})},
+ ref(emqx_dashboard_swagger, page),
+ ref(emqx_dashboard_swagger, limit),
{like_username,
mk(binary(), #{
in => query,
@@ -483,10 +476,8 @@ schema("/listeners/:listener_id/authentication/:id/users") ->
parameters => [
param_listener_id(),
param_auth_id(),
- {page,
- mk(pos_integer(), #{in => query, desc => <<"Page Index">>, required => false})},
- {limit,
- mk(pos_integer(), #{in => query, desc => <<"Page Limit">>, required => false})}
+ ref(emqx_dashboard_swagger, page),
+ ref(emqx_dashboard_swagger, limit)
],
responses => #{
200 => emqx_dashboard_swagger:schema_with_example(
@@ -587,7 +578,8 @@ param_auth_id() ->
id,
mk(binary(), #{
in => path,
- desc => <<"Authenticator ID">>
+ desc => <<"Authenticator ID">>,
+ required => true
})
}.
@@ -597,6 +589,7 @@ param_listener_id() ->
mk(binary(), #{
in => path,
desc => <<"Listener ID">>,
+ required => true,
example => emqx_listeners:id_example()
})
}.
@@ -1182,7 +1175,7 @@ update_config(Path, ConfigRequest) ->
get_raw_config_with_defaults(ConfKeyPath) ->
NConfKeyPath = [atom_to_binary(Key, utf8) || Key <- ConfKeyPath],
- RawConfig = emqx_map_lib:deep_get(NConfKeyPath, emqx_config:get_raw([]), []),
+ RawConfig = emqx:get_raw_config(NConfKeyPath, []),
ensure_list(fill_defaults(RawConfig)).
find_config(AuthenticatorID, AuthenticatorsConfig) ->
@@ -1200,7 +1193,24 @@ find_config(AuthenticatorID, AuthenticatorsConfig) ->
fill_defaults(Configs) when is_list(Configs) ->
lists:map(fun fill_defaults/1, Configs);
fill_defaults(Config) ->
- emqx_authn:check_config(Config, #{only_fill_defaults => true}).
+ emqx_authn:check_config(merge_default_headers(Config), #{only_fill_defaults => true}).
+
+merge_default_headers(Config) ->
+ case maps:find(<<"headers">>, Config) of
+ {ok, Headers} ->
+ NewHeaders =
+ case Config of
+ #{<<"method">> := <<"get">>} ->
+ (emqx_authn_http:headers_no_content_type(converter))(Headers);
+ #{<<"method">> := <<"post">>} ->
+ (emqx_authn_http:headers(converter))(Headers);
+ _ ->
+ Headers
+ end,
+ Config#{<<"headers">> => NewHeaders};
+ error ->
+ Config
+ end.
convert_certs(#{ssl := SSL} = Config) when SSL =/= undefined ->
Config#{ssl := emqx_tls_lib:drop_invalid_certs(SSL)};
@@ -1316,7 +1326,7 @@ binfmt(Fmt, Args) -> iolist_to_binary(io_lib:format(Fmt, Args)).
paginated_list_type(Type) ->
[
{data, hoconsc:array(Type)},
- {meta, ref(pagination_meta)}
+ {meta, ref(emqx_dashboard_swagger, meta)}
].
authenticator_array_example() ->
diff --git a/apps/emqx_authn/src/emqx_authn_password_hashing.erl b/apps/emqx_authn/src/emqx_authn_password_hashing.erl
index 959d9030a..6824f69d9 100644
--- a/apps/emqx_authn/src/emqx_authn_password_hashing.erl
+++ b/apps/emqx_authn/src/emqx_authn_password_hashing.erl
@@ -68,21 +68,31 @@ roots() -> [pbkdf2, bcrypt, bcrypt_rw, other_algorithms].
fields(bcrypt_rw) ->
fields(bcrypt) ++
- [{salt_rounds, fun salt_rounds/1}];
+ [
+ {salt_rounds,
+ sc(
+ integer(),
+ #{
+ default => 10,
+ example => 10,
+ desc => "Salt rounds for BCRYPT password generation."
+ }
+ )}
+ ];
fields(bcrypt) ->
- [{name, sc(bcrypt, #{desc => "BCRYPT password hashing."})}];
+ [{name, sc(bcrypt, #{required => true, desc => "BCRYPT password hashing."})}];
fields(pbkdf2) ->
[
- {name, sc(pbkdf2, #{desc => "PBKDF2 password hashing."})},
+ {name, sc(pbkdf2, #{required => true, desc => "PBKDF2 password hashing."})},
{mac_fun,
sc(
hoconsc:enum([md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512]),
- #{desc => "Specifies mac_fun for PBKDF2 hashing algorithm."}
+ #{required => true, desc => "Specifies mac_fun for PBKDF2 hashing algorithm."}
)},
{iterations,
sc(
integer(),
- #{desc => "Iteration count for PBKDF2 hashing algorithm."}
+ #{required => true, desc => "Iteration count for PBKDF2 hashing algorithm."}
)},
{dk_length, fun dk_length/1}
];
@@ -91,10 +101,7 @@ fields(other_algorithms) ->
{name,
sc(
hoconsc:enum([plain, md5, sha, sha256, sha512]),
- #{
- desc =>
- "Simple password hashing algorithm."
- }
+ #{required => true, desc => "Simple password hashing algorithm."}
)},
{salt_position, fun salt_position/1}
].
@@ -115,11 +122,6 @@ salt_position(default) -> prefix;
salt_position(desc) -> "Salt position for PLAIN, MD5, SHA, SHA256 and SHA512 algorithms.";
salt_position(_) -> undefined.
-salt_rounds(type) -> integer();
-salt_rounds(default) -> 10;
-salt_rounds(desc) -> "Salt rounds for BCRYPT password generation.";
-salt_rounds(_) -> undefined.
-
dk_length(type) ->
integer();
dk_length(required) ->
@@ -130,6 +132,7 @@ dk_length(desc) ->
dk_length(_) ->
undefined.
+%% for simple_authn/emqx_authn_mnesia
type_rw(type) ->
hoconsc:union(rw_refs());
type_rw(default) ->
@@ -139,6 +142,7 @@ type_rw(desc) ->
type_rw(_) ->
undefined.
+%% for other authn resources
type_ro(type) ->
hoconsc:union(ro_refs());
type_ro(default) ->
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl
index 3dee4c2a3..d13f2c69f 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl
@@ -32,6 +32,11 @@
validations/0
]).
+-export([
+ headers_no_content_type/1,
+ headers/1
+]).
+
-export([
refs/0,
create/2,
@@ -57,12 +62,12 @@ roots() ->
fields(get) ->
[
- {method, #{type => get, default => post, desc => "HTTP method."}},
+ {method, #{type => get, required => true, default => post, desc => "HTTP method."}},
{headers, fun headers_no_content_type/1}
] ++ common_fields();
fields(post) ->
[
- {method, #{type => post, default => post, desc => "HTTP method."}},
+ {method, #{type => post, required => true, default => post, desc => "HTTP method."}},
{headers, fun headers/1}
] ++ common_fields().
@@ -75,7 +80,7 @@ desc(_) ->
common_fields() ->
[
- {mechanism, emqx_authn_schema:mechanism('password_based')},
+ {mechanism, emqx_authn_schema:mechanism(password_based)},
{backend, emqx_authn_schema:backend(http)},
{url, fun url/1},
{body,
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl
index 6b0924760..b296e32a6 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl
@@ -55,20 +55,22 @@ roots() ->
fields('hmac-based') ->
[
- {use_jwks, sc(hoconsc:enum([false]), #{desc => ""})},
- {algorithm, sc(hoconsc:enum(['hmac-based']), #{desc => "Signing algorithm."})},
+ {use_jwks, sc(hoconsc:enum([false]), #{required => true, desc => ""})},
+ {algorithm,
+ sc(hoconsc:enum(['hmac-based']), #{required => true, desc => "Signing algorithm."})},
{secret, fun secret/1},
{secret_base64_encoded, fun secret_base64_encoded/1}
] ++ common_fields();
fields('public-key') ->
[
- {use_jwks, sc(hoconsc:enum([false]), #{desc => ""})},
- {algorithm, sc(hoconsc:enum(['public-key']), #{desc => "Signing algorithm."})},
+ {use_jwks, sc(hoconsc:enum([false]), #{required => true, desc => ""})},
+ {algorithm,
+ sc(hoconsc:enum(['public-key']), #{required => true, desc => "Signing algorithm."})},
{certificate, fun certificate/1}
] ++ common_fields();
fields('jwks') ->
[
- {use_jwks, sc(hoconsc:enum([true]), #{desc => ""})},
+ {use_jwks, sc(hoconsc:enum([true]), #{required => true, desc => ""})},
{endpoint, fun endpoint/1},
{pool_size, fun pool_size/1},
{refresh_interval, fun refresh_interval/1},
@@ -78,7 +80,8 @@ fields('jwks') ->
hoconsc:ref(?MODULE, ssl_disable)
]),
desc => "Enable/disable SSL.",
- default => #{<<"enable">> => false}
+ default => #{<<"enable">> => false},
+ required => false
}}
] ++ common_fields();
fields(ssl_enable) ->
@@ -114,6 +117,7 @@ common_fields() ->
secret(type) -> binary();
secret(desc) -> "The key to verify the JWT Token using HMAC algorithm.";
+secret(required) -> true;
secret(_) -> undefined.
secret_base64_encoded(type) -> boolean();
@@ -123,10 +127,12 @@ secret_base64_encoded(_) -> undefined.
certificate(type) -> string();
certificate(desc) -> "The certificate used for signing the token.";
+certificate(required) -> ture;
certificate(_) -> undefined.
endpoint(type) -> string();
endpoint(desc) -> "JWKs endpoint.";
+endpoint(required) -> true;
endpoint(_) -> undefined.
refresh_interval(type) -> integer();
@@ -168,6 +174,8 @@ verify_claims(converter) ->
fun(VerifyClaims) ->
[{to_binary(K), V} || {K, V} <- maps:to_list(VerifyClaims)]
end;
+verify_claims(required) ->
+ false;
verify_claims(_) ->
undefined.
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl
index 9d6d1820a..7971ce049 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl
@@ -103,8 +103,8 @@ roots() -> [?CONF_NS].
fields(?CONF_NS) ->
[
- {mechanism, emqx_authn_schema:mechanism('password_based')},
- {backend, emqx_authn_schema:backend('built_in_database')},
+ {mechanism, emqx_authn_schema:mechanism(password_based)},
+ {backend, emqx_authn_schema:backend(built_in_database)},
{user_id_type, fun user_id_type/1},
{password_hash_algorithm, fun emqx_authn_password_hashing:type_rw/1}
] ++ emqx_authn_schema:common_fields().
@@ -117,6 +117,7 @@ desc(_) ->
user_id_type(type) -> user_id_type();
user_id_type(desc) -> "Authenticate by client ID or username.";
user_id_type(default) -> <<"username">>;
+user_id_type(required) -> true;
user_id_type(_) -> undefined.
%%------------------------------------------------------------------------------
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl
index c177316db..f5ceaa577 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl
@@ -83,6 +83,7 @@ common_fields() ->
collection(type) -> binary();
collection(desc) -> "Collection used to store authentication data.";
+collection(required) -> true;
collection(_) -> undefined.
selector(type) ->
@@ -97,6 +98,7 @@ selector(_) ->
password_hash_field(type) -> binary();
password_hash_field(desc) -> "Document field that contains password hash.";
+password_hash_field(required) -> false;
password_hash_field(_) -> undefined.
salt_field(type) -> binary();
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl
index 11e670f4f..4152bd888 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl
@@ -48,7 +48,7 @@ roots() -> [?CONF_NS].
fields(?CONF_NS) ->
[
- {mechanism, emqx_authn_schema:mechanism('password_based')},
+ {mechanism, emqx_authn_schema:mechanism(password_based)},
{backend, emqx_authn_schema:backend(mysql)},
{password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1},
{query, fun query/1},
@@ -63,6 +63,7 @@ desc(_) ->
query(type) -> string();
query(desc) -> "SQL query used to lookup client data.";
+query(required) -> true;
query(_) -> undefined.
query_timeout(type) -> emqx_schema:duration_ms();
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl
index 34239cb87..3a9f390dd 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl
@@ -54,7 +54,7 @@ roots() -> [?CONF_NS].
fields(?CONF_NS) ->
[
- {mechanism, emqx_authn_schema:mechanism('password_based')},
+ {mechanism, emqx_authn_schema:mechanism(password_based)},
{backend, emqx_authn_schema:backend(postgresql)},
{password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1},
{query, fun query/1}
@@ -69,6 +69,7 @@ desc(_) ->
query(type) -> string();
query(desc) -> "`SQL` query for looking up authentication data.";
+query(required) -> true;
query(_) -> undefined.
%%------------------------------------------------------------------------------
diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl
index 1d37db10a..b29f0acdf 100644
--- a/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl
+++ b/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl
@@ -79,6 +79,7 @@ common_fields() ->
cmd(type) -> string();
cmd(desc) -> "Redis query.";
+cmd(required) -> true;
cmd(_) -> undefined.
%%------------------------------------------------------------------------------
diff --git a/apps/emqx_authz/include/emqx_authz.hrl b/apps/emqx_authz/include/emqx_authz.hrl
index a3fe2d1f9..5c7602658 100644
--- a/apps/emqx_authz/include/emqx_authz.hrl
+++ b/apps/emqx_authz/include/emqx_authz.hrl
@@ -14,6 +14,8 @@
%% limitations under the License.
%%--------------------------------------------------------------------
+-include_lib("emqx/include/emqx_access_control.hrl").
+
-define(APP, emqx_authz).
-define(ALLOW_DENY(A),
@@ -45,6 +47,11 @@
-define(RE_PLACEHOLDER, "\\$\\{[a-z0-9_]+\\}").
+%% has to be the same as the root field name defined in emqx_schema
+-define(CONF_NS, ?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME).
+-define(CONF_NS_ATOM, ?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_ATOM).
+-define(CONF_NS_BINARY, ?EMQX_AUTHORIZATION_CONFIG_ROOT_NAME_BINARY).
+
%% API examples
-define(USERNAME_RULES_EXAMPLE, #{
username => user1,
diff --git a/apps/emqx_authz/src/emqx_authz_api_mnesia.erl b/apps/emqx_authz/src/emqx_authz_api_mnesia.erl
index dcdcf6878..addb11584 100644
--- a/apps/emqx_authz/src/emqx_authz_api_mnesia.erl
+++ b/apps/emqx_authz/src/emqx_authz_api_mnesia.erl
@@ -383,7 +383,7 @@ fields(rules_for_username) ->
fields(username_response_data) ->
[
{data, mk(array(ref(rules_for_username)), #{})},
- {meta, ref(meta)}
+ {meta, ref(emqx_dashboard_swagger, meta)}
];
fields(rules_for_clientid) ->
fields(rules) ++
@@ -391,14 +391,10 @@ fields(rules_for_clientid) ->
fields(clientid_response_data) ->
[
{data, mk(array(ref(rules_for_clientid)), #{})},
- {meta, ref(meta)}
+ {meta, ref(emqx_dashboard_swagger, meta)}
];
fields(rules) ->
- [{rules, mk(array(ref(rule_item)))}];
-fields(meta) ->
- emqx_dashboard_swagger:fields(page) ++
- emqx_dashboard_swagger:fields(limit) ++
- [{count, mk(integer(), #{example => 1})}].
+ [{rules, mk(array(ref(rule_item)))}].
%%--------------------------------------------------------------------
%% HTTP API
diff --git a/apps/emqx_authz/src/emqx_authz_api_schema.erl b/apps/emqx_authz/src/emqx_authz_api_schema.erl
index 78c8539ae..b4ea40ccc 100644
--- a/apps/emqx_authz/src/emqx_authz_api_schema.erl
+++ b/apps/emqx_authz/src/emqx_authz_api_schema.erl
@@ -16,37 +16,34 @@
-module(emqx_authz_api_schema).
+-include("emqx_authz.hrl").
-include_lib("typerefl/include/types.hrl").
-include_lib("emqx_connector/include/emqx_connector.hrl").
-import(hoconsc, [mk/2, enum/1]).
-import(emqx_schema, [mk_duration/2]).
--export([fields/1, authz_sources_types/1]).
+-export([
+ fields/1,
+ authz_sources_types/1
+]).
-fields(http) ->
- authz_common_fields(http) ++
- [
- {url, fun url/1},
- {method, #{
- type => enum([get, post]),
- default => get
- }},
- {headers, fun headers/1},
- {body, map([{fuzzy, term(), binary()}])},
- {request_timeout, mk_duration("Request timeout", #{default => "30s"})}
- ] ++
- maps:to_list(
- maps:without(
- [
- base_url,
- pool_type
- ],
- maps:from_list(emqx_connector_http:fields(config))
- )
- );
-fields('built_in_database') ->
- authz_common_fields('built_in_database');
+%%------------------------------------------------------------------------------
+%% Hocon Schema
+%%------------------------------------------------------------------------------
+
+fields(http_get) ->
+ [
+ {method, #{type => get, default => get, required => true}},
+ {headers, fun headers_no_content_type/1}
+ ] ++ authz_http_common_fields();
+fields(http_post) ->
+ [
+ {method, #{type => post, default => post, required => true}},
+ {headers, fun headers/1}
+ ] ++ authz_http_common_fields();
+fields(built_in_database) ->
+ authz_common_fields(built_in_database);
fields(mongo_single) ->
authz_mongo_common_fields() ++
emqx_connector_mongo:fields(single);
@@ -58,11 +55,11 @@ fields(mongo_sharded) ->
emqx_connector_mongo:fields(sharded);
fields(mysql) ->
authz_common_fields(mysql) ++
- [{query, #{type => binary()}}] ++
+ [{query, mk(binary(), #{required => true})}] ++
emqx_connector_mysql:fields(config);
fields(postgresql) ->
authz_common_fields(postgresql) ++
- [{query, #{type => binary()}}] ++
+ [{query, mk(binary(), #{required => true})}] ++
proplists:delete(named_queries, emqx_connector_pgsql:fields(config));
fields(redis_single) ->
authz_redis_common_fields() ++
@@ -100,6 +97,23 @@ fields(position) ->
%%------------------------------------------------------------------------------
%% http type funcs
+authz_http_common_fields() ->
+ authz_common_fields(http) ++
+ [
+ {url, fun url/1},
+ {body, map([{fuzzy, term(), binary()}])},
+ {request_timeout, mk_duration("Request timeout", #{default => "30s"})}
+ ] ++
+ maps:to_list(
+ maps:without(
+ [
+ base_url,
+ pool_type
+ ],
+ maps:from_list(emqx_connector_http:fields(config))
+ )
+ ).
+
url(type) -> binary();
url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")];
url(required) -> true;
@@ -107,6 +121,8 @@ url(_) -> undefined.
headers(type) ->
map();
+headers(desc) ->
+ "List of HTTP headers.";
headers(converter) ->
fun(Headers) ->
maps:merge(default_headers(), transform_header_name(Headers))
@@ -116,6 +132,19 @@ headers(default) ->
headers(_) ->
undefined.
+headers_no_content_type(type) ->
+ map();
+headers_no_content_type(desc) ->
+ "List of HTTP headers.";
+headers_no_content_type(converter) ->
+ fun(Headers) ->
+ maps:merge(default_headers_no_content_type(), transform_header_name(Headers))
+ end;
+headers_no_content_type(default) ->
+ default_headers_no_content_type();
+headers_no_content_type(_) ->
+ undefined.
+
%% headers
default_headers() ->
maps:put(
@@ -153,10 +182,19 @@ authz_mongo_common_fields() ->
].
collection(type) -> binary();
+collection(desc) -> "Collection used to store authentication data.";
+collection(required) -> true;
collection(_) -> undefined.
-selector(type) -> map();
-selector(_) -> undefined.
+selector(type) ->
+ map();
+selector(desc) ->
+ "Statement that is executed during the authentication process. "
+ "Commands can support following wildcards:\n"
+ " - `${username}`: substituted with client's username\n"
+ " - `${clientid}`: substituted with the clientid";
+selector(_) ->
+ undefined.
%%------------------------------------------------------------------------------
%% Redis type funcs
@@ -164,10 +202,11 @@ selector(_) -> undefined.
authz_redis_common_fields() ->
authz_common_fields(redis) ++
[
- {cmd, #{
- type => binary(),
- example => <<"HGETALL mqtt_authz">>
- }}
+ {cmd,
+ mk(binary(), #{
+ required => true,
+ example => <<"HGETALL mqtt_authz">>
+ })}
].
%%------------------------------------------------------------------------------
@@ -179,6 +218,7 @@ authz_common_fields(Type) when is_atom(Type) ->
{type, #{
type => enum([Type]),
default => Type,
+ required => true,
in => body
}}
].
@@ -194,9 +234,11 @@ enable(_) -> undefined.
authz_sources_types(Type) ->
case Type of
simple ->
- [mongodb, redis];
+ [http, mongodb, redis];
detailed ->
[
+ http_get,
+ http_post,
mongo_single,
mongo_rs,
mongo_sharded,
@@ -206,8 +248,7 @@ authz_sources_types(Type) ->
]
end ++
[
- http,
- 'built_in_database',
+ built_in_database,
mysql,
postgresql,
file
diff --git a/apps/emqx_authz/src/emqx_authz_schema.erl b/apps/emqx_authz/src/emqx_authz_schema.erl
index 0123ee951..011b1585d 100644
--- a/apps/emqx_authz/src/emqx_authz_schema.erl
+++ b/apps/emqx_authz/src/emqx_authz_schema.erl
@@ -16,6 +16,7 @@
-module(emqx_authz_schema).
+-include("emqx_authz.hrl").
-include_lib("typerefl/include/types.hrl").
-include_lib("emqx_connector/include/emqx_connector.hrl").
@@ -40,9 +41,6 @@
headers/1
]).
--import(emqx_schema, [mk_duration/2]).
--include_lib("hocon/include/hoconsc.hrl").
-
%%--------------------------------------------------------------------
%% Hocon Schema
%%--------------------------------------------------------------------
@@ -96,7 +94,7 @@ fields("authorization") ->
];
fields(file) ->
[
- {type, #{type => file, desc => "Backend type."}},
+ {type, #{type => file, required => true, desc => "Backend type."}},
{enable, #{
type => boolean(),
default => true,
@@ -118,17 +116,17 @@ fields(file) ->
];
fields(http_get) ->
[
- {method, #{type => get, default => get, desc => "HTTP method."}},
+ {method, #{type => get, default => get, required => true, desc => "HTTP method."}},
{headers, fun headers_no_content_type/1}
] ++ http_common_fields();
fields(http_post) ->
[
- {method, #{type => post, default => post, desc => "HTTP method."}},
+ {method, #{type => post, default => post, required => true, desc => "HTTP method."}},
{headers, fun headers/1}
] ++ http_common_fields();
fields(mnesia) ->
[
- {type, #{type => 'built_in_database', desc => "Backend type."}},
+ {type, #{type => 'built_in_database', required => true, desc => "Backend type."}},
{enable, #{
type => boolean(),
default => true,
@@ -147,7 +145,7 @@ fields(mysql) ->
fields(postgresql) ->
[
{query, query()},
- {type, #{type => postgresql, desc => "Backend type."}},
+ {type, #{type => postgresql, required => true, desc => "Backend type."}},
{enable, #{
type => boolean(),
desc => "Enable this backend.",
@@ -197,7 +195,9 @@ http_common_fields() ->
[
{url, fun url/1},
{request_timeout,
- mk_duration("Request timeout", #{default => "30s", desc => "Request timeout."})},
+ emqx_schema:mk_duration("Request timeout", #{
+ default => "30s", desc => "Request timeout."
+ })},
{body, #{type => map(), required => false, desc => "HTTP request body."}}
] ++
maps:to_list(
@@ -213,10 +213,16 @@ http_common_fields() ->
mongo_common_fields() ->
[
{collection, #{
- type => atom(), desc => "`MongoDB` collection containing the authorization data."
+ type => atom(),
+ required => true,
+ desc => "`MongoDB` collection containing the authorization data."
}},
- {selector, #{type => map(), desc => "MQL query used to select the authorization record."}},
- {type, #{type => mongodb, desc => "Database backend."}},
+ {selector, #{
+ type => map(),
+ required => true,
+ desc => "MQL query used to select the authorization record."
+ }},
+ {type, #{type => mongodb, required => true, desc => "Database backend."}},
{enable, #{
type => boolean(),
default => true,
@@ -226,8 +232,7 @@ mongo_common_fields() ->
validations() ->
[
- {check_ssl_opts, fun check_ssl_opts/1},
- {check_headers, fun check_headers/1}
+ {check_ssl_opts, fun check_ssl_opts/1}
].
headers(type) ->
@@ -253,6 +258,13 @@ headers_no_content_type(converter) ->
end;
headers_no_content_type(default) ->
default_headers_no_content_type();
+headers_no_content_type(validator) ->
+ fun(Headers) ->
+ case lists:keyfind(<<"content-type">>, 1, Headers) of
+ false -> ok;
+ _ -> {error, do_not_include_content_type}
+ end
+ end;
headers_no_content_type(_) ->
undefined.
@@ -291,6 +303,7 @@ transform_header_name(Headers) ->
Headers
).
+%% TODO: fix me, not work
check_ssl_opts(Conf) ->
case hocon_maps:get("config.url", Conf) of
undefined ->
@@ -309,25 +322,6 @@ check_ssl_opts(Conf) ->
end
end.
-check_headers(Conf) ->
- case hocon_maps:get("config.method", Conf) of
- undefined ->
- true;
- Method0 ->
- Method = to_bin(Method0),
- Headers = hocon_maps:get("config.headers", Conf),
- case Method of
- <<"post">> ->
- true;
- _ when Headers =:= undefined -> true;
- _ when is_list(Headers) ->
- case lists:member(<<"content-type">>, Headers) of
- false -> true;
- true -> {Method0, do_not_include_content_type}
- end
- end
- end.
-
union_array(Item) when is_list(Item) ->
hoconsc:array(hoconsc:union(Item)).
@@ -335,6 +329,7 @@ query() ->
#{
type => binary(),
desc => "Database query used to retrieve authorization data.",
+ required => true,
validator => fun(S) ->
case size(S) > 0 of
true -> ok;
@@ -369,10 +364,3 @@ to_list(A) when is_atom(A) ->
atom_to_list(A);
to_list(B) when is_binary(B) ->
binary_to_list(B).
-
-to_bin(A) when is_atom(A) ->
- atom_to_binary(A);
-to_bin(B) when is_binary(B) ->
- B;
-to_bin(L) when is_list(L) ->
- list_to_binary(L).
diff --git a/apps/emqx_connector/src/emqx_connector_mongo.erl b/apps/emqx_connector/src/emqx_connector_mongo.erl
index ca5143d44..51da5d68e 100644
--- a/apps/emqx_connector/src/emqx_connector_mongo.erl
+++ b/apps/emqx_connector/src/emqx_connector_mongo.erl
@@ -56,6 +56,7 @@ roots() ->
fields(single) ->
[ {mongo_type, #{type => single,
default => single,
+ required => true,
desc => ?DESC("single_mongo_type")}}
, {server, fun server/1}
, {w_mode, fun w_mode/1}
@@ -63,6 +64,7 @@ fields(single) ->
fields(rs) ->
[ {mongo_type, #{type => rs,
default => rs,
+ required => true,
desc => ?DESC("rs_mongo_type")}}
, {servers, fun servers/1}
, {w_mode, fun w_mode/1}
@@ -72,6 +74,7 @@ fields(rs) ->
fields(sharded) ->
[ {mongo_type, #{type => sharded,
default => sharded,
+ required => true,
desc => ?DESC("sharded_mongo_type")}}
, {servers, fun servers/1}
, {w_mode, fun w_mode/1}
@@ -336,7 +339,7 @@ max_overflow(_) -> undefined.
replica_set_name(type) -> binary();
replica_set_name(desc) -> ?DESC("replica_set_name");
-replica_set_name(required) -> false;
+replica_set_name(required) -> true;
replica_set_name(_) -> undefined.
srv_record(type) -> boolean();
diff --git a/apps/emqx_connector/src/emqx_connector_redis.erl b/apps/emqx_connector/src/emqx_connector_redis.erl
index 77180391c..2fb7afad0 100644
--- a/apps/emqx_connector/src/emqx_connector_redis.erl
+++ b/apps/emqx_connector/src/emqx_connector_redis.erl
@@ -57,6 +57,7 @@ fields(single) ->
[ {server, fun server/1}
, {redis_type, #{type => hoconsc:enum([single]),
default => single,
+ required => true,
desc => ?DESC("single")
}}
] ++
@@ -66,6 +67,7 @@ fields(cluster) ->
[ {servers, fun servers/1}
, {redis_type, #{type => hoconsc:enum([cluster]),
default => cluster,
+ required => true,
desc => ?DESC("cluster")
}}
] ++
@@ -75,6 +77,7 @@ fields(sentinel) ->
[ {servers, fun servers/1}
, {redis_type, #{type => hoconsc:enum([sentinel]),
default => sentinel,
+ required => true,
desc => ?DESC("sentinel")
}}
, {sentinel, #{type => string(), desc => ?DESC("sentinel_desc")
@@ -210,6 +213,7 @@ redis_fields() ->
, {password, fun emqx_connector_schema_lib:password/1}
, {database, #{type => integer(),
default => 0,
+ required => true,
desc => ?DESC("database")
}}
, {auto_reconnect, fun emqx_connector_schema_lib:auto_reconnect/1}
diff --git a/apps/emqx_dashboard/src/emqx_dashboard_swagger.erl b/apps/emqx_dashboard/src/emqx_dashboard_swagger.erl
index a6a2d1bc4..cc4166193 100644
--- a/apps/emqx_dashboard/src/emqx_dashboard_swagger.erl
+++ b/apps/emqx_dashboard/src/emqx_dashboard_swagger.erl
@@ -135,7 +135,12 @@ fields(limit) ->
<<")">>
]),
Meta = #{in => query, desc => Desc, default => ?DEFAULT_ROW, example => 50},
- [{limit, hoconsc:mk(range(1, ?MAX_ROW_LIMIT), Meta)}].
+ [{limit, hoconsc:mk(range(1, ?MAX_ROW_LIMIT), Meta)}];
+fields(count) ->
+ Meta = #{desc => <<"Results count.">>, required => true},
+ [{count, hoconsc:mk(range(0, inf), Meta)}];
+fields(meta) ->
+ fields(page) ++ fields(limit) ++ fields(count).
-spec schema_with_example(hocon_schema:type(), term()) -> hocon_schema:field_schema_map().
schema_with_example(Type, Example) ->
@@ -574,7 +579,7 @@ hocon_schema_to_spec(Atom, _LocalModule) when is_atom(Atom) ->
typename_to_spec("user_id_type()", _Mod) ->
#{type => string, enum => [clientid, username]};
typename_to_spec("term()", _Mod) ->
- #{type => string};
+ #{type => string, example => <<"any">>};
typename_to_spec("boolean()", _Mod) ->
#{type => boolean};
typename_to_spec("binary()", _Mod) ->
diff --git a/apps/emqx_management/src/emqx_mgmt_api_alarms.erl b/apps/emqx_management/src/emqx_mgmt_api_alarms.erl
index 5c455b4e4..cc9b073b1 100644
--- a/apps/emqx_management/src/emqx_mgmt_api_alarms.erl
+++ b/apps/emqx_management/src/emqx_mgmt_api_alarms.erl
@@ -53,7 +53,7 @@ schema("/alarms") ->
responses => #{
200 => [
{data, hoconsc:mk(hoconsc:array(hoconsc:ref(?MODULE, alarm)), #{})},
- {meta, hoconsc:mk(hoconsc:ref(?MODULE, meta), #{})}
+ {meta, hoconsc:mk(hoconsc:ref(emqx_dashboard_swagger, meta), #{})}
]
}
},
@@ -98,11 +98,8 @@ fields(alarm) ->
desc => ?DESC(deactivate_at),
example => <<"2021-10-31T10:52:52.548+08:00">>
})}
- ];
-fields(meta) ->
- emqx_dashboard_swagger:fields(page) ++
- emqx_dashboard_swagger:fields(limit) ++
- [{count, hoconsc:mk(integer(), #{example => 1})}].
+ ].
+
%%%==============================================================================================
%% parameters trans
alarms(get, #{query_string := QString}) ->
diff --git a/apps/emqx_management/src/emqx_mgmt_api_banned.erl b/apps/emqx_management/src/emqx_mgmt_api_banned.erl
index 1354b9ac8..fc9ece871 100644
--- a/apps/emqx_management/src/emqx_mgmt_api_banned.erl
+++ b/apps/emqx_management/src/emqx_mgmt_api_banned.erl
@@ -62,7 +62,7 @@ schema("/banned") ->
responses => #{
200 => [
{data, hoconsc:mk(hoconsc:array(hoconsc:ref(ban)), #{})},
- {meta, hoconsc:mk(hoconsc:ref(meta), #{})}
+ {meta, hoconsc:mk(hoconsc:ref(emqx_dashboard_swagger, meta), #{})}
]
}
},
@@ -147,11 +147,7 @@ fields(ban) ->
required => false,
example => <<"2021-10-25T21:53:47+08:00">>
})}
- ];
-fields(meta) ->
- emqx_dashboard_swagger:fields(page) ++
- emqx_dashboard_swagger:fields(limit) ++
- [{count, hoconsc:mk(integer(), #{example => 1})}].
+ ].
banned(get, #{query_string := Params}) ->
Response = emqx_mgmt_api:paginate(?TAB, Params, ?FORMAT_FUN),
diff --git a/apps/emqx_management/src/emqx_mgmt_api_clients.erl b/apps/emqx_management/src/emqx_mgmt_api_clients.erl
index 393af6b5d..8330c11b4 100644
--- a/apps/emqx_management/src/emqx_mgmt_api_clients.erl
+++ b/apps/emqx_management/src/emqx_mgmt_api_clients.erl
@@ -203,7 +203,7 @@ schema("/clients") ->
responses => #{
200 => [
{data, hoconsc:mk(hoconsc:array(hoconsc:ref(?MODULE, client)), #{})},
- {meta, hoconsc:mk(hoconsc:ref(?MODULE, meta), #{})}
+ {meta, hoconsc:mk(hoconsc:ref(emqx_dashboard_swagger, meta), #{})}
],
400 =>
emqx_dashboard_swagger:error_codes(
@@ -518,11 +518,7 @@ fields(subscribe) ->
fields(unsubscribe) ->
[
{topic, hoconsc:mk(binary(), #{desc => <<"Topic">>})}
- ];
-fields(meta) ->
- emqx_dashboard_swagger:fields(page) ++
- emqx_dashboard_swagger:fields(limit) ++
- [{count, hoconsc:mk(integer(), #{example => 1})}].
+ ].
%%%==============================================================================================
%% parameters trans
diff --git a/apps/emqx_management/src/emqx_mgmt_api_configs.erl b/apps/emqx_management/src/emqx_mgmt_api_configs.erl
index 5143ff892..283140aa1 100644
--- a/apps/emqx_management/src/emqx_mgmt_api_configs.erl
+++ b/apps/emqx_management/src/emqx_mgmt_api_configs.erl
@@ -106,9 +106,11 @@ schema("/configs_reset/:rootname") ->
post => #{
tags => [conf],
description =>
- <<"Reset the config entry specified by the query string parameter `conf_path`.
\n"
- "- For a config entry that has default value, this resets it to the default value;\n"
- "- For a config entry that has no default value, an error 400 will be returned">>,
+ <<
+ "Reset the config entry specified by the query string parameter `conf_path`.
\n"
+ "- For a config entry that has default value, this resets it to the default value;\n"
+ "- For a config entry that has no default value, an error 400 will be returned"
+ >>,
%% We only return "200" rather than the new configs that has been changed, as
%% the schema of the changed configs is depends on the request parameter
%% `conf_path`, it cannot be defined here.
diff --git a/apps/emqx_management/src/emqx_mgmt_api_topics.erl b/apps/emqx_management/src/emqx_mgmt_api_topics.erl
index ef07cabe3..3b54a3bee 100644
--- a/apps/emqx_management/src/emqx_mgmt_api_topics.erl
+++ b/apps/emqx_management/src/emqx_mgmt_api_topics.erl
@@ -60,7 +60,7 @@ schema("/topics") ->
responses => #{
200 => [
{data, hoconsc:mk(hoconsc:array(hoconsc:ref(topic)), #{})},
- {meta, hoconsc:mk(hoconsc:ref(meta), #{})}
+ {meta, hoconsc:mk(hoconsc:ref(emqx_dashboard_swagger, meta), #{})}
]
}
}
@@ -91,11 +91,7 @@ fields(topic) ->
desc => <<"Node">>,
required => true
})}
- ];
-fields(meta) ->
- emqx_dashboard_swagger:fields(page) ++
- emqx_dashboard_swagger:fields(limit) ++
- [{count, hoconsc:mk(integer(), #{example => 1})}].
+ ].
%%%==============================================================================================
%% parameters trans
diff --git a/git-blame-ignore-revs b/git-blame-ignore-revs
index d76cf9165..aa78ef23b 100644
--- a/git-blame-ignore-revs
+++ b/git-blame-ignore-revs
@@ -23,5 +23,6 @@ acb3544d4b112121b5d9414237d2af7860ccc2a3
f1acfece6b79ed69b491da03783a7adaa7627b96
# reformat apps/emqx_management
aa7807baebfa5d8678025e43f386bcd9b3259d6a
+bf54f571fb8b27e76ada4ca75137d96ce4211d60
# reformat apps/emqx_slow_subs
83511f8a4c1570a2c89d9c6c5b6f462520199ed8
diff --git a/scripts/check-format.sh b/scripts/check-format.sh
index 9b5ada731..81108192b 100755
--- a/scripts/check-format.sh
+++ b/scripts/check-format.sh
@@ -13,6 +13,7 @@ APPS+=( 'apps/emqx_authn' 'apps/emqx_authz' )
APPS+=( 'lib-ee/emqx_enterprise_conf' 'lib-ee/emqx_license' )
APPS+=( 'apps/emqx_exhook')
APPS+=( 'apps/emqx_retainer' 'apps/emqx_slow_subs')
+APPS+=( 'apps/emqx_management')
for app in "${APPS[@]}"; do
echo "$app ..."