diff --git a/apps/emqx_authz/i18n/emqx_authz_api_cache_i18n.conf b/apps/emqx_authz/i18n/emqx_authz_api_cache_i18n.conf
new file mode 100644
index 000000000..25bf5273e
--- /dev/null
+++ b/apps/emqx_authz/i18n/emqx_authz_api_cache_i18n.conf
@@ -0,0 +1,6 @@
+emqx_authz_api_cache {
+ authorization_cache_delete {
+ en: """Clean all authorization cache in the cluster."""
+ zh: """清除集群中所有鉴权数据缓存"""
+ }
+}
diff --git a/apps/emqx_authz/i18n/emqx_authz_api_mnesia_i18n.conf b/apps/emqx_authz/i18n/emqx_authz_api_mnesia_i18n.conf
new file mode 100644
index 000000000..50f644097
--- /dev/null
+++ b/apps/emqx_authz/i18n/emqx_authz_api_mnesia_i18n.conf
@@ -0,0 +1,177 @@
+emqx_authz_api_mnesia {
+ users_username_get {
+ desc {
+ en: """Show the list of record for username"""
+ zh: """获取内置数据库中所有用户名类型的规则记录"""
+ }
+ }
+
+ users_username_post {
+ desc {
+ en: """Add new records for username"""
+ zh: """添加内置数据库中用户名类型的规则记录"""
+ }
+ }
+
+ users_clientid_get {
+ desc {
+ en: """Show the list of record for clientid"""
+ zh: """获取内置数据库中所有客户端标识符类型的规则记录"""
+ }
+ }
+
+ users_clientid_post {
+ desc {
+ en: """Add new records for clientid"""
+ zh: """添加内置数据库中客户端标识符类型的规则记录"""
+ }
+ }
+
+
+ user_username_get {
+ desc {
+ en: """Get record info for username"""
+ zh: """获取内置数据库中指定用户名类型的规则记录"""
+ }
+ }
+
+ user_username_put {
+ desc {
+ en: """Set record for username"""
+ zh: """更新内置数据库中指定用户名类型的规则记录"""
+ }
+ }
+
+ user_username_delete {
+ desc {
+ en: """Delete one record for username"""
+ zh: """删除内置数据库中指定用户名类型的规则记录"""
+ }
+ }
+
+ user_clientid_get {
+ desc {
+ en: """Get record info for clientid"""
+ zh: """获取内置数据库中指定客户端标识符类型的规则记录"""
+ }
+ }
+
+ user_clientid_put {
+ desc {
+ en: """Set record for clientid"""
+ zh: """更新内置数据库中指定客户端标识符类型的规则记录"""
+ }
+ }
+
+ user_clientid_delete {
+ desc {
+ en: """Delete one record for clientid"""
+ zh: """删除内置数据库中指定客户端标识符类型的规则记录"""
+ }
+ }
+
+
+ rules_for_all_get {
+ desc {
+ en: """Show the list of rules for all"""
+ zh: """列出为所有客户端启用的规则列表"""
+ }
+ }
+
+ rules_for_all_post {
+ desc {
+ en: """
+Create/Update the list of rules for all.
+Set a empty list to clean up rules
+"""
+ zh: """
+创建/更新 为所有客户端启用的规则列表。
+设为空列表以清楚所有规则
+"""
+ }
+ }
+
+ purge_all_delete {
+ desc {
+ en: """Purge all records for username/clientid/all"""
+ zh: """清除所有内置数据库中的规则, 用户名/客户端标识符/所有"""
+ }
+ }
+
+ fuzzy_username {
+ desc {
+ en: """Fuzzy search `username` as substring"""
+ zh: """使用字串匹配模糊搜索用户名"""
+ }
+ label {
+ en: """fuzzy_username"""
+ zh: """用户名子串"""
+ }
+ }
+
+ fuzzy_clientid {
+ desc {
+ en: """Fuzzy search `clientid` as substring"""
+ zh: """使用字串匹配模糊搜索客户端标识符"""
+ }
+ label {
+ en: """fuzzy_clientid"""
+ zh: """客户端标识符子串"""
+ }
+ }
+
+ topic {
+ desc {
+ en: """Rule on specific topic"""
+ zh: """在指定主题上的规则"""
+ }
+ label {
+ en: """topic"""
+ zh: """主题"""
+ }
+ }
+
+ permission {
+ desc {
+ en: """Permission"""
+ zh: """权限"""
+ }
+ label {
+ en: """permission"""
+ zh: """权限"""
+ }
+ }
+
+ action {
+ desc {
+ en: """Authorized action (pub/sub/all)"""
+ zh: """被授权的行为 (发布/订阅/所有)"""
+ }
+ label {
+ en: """action"""
+ zh: """行为"""
+ }
+ }
+
+ clientid {
+ desc {
+ en: """ClientID"""
+ zh: """客户端标识符"""
+ }
+ label {
+ en: """clientid"""
+ zh: """客户端标识符"""
+ }
+ }
+
+ username {
+ desc {
+ en: """Username"""
+ zh: """用户名"""
+ }
+ label {
+ en: """username"""
+ zh: """用户名"""
+ }
+ }
+}
diff --git a/apps/emqx_authz/i18n/emqx_authz_api_schema_i18n.conf b/apps/emqx_authz/i18n/emqx_authz_api_schema_i18n.conf
new file mode 100644
index 000000000..28fae341d
--- /dev/null
+++ b/apps/emqx_authz/i18n/emqx_authz_api_schema_i18n.conf
@@ -0,0 +1,188 @@
+emqx_authz_api_schema {
+ enable {
+ desc {
+ en: """Set to true
or false
to disable this ACL provider"""
+ zh: """设为 true
或 false
以启用或禁用此访问控制数据源"""
+ }
+ label {
+ en: """enable"""
+ zh: """enable"""
+ }
+ }
+
+ type {
+ desc {
+ en: """Backend type."""
+ zh: """数据后端类型"""
+ }
+ label {
+ en: """type"""
+ zh: """type"""
+ }
+ }
+
+#==== authz_file
+
+ rules {
+ desc {
+ en: """Authorization static file rules."""
+ zh: """静态鉴权文件规则"""
+ }
+ label {
+ en: """rules"""
+ zh: """规则"""
+ }
+ }
+
+#==== authz_http
+
+ method {
+ desc {
+ en: """HTTP method."""
+ zh: """HTTP 请求方法"""
+ }
+ label {
+ en: """method"""
+ zh: """method"""
+ }
+ }
+
+ url {
+ desc {
+ en: """URL of the auth server."""
+ zh: """认证服务器 URL"""
+ }
+ label {
+ en: """url"""
+ zh: """url"""
+ }
+ }
+
+ headers {
+ desc {
+ en: """List of HTTP headers."""
+ zh: """"""
+ }
+ label {
+ en: """headers"""
+ zh: """请求头"""
+ }
+ }
+
+ headers_no_content_type {
+ desc {
+ en: """List of HTTP headers (without `content_type`)."""
+ zh: """"""
+ }
+ label {
+ en: """headers_no_content_type"""
+ zh: """请求头(无 content-type)"""
+ }
+ }
+
+ body {
+ desc {
+ en: """HTTP request body."""
+ zh: """HTTP 请求体"""
+ }
+ label {
+ en: """body"""
+ zh: """请求体"""
+ }
+ }
+
+ request_timeout {
+ desc {
+ en: """Request timeout."""
+ zh: """请求超时时间"""
+ }
+ label {
+ en: """request_timeout"""
+ zh: """请求超时"""
+ }
+ }
+
+#==== authz_mnesia
+
+# only common fields(`enable` and `type`)
+
+#==== authz_mongo
+
+ collection {
+ desc {
+ en: """`MongoDB` collection containing the authorization data."""
+ zh: """`MongoDB` 鉴权数据集"""
+ }
+ label {
+ en: """collection"""
+ zh: """数据集"""
+ }
+ }
+
+ selector {
+ desc {
+ en: """
+Statement that is executed during the authorize process.
+Commands can support following wildcards:\n
+ - `${username}`: substituted with client's username\n
+ - `${clientid}`: substituted with the clientid
+"""
+ zh: """
+鉴权过程中所使用的查询命令。
+查询命令支持如下占位符:
+- `${username}`: 代替客户端的用户名
+- `${clientid}`: 代替客户端的客户端标识符"""
+ }
+ label {
+ en: """selector"""
+ zh: """selector"""
+ }
+ }
+
+#==== authz_mysql
+
+# `query`, is common field
+
+#==== authz_pgsql
+
+# `query`, is common field
+
+#==== authz_redis
+
+ cmd {
+ desc {
+ en: """Database query used to retrieve authorization data."""
+ zh: """访问控制数据查询命令"""
+ }
+ label {
+ en: """cmd"""
+ zh: """查询命令"""
+ }
+ }
+
+#==== common field for DBs (except mongodb and redis)
+
+ query {
+ desc {
+ en: """Database query used to retrieve authorization data."""
+ zh: """访问控制数据查询语句"""
+ }
+ label {
+ en: """query"""
+ zh: """查询语句"""
+ }
+ }
+
+#==== fields
+
+ position {
+ desc {
+ en: """Where to place the source"""
+ zh: """认证数据源位置"""
+ }
+ label {
+ en: """position"""
+ zh: """位置"""
+ }
+ }
+}
diff --git a/apps/emqx_authz/i18n/emqx_authz_api_settings_i18n.conf b/apps/emqx_authz/i18n/emqx_authz_api_settings_i18n.conf
new file mode 100644
index 000000000..72249e236
--- /dev/null
+++ b/apps/emqx_authz/i18n/emqx_authz_api_settings_i18n.conf
@@ -0,0 +1,11 @@
+emqx_authz_api_settings {
+ authorization_settings_get {
+ en: """Get authorization settings"""
+ zh: """获取鉴权配置"""
+ }
+
+ authorization_settings_put {
+ en: """Update authorization settings"""
+ zh: """更新鉴权配置"""
+ }
+}
diff --git a/apps/emqx_authz/i18n/emqx_authz_api_sources_i18n.conf b/apps/emqx_authz/i18n/emqx_authz_api_sources_i18n.conf
new file mode 100644
index 000000000..6c3f59d93
--- /dev/null
+++ b/apps/emqx_authz/i18n/emqx_authz_api_sources_i18n.conf
@@ -0,0 +1,116 @@
+emqx_authz_api_sources {
+ authorization_sources_get {
+ desc {
+ en: """List all authorization sources"""
+ zh: """列出所有鉴权数据源"""
+ }
+ }
+
+ authorization_sources_post {
+ desc {
+ en: """Add a new source"""
+ zh: """添加鉴权数据源"""
+ }
+ }
+
+ authorization_sources_type_get {
+ desc {
+ en: """Get a authorization source"""
+ zh: """获取指定类型的鉴权数据源"""
+ }
+ }
+
+ authorization_sources_type_put {
+ desc {
+ en: """Update source"""
+ zh: """更新指定类型的鉴权数据源"""
+ }
+ }
+
+ authorization_sources_type_delete {
+ desc {
+ en: """Delete source"""
+ zh: """删除指定类型的鉴权数据源"""
+ }
+ }
+
+ authorization_sources_type_status_get {
+ desc {
+ en: """Get a authorization source"""
+ zh: """获取指定鉴权数据源的状态"""
+ }
+ }
+
+ authorization_sources_type_move_post {
+ desc {
+ en: """Change the exection order of sources"""
+ zh: """更新鉴权数据源的优先执行顺序"""
+ }
+ }
+
+ sources {
+ desc {
+ en: """Authorization source"""
+ zh: """鉴权数据源列表"""
+ }
+ label {
+ en: """sources"""
+ zh: """数据源列表"""
+ }
+ }
+
+ sources {
+ desc {
+ en: """Authorization sources"""
+ zh: """鉴权数据源列表"""
+ }
+ label {
+ en: """sources"""
+ zh: """数据源列表"""
+ }
+ }
+
+ source_config {
+ desc {
+ en: """Source config"""
+ zh: """数据源配置"""
+ }
+ label {
+ en: """source_config"""
+ zh: """数据源配置"""
+ }
+ }
+
+ source {
+ desc {
+ en: """Authorization source"""
+ zh: """鉴权数据源"""
+ }
+ label {
+ en: """source"""
+ zh: """数据源"""
+ }
+ }
+
+ source_config {
+ desc {
+ en: """Source config"""
+ zh: """数据源配置"""
+ }
+ label {
+ en: """source_config"""
+ zh: """数据源配置"""
+ }
+ }
+
+ source_type {
+ desc {
+ en: """Authorization type"""
+ zh: """数据源类型"""
+ }
+ label {
+ en: """source_type"""
+ zh: """数据源类型"""
+ }
+ }
+}
diff --git a/apps/emqx_authz/i18n/emqx_authz_schema_i18n.conf b/apps/emqx_authz/i18n/emqx_authz_schema_i18n.conf
new file mode 100644
index 000000000..39cc3abb2
--- /dev/null
+++ b/apps/emqx_authz/i18n/emqx_authz_schema_i18n.conf
@@ -0,0 +1,349 @@
+emqx_authz_schema {
+ sources {
+ desc {
+ en: """
+Authorization data sources.
+An array of authorization (ACL) data providers.
+It is designed as an array, not a hash-map, so the sources can be
+ordered to form a chain of access controls.
+
+When authorizing a 'publish' or 'subscribe' action, the configured
+sources are checked in order. When checking an ACL source,
+in case the client (identified by username or client ID) is not found,
+it moves on to the next source. And it stops immediately
+once an 'allow' or 'deny' decision is returned.
+
+If the client is not found in any of the sources,
+the default action configured in 'authorization.no_match' is applied.
+
+NOTE:
+The source elements are identified by their 'type'.
+It is NOT allowed to configure two or more sources of the same type.
+"""
+ zh: """"""
+ }
+ label {
+ en: """sources"""
+ zh: """"""
+ }
+ }
+
+ authorization {
+ desc {
+ en: """Configuration related to the client authorization."""
+ zh: """"""
+ }
+ label {
+ en: """authorization"""
+ zh: """授权"""
+ }
+ }
+
+ enable {
+ desc {
+ en: """Set to true
or false
to disable this ACL provider"""
+ zh: """设为 true
或 false
以启用或禁用此访问控制数据源"""
+ }
+ label {
+ en: """enable"""
+ zh: """enable"""
+ }
+ }
+
+ type {
+ desc {
+ en: """Backend type."""
+ zh: """数据后端类型"""
+ }
+ label {
+ en: """type"""
+ zh: """type"""
+ }
+ }
+
+#==== authz_file
+
+ file {
+ desc {
+ en: """Authorization using a static file."""
+ zh: """使用静态文件鉴权"""
+ }
+ label {
+ en: """file"""
+ zh: """文件"""
+ }
+ }
+
+ path {
+ desc {
+ en: """
+Path to the file which contains the ACL rules.
+If the file provisioned before starting EMQX node,
+it can be placed anywhere as long as EMQX has read access to it.
+
+In case the rule-set is created from EMQX dashboard or management API,
+the file will be placed in `authz` subdirectory inside EMQX's `data_dir`,
+and the new rules will override all rules from the old config file.
+"""
+ zh: """"""
+ }
+ label {
+ en: """path"""
+ zh: """path"""
+ }
+ }
+
+#==== authz_http
+
+ http_get {
+ desc {
+ en: """Authorization using an external HTTP server (via GET requests)."""
+ zh: """使用外部 HTTP 服务器鉴权(GET 请求)."""
+ }
+ label {
+ en: """http_get"""
+ zh: """http_get"""
+ }
+ }
+
+ http_post {
+ desc {
+ en: """Authorization using an external HTTP server (via POST requests)."""
+ zh: """使用外部 HTTP 服务器鉴权(POST 请求)."""
+ }
+ label {
+ en: """http_post"""
+ zh: """http_post"""
+ }
+ }
+
+ method {
+ desc {
+ en: """HTTP method."""
+ zh: """HTTP 请求方法"""
+ }
+ label {
+ en: """method"""
+ zh: """method"""
+ }
+ }
+
+ url {
+ desc {
+ en: """URL of the auth server."""
+ zh: """认证服务器 URL"""
+ }
+ label {
+ en: """url"""
+ zh: """url"""
+ }
+ }
+
+ headers {
+ desc {
+ en: """List of HTTP headers."""
+ zh: """"""
+ }
+ label {
+ en: """headers"""
+ zh: """请求头"""
+ }
+ }
+
+ headers_no_content_type {
+ desc {
+ en: """List of HTTP headers (without `content_type`)."""
+ zh: """"""
+ }
+ label {
+ en: """headers_no_content_type"""
+ zh: """请求头(无 content-type)"""
+ }
+ }
+
+ body {
+ desc {
+ en: """HTTP request body."""
+ zh: """HTTP 请求体"""
+ }
+ label {
+ en: """body"""
+ zh: """请求体"""
+ }
+ }
+
+ request_timeout {
+ desc {
+ en: """Request timeout."""
+ zh: """请求超时时间"""
+ }
+ label {
+ en: """request_timeout"""
+ zh: """请求超时"""
+ }
+ }
+
+#==== authz_mnesia
+
+ mnesia {
+ desc {
+ en: """Authorization using a built-in database (mnesia)."""
+ zh: """使用内部数据库鉴权 (mnesia)."""
+ }
+ label {
+ en: """mnesia"""
+ zh: """mnesia"""
+ }
+ }
+
+#==== authz_mongo
+
+ mongo_single {
+ desc {
+ en: """Authorization using a single MongoDB instance."""
+ zh: """使用 MongoDB 鉴权(单实例)"""
+ }
+ label {
+ en: """mongo_single"""
+ zh: """mongo_single"""
+ }
+ }
+
+ mongo_rs {
+ desc {
+ en: """Authorization using a MongoDB replica set."""
+ zh: """使用 MongoDB 鉴权(副本集模式)"""
+ }
+ label {
+ en: """mongo_rs"""
+ zh: """mongo_rs"""
+ }
+ }
+
+ mongo_sharded {
+ desc {
+ en: """Authorization using a sharded MongoDB cluster."""
+ zh: """使用 MongoDB 鉴权(分片集群模式)"""
+ }
+ label {
+ en: """mongo_sharded"""
+ zh: """mongo_sharded"""
+ }
+ }
+
+ collection {
+ desc {
+ en: """`MongoDB` collection containing the authorization data."""
+ zh: """`MongoDB` 鉴权数据集"""
+ }
+ label {
+ en: """collection"""
+ zh: """数据集"""
+ }
+ }
+
+ selector {
+ desc {
+ en: """
+Statement that is executed during the authorize process.
+Commands can support following wildcards:\n
+ - `${username}`: substituted with client's username\n
+ - `${clientid}`: substituted with the clientid
+"""
+ zh: """
+鉴权过程中所使用的查询命令。
+查询命令支持如下占位符:
+- `${username}`: 代替客户端的用户名
+- `${clientid}`: 代替客户端的客户端标识符"""
+ }
+ label {
+ en: """selector"""
+ zh: """selector"""
+ }
+ }
+
+#==== authz_mysql
+
+ mysql {
+ desc {
+ en: """Authorization using a MySQL database."""
+ zh: """使用 MySOL 数据库鉴权"""
+ }
+ label {
+ en: """mysql"""
+ zh: """mysql"""
+ }
+ }
+
+#==== authz_pgsql
+
+ postgresql {
+ desc {
+ en: """Authorization using a PostgreSQL database."""
+ zh: """使用 PostgreSQL 数据库鉴权"""
+ }
+ label {
+ en: """postgresql"""
+ zh: """postgresql"""
+ }
+ }
+
+#==== authz_redis
+
+ redis_single {
+ desc {
+ en: """Authorization using a single Redis instance."""
+ zh: """使用 Redis 鉴权(单实例)"""
+ }
+ label {
+ en: """redis_single"""
+ zh: """redis_single"""
+ }
+ }
+
+ redis_sentinel {
+ desc {
+ en: """Authorization using a Redis Sentinel."""
+ zh: """使用 Redis 鉴权(哨兵模式)"""
+ }
+ label {
+ en: """redis_sentinel"""
+ zh: """redis_sentinel"""
+ }
+ }
+
+ redis_cluster {
+ desc {
+ en: """Authorization using a Redis cluster."""
+ zh: """使用 Redis 鉴权(集群模式)"""
+ }
+ label {
+ en: """redis_cluster"""
+ zh: """redis_cluster"""
+ }
+ }
+
+ cmd {
+ desc {
+ en: """Database query used to retrieve authorization data."""
+ zh: """访问控制数据查查询命令"""
+ }
+ label {
+ en: """cmd"""
+ zh: """查询命令"""
+ }
+ }
+
+#==== common field for DBs (except redis)
+
+ query {
+ desc {
+ en: """Database query used to retrieve authorization data."""
+ zh: """访问控制数据查询语句/查询命令"""
+ }
+ label {
+ en: """query"""
+ zh: """查询语句"""
+ }
+ }
+}
diff --git a/apps/emqx_authz/src/emqx_authz_api_cache.erl b/apps/emqx_authz/src/emqx_authz_api_cache.erl
index e6d3b941c..bfcab823c 100644
--- a/apps/emqx_authz/src/emqx_authz_api_cache.erl
+++ b/apps/emqx_authz/src/emqx_authz_api_cache.erl
@@ -18,6 +18,8 @@
-behaviour(minirest_api).
+-include_lib("hocon/include/hoconsc.hrl").
+
-export([
api_spec/0,
paths/0,
@@ -47,7 +49,7 @@ schema("/authorization/cache") ->
'operationId' => clean_cache,
delete =>
#{
- description => <<"Clean all authorization cache in the cluster.">>,
+ description => ?DESC(authorization_cache_delete),
responses =>
#{
204 => <<"No Content">>,
diff --git a/apps/emqx_authz/src/emqx_authz_api_mnesia.erl b/apps/emqx_authz/src/emqx_authz_api_mnesia.erl
index addb11584..452e34e5e 100644
--- a/apps/emqx_authz/src/emqx_authz_api_mnesia.erl
+++ b/apps/emqx_authz/src/emqx_authz_api_mnesia.erl
@@ -20,7 +20,7 @@
-include("emqx_authz.hrl").
-include_lib("emqx/include/logger.hrl").
--include_lib("typerefl/include/types.hrl").
+-include_lib("hocon/include/hoconsc.hrl").
-import(hoconsc, [mk/1, mk/2, ref/1, ref/2, array/1, enum/1]).
@@ -87,7 +87,7 @@ schema("/authorization/sources/built_in_database/username") ->
get =>
#{
tags => [<<"authorization">>],
- description => <<"Show the list of record for username">>,
+ description => ?DESC(users_username_get),
parameters =>
[
ref(emqx_dashboard_swagger, page),
@@ -96,7 +96,7 @@ schema("/authorization/sources/built_in_database/username") ->
mk(binary(), #{
in => query,
required => false,
- desc => <<"Fuzzy search `username` as substring">>
+ desc => ?DESC(fuzzy_username)
})}
],
responses =>
@@ -110,7 +110,7 @@ schema("/authorization/sources/built_in_database/username") ->
post =>
#{
tags => [<<"authorization">>],
- description => <<"Add new records for username">>,
+ description => ?DESC(users_username_post),
'requestBody' => swagger_with_example(
{rules_for_username, ?TYPE_ARRAY},
{username, ?POST_ARRAY_EXAMPLE}
@@ -130,7 +130,7 @@ schema("/authorization/sources/built_in_database/clientid") ->
get =>
#{
tags => [<<"authorization">>],
- description => <<"Show the list of record for clientid">>,
+ description => ?DESC(users_clientid_get),
parameters =>
[
ref(emqx_dashboard_swagger, page),
@@ -141,7 +141,7 @@ schema("/authorization/sources/built_in_database/clientid") ->
#{
in => query,
required => false,
- desc => <<"Fuzzy search `clientid` as substring">>
+ desc => ?DESC(fuzzy_clientid)
}
)}
],
@@ -156,7 +156,7 @@ schema("/authorization/sources/built_in_database/clientid") ->
post =>
#{
tags => [<<"authorization">>],
- description => <<"Add new records for clientid">>,
+ description => ?DESC(users_clientid_post),
'requestBody' => swagger_with_example(
{rules_for_clientid, ?TYPE_ARRAY},
{clientid, ?POST_ARRAY_EXAMPLE}
@@ -176,7 +176,7 @@ schema("/authorization/sources/built_in_database/username/:username") ->
get =>
#{
tags => [<<"authorization">>],
- description => <<"Get record info for username">>,
+ description => ?DESC(user_username_get),
parameters => [ref(username)],
responses =>
#{
@@ -192,7 +192,7 @@ schema("/authorization/sources/built_in_database/username/:username") ->
put =>
#{
tags => [<<"authorization">>],
- description => <<"Set record for username">>,
+ description => ?DESC(user_username_put),
parameters => [ref(username)],
'requestBody' => swagger_with_example(
{rules_for_username, ?TYPE_REF},
@@ -209,7 +209,7 @@ schema("/authorization/sources/built_in_database/username/:username") ->
delete =>
#{
tags => [<<"authorization">>],
- description => <<"Delete one record for username">>,
+ description => ?DESC(user_username_delete),
parameters => [ref(username)],
responses =>
#{
@@ -229,7 +229,7 @@ schema("/authorization/sources/built_in_database/clientid/:clientid") ->
get =>
#{
tags => [<<"authorization">>],
- description => <<"Get record info for clientid">>,
+ description => ?DESC(user_clientid_get),
parameters => [ref(clientid)],
responses =>
#{
@@ -245,7 +245,7 @@ schema("/authorization/sources/built_in_database/clientid/:clientid") ->
put =>
#{
tags => [<<"authorization">>],
- description => <<"Set record for clientid">>,
+ description => ?DESC(user_clientid_put),
parameters => [ref(clientid)],
'requestBody' => swagger_with_example(
{rules_for_clientid, ?TYPE_REF},
@@ -262,7 +262,7 @@ schema("/authorization/sources/built_in_database/clientid/:clientid") ->
delete =>
#{
tags => [<<"authorization">>],
- description => <<"Delete one record for clientid">>,
+ description => ?DESC(user_clientid_delete),
parameters => [ref(clientid)],
responses =>
#{
@@ -282,17 +282,14 @@ schema("/authorization/sources/built_in_database/all") ->
get =>
#{
tags => [<<"authorization">>],
- description => <<"Show the list of rules for all">>,
+ description => ?DESC(rules_for_all_get),
responses =>
#{200 => swagger_with_example({rules, ?TYPE_REF}, {all, ?PUT_MAP_EXAMPLE})}
},
post =>
#{
tags => [<<"authorization">>],
- description => <<
- "Create/Update the list of rules for all. "
- "Set a empty list to clean up rules"
- >>,
+ description => ?DESC(rules_for_all_post),
'requestBody' =>
swagger_with_example({rules, ?TYPE_REF}, {all, ?PUT_MAP_EXAMPLE}),
responses =>
@@ -310,7 +307,7 @@ schema("/authorization/sources/built_in_database/purge-all") ->
delete =>
#{
tags => [<<"authorization">>],
- description => <<"Purge all records">>,
+ description => ?DESC(purge_all_delete),
responses =>
#{
204 => <<"Deleted">>,
@@ -328,7 +325,7 @@ fields(rule_item) ->
string(),
#{
required => true,
- desc => <<"Rule on specific topic">>,
+ desc => ?DESC(topic),
example => <<"test/topic/1">>
}
)},
@@ -336,7 +333,7 @@ fields(rule_item) ->
mk(
enum([allow, deny]),
#{
- desc => <<"Permission">>,
+ desc => ?DESC(permission),
required => true,
example => allow
}
@@ -345,9 +342,9 @@ fields(rule_item) ->
mk(
enum([publish, subscribe, all]),
#{
+ desc => ?DESC(action),
required => true,
- example => publish,
- desc => <<"Authorized action">>
+ example => publish
}
)}
];
@@ -359,7 +356,7 @@ fields(clientid) ->
#{
in => path,
required => true,
- desc => <<"ClientID">>,
+ desc => ?DESC(clientid),
example => <<"client1">>
}
)}
@@ -372,7 +369,7 @@ fields(username) ->
#{
in => path,
required => true,
- desc => <<"Username">>,
+ desc => ?DESC(username),
example => <<"user1">>
}
)}
diff --git a/apps/emqx_authz/src/emqx_authz_api_schema.erl b/apps/emqx_authz/src/emqx_authz_api_schema.erl
index 19c7e5cfd..a398479bd 100644
--- a/apps/emqx_authz/src/emqx_authz_api_schema.erl
+++ b/apps/emqx_authz/src/emqx_authz_api_schema.erl
@@ -17,7 +17,7 @@
-module(emqx_authz_api_schema).
-include("emqx_authz.hrl").
--include_lib("typerefl/include/types.hrl").
+-include_lib("hocon/include/hoconsc.hrl").
-include_lib("emqx_connector/include/emqx_connector.hrl").
-import(hoconsc, [mk/2, enum/1]).
@@ -32,14 +32,26 @@
%% Hocon Schema
%%------------------------------------------------------------------------------
+fields(file) ->
+ authz_common_fields(file) ++
+ [
+ {rules, #{
+ type => binary(),
+ required => true,
+ example =>
+ <<"{allow,{username,\"^dashboard?\"},", "subscribe,[\"$SYS/#\"]}.\n",
+ "{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">>,
+ desc => ?DESC(rules)
+ }}
+ ];
fields(http_get) ->
[
- {method, #{type => get, default => get, required => true}},
+ {method, #{type => get, default => get, required => true, desc => ?DESC(method)}},
{headers, fun headers_no_content_type/1}
] ++ authz_http_common_fields();
fields(http_post) ->
[
- {method, #{type => post, default => post, required => true}},
+ {method, #{type => post, default => post, required => true, desc => ?DESC(method)}},
{headers, fun headers/1}
] ++ authz_http_common_fields();
fields(built_in_database) ->
@@ -55,11 +67,11 @@ fields(mongo_sharded) ->
emqx_connector_mongo:fields(sharded);
fields(mysql) ->
authz_common_fields(mysql) ++
- [{query, mk(binary(), #{required => true})}] ++
+ [{query, query()}] ++
proplists:delete(prepare_statement, emqx_connector_mysql:fields(config));
fields(postgresql) ->
authz_common_fields(postgresql) ++
- [{query, mk(binary(), #{required => true})}] ++
+ [{query, query()}] ++
proplists:delete(prepare_statement, emqx_connector_pgsql:fields(config));
fields(redis_single) ->
authz_redis_common_fields() ++
@@ -70,24 +82,13 @@ fields(redis_sentinel) ->
fields(redis_cluster) ->
authz_redis_common_fields() ++
emqx_connector_redis:fields(cluster);
-fields(file) ->
- authz_common_fields(file) ++
- [
- {rules, #{
- type => binary(),
- required => true,
- example =>
- <<"{allow,{username,\"^dashboard?\"},", "subscribe,[\"$SYS/#\"]}.\n",
- "{allow,{ipaddr,\"127.0.0.1\"},all,[\"$SYS/#\",\"#\"]}.">>
- }}
- ];
fields(position) ->
[
{position,
mk(
string(),
#{
- desc => <<"Where to place the source">>,
+ desc => ?DESC(position),
required => true,
in => body
}
@@ -102,7 +103,8 @@ authz_http_common_fields() ->
[
{url, fun url/1},
{body, map([{fuzzy, term(), binary()}])},
- {request_timeout, mk_duration("Request timeout", #{default => "30s"})}
+ {request_timeout,
+ mk_duration("Request timeout", #{default => "30s", desc => ?DESC(request_timeout)})}
] ++
maps:to_list(
maps:without(
@@ -117,12 +119,13 @@ authz_http_common_fields() ->
url(type) -> binary();
url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")];
url(required) -> true;
+url(desc) -> ?DESC(?FUNCTION_NAME);
url(_) -> undefined.
headers(type) ->
map();
headers(desc) ->
- "List of HTTP headers.";
+ ?DESC(?FUNCTION_NAME);
headers(converter) ->
fun(Headers) ->
maps:merge(default_headers(), transform_header_name(Headers))
@@ -135,7 +138,7 @@ headers(_) ->
headers_no_content_type(type) ->
map();
headers_no_content_type(desc) ->
- "List of HTTP headers.";
+ ?DESC(?FUNCTION_NAME);
headers_no_content_type(converter) ->
fun(Headers) ->
maps:merge(default_headers_no_content_type(), transform_header_name(Headers))
@@ -182,17 +185,14 @@ authz_mongo_common_fields() ->
].
collection(type) -> binary();
-collection(desc) -> "Collection used to store authentication data.";
+collection(desc) -> ?DESC(?FUNCTION_NAME);
collection(required) -> true;
collection(_) -> 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";
+ ?DESC(?FUNCTION_NAME);
selector(_) ->
undefined.
@@ -205,6 +205,7 @@ authz_redis_common_fields() ->
{cmd,
mk(binary(), #{
required => true,
+ desc => ?DESC(cmd),
example => <<"HGETALL mqtt_authz">>
})}
].
@@ -216,18 +217,35 @@ authz_common_fields(Type) when is_atom(Type) ->
[
{enable, fun enable/1},
{type, #{
- type => enum([Type]),
+ type => Type,
default => Type,
required => true,
+ desc => ?DESC(type),
in => body
}}
].
enable(type) -> boolean();
enable(default) -> true;
-enable(desc) -> "Set to false
to disable this auth provider";
+enable(desc) -> ?DESC(?FUNCTION_NAME);
enable(_) -> undefined.
+%%------------------------------------------------------------------------------
+%% Authz DB query
+
+query() ->
+ #{
+ type => binary(),
+ desc => ?DESC(query),
+ required => true,
+ validator => fun(S) ->
+ case size(S) > 0 of
+ true -> ok;
+ _ -> {error, "Request query"}
+ end
+ end
+ }.
+
%%------------------------------------------------------------------------------
%% Internal funcs
diff --git a/apps/emqx_authz/src/emqx_authz_api_settings.erl b/apps/emqx_authz/src/emqx_authz_api_settings.erl
index cd00d131e..c5409b16b 100644
--- a/apps/emqx_authz/src/emqx_authz_api_settings.erl
+++ b/apps/emqx_authz/src/emqx_authz_api_settings.erl
@@ -18,6 +18,8 @@
-behaviour(minirest_api).
+-include_lib("hocon/include/hoconsc.hrl").
+
-import(hoconsc, [mk/1, ref/2]).
-export([
@@ -45,13 +47,13 @@ schema("/authorization/settings") ->
'operationId' => settings,
get =>
#{
- description => <<"Get authorization settings">>,
+ description => ?DESC(authorization_settings_get),
responses =>
#{200 => ref_authz_schema()}
},
put =>
#{
- description => <<"Update authorization settings">>,
+ description => ?DESC(authorization_settings_put),
'requestBody' => ref_authz_schema(),
responses =>
#{
diff --git a/apps/emqx_authz/src/emqx_authz_api_sources.erl b/apps/emqx_authz/src/emqx_authz_api_sources.erl
index 5529bbfca..c05f9f133 100644
--- a/apps/emqx_authz/src/emqx_authz_api_sources.erl
+++ b/apps/emqx_authz/src/emqx_authz_api_sources.erl
@@ -18,9 +18,9 @@
-behaviour(minirest_api).
--include_lib("typerefl/include/types.hrl").
-include("emqx_authz.hrl").
-include_lib("emqx/include/logger.hrl").
+-include_lib("hocon/include/hoconsc.hrl").
-import(hoconsc, [mk/1, mk/2, ref/2, array/1, enum/1]).
@@ -63,27 +63,26 @@ paths() ->
%%--------------------------------------------------------------------
%% Schema for each URI
%%--------------------------------------------------------------------
-
schema("/authorization/sources") ->
#{
'operationId' => sources,
get =>
#{
- description => <<"List all authorization sources">>,
+ description => ?DESC(authorization_sources_get),
responses =>
#{
200 => mk(
array(hoconsc:union(authz_sources_type_refs())),
- #{desc => <<"Authorization source">>}
+ #{desc => ?DESC(sources)}
)
}
},
post =>
#{
- description => <<"Add a new source">>,
+ description => ?DESC(authorization_sources_post),
'requestBody' => mk(
hoconsc:union(authz_sources_type_refs()),
- #{desc => <<"Source config">>}
+ #{desc => ?DESC(source_config)}
),
responses =>
#{
@@ -100,20 +99,20 @@ schema("/authorization/sources/:type") ->
'operationId' => source,
get =>
#{
- description => <<"Get a authorization source">>,
+ description => ?DESC(authorization_sources_type_get),
parameters => parameters_field(),
responses =>
#{
200 => mk(
hoconsc:union(authz_sources_type_refs()),
- #{desc => <<"Authorization source">>}
+ #{desc => ?DESC(source)}
),
404 => emqx_dashboard_swagger:error_codes([?NOT_FOUND], <<"Not Found">>)
}
},
put =>
#{
- description => <<"Update source">>,
+ description => ?DESC(authorization_sources_type_put),
parameters => parameters_field(),
'requestBody' => mk(hoconsc:union(authz_sources_type_refs())),
responses =>
@@ -124,7 +123,7 @@ schema("/authorization/sources/:type") ->
},
delete =>
#{
- description => <<"Delete source">>,
+ description => ?DESC(authorization_sources_type_delete),
parameters => parameters_field(),
responses =>
#{
@@ -138,7 +137,7 @@ schema("/authorization/sources/:type/status") ->
'operationId' => source_status,
get =>
#{
- description => <<"Get a authorization source">>,
+ description => ?DESC(authorization_sources_type_status_get),
parameters => parameters_field(),
responses =>
#{
@@ -158,7 +157,7 @@ schema("/authorization/sources/:type/move") ->
'operationId' => move_source,
post =>
#{
- description => <<"Change the order of sources">>,
+ description => ?DESC(authorization_sources_type_move_post),
parameters => parameters_field(),
'requestBody' =>
emqx_dashboard_swagger:schema_with_examples(
@@ -508,7 +507,7 @@ parameters_field() ->
{type,
mk(
enum(?API_SCHEMA_MODULE:authz_sources_types(simple)),
- #{in => path, desc => <<"Authorization type">>}
+ #{in => path, desc => ?DESC(source_type)}
)}
].
diff --git a/apps/emqx_authz/src/emqx_authz_mnesia.erl b/apps/emqx_authz/src/emqx_authz_mnesia.erl
index 0bb85b96a..267338493 100644
--- a/apps/emqx_authz/src/emqx_authz_mnesia.erl
+++ b/apps/emqx_authz/src/emqx_authz_mnesia.erl
@@ -99,7 +99,7 @@ authorize(
} = Client,
PubSub,
Topic,
- #{type := 'built_in_database'}
+ #{type := built_in_database}
) ->
Rules =
case mnesia:dirty_read(?ACL_TABLE, {?ACL_TABLE_CLIENTID, Clientid}) of
diff --git a/apps/emqx_authz/src/emqx_authz_schema.erl b/apps/emqx_authz/src/emqx_authz_schema.erl
index 011b1585d..042568ce4 100644
--- a/apps/emqx_authz/src/emqx_authz_schema.erl
+++ b/apps/emqx_authz/src/emqx_authz_schema.erl
@@ -17,7 +17,7 @@
-module(emqx_authz_schema).
-include("emqx_authz.hrl").
--include_lib("typerefl/include/types.hrl").
+-include_lib("hocon/include/hoconsc.hrl").
-include_lib("emqx_connector/include/emqx_connector.hrl").
-reflect_type([
@@ -71,134 +71,104 @@ fields("authorization") ->
]
),
default => [],
- desc =>
- "\n"
- "Authorization data sources.
\n"
- "An array of authorization (ACL) data providers.\n"
- "It is designed as an array, not a hash-map, so the sources can be\n"
- "ordered to form a chain of access controls.
\n"
- "\n"
- "When authorizing a 'publish' or 'subscribe' action, the configured\n"
- "sources are checked in order. When checking an ACL source,\n"
- "in case the client (identified by username or client ID) is not found,\n"
- "it moves on to the next source. And it stops immediately\n"
- "once an 'allow' or 'deny' decision is returned.
\n"
- "\n"
- "If the client is not found in any of the sources,\n"
- "the default action configured in 'authorization.no_match' is applied.
\n"
- "\n"
- "NOTE:\n"
- "The source elements are identified by their 'type'.\n"
- "It is NOT allowed to configure two or more sources of the same type.\n"
+ desc => ?DESC(sources)
}}
];
fields(file) ->
- [
- {type, #{type => file, required => true, desc => "Backend type."}},
- {enable, #{
- type => boolean(),
- default => true,
- desc => "Enable this backend."
- }},
- {path, #{
- type => string(),
- required => true,
- desc =>
- "\n"
- "Path to the file which contains the ACL rules.
\n"
- "If the file provisioned before starting EMQX node,\n"
- "it can be placed anywhere as long as EMQX has read access to it.\n"
- "\n"
- "In case the rule-set is created from EMQX dashboard or management API,\n"
- "the file will be placed in `authz` subdirectory inside EMQX's `data_dir`,\n"
- "and the new rules will override all rules from the old config file.\n"
- }}
- ];
+ authz_common_fields(file) ++
+ [{path, #{type => string(), required => true, desc => ?DESC(path)}}];
fields(http_get) ->
- [
- {method, #{type => get, default => get, required => true, desc => "HTTP method."}},
- {headers, fun headers_no_content_type/1}
- ] ++ http_common_fields();
+ authz_common_fields(http) ++
+ http_common_fields() ++
+ [
+ {method, #{type => get, default => get, required => true, desc => ?DESC(method)}},
+ {headers, fun headers_no_content_type/1}
+ ];
fields(http_post) ->
- [
- {method, #{type => post, default => post, required => true, desc => "HTTP method."}},
- {headers, fun headers/1}
- ] ++ http_common_fields();
+ authz_common_fields(http) ++
+ http_common_fields() ++
+ [
+ {method, #{type => post, default => post, required => true, desc => ?DESC(method)}},
+ {headers, fun headers/1}
+ ];
fields(mnesia) ->
- [
- {type, #{type => 'built_in_database', required => true, desc => "Backend type."}},
- {enable, #{
- type => boolean(),
- default => true,
- desc => "Enable this backend."
- }}
- ];
+ authz_common_fields(built_in_database);
fields(mongo_single) ->
- mongo_common_fields() ++ emqx_connector_mongo:fields(single);
+ authz_common_fields(mongodb) ++
+ mongo_common_fields() ++
+ emqx_connector_mongo:fields(single);
fields(mongo_rs) ->
- mongo_common_fields() ++ emqx_connector_mongo:fields(rs);
+ authz_common_fields(mongodb) ++
+ mongo_common_fields() ++
+ emqx_connector_mongo:fields(rs);
fields(mongo_sharded) ->
- mongo_common_fields() ++ emqx_connector_mongo:fields(sharded);
+ authz_common_fields(mongodb) ++
+ mongo_common_fields() ++
+ emqx_connector_mongo:fields(sharded);
fields(mysql) ->
- connector_fields(mysql) ++
+ authz_common_fields(mysql) ++
+ connector_fields(mysql) ++
[{query, query()}];
fields(postgresql) ->
- [
- {query, query()},
- {type, #{type => postgresql, required => true, desc => "Backend type."}},
- {enable, #{
- type => boolean(),
- desc => "Enable this backend.",
- default => true
- }}
- ] ++ emqx_connector_pgsql:fields(config);
+ authz_common_fields(postgresql) ++
+ emqx_connector_pgsql:fields(config) ++
+ [{query, query()}];
fields(redis_single) ->
- connector_fields(redis, single) ++
- [{cmd, query()}];
+ authz_common_fields(redis) ++
+ connector_fields(redis, single) ++
+ [{cmd, cmd()}];
fields(redis_sentinel) ->
- connector_fields(redis, sentinel) ++
- [{cmd, query()}];
+ authz_common_fields(redis) ++
+ connector_fields(redis, sentinel) ++
+ [{cmd, cmd()}];
fields(redis_cluster) ->
- connector_fields(redis, cluster) ++
- [{cmd, query()}].
+ authz_common_fields(redis) ++
+ connector_fields(redis, cluster) ++
+ [{cmd, cmd()}].
-desc("authorization") ->
- "Configuration related to the client authorization.";
+desc(?CONF_NS) ->
+ ?DESC(?CONF_NS);
desc(file) ->
- "Authorization using a static file.";
+ ?DESC(file);
desc(http_get) ->
- "Authorization using an external HTTP server (via GET requests).";
+ ?DESC(http_get);
desc(http_post) ->
- "Authorization using an external HTTP server (via POST requests).";
+ ?DESC(http_post);
desc(mnesia) ->
- "Authorization using a built-in database (mnesia).";
+ ?DESC(mnesia);
desc(mongo_single) ->
- "Authorization using a single MongoDB instance.";
+ ?DESC(mongo_single);
desc(mongo_rs) ->
- "Authorization using a MongoDB replica set.";
+ ?DESC(mongo_rs);
desc(mongo_sharded) ->
- "Authorization using a sharded MongoDB cluster.";
+ ?DESC(mongo_sharded);
desc(mysql) ->
- "Authorization using a MySQL database.";
+ ?DESC(mysql);
desc(postgresql) ->
- "Authorization using a PostgreSQL database.";
+ ?DESC(postgresql);
desc(redis_single) ->
- "Authorization using a single Redis instance.";
+ ?DESC(redis_single);
desc(redis_sentinel) ->
- "Authorization using a Redis Sentinel.";
+ ?DESC(redis_sentinel);
desc(redis_cluster) ->
- "Authorization using a Redis cluster.";
+ ?DESC(redis_cluster);
desc(_) ->
undefined.
+authz_common_fields(Type) ->
+ [
+ {type, #{type => Type, required => true, desc => ?DESC(type)}},
+ {enable, #{type => boolean(), default => true, desc => ?DESC(enable)}}
+ ].
+
http_common_fields() ->
[
{url, fun url/1},
{request_timeout,
emqx_schema:mk_duration("Request timeout", #{
- default => "30s", desc => "Request timeout."
+ default => "30s", desc => ?DESC(request_timeout)
})},
- {body, #{type => map(), required => false, desc => "HTTP request body."}}
+ {body, #{type => map(), required => false, desc => ?DESC(body)}}
] ++
maps:to_list(
maps:without(
@@ -215,18 +185,12 @@ mongo_common_fields() ->
{collection, #{
type => atom(),
required => true,
- desc => "`MongoDB` collection containing the authorization data."
+ desc => ?DESC(collection)
}},
{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,
- desc => "Enable or disable the backend."
+ desc => ?DESC(selector)
}}
].
@@ -238,7 +202,7 @@ validations() ->
headers(type) ->
list({binary(), binary()});
headers(desc) ->
- "List of HTTP headers.";
+ ?DESC(?FUNCTION_NAME);
headers(converter) ->
fun(Headers) ->
maps:to_list(maps:merge(default_headers(), transform_header_name(Headers)))
@@ -251,7 +215,7 @@ headers(_) ->
headers_no_content_type(type) ->
list({binary(), binary()});
headers_no_content_type(desc) ->
- "List of HTTP headers.";
+ ?DESC(?FUNCTION_NAME);
headers_no_content_type(converter) ->
fun(Headers) ->
maps:to_list(maps:merge(default_headers_no_content_type(), transform_header_name(Headers)))
@@ -269,7 +233,7 @@ headers_no_content_type(_) ->
undefined.
url(type) -> binary();
-url(desc) -> "URL of the auth server.";
+url(desc) -> ?DESC(?FUNCTION_NAME);
url(validator) -> [?NOT_EMPTY("the value of the field 'url' cannot be empty")];
url(required) -> true;
url(_) -> undefined.
@@ -328,7 +292,20 @@ union_array(Item) when is_list(Item) ->
query() ->
#{
type => binary(),
- desc => "Database query used to retrieve authorization data.",
+ desc => ?DESC(query),
+ required => true,
+ validator => fun(S) ->
+ case size(S) > 0 of
+ true -> ok;
+ _ -> {error, "Request query"}
+ end
+ end
+ }.
+
+cmd() ->
+ #{
+ type => binary(),
+ desc => ?DESC(cmd),
required => true,
validator => fun(S) ->
case size(S) > 0 of
@@ -351,14 +328,7 @@ connector_fields(DB, Fields) ->
error:Reason ->
erlang:error(Reason)
end,
- [
- {type, #{type => DB, desc => "Database backend."}},
- {enable, #{
- type => boolean(),
- default => true,
- desc => "Enable or disable the backend."
- }}
- ] ++ erlang:apply(Mod, fields, [Fields]).
+ erlang:apply(Mod, fields, [Fields]).
to_list(A) when is_atom(A) ->
atom_to_list(A);