diff --git a/apps/emqx_authn/i18n/emqx_authn_api_i18n.conf b/apps/emqx_authn/i18n/emqx_authn_api_i18n.conf new file mode 100644 index 000000000..56f41aa89 --- /dev/null +++ b/apps/emqx_authn/i18n/emqx_authn_api_i18n.conf @@ -0,0 +1,228 @@ +emqx_authn_api { + + authentication_get { + desc { + en: """List authenticators for global authentication""" + zh: """列出全局认证链上的认证器信息""" + } + } + + authentication_post { + desc { + en: """Create authenticator for global authentication""" + zh: """为全局认证链创建认证器信息""" + } + } + + authentication_id_get { + desc { + en: """Get authenticator from global authentication chain""" + zh: """获取全局认证链上指定的认证器信息""" + } + } + + authentication_id_put { + desc { + en: """Update authenticator from global authentication chain""" + zh: """更新全局认证链上指定的认证器""" + } + } + + authentication_id_delete { + desc { + en: """Delete authenticator from global authentication chain""" + zh: """删除全局认证链上指定的认证器信息""" + } + } + + authentication_id_status_get { + desc { + en: """Get authenticator status from global authentication chain""" + zh: """获取全局认证链上指定的认证器状态""" + } + } + + listeners_listener_id_authentication_get { + desc { + en: """List authenticators for listener authentication""" + zh: """列出监听器上的认证器信息""" + } + } + + listeners_listener_id_authentication_post { + desc { + en: """Create authenticator for listener authentication""" + zh: """在指定的监听器上创建认证器""" + } + } + + listeners_listener_id_authentication_id_get { + desc { + en: """Get authenticator from listener authentication chain""" + zh: """获取监听器上指定的认证器信息""" + } + } + + listeners_listener_id_authentication_id_put { + desc { + en: """Update authenticator from listener authentication chain""" + zh: """更新监听器上指定的认证器""" + } + } + + listeners_listener_id_authentication_id_delete { + desc { + en: """Delete authenticator from listener authentication chain""" + zh: """删除监听器上指定的认证器""" + } + } + + listeners_listener_id_authentication_id_status_get { + desc { + en: """Get authenticator status from listener authentication chain""" + zh: """获取监听器上指定的认证器状态""" + } + } + + authentication_id_move_post { + desc { + en: """Move authenticator in global authentication chain""" + zh: """移动全局认证链上认证器的顺序""" + } + } + + listeners_listener_id_authentication_id_move_post { + desc { + en: """Move authenticator in listener authentication chain""" + zh: """移动监听器上认证器的顺序""" + } + } + + authentication_id_import_users_post { + desc { + en: """Import users into authenticator in global authentication chain""" + zh: """为全局认证链上的认证器导入用户数据""" + } + } + + listeners_listener_id_authentication_id_import_users_post { + desc { + en: """Import users into authenticator in listener authentication chain""" + zh: """为监听器上的认证器导入用户数据""" + } + } + + authentication_id_users_post { + desc { + en: """Create users for authenticator in global authentication chain""" + zh: """为全局认证链上的认证器创建用户数据""" + } + } + + authentication_id_users_get { + desc { + en: """List users in authenticator in global authentication chain""" + zh: """获取全局认证链上指定的认证器中的用户数据""" + } + } + + listeners_listener_id_authentication_id_users_post { + desc { + en: """Create users for authenticator in listener authentication chain""" + zh: """更新指定监听器上认证器中的用户数据""" + } + } + + listeners_listener_id_authentication_id_users_get { + desc { + en: """List users in authenticator in listener authentication chain""" + zh: """列出指定监听器上认证器中的用户数据""" + } + } + + authentication_id_users_user_id_get { + desc { + en: """Get user from authenticator in global authentication chain""" + zh: """获取指定监听器上认证器中指定的用户数据""" + } + } + + authentication_id_users_user_id_put { + desc { + en: """Update user in authenticator in global authentication chain""" + zh: """为指定监听器上认证器添加用户数据""" + } + } + + authentication_id_users_user_id_delete { + desc { + en: """Update user in authenticator in global authentication chain""" + zh: """更新指定监听器上认证器中的用户数据""" + } + } + + listeners_listener_id_authentication_id_users_user_id_get { + desc { + en: """Get user from authenticator in listener authentication chain""" + zh: """获取指定监听器上认证器中指定的用户数据""" + } + } + + listeners_listener_id_authentication_id_users_user_id_put { + desc { + en: """Update user in authenticator in listener authentication chain""" + zh: """更新指定监听器上认证器中的用户数据""" + } + } + + listeners_listener_id_authentication_id_users_user_id_delete { + desc { + en: """Delete user in authenticator in listener authentication chain""" + zh: """删除指定监听器上认证器中的用户数据""" + } + } + + param_auth_id { + desc { + en: """Authenticator ID""" + zh: """认证器 ID""" + } + } + + param_listener_id { + desc { + en: """Listener ID""" + zh: """监听器 ID""" + } + } + + + param_user_id { + desc { + en: """User ID""" + zh: """用户 ID""" + } + } + + like_username { + desc { + en: """Fuzzy search username""" + zh: """使用用户名模糊查询""" + } + label { + en: """like_username""" + zh: """模糊用户名""" + } + } + + like_clientid { + desc { + en: """Fuzzy search clientid""" + zh: """使用客户端标识符模糊查询""" + } + label { + en: """like_clientid""" + zh: """模糊用户名""" + } + } +} diff --git a/apps/emqx_authn/i18n/emqx_authn_http_i18n.conf b/apps/emqx_authn/i18n/emqx_authn_http_i18n.conf new file mode 100644 index 000000000..634d5433f --- /dev/null +++ b/apps/emqx_authn/i18n/emqx_authn_http_i18n.conf @@ -0,0 +1,89 @@ +emqx_authn_http { + get { + desc { + en: """Settings for HTTP-based authentication (GET).""" + zh: """基于 HTTP 的认证请求 (GET)""" + } + label: { + en: """get""" + zh: """get""" + } + } + + post { + desc { + en: """Settings for HTTP-based authentication (POST).""" + zh: """基于 HTTP 的认证请求 (POST)""" + } + label: { + en: """post""" + zh: """post""" + } + } + + method { + desc { + en: """HTTP method.""" + zh: """HTTP 请求方法""" + } + label: { + en: """method""" + zh: """请求方法""" + } + } + + url { + desc { + en: """URL of the auth server.""" + zh: """认证服务器地址""" + } + label: { + en: """url""" + zh: """统一资源定位符""" + } + } + + headers { + desc { + en: """List of HTTP headers.""" + zh: """HTTP 请求头列表""" + } + label: { + en: """headers""" + zh: """请求头""" + } + } + + headers_no_content_type { + desc { + en: """List of HTTP headers. (without content-type)""" + zh: """HTTP 请求头列表(无 content-type)""" + } + label: { + en: """headers_no_content_type""" + zh: """无content_type的请求头""" + } + } + + body { + desc { + en: """Body of the HTTP request.""" + zh: """HTTP 请求体""" + } + label: { + en: """body""" + zh: """请求体""" + } + } + + request_timeout { + desc { + en: """HTTP request timeout""" + zh: """HTTP 请求超时时长""" + } + label: { + en: """request_timeout""" + zh: """请求超时时间""" + } + } +} diff --git a/apps/emqx_authn/i18n/emqx_authn_i18n.conf b/apps/emqx_authn/i18n/emqx_authn_i18n.conf new file mode 100644 index 000000000..b2573357c --- /dev/null +++ b/apps/emqx_authn/i18n/emqx_authn_i18n.conf @@ -0,0 +1,155 @@ +emqx_authn_schema { + enable { + desc { + en: """Set to true or false to disable this auth provider""" + zh: """设为 truefalse 以启用或禁用此认证数据源""" + } + label: { + en: """enable""" + zh: """启用""" + } + } + + mechanism { + desc { + en: """Authentication mechanism.""" + zh: """认证机制""" + } + label: { + en: """mechanism""" + zh: """机制""" + } + } + + backend { + desc { + en: """Backend type.""" + zh: """后端类型""" + } + label: { + en: """backend""" + zh: """后端""" + } + } + + metrics { + desc { + en: """The metrics of the resource""" + zh: """统计指标""" + } + label: { + en: """metrics""" + zh: """指标""" + } + } + + node_metrics { + desc { + en: """The metrics of the resource for each node""" + zh: """每个节点上资源的统计指标""" + } + label: { + en: """node_metrics""" + zh: """节点指标""" + } + } + + status { + desc { + en: """The status of the resource""" + zh: """资源状态""" + } + label: { + en: """status""" + zh: """状态""" + } + } + + node_status { + desc { + en: """The status of the resource for each node""" + zh: """每个节点上资源的状态""" + } + label: { + en: """node_status""" + zh: """节点状态""" + } + } + + matched { + desc { + en: """Count of this resource is queried""" + zh: """请求命中次数""" + } + label: { + en: """matched""" + zh: """已命中""" + } + } + + success { + desc { + en: """Count of query success""" + zh: """请求成功次数""" + } + label: { + en: """success""" + zh: """成功""" + } + } + + failed { + desc { + en: """Count of query failed""" + zh: """请求失败次数""" + } + label: { + en: """failed""" + zh: """失败""" + } + } + + rate { + desc { + en: """The rate of matched, times/second""" + zh: """命中速率,单位: 次/秒""" + } + label: { + en: """rate""" + zh: """速率""" + } + } + + rate_max { + desc { + en: """The max rate of matched, times/second""" + zh: """最大命中速率,单位: 次/秒""" + } + label: { + en: """rate_max""" + zh: """最大速率""" + } + } + + rate_last5m { + desc { + en: """The average rate of matched in the last 5 minutes, times/second""" + zh: """5分钟内平均命中速率,单位: 次/秒""" + } + label: { + en: """rate_last5m""" + zh: """5分钟内速率""" + } + } + + node { + desc { + en: """The node name""" + zh: """节点名""" + } + label: { + en: """node""" + zh: """节点""" + } + } +} diff --git a/apps/emqx_authn/i18n/emqx_authn_jwt_i18n.conf b/apps/emqx_authn/i18n/emqx_authn_jwt_i18n.conf new file mode 100644 index 000000000..44a903f47 --- /dev/null +++ b/apps/emqx_authn/i18n/emqx_authn_jwt_i18n.conf @@ -0,0 +1,232 @@ +emqx_authn_jwt { + use_jwks { + desc { + en: """jwks flag""" + zh: """jwks 状态""" + } + label { + en: """use_jwks""" + zh: """使用jwks""" + } + } + + algorithm { + desc { + en: """Signing algorithm.""" + zh: """签名算法""" + } + label { + en: """algorithm""" + zh: """算法""" + } + } + + certificate { + desc { + en: """The certificate used for signing the token.""" + zh: """用于签名token的证书""" + } + label { + en: """certificate""" + zh: """证书""" + } + } + + secret_base64_encoded { + desc { + en: """Enable/disable base64 encoding of the secret.""" + zh: """启用/关闭私匙 base64 编码""" + } + label { + en: """secret_base64_encoded""" + zh: """密钥 base64 编码""" + } + } + + secret { + desc { + en: """The key to verify the JWT Token using HMAC algorithm.""" + zh: """使用对称加密的算法""" + } + label { + en: """secret""" + zh: """secret""" + } + } + + endpoint { + desc { + en: """JWKs endpoint""" + zh: """JWKs endpoint""" + } + label { + en: """endpoint""" + zh: """endpoint""" + } + } + + refresh_interval { + desc { + en: """JWKs refresh interval""" + zh: """JWKs 更新间隔""" + } + label { + en: """refresh_interval""" + zh: """更新间隔""" + } + } + + cacertfile { + desc { + en: """Path to the SSL CA certificate file.""" + zh: """SSL CA 证书公钥文件路径""" + } + label { + en: """cacertfile""" + zh: """CA 证书文件""" + } + } + + certfile { + desc { + en: """Path to the SSL certificate file.""" + zh: """证书文件路径""" + } + label { + en: """certfile""" + zh: """证书文件""" + } + } + + keyfile { + desc { + en: """Path to the SSL secret key file.""" + zh: """SSL 私钥文件路径""" + } + label { + en: """keyfile""" + zh: """私钥文件""" + } + } + + verify { + desc { + en: """Enable or disable SSL peer verification.""" + zh: """指定握手过程中是否校验客户端""" + } + label { + en: """verify""" + zh: """verify""" + } + } + + server_name_indication { + desc { + en: """SSL SNI (Server Name Indication)""" + zh: """SSL SNI (服务器名称指示)""" + } + label { + en: """server_name_indication""" + zh: """服务器名称指示""" + } + } + + verify_claims { + desc { + en: """The list of claims to verify.""" + zh: """The list of claims to verify.""" + } + label { + en: """verify_claims""" + zh: """verify_claims""" + } + } + + pool_size { + desc { + en: """JWKs connection count""" + zh: """JWKs 连接数量""" + } + label { + en: """pool_size""" + zh: """pool_size""" + } + } + + ssl { + desc { + en: """SSL options.""" + zh: """SSL 选项""" + } + label { + en: """ssl""" + zh: """ssl""" + } + } + + enable { + desc { + en: """Enable/disable SSL.""" + zh: """启用/禁用 SSL""" + } + label { + en: """enable""" + zh: """启用""" + } + } + + hmac-based { + desc { + en: """Settings for HMAC-based token signing algorithm.""" + zh: """HMAC-based token 签名配置""" + } + label { + en: """hmac-based""" + zh: """hmac-based""" + } + } + + public-key { + desc { + en: """Settings for public key-based token signing algorithm.""" + zh: """公钥token签名配置""" + } + label { + en: """public-key""" + zh: """public-key""" + } + } + + jwks { + desc { + en: """Settings for a signing using JSON Web Key Set (JWKs).""" + zh: """JWks 签名配置""" + } + label { + en: """jwks""" + zh: """jwks""" + } + } + + ssl_disable { + desc { + en: """SSL disabled""" + zh: """SSL 关闭""" + } + label { + en: """ssl_disable""" + zh: """关闭SSL""" + } + } + + ssl_enable { + desc { + en: """SSL configuration.""" + zh: """SSL 配置""" + } + label { + en: """ssl_enable""" + zh: """启用SSL""" + } + } +} diff --git a/apps/emqx_authn/i18n/emqx_authn_mnesia_i18n.conf b/apps/emqx_authn/i18n/emqx_authn_mnesia_i18n.conf new file mode 100644 index 000000000..a545acaa7 --- /dev/null +++ b/apps/emqx_authn/i18n/emqx_authn_mnesia_i18n.conf @@ -0,0 +1,23 @@ +emqx_authn_mnesia { + authentication { + desc { + en: """Configuration for authentication using the built-in database.""" + zh: """内置数据库认证配置""" + } + label: { + en: """authentication""" + zh: """认证配置""" + } + } + + user_id_type { + desc { + en: """Authenticate by Client ID or Username.""" + zh: """认证类型,基于 Client ID 或 Username""" + } + label: { + en: """user_id_type""" + zh: """用户标识类型""" + } + } +} diff --git a/apps/emqx_authn/i18n/emqx_authn_mongo_i18n.conf b/apps/emqx_authn/i18n/emqx_authn_mongo_i18n.conf new file mode 100644 index 000000000..740d842ec --- /dev/null +++ b/apps/emqx_authn/i18n/emqx_authn_mongo_i18n.conf @@ -0,0 +1,99 @@ +emqx_authn_mongodb { + standalone { + desc { + en: """Configuration for a standalone MongoDB instance.""" + zh: """MongoDB 单节点认证配置""" + } + label: { + en: """standalone""" + zh: """standalone 模式""" + } + } + + replica-set { + desc { + en: """Configuration for a replica set.""" + zh: """MongoDB replica set 模式认证配置""" + } + label: { + en: """replica-set""" + zh: """replica-set 模式""" + } + } + + sharded-cluster { + desc { + en: """Configuration for a sharded cluster.""" + zh: """MongoDB sharded cluster 模式认证配置""" + } + label: { + en: """sharded-cluster""" + zh: """sharded-cluster 模式""" + } + } + + collection { + desc { + en: """Collection used to store authentication data.""" + zh: """认证数据集名称""" + } + label: { + en: """collection""" + zh: """数据集""" + } + } + + selector { + desc { + en: """ +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 +""" + zh: """ +认证过程中所使用的查询命令。 +查询命令支持如下占位符: +- `${username}`: 代替客户端的用户名 +- `${clientid}`: 代替客户端的客户端标识符 +""" + } + label: { + en: """selector""" + zh: """认证查询""" + } + } + + password_hash_field { + desc { + en: """Document field that contains password hash.""" + zh: """数据文档中的密码散列值""" + } + label: { + en: """password_hash_field""" + zh: """密码散列字段""" + } + } + + salt_field { + desc { + en: """Document field that contains the password salt.""" + zh: """数据文档中的密码加盐""" + } + label: { + en: """salt_field""" + zh: """加盐字段""" + } + } + + is_superuser_field { + desc { + en: """Document field that defines if the user has superuser privileges.""" + zh: """数据文档中的超级用户权限记录""" + } + label: { + en: """is_superuser_field""" + zh: """超级用户字段""" + } + } +} diff --git a/apps/emqx_authn/i18n/emqx_authn_mysql_i18n.conf b/apps/emqx_authn/i18n/emqx_authn_mysql_i18n.conf new file mode 100644 index 000000000..1c931cd0e --- /dev/null +++ b/apps/emqx_authn/i18n/emqx_authn_mysql_i18n.conf @@ -0,0 +1,34 @@ +emqx_authn_mysql { + authentication { + desc { + en: """Configuration for authentication using MySQL database.""" + zh: """MySQL 认证配置""" + } + label: { + en: """authentication""" + zh: """认证配置""" + } + } + + query { + desc { + en: """SQL query used to lookup client data.""" + zh: """客户端数据查询 SQL 语句""" + } + label: { + en: """query""" + zh: """请求""" + } + } + + query_timeout { + dsec { + en: """Timeout for the SQL query.""" + zh: """SQL 查询超时时长""" + } + label: { + en: """query_timeout""" + zh: """请求超时""" + } + } +} diff --git a/apps/emqx_authn/i18n/emqx_authn_pgsql_i18n.conf b/apps/emqx_authn/i18n/emqx_authn_pgsql_i18n.conf new file mode 100644 index 000000000..4f936be1d --- /dev/null +++ b/apps/emqx_authn/i18n/emqx_authn_pgsql_i18n.conf @@ -0,0 +1,23 @@ +emqx_authn_pgsql { + authentication { + desc { + en: """Configuration for PostgreSQL authentication backend.""" + zh: """PostgreSQL 认证配置""" + } + label: { + en: """authentication""" + zh: """认证配置""" + } + } + + query { + desc { + en: """`SQL` query for looking up authentication data.""" + zh: """客户端数据查询 SQL 语句""" + } + label: { + en: """query""" + zh: """请求""" + } + } +} diff --git a/apps/emqx_authn/i18n/emqx_authn_redis_i18n.conf b/apps/emqx_authn/i18n/emqx_authn_redis_i18n.conf new file mode 100644 index 000000000..0ae12b959 --- /dev/null +++ b/apps/emqx_authn/i18n/emqx_authn_redis_i18n.conf @@ -0,0 +1,45 @@ +emqx_authn_redis { + standalone { + desc { + en: """Configuration for a standalone Redis instance.""" + zh: """Redis 单节点认证配置""" + } + label: { + en: """standalone""" + zh: """standalone""" + } + } + + cluster { + desc { + en: """Configuration for a Redis cluster.""" + zh: """Redis 集群模式认证配置""" + } + label: { + en: """cluster""" + zh: """cluster""" + } + } + + sentinel { + desc { + en: """Configuration for a Redis Sentinel.""" + zh: """Redis 哨兵模式认证配置""" + } + label: { + en: """sentinel""" + zh: """sentinel""" + } + } + + cmd { + desc { + en: """Database query used to retrieve authentication data.""" + zh: """Redis 认证数据查询语句""" + } + label: { + en: """cmd""" + zh: """查询命令""" + } + } +} diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index c785bf0f6..2e4c7a579 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -18,11 +18,11 @@ -behaviour(minirest_api). --include_lib("typerefl/include/types.hrl"). -include("emqx_authn.hrl"). --include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("emqx/include/logger.hrl"). +-include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("emqx/include/emqx_authentication.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). -import(hoconsc, [mk/2, ref/1, ref/2]). -import(emqx_dashboard_swagger, [error_codes/2]). @@ -154,7 +154,7 @@ schema("/authentication") -> 'operationId' => authenticators, get => #{ tags => ?API_TAGS_GLOBAL, - description => <<"List authenticators for global authentication">>, + description => ?DESC(authentication_get), responses => #{ 200 => emqx_dashboard_swagger:schema_with_example( hoconsc:array(emqx_authn_schema:authenticator_type()), @@ -164,7 +164,7 @@ schema("/authentication") -> }, post => #{ tags => ?API_TAGS_GLOBAL, - description => <<"Create authenticator for global authentication">>, + description => ?DESC(authentication_post), 'requestBody' => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), authenticator_examples() @@ -184,7 +184,7 @@ schema("/authentication/:id") -> 'operationId' => authenticator, get => #{ tags => ?API_TAGS_GLOBAL, - description => <<"Get authenticator from global authentication chain">>, + description => ?DESC(authentication_id_get), parameters => [param_auth_id()], responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( @@ -196,7 +196,7 @@ schema("/authentication/:id") -> }, put => #{ tags => ?API_TAGS_GLOBAL, - description => <<"Update authenticator from global authentication chain">>, + description => ?DESC(authentication_id_put), parameters => [param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), @@ -214,7 +214,7 @@ schema("/authentication/:id") -> }, delete => #{ tags => ?API_TAGS_GLOBAL, - description => <<"Delete authenticator from global authentication chain">>, + description => ?DESC(authentication_id_delete), parameters => [param_auth_id()], responses => #{ 204 => <<"Authenticator deleted">>, @@ -227,7 +227,7 @@ schema("/authentication/:id/status") -> 'operationId' => authenticator_status, get => #{ tags => ?API_TAGS_GLOBAL, - description => <<"Get authenticator status from global authentication chain">>, + description => ?DESC(authentication_id_status_get), parameters => [param_auth_id()], responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( @@ -243,7 +243,7 @@ schema("/listeners/:listener_id/authentication") -> 'operationId' => listener_authenticators, get => #{ tags => ?API_TAGS_SINGLE, - description => <<"List authenticators for listener authentication">>, + description => ?DESC(listeners_listener_id_authentication_get), parameters => [param_listener_id()], responses => #{ 200 => emqx_dashboard_swagger:schema_with_example( @@ -254,7 +254,7 @@ schema("/listeners/:listener_id/authentication") -> }, post => #{ tags => ?API_TAGS_SINGLE, - description => <<"Create authenticator for listener authentication">>, + description => ?DESC(listeners_listener_id_authentication_post), parameters => [param_listener_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), @@ -275,7 +275,7 @@ schema("/listeners/:listener_id/authentication/:id") -> 'operationId' => listener_authenticator, get => #{ tags => ?API_TAGS_SINGLE, - description => <<"Get authenticator from listener authentication chain">>, + description => ?DESC(listeners_listener_id_authentication_id_get), parameters => [param_listener_id(), param_auth_id()], responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( @@ -287,7 +287,7 @@ schema("/listeners/:listener_id/authentication/:id") -> }, put => #{ tags => ?API_TAGS_SINGLE, - description => <<"Update authenticator from listener authentication chain">>, + description => ?DESC(listeners_listener_id_authentication_id_put), parameters => [param_listener_id(), param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), @@ -305,7 +305,7 @@ schema("/listeners/:listener_id/authentication/:id") -> }, delete => #{ tags => ?API_TAGS_SINGLE, - description => <<"Delete authenticator from listener authentication chain">>, + description => ?DESC(listeners_listener_id_authentication_id_delete), parameters => [param_listener_id(), param_auth_id()], responses => #{ 204 => <<"Authenticator deleted">>, @@ -318,7 +318,7 @@ schema("/listeners/:listener_id/authentication/:id/status") -> 'operationId' => listener_authenticator_status, get => #{ tags => ?API_TAGS_SINGLE, - description => <<"Get authenticator status from listener authentication chain">>, + description => ?DESC(listeners_listener_id_authentication_id_status_get), parameters => [param_listener_id(), param_auth_id()], responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( @@ -334,7 +334,7 @@ schema("/authentication/:id/move") -> 'operationId' => authenticator_move, post => #{ tags => ?API_TAGS_GLOBAL, - description => <<"Move authenticator in global authentication chain">>, + description => ?DESC(authentication_id_move_post), parameters => [param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_move), @@ -352,7 +352,7 @@ schema("/listeners/:listener_id/authentication/:id/move") -> 'operationId' => listener_authenticator_move, post => #{ tags => ?API_TAGS_SINGLE, - description => <<"Move authenticator in listener authentication chain">>, + description => ?DESC(listeners_listener_id_authentication_id_move_post), parameters => [param_listener_id(), param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_move), @@ -370,7 +370,7 @@ schema("/authentication/:id/import_users") -> 'operationId' => authenticator_import_users, post => #{ tags => ?API_TAGS_GLOBAL, - description => <<"Import users into authenticator in global authentication chain">>, + description => ?DESC(authentication_id_import_users_post), parameters => [param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_import_users), @@ -388,7 +388,7 @@ schema("/listeners/:listener_id/authentication/:id/import_users") -> 'operationId' => listener_authenticator_import_users, post => #{ tags => ?API_TAGS_SINGLE, - description => <<"Import users into authenticator in listener authentication chain">>, + description => ?DESC(listeners_listener_id_authentication_id_import_users_post), parameters => [param_listener_id(), param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_import_users), @@ -406,7 +406,7 @@ schema("/authentication/:id/users") -> 'operationId' => authenticator_users, post => #{ tags => ?API_TAGS_GLOBAL, - description => <<"Create users for authenticator in global authentication chain">>, + description => ?DESC(authentication_id_users_post), parameters => [param_auth_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_user_create), @@ -423,7 +423,7 @@ schema("/authentication/:id/users") -> }, get => #{ tags => ?API_TAGS_GLOBAL, - description => <<"List users in authenticator in global authentication chain">>, + description => ?DESC(authentication_id_users_get), parameters => [ param_auth_id(), ref(emqx_dashboard_swagger, page), @@ -431,13 +431,13 @@ schema("/authentication/:id/users") -> {like_username, mk(binary(), #{ in => query, - desc => <<"Fuzzy search username">>, + desc => ?DESC(like_username), required => false })}, {like_clientid, mk(binary(), #{ in => query, - desc => <<"Fuzzy search clientid">>, + desc => ?DESC(like_clientid), required => false })} ], @@ -455,7 +455,7 @@ schema("/listeners/:listener_id/authentication/:id/users") -> 'operationId' => listener_authenticator_users, post => #{ tags => ?API_TAGS_SINGLE, - description => <<"Create users for authenticator in global authentication chain">>, + description => ?DESC(listeners_listener_id_authentication_id_users_post), parameters => [param_auth_id(), param_listener_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_user_create), @@ -472,7 +472,7 @@ schema("/listeners/:listener_id/authentication/:id/users") -> }, get => #{ tags => ?API_TAGS_SINGLE, - description => <<"List users in authenticator in listener authentication chain">>, + description => ?DESC(listeners_listener_id_authentication_id_users_get), parameters => [ param_listener_id(), param_auth_id(), @@ -493,7 +493,7 @@ schema("/authentication/:id/users/:user_id") -> 'operationId' => authenticator_user, get => #{ tags => ?API_TAGS_GLOBAL, - description => <<"Get user from authenticator in global authentication chain">>, + description => ?DESC(authentication_id_users_user_id_get), parameters => [param_auth_id(), param_user_id()], responses => #{ 200 => emqx_dashboard_swagger:schema_with_examples( @@ -505,7 +505,7 @@ schema("/authentication/:id/users/:user_id") -> }, put => #{ tags => ?API_TAGS_GLOBAL, - description => <<"Update user in authenticator in global authentication chain">>, + description => ?DESC(authentication_id_users_user_id_put), parameters => [param_auth_id(), param_user_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_examples( ref(request_user_update), @@ -522,7 +522,7 @@ schema("/authentication/:id/users/:user_id") -> }, delete => #{ tags => ?API_TAGS_GLOBAL, - description => <<"Update user in authenticator in global authentication chain">>, + description => ?DESC(authentication_id_users_user_id_delete), parameters => [param_auth_id(), param_user_id()], responses => #{ 204 => <<"User deleted">>, @@ -535,7 +535,7 @@ schema("/listeners/:listener_id/authentication/:id/users/:user_id") -> 'operationId' => listener_authenticator_user, get => #{ tags => ?API_TAGS_SINGLE, - description => <<"Get user from authenticator in listener authentication chain">>, + description => ?DESC(listeners_listener_id_authentication_id_users_user_id_get), parameters => [param_listener_id(), param_auth_id(), param_user_id()], responses => #{ 200 => emqx_dashboard_swagger:schema_with_example( @@ -547,7 +547,7 @@ schema("/listeners/:listener_id/authentication/:id/users/:user_id") -> }, put => #{ tags => ?API_TAGS_SINGLE, - description => <<"Update user in authenticator in listener authentication chain">>, + description => ?DESC(listeners_listener_id_authentication_id_users_user_id_put), parameters => [param_listener_id(), param_auth_id(), param_user_id()], 'requestBody' => emqx_dashboard_swagger:schema_with_example( ref(request_user_update), @@ -564,7 +564,7 @@ schema("/listeners/:listener_id/authentication/:id/users/:user_id") -> }, delete => #{ tags => ?API_TAGS_SINGLE, - description => <<"Update user in authenticator in listener authentication chain">>, + description => ?DESC(listeners_listener_id_authentication_id_users_user_id_delete), parameters => [param_listener_id(), param_auth_id(), param_user_id()], responses => #{ 204 => <<"User deleted">>, @@ -578,7 +578,7 @@ param_auth_id() -> id, mk(binary(), #{ in => path, - desc => <<"Authenticator ID">>, + desc => ?DESC(param_auth_id), required => true }) }. @@ -588,7 +588,7 @@ param_listener_id() -> listener_id, mk(binary(), #{ in => path, - desc => <<"Listener ID">>, + desc => ?DESC(param_listener_id), required => true, example => emqx_listeners:id_example() }) @@ -599,7 +599,7 @@ param_user_id() -> user_id, mk(binary(), #{ in => path, - desc => <<"User ID">> + desc => ?DESC(param_user_id) }) }. diff --git a/apps/emqx_authn/src/emqx_authn_schema.erl b/apps/emqx_authn/src/emqx_authn_schema.erl index 32994cbbe..ef20e36cf 100644 --- a/apps/emqx_authn/src/emqx_authn_schema.erl +++ b/apps/emqx_authn/src/emqx_authn_schema.erl @@ -17,7 +17,7 @@ -module(emqx_authn_schema). -elvis([{elvis_style, invalid_dynamic_call, disable}]). --include_lib("typerefl/include/types.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). -import(hoconsc, [mk/2, ref/2]). -export([ @@ -38,7 +38,7 @@ common_fields() -> enable(type) -> boolean(); enable(default) -> true; -enable(desc) -> "Set to false to disable this auth provider"; +enable(desc) -> ?DESC(?FUNCTION_NAME); enable(_) -> undefined. authenticator_type() -> @@ -74,7 +74,7 @@ mechanism(Name) -> hoconsc:enum([Name]), #{ required => true, - desc => "Authentication mechanism." + desc => ?DESC("mechanism") } ). @@ -83,51 +83,47 @@ backend(Name) -> hoconsc:enum([Name]), #{ required => true, - desc => "Backend type." + desc => ?DESC("backend") } ). fields("metrics_status_fields") -> [ - {"metrics", mk(ref(?MODULE, "metrics"), #{desc => "The metrics of the resource"})}, + {"metrics", mk(ref(?MODULE, "metrics"), #{desc => ?DESC("metrics")})}, {"node_metrics", mk( hoconsc:array(ref(?MODULE, "node_metrics")), - #{desc => "The metrics of the resource for each node"} + #{desc => ?DESC("node_metrics")} )}, - {"status", mk(status(), #{desc => "The status of the resource"})}, + {"status", mk(status(), #{desc => ?DESC("status")})}, {"node_status", mk( hoconsc:array(ref(?MODULE, "node_status")), - #{desc => "The status of the resource for each node"} + #{desc => ?DESC("node_status")} )} ]; fields("metrics") -> [ - {"matched", mk(integer(), #{desc => "Count of this resource is queried"})}, - {"success", mk(integer(), #{desc => "Count of query success"})}, - {"failed", mk(integer(), #{desc => "Count of query failed"})}, - {"rate", mk(float(), #{desc => "The rate of matched, times/second"})}, - {"rate_max", mk(float(), #{desc => "The max rate of matched, times/second"})}, - {"rate_last5m", - mk( - float(), - #{desc => "The average rate of matched in the last 5 minutes, times/second"} - )} + {"matched", mk(integer(), #{desc => ?DESC("matched")})}, + {"success", mk(integer(), #{desc => ?DESC("success")})}, + {"failed", mk(integer(), #{desc => ?DESC("failed")})}, + {"rate", mk(float(), #{desc => ?DESC("rate")})}, + {"rate_max", mk(float(), #{desc => ?DESC("rate_max")})}, + {"rate_last5m", mk(float(), #{desc => ?DESC("rate_last5m")})} ]; fields("node_metrics") -> [ node_name(), - {"metrics", mk(ref(?MODULE, "metrics"), #{})} + {"metrics", mk(ref(?MODULE, "metrics"), #{desc => ?DESC("metrics")})} ]; fields("node_status") -> [ node_name(), - {"status", mk(status(), #{desc => "Status of the node."})} + {"status", mk(status(), #{desc => ?DESC("node_status")})} ]. status() -> hoconsc:enum([connected, disconnected, connecting]). node_name() -> - {"node", mk(binary(), #{desc => "The node name", example => "emqx@127.0.0.1"})}. + {"node", mk(binary(), #{desc => ?DESC("node"), example => "emqx@127.0.0.1"})}. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl index d13f2c69f..b03a7f1b1 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_http.erl @@ -18,7 +18,7 @@ -include("emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("typerefl/include/types.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). -include_lib("emqx_connector/include/emqx_connector.hrl"). -behaviour(hocon_schema). @@ -62,19 +62,19 @@ roots() -> fields(get) -> [ - {method, #{type => get, required => true, default => post, desc => "HTTP method."}}, + {method, #{type => get, required => true, default => post, desc => ?DESC(method)}}, {headers, fun headers_no_content_type/1} ] ++ common_fields(); fields(post) -> [ - {method, #{type => post, required => true, default => post, desc => "HTTP method."}}, + {method, #{type => post, required => true, default => post, desc => ?DESC(method)}}, {headers, fun headers/1} ] ++ common_fields(). desc(get) -> - "Settings for HTTP-based authentication (GET)."; + ?DESC(get); desc(post) -> - "Settings for HTTP-based authentication (POST)."; + ?DESC(post); desc(_) -> undefined. @@ -83,8 +83,7 @@ common_fields() -> {mechanism, emqx_authn_schema:mechanism(password_based)}, {backend, emqx_authn_schema:backend(http)}, {url, fun url/1}, - {body, - hoconsc:mk(map([{fuzzy, term(), binary()}]), #{desc => "Body of the HTTP request."})}, + {body, hoconsc:mk(map([{fuzzy, term(), binary()}]), #{desc => ?DESC(body)})}, {request_timeout, fun request_timeout/1} ] ++ emqx_authn_schema:common_fields() ++ maps:to_list( @@ -104,7 +103,7 @@ validations() -> ]. 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. @@ -112,7 +111,7 @@ 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)) @@ -125,7 +124,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)) @@ -136,7 +135,7 @@ headers_no_content_type(_) -> undefined. request_timeout(type) -> emqx_schema:duration_ms(); -request_timeout(desc) -> "HTTP request timeout"; +request_timeout(desc) -> ?DESC(?FUNCTION_NAME); request_timeout(default) -> <<"5s">>; request_timeout(_) -> undefined. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl index b296e32a6..c5d84e98d 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl @@ -17,8 +17,8 @@ -module(emqx_authn_jwt). -include("emqx_authn.hrl"). --include_lib("typerefl/include/types.hrl"). -include_lib("emqx/include/logger.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). -behaviour(hocon_schema). -behaviour(emqx_authentication). @@ -55,22 +55,22 @@ roots() -> fields('hmac-based') -> [ - {use_jwks, sc(hoconsc:enum([false]), #{required => true, desc => ""})}, + {use_jwks, sc(hoconsc:enum([false]), #{required => true, desc => ?DESC(use_jwks)})}, {algorithm, - sc(hoconsc:enum(['hmac-based']), #{required => true, desc => "Signing algorithm."})}, + sc(hoconsc:enum(['hmac-based']), #{required => true, desc => ?DESC(algorithm)})}, {secret, fun secret/1}, {secret_base64_encoded, fun secret_base64_encoded/1} ] ++ common_fields(); fields('public-key') -> [ - {use_jwks, sc(hoconsc:enum([false]), #{required => true, desc => ""})}, + {use_jwks, sc(hoconsc:enum([false]), #{required => true, desc => ?DESC(use_jwks)})}, {algorithm, - sc(hoconsc:enum(['public-key']), #{required => true, desc => "Signing algorithm."})}, + sc(hoconsc:enum(['public-key']), #{required => true, desc => ?DESC(algorithm)})}, {certificate, fun certificate/1} ] ++ common_fields(); fields('jwks') -> [ - {use_jwks, sc(hoconsc:enum([true]), #{required => true, desc => ""})}, + {use_jwks, sc(hoconsc:enum([true]), #{required => true, desc => ?DESC(use_jwks)})}, {endpoint, fun endpoint/1}, {pool_size, fun pool_size/1}, {refresh_interval, fun refresh_interval/1}, @@ -79,14 +79,14 @@ fields('jwks') -> hoconsc:ref(?MODULE, ssl_enable), hoconsc:ref(?MODULE, ssl_disable) ]), - desc => "Enable/disable SSL.", + desc => ?DESC(ssl), default => #{<<"enable">> => false}, required => false }} ] ++ common_fields(); fields(ssl_enable) -> [ - {enable, #{type => true, desc => ""}}, + {enable, #{type => true, desc => ?DESC(enable)}}, {cacertfile, fun cacertfile/1}, {certfile, fun certfile/1}, {keyfile, fun keyfile/1}, @@ -94,18 +94,18 @@ fields(ssl_enable) -> {server_name_indication, fun server_name_indication/1} ]; fields(ssl_disable) -> - [{enable, #{type => false, desc => ""}}]. + [{enable, #{type => false, desc => ?DESC(enable)}}]. desc('hmac-based') -> - "Settings for HMAC-based token signing algorithm."; + ?DESC('hmac-based'); desc('public-key') -> - "Settings for public key-based token signing algorithm."; + ?DESC('public-key'); desc('jwks') -> - "Settings for a signing using JSON Web Key Set (JWKs)."; + ?DESC('jwks'); desc(ssl_disable) -> - ""; + ?DESC(ssl_disable); desc(ssl_enable) -> - "SSL configuration."; + ?DESC(ssl_enable); desc(_) -> undefined. @@ -116,56 +116,56 @@ common_fields() -> ] ++ emqx_authn_schema:common_fields(). secret(type) -> binary(); -secret(desc) -> "The key to verify the JWT Token using HMAC algorithm."; +secret(desc) -> ?DESC(?FUNCTION_NAME); secret(required) -> true; secret(_) -> undefined. secret_base64_encoded(type) -> boolean(); -secret_base64_encoded(desc) -> "Enable/disable base64 encoding of the secret."; +secret_base64_encoded(desc) -> ?DESC(?FUNCTION_NAME); secret_base64_encoded(default) -> false; secret_base64_encoded(_) -> undefined. certificate(type) -> string(); -certificate(desc) -> "The certificate used for signing the token."; +certificate(desc) -> ?DESC(?FUNCTION_NAME); certificate(required) -> ture; certificate(_) -> undefined. endpoint(type) -> string(); -endpoint(desc) -> "JWKs endpoint."; +endpoint(desc) -> ?DESC(?FUNCTION_NAME); endpoint(required) -> true; endpoint(_) -> undefined. refresh_interval(type) -> integer(); -refresh_interval(desc) -> "JWKs refresh interval"; +refresh_interval(desc) -> ?DESC(?FUNCTION_NAME); refresh_interval(default) -> 300; refresh_interval(validator) -> [fun(I) -> I > 0 end]; refresh_interval(_) -> undefined. cacertfile(type) -> string(); -cacertfile(desc) -> "Path to the SSL CA certificate file."; +cacertfile(desc) -> ?DESC(?FUNCTION_NAME); cacertfile(_) -> undefined. certfile(type) -> string(); -certfile(desc) -> "Path to the SSL certificate file."; +certfile(desc) -> ?DESC(?FUNCTION_NAME); certfile(_) -> undefined. keyfile(type) -> string(); -keyfile(desc) -> "Path to the SSL secret key file."; +keyfile(desc) -> ?DESC(?FUNCTION_NAME); keyfile(_) -> undefined. verify(type) -> hoconsc:enum([verify_peer, verify_none]); -verify(desc) -> "Enable or disable SSL peer verification."; +verify(desc) -> ?DESC(?FUNCTION_NAME); verify(default) -> verify_none; verify(_) -> undefined. server_name_indication(type) -> string(); -server_name_indication(desc) -> "SSL SNI (Server Name Indication)"; +server_name_indication(desc) -> ?DESC(?FUNCTION_NAME); server_name_indication(_) -> undefined. verify_claims(type) -> list(); verify_claims(desc) -> - "The list of claims to verify."; + ?DESC(?FUNCTION_NAME); verify_claims(default) -> #{}; verify_claims(validator) -> @@ -180,7 +180,7 @@ verify_claims(_) -> undefined. pool_size(type) -> integer(); -pool_size(desc) -> "JWKS connection count"; +pool_size(desc) -> ?DESC(?FUNCTION_NAME); pool_size(default) -> 8; pool_size(validator) -> [fun(I) -> I > 0 end]; pool_size(_) -> undefined. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl index 7971ce049..b3bb91ff7 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mnesia.erl @@ -18,7 +18,7 @@ -include("emqx_authn.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). --include_lib("typerefl/include/types.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). -behaviour(hocon_schema). -behaviour(emqx_authentication). @@ -110,12 +110,12 @@ fields(?CONF_NS) -> ] ++ emqx_authn_schema:common_fields(). desc(?CONF_NS) -> - "Configuration for authentication using the built-in database."; + ?DESC(?CONF_NS); desc(_) -> undefined. user_id_type(type) -> user_id_type(); -user_id_type(desc) -> "Authenticate by client ID or username."; +user_id_type(desc) -> ?DESC(?FUNCTION_NAME); user_id_type(default) -> <<"username">>; user_id_type(required) -> true; user_id_type(_) -> undefined. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl index f5ceaa577..2174406e0 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mongodb.erl @@ -18,7 +18,7 @@ -include("emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("typerefl/include/types.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). -behaviour(hocon_schema). -behaviour(emqx_authentication). @@ -61,17 +61,17 @@ fields('sharded-cluster') -> common_fields() ++ emqx_connector_mongo:fields(sharded). desc(standalone) -> - "Configuration for a standalone MongoDB instance."; + ?DESC(standalone); desc('replica-set') -> - "Configuration for a replica set."; + ?DESC('replica-set'); desc('sharded-cluster') -> - "Configuration for a sharded cluster."; + ?DESC('sharded-cluster'); desc(_) -> undefined. common_fields() -> [ - {mechanism, emqx_authn_schema:mechanism('password_based')}, + {mechanism, emqx_authn_schema:mechanism(password_based)}, {backend, emqx_authn_schema:backend(mongodb)}, {collection, fun collection/1}, {selector, fun selector/1}, @@ -82,32 +82,29 @@ common_fields() -> ] ++ emqx_authn_schema: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. password_hash_field(type) -> binary(); -password_hash_field(desc) -> "Document field that contains password hash."; +password_hash_field(desc) -> ?DESC(?FUNCTION_NAME); password_hash_field(required) -> false; password_hash_field(_) -> undefined. salt_field(type) -> binary(); -salt_field(desc) -> "Document field that contains the password salt."; +salt_field(desc) -> ?DESC(?FUNCTION_NAME); salt_field(required) -> false; salt_field(_) -> undefined. is_superuser_field(type) -> binary(); -is_superuser_field(desc) -> "Document field that defines if the user has superuser privileges."; +is_superuser_field(desc) -> ?DESC(?FUNCTION_NAME); is_superuser_field(required) -> false; is_superuser_field(_) -> undefined. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl index 9cbe0c174..11575149c 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_mysql.erl @@ -18,7 +18,7 @@ -include("emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("typerefl/include/types.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). -behaviour(hocon_schema). -behaviour(emqx_authentication). @@ -56,20 +56,20 @@ fields(?CONF_NS) -> {query, fun query/1}, {query_timeout, fun query_timeout/1} ] ++ emqx_authn_schema:common_fields() ++ - proplists:delete(prepare_statement, emqx_connector_mysql:fields(config)). + proplists:delete(prepare_statement, emqx_connector_mysql:fields(config)). desc(?CONF_NS) -> - "Configuration for authentication using MySQL database."; + ?DESC(?CONF_NS); desc(_) -> undefined. query(type) -> string(); -query(desc) -> "SQL query used to lookup client data."; +query(desc) -> ?DESC(?FUNCTION_NAME); query(required) -> true; query(_) -> undefined. query_timeout(type) -> emqx_schema:duration_ms(); -query_timeout(desc) -> "Timeout for the SQL query."; +query_timeout(desc) -> ?DESC(?FUNCTION_NAME); query_timeout(default) -> "5s"; query_timeout(_) -> undefined. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl index cb8340a4a..47eb57634 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_pgsql.erl @@ -19,7 +19,7 @@ -include("emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("epgsql/include/epgsql.hrl"). --include_lib("typerefl/include/types.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). -behaviour(hocon_schema). -behaviour(emqx_authentication). @@ -63,12 +63,12 @@ fields(?CONF_NS) -> proplists:delete(prepare_statement, emqx_connector_pgsql:fields(config)). desc(?CONF_NS) -> - "Configuration for PostgreSQL authentication backend."; + ?DESC(?CONF_NS); desc(_) -> undefined. query(type) -> string(); -query(desc) -> "`SQL` query for looking up authentication data."; +query(desc) -> ?DESC(?FUNCTION_NAME); query(required) -> true; query(_) -> undefined. diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl index b29f0acdf..37870448c 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_redis.erl @@ -18,7 +18,7 @@ -include("emqx_authn.hrl"). -include_lib("emqx/include/logger.hrl"). --include_lib("typerefl/include/types.hrl"). +-include_lib("hocon/include/hoconsc.hrl"). -behaviour(hocon_schema). -behaviour(emqx_authentication). @@ -61,24 +61,24 @@ fields(sentinel) -> common_fields() ++ emqx_connector_redis:fields(sentinel). desc(standalone) -> - "Configuration for a standalone Redis instance."; + ?DESC(standalone); desc(cluster) -> - "Configuration for a Redis cluster."; + ?DESC(cluster); desc(sentinel) -> - "Configuration for a Redis Sentinel."; + ?DESC(sentinel); desc(_) -> "". common_fields() -> [ - {mechanism, emqx_authn_schema:mechanism('password_based')}, + {mechanism, emqx_authn_schema:mechanism(password_based)}, {backend, emqx_authn_schema:backend(redis)}, {cmd, fun cmd/1}, {password_hash_algorithm, fun emqx_authn_password_hashing:type_ro/1} ] ++ emqx_authn_schema:common_fields(). cmd(type) -> string(); -cmd(desc) -> "Redis query."; +cmd(desc) -> ?DESC(?FUNCTION_NAME); cmd(required) -> true; cmd(_) -> undefined. diff --git a/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl b/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl index b6ae6d514..3d2dba895 100644 --- a/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_mysql_SUITE.erl @@ -198,10 +198,10 @@ t_update(_Config) -> >> }, - {ok, _} = emqx:update_config( - ?PATH, - {create_authenticator, ?GLOBAL, IncorrectConfig} - ), + {ok, _} = emqx:update_config( + ?PATH, + {create_authenticator, ?GLOBAL, IncorrectConfig} + ), {error, not_authorized} = emqx_access_control:authenticate( #{ 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);