From 11d2ae117cb81ec6de825315f7bac48080e08259 Mon Sep 17 00:00:00 2001 From: JimMoen Date: Wed, 20 Apr 2022 17:28:10 +0800 Subject: [PATCH] docs: authz i18n zh_CN support --- .../i18n/emqx_authz_api_cache_i18n.conf | 6 + .../i18n/emqx_authz_api_mnesia_i18n.conf | 177 +++++++++ .../i18n/emqx_authz_api_schema_i18n.conf | 188 ++++++++++ .../i18n/emqx_authz_api_settings_i18n.conf | 11 + .../i18n/emqx_authz_api_sources_i18n.conf | 116 ++++++ .../i18n/emqx_authz_schema_i18n.conf | 349 ++++++++++++++++++ apps/emqx_authz/src/emqx_authz_api_cache.erl | 4 +- apps/emqx_authz/src/emqx_authz_api_mnesia.erl | 47 ++- apps/emqx_authz/src/emqx_authz_api_schema.erl | 72 ++-- .../src/emqx_authz_api_settings.erl | 6 +- .../emqx_authz/src/emqx_authz_api_sources.erl | 25 +- apps/emqx_authz/src/emqx_authz_mnesia.erl | 2 +- apps/emqx_authz/src/emqx_authz_schema.erl | 194 ++++------ 13 files changed, 1016 insertions(+), 181 deletions(-) create mode 100644 apps/emqx_authz/i18n/emqx_authz_api_cache_i18n.conf create mode 100644 apps/emqx_authz/i18n/emqx_authz_api_mnesia_i18n.conf create mode 100644 apps/emqx_authz/i18n/emqx_authz_api_schema_i18n.conf create mode 100644 apps/emqx_authz/i18n/emqx_authz_api_settings_i18n.conf create mode 100644 apps/emqx_authz/i18n/emqx_authz_api_sources_i18n.conf create mode 100644 apps/emqx_authz/i18n/emqx_authz_schema_i18n.conf 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: """设为 truefalse 以启用或禁用此访问控制数据源""" + } + 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: """设为 truefalse 以启用或禁用此访问控制数据源""" + } + 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);