docs: authz i18n zh_CN support

This commit is contained in:
JimMoen 2022-04-20 17:28:10 +08:00
parent 86c38d283d
commit 11d2ae117c
13 changed files with 1016 additions and 181 deletions

View File

@ -0,0 +1,6 @@
emqx_authz_api_cache {
authorization_cache_delete {
en: """Clean all authorization cache in the cluster."""
zh: """清除集群中所有鉴权数据缓存"""
}
}

View File

@ -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: """用户名"""
}
}
}

View File

@ -0,0 +1,188 @@
emqx_authz_api_schema {
enable {
desc {
en: """Set to <code>true</code> or <code>false</code> to disable this ACL provider"""
zh: """设为 <code>true</code> 或 <code>false</code> 以启用或禁用此访问控制数据源"""
}
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: """位置"""
}
}
}

View File

@ -0,0 +1,11 @@
emqx_authz_api_settings {
authorization_settings_get {
en: """Get authorization settings"""
zh: """获取鉴权配置"""
}
authorization_settings_put {
en: """Update authorization settings"""
zh: """更新鉴权配置"""
}
}

View File

@ -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: """数据源类型"""
}
}
}

View File

@ -0,0 +1,349 @@
emqx_authz_schema {
sources {
desc {
en: """
Authorization data sources.<br>
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.<br>
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.<br>
If the client is not found in any of the sources,
the default action configured in 'authorization.no_match' is applied.<br>
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 <code>true</code> or <code>false</code> to disable this ACL provider"""
zh: """设为 <code>true</code> 或 <code>false</code> 以启用或禁用此访问控制数据源"""
}
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.<br>
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: """查询语句"""
}
}
}

View File

@ -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">>,

View File

@ -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">>
}
)}

View File

@ -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 <code>false</code> 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

View File

@ -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 =>
#{

View File

@ -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)}
)}
].

View File

@ -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

View File

@ -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.<br>\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.<br>\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.<br>\n"
"\n"
"If the client is not found in any of the sources,\n"
"the default action configured in 'authorization.no_match' is applied.<br>\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.<br>\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);