Merge branch 'release-v43' into bump-minirest-0.3.10

This commit is contained in:
zhongwencool 2022-11-01 07:46:50 +08:00 committed by GitHub
commit 704bf3eade
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 561 additions and 120 deletions

View File

@ -39,7 +39,7 @@ jobs:
with: with:
# dialyzer PLTs # dialyzer PLTs
path: ~/.cache/rebar3/ path: ~/.cache/rebar3/
key: dialyer-${{ matrix.otp }} key: dialyzer-${{ matrix.otp }}
- name: make xref - name: make xref
run: make xref run: make xref
- name: make dialyzer - name: make dialyzer

View File

@ -46,13 +46,13 @@ check_acl(ClientInfo, PubSub, Topic, _AclResult, #{acl := ACLParams = #{path :=
{ok, 200, <<"ignore">>} -> ok; {ok, 200, <<"ignore">>} -> ok;
{ok, 200, _Body} -> {stop, allow}; {ok, 200, _Body} -> {stop, allow};
{ok, Code, _Body} -> {ok, Code, _Body} ->
?LOG(error, "Deny ~s to topic ~ts, username: ~ts, http response code: ~p", ?LOG(warning, "Deny ~s to topic ~ts, username: ~ts, http response code: ~p",
[PubSub, Topic, Username, Code]), [PubSub, Topic, Username, Code]),
{stop, deny}; {stop, deny};
{error, Error} -> {error, Error} ->
?LOG(error, "Deny ~s to topic ~ts, username: ~ts, due to request " ?LOG(warning, "Deny ~s to topic ~ts, username: ~ts, due to request "
"http server failure, path: ~p, error: ~0p", "http server failure, path: ~p, error: ~0p",
[PubSub, Topic, Username, Path, Error]), [PubSub, Topic, Username, Path, Error]),
ok ok
end. end.

View File

@ -47,15 +47,14 @@ check(ClientInfo, AuthResult, #{auth := AuthParms = #{path := Path},
anonymous => false, anonymous => false,
mountpoint => mountpoint(Body, ClientInfo)}}; mountpoint => mountpoint(Body, ClientInfo)}};
{ok, Code, _Body} -> {ok, Code, _Body} ->
?LOG(error, "Deny connection from path: ~s, username: ~ts, http " ?LOG(warning, "Deny connection from path: ~s, username: ~ts, http "
"response code: ~p", "response code: ~p",
[Path, Username, Code]), [Path, Username, Code]),
{stop, AuthResult#{auth_result => http_to_connack_error(Code), {stop, AuthResult#{auth_result => http_to_connack_error(Code),
anonymous => false}}; anonymous => false}};
{error, Error} -> {error, Error} ->
?LOG(error, "Deny connection from path: ~s, username: ~ts, due to " ?LOG_SENSITIVE(warning, "Deny connection from path: ~s, username: ~ts, due to "
"request http-server failed: ~0p", "request http-server failed: ~0p", [Path, Username, Error]),
[Path, Username, Error]),
%%FIXME later: server_unavailable is not right. %%FIXME later: server_unavailable is not right.
{stop, AuthResult#{auth_result => server_unavailable, {stop, AuthResult#{auth_result => server_unavailable,
anonymous => false}} anonymous => false}}
@ -89,10 +88,13 @@ is_superuser(SuperParams =
timeout := Timeout}, ClientInfo) -> timeout := Timeout}, ClientInfo) ->
Retry = maps:get(retry_times, SuperParams, ?DEFAULT_RETRY_TIMES), Retry = maps:get(retry_times, SuperParams, ?DEFAULT_RETRY_TIMES),
case request(PoolName, Method, Path, Headers, feedvar(Params, ClientInfo), Timeout, Retry) of case request(PoolName, Method, Path, Headers, feedvar(Params, ClientInfo), Timeout, Retry) of
{ok, 200, _Body} -> true; {ok, 200, _Body} ->
{ok, _Code, _Body} -> false; true;
{error, Error} -> ?LOG(error, "Request superuser path ~s, error: ~p", [Path, Error]), {ok, _Code, _Body} ->
false false;
{error, Error} ->
?LOG_SENSITIVE(warning, "Request superuser path ~s, error: ~p", [Path, Error]),
false
end. end.
mountpoint(Body, #{mountpoint := Mountpoint}) -> mountpoint(Body, #{mountpoint := Mountpoint}) ->

View File

@ -1,13 +1,17 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
%% Unless you know what you are doing, DO NOT edit manually!! %% Unless you know what you are doing, DO NOT edit manually!!
{VSN, {VSN,
[{"4.3.7",[{load_module,emqx_auth_jwt,brutal_purge,soft_purge,[]}]}, [{"4.3.7",
[{load_module,emqx_auth_jwt_svr,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_jwt,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[3-6]">>, {<<"4\\.3\\.[3-6]">>,
[{load_module,emqx_auth_jwt,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_jwt,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_jwt_svr,brutal_purge,soft_purge,[]}]}, {load_module,emqx_auth_jwt_svr,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[0-2]">>,[{restart_application,emqx_auth_jwt}]}, {<<"4\\.3\\.[0-2]">>,[{restart_application,emqx_auth_jwt}]},
{<<".*">>,[]}], {<<".*">>,[]}],
[{"4.3.7",[{load_module,emqx_auth_jwt,brutal_purge,soft_purge,[]}]}, [{"4.3.7",
[{load_module,emqx_auth_jwt_svr,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_jwt,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[3-6]">>, {<<"4\\.3\\.[3-6]">>,
[{load_module,emqx_auth_jwt,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_jwt,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_jwt_svr,brutal_purge,soft_purge,[]}]}, {load_module,emqx_auth_jwt_svr,brutal_purge,soft_purge,[]}]},

View File

@ -27,7 +27,7 @@
%% APIs %% APIs
-export([start_link/1]). -export([start_link/1]).
-export([verify/1]). -export([verify/1, trace/2]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([ init/1
@ -143,7 +143,8 @@ request_jwks(Addr) ->
?tp(debug, emqx_auth_jwt_svr_jwks_updated, #{jwks => Jwks, pid => self()}), ?tp(debug, emqx_auth_jwt_svr_jwks_updated, #{jwks => Jwks, pid => self()}),
Jwks Jwks
catch _:_ -> catch _:_ ->
?LOG(error, "Invalid jwks server response: ~p~n", [Body]), ?MODULE:trace(jwks_server_reesponse, Body),
?LOG(error, "Invalid jwks server response, body is not logged for security reasons, trace it if inspection is required", []),
error(badarg) error(badarg)
end end
end. end.
@ -174,7 +175,7 @@ do_verify(JwsCompacted) ->
end end
catch catch
Class : Reason : Stk -> Class : Reason : Stk ->
?LOG(error, "verify JWK crashed: ~p, ~p, stacktrace: ~p~n", ?LOG_SENSITIVE(error, "verify JWK crashed: ~p, ~p, stacktrace: ~p~n",
[Class, Reason, Stk]), [Class, Reason, Stk]),
{error, invalid_signature} {error, invalid_signature}
end. end.
@ -249,13 +250,15 @@ key2jwt_value(Key, Func, Options) ->
V -> V ->
try Func(V) of try Func(V) of
{error, Reason} -> {error, Reason} ->
?LOG(warning, "Build ~p JWK ~p failed: {error, ~p}~n", ?LOG_SENSITIVE(warning, "Build ~p JWK ~p failed: {error, ~p}~n",
[Key, V, Reason]), [Key, V, Reason]),
undefined; undefined;
J -> J J -> J
catch T:R -> catch T:R ->
?LOG(warning, "Build ~p JWK ~p failed: {~p, ~p}~n", ?LOG_SENSITIVE(warning, "Build ~p JWK ~p failed: {~p, ~p}~n",
[Key, V, T, R]), [Key, V, T, R]),
undefined undefined
end end
end. end.
trace(_Tag, _Data) -> ok.

View File

@ -1,6 +1,6 @@
{application, emqx_auth_ldap, {application, emqx_auth_ldap,
[{description, "EMQ X Authentication/ACL with LDAP"}, [{description, "EMQ X Authentication/ACL with LDAP"},
{vsn, "4.3.5"}, % strict semver, bump manually! {vsn, "4.3.6"}, % strict semver, bump manually!
{modules, []}, {modules, []},
{registered, [emqx_auth_ldap_sup]}, {registered, [emqx_auth_ldap_sup]},
{applications, [kernel,stdlib,eldap2,ecpool]}, {applications, [kernel,stdlib,eldap2,ecpool]},

View File

@ -1,11 +1,16 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
%% Unless you know what you are doing, DO NOT edit manually!! %% Unless you know what you are doing, DO NOT edit manually!!
{VSN, {VSN,
[{<<"4\\.3\\.[3-4]">>, [{"4.3.5",
[{load_module,emqx_auth_ldap_app,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_ldap,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_ldap_cli,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[3-4]">>,
[{load_module,emqx_auth_ldap_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_ldap_app,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_ldap,brutal_purge,soft_purge,[]}]}, {load_module,emqx_auth_ldap,brutal_purge,soft_purge,[]}]},
{"4.3.2", {"4.3.2",
[{load_module,emqx_auth_ldap_app,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_ldap_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_ldap_app,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_ldap,brutal_purge,soft_purge,[]}, {load_module,emqx_auth_ldap,brutal_purge,soft_purge,[]},
{load_module,emqx_acl_ldap,brutal_purge,soft_purge,[]}]}, {load_module,emqx_acl_ldap,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[0-1]">>, {<<"4\\.3\\.[0-1]">>,
@ -14,11 +19,16 @@
{load_module,emqx_acl_ldap,brutal_purge,soft_purge,[]}, {load_module,emqx_acl_ldap,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_ldap_cli,brutal_purge,soft_purge,[]}]}, {load_module,emqx_auth_ldap_cli,brutal_purge,soft_purge,[]}]},
{<<".*">>,[]}], {<<".*">>,[]}],
[{<<"4\\.3\\.[3-4]">>, [{"4.3.5",
[{load_module,emqx_auth_ldap_app,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_ldap,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_ldap_cli,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[3-4]">>,
[{load_module,emqx_auth_ldap_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_ldap_app,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_ldap,brutal_purge,soft_purge,[]}]}, {load_module,emqx_auth_ldap,brutal_purge,soft_purge,[]}]},
{"4.3.2", {"4.3.2",
[{load_module,emqx_auth_ldap_app,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_ldap_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_ldap_app,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_ldap,brutal_purge,soft_purge,[]}, {load_module,emqx_auth_ldap,brutal_purge,soft_purge,[]},
{load_module,emqx_acl_ldap,brutal_purge,soft_purge,[]}]}, {load_module,emqx_acl_ldap,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[0-1]">>, {<<"4\\.3\\.[0-1]">>,

View File

@ -62,7 +62,7 @@ check(ClientInfo = #{username := Username, password := Password}, AuthResult,
{error, not_found} -> {error, not_found} ->
ok; ok;
{error, ResultCode} -> {error, ResultCode} ->
?LOG(error, "[LDAP] Auth from ldap failed: ~p", [ResultCode]), ?LOG_SENSITIVE(error, "[LDAP] Auth from ldap failed: ~p", [ResultCode]),
{stop, AuthResult#{auth_result => ResultCode, anonymous => false}} {stop, AuthResult#{auth_result => ResultCode, anonymous => false}}
end. end.

View File

@ -54,22 +54,22 @@ connect(Opts) ->
false -> false ->
[{port, Port}, {timeout, Timeout}] [{port, Port}, {timeout, Timeout}]
end, end,
?LOG(debug, "[LDAP] Connecting to OpenLDAP server: ~p, Opts:~p ...", [Servers, LdapOpts]), ?LOG_SENSITIVE(debug, "[LDAP] Connecting to OpenLDAP server: ~p, Opts:~p ...", [Servers, LdapOpts]),
case eldap2:open(Servers, LdapOpts) of case eldap2:open(Servers, LdapOpts) of
{ok, LDAP} -> {ok, LDAP} ->
try eldap2:simple_bind(LDAP, BindDn, BindPassword) of try eldap2:simple_bind(LDAP, BindDn, BindPassword) of
ok -> {ok, LDAP}; ok -> {ok, LDAP};
{error, Error} -> {error, Error} ->
?LOG(error, "[LDAP] Can't authenticated to OpenLDAP server: ~p", [Error]), ?LOG_SENSITIVE(error, "[LDAP] Can't authenticated to OpenLDAP server: ~p", [Error]),
{error, Error} {error, Error}
catch catch
error:Reason -> error:Reason ->
?LOG(error, "[LDAP] Can't authenticated to OpenLDAP server: ~p", [Reason]), ?LOG_SENSITIVE(error, "[LDAP] Can't authenticated to OpenLDAP server: ~p", [Reason]),
{error, Reason} {error, Reason}
end; end;
{error, Reason} -> {error, Reason} ->
?LOG(error, "[LDAP] Can't connect to OpenLDAP server: ~p", [Reason]), ?LOG_SENSITIVE(error, "[LDAP] Can't connect to OpenLDAP server: ~p", [Reason]),
{error, Reason} {error, Reason}
end. end.
@ -147,4 +147,3 @@ init_args(ENVS) ->
match_objectclass => ObjectClass, match_objectclass => ObjectClass,
username_attr => UidAttr, username_attr => UidAttr,
password_attr => PasswdAttr}}. password_attr => PasswdAttr}}.

View File

@ -122,8 +122,8 @@ cli(_) ->
, {"acl list ", "List all acls"} , {"acl list ", "List all acls"}
, {"acl show clientid <Clientid>", "Lookup clientid acl detail"} , {"acl show clientid <Clientid>", "Lookup clientid acl detail"}
, {"acl show username <Username>", "Lookup username acl detail"} , {"acl show username <Username>", "Lookup username acl detail"}
, {"acl aad clientid <Clientid> <Topic> <Action> <Access>", "Add clientid acl"} , {"acl add clientid <Clientid> <Topic> <Action> <Access>", "Add clientid acl"}
, {"acl add Username <Username> <Topic> <Action> <Access>", "Add username acl"} , {"acl add username <Username> <Topic> <Action> <Access>", "Add username acl"}
, {"acl add _all <Topic> <Action> <Access>", "Add $all acl"} , {"acl add _all <Topic> <Action> <Access>", "Add $all acl"}
, {"acl delete clientid <Clientid> <Topic>", "Delete clientid acl"} , {"acl delete clientid <Clientid> <Topic>", "Delete clientid acl"}
, {"acl delete username <Username> <Topic>", "Delete username acl"} , {"acl delete username <Username> <Topic>", "Delete username acl"}

View File

@ -1,6 +1,6 @@
{application, emqx_auth_mnesia, {application, emqx_auth_mnesia,
[{description, "EMQ X Authentication with Mnesia"}, [{description, "EMQ X Authentication with Mnesia"},
{vsn, "4.3.9"}, % strict semver, bump manually {vsn, "4.3.10"}, % strict semver, bump manually
{modules, []}, {modules, []},
{registered, []}, {registered, []},
{applications, [kernel,stdlib,mnesia]}, {applications, [kernel,stdlib,mnesia]},

View File

@ -1,7 +1,9 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
%% Unless you know what you are doing, DO NOT edit manually!! %% Unless you know what you are doing, DO NOT edit manually!!
{VSN, {VSN,
[{"4.3.7", [{"4.3.9",[{load_module,emqx_acl_mnesia_cli,brutal_purge,soft_purge,[]}]},
{"4.3.8",[{load_module,emqx_acl_mnesia_cli,brutal_purge,soft_purge,[]}]},
{"4.3.7",
[{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]},
{load_module,emqx_acl_mnesia_cli,brutal_purge,soft_purge,[]}]}, {load_module,emqx_acl_mnesia_cli,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[5-6]">>, {<<"4\\.3\\.[5-6]">>,
@ -33,7 +35,9 @@
{load_module,emqx_acl_mnesia_cli,brutal_purge,soft_purge,[]}, {load_module,emqx_acl_mnesia_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_mnesia_app,brutal_purge,soft_purge,[]}]}, {load_module,emqx_auth_mnesia_app,brutal_purge,soft_purge,[]}]},
{<<".*">>,[]}], {<<".*">>,[]}],
[{"4.3.7", [{"4.3.9",[{load_module,emqx_acl_mnesia_cli,brutal_purge,soft_purge,[]}]},
{"4.3.8",[{load_module,emqx_acl_mnesia_cli,brutal_purge,soft_purge,[]}]},
{"4.3.7",
[{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]},
{load_module,emqx_acl_mnesia_cli,brutal_purge,soft_purge,[]}]}, {load_module,emqx_acl_mnesia_cli,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[5-6]">>, {<<"4\\.3\\.[5-6]">>,

View File

@ -55,7 +55,7 @@ check(ClientInfo = #{password := Password}, AuthResult,
undefined -> ok; undefined -> ok;
{error, Reason} -> {error, Reason} ->
?tp(emqx_auth_mongo_check_authn_error, #{error => Reason}), ?tp(emqx_auth_mongo_check_authn_error, #{error => Reason}),
?LOG(error, "[MongoDB] Can't connect to MongoDB server: ~0p", [Reason]), ?LOG_SENSITIVE(error, "[MongoDB] Can't connect to MongoDB server: ~0p", [Reason]),
{stop, AuthResult#{auth_result => not_authorized, anonymous => false}}; {stop, AuthResult#{auth_result => not_authorized, anonymous => false}};
UserMap -> UserMap ->
Result = case [maps:get(Field, UserMap, undefined) || Field <- Fields] of Result = case [maps:get(Field, UserMap, undefined) || Field <- Fields] of
@ -72,7 +72,7 @@ check(ClientInfo = #{password := Password}, AuthResult,
anonymous => false, anonymous => false,
auth_result => success}}; auth_result => success}};
{error, Error} -> {error, Error} ->
?LOG(error, "[MongoDB] check auth fail: ~p", [Error]), ?LOG_SENSITIVE(error, "[MongoDB] check auth fail: ~p", [Error]),
{stop, AuthResult#{auth_result => Error, anonymous => false}} {stop, AuthResult#{auth_result => Error, anonymous => false}}
end end
end. end.
@ -99,7 +99,7 @@ is_superuser(Pool, #superquery{collection = Coll, field = Field, selector = Sele
false; false;
{error, Reason} -> {error, Reason} ->
?tp(emqx_auth_mongo_superuser_query_error, #{error => Reason}), ?tp(emqx_auth_mongo_superuser_query_error, #{error => Reason}),
?LOG(error, "[MongoDB] Can't connect to MongoDB server: ~0p", [Reason]), ?LOG_SENSITIVE(error, "[MongoDB] Can't connect to MongoDB server: ~0p", [Reason]),
false; false;
Row -> Row ->
case maps:get(Field, Row, false) of case maps:get(Field, Row, false) of

View File

@ -1,6 +1,6 @@
{application, emqx_auth_mysql, {application, emqx_auth_mysql,
[{description, "EMQ X Authentication/ACL with MySQL"}, [{description, "EMQ X Authentication/ACL with MySQL"},
{vsn, "4.3.3"}, % strict semver, bump manually! {vsn, "4.3.4"}, % strict semver, bump manually!
{modules, []}, {modules, []},
{registered, [emqx_auth_mysql_sup]}, {registered, [emqx_auth_mysql_sup]},
{applications, [kernel,stdlib,mysql,ecpool]}, {applications, [kernel,stdlib,mysql,ecpool]},

View File

@ -1,18 +1,28 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
{VSN, {VSN,
[{<<"4\\.3\\.[1-2]">>, [{"4.3.3",
[{load_module,emqx_auth_mysql_app,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_mysql,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_mysql_cli,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[1-2]">>,
[{load_module,emqx_auth_mysql_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_mysql_app,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_mysql,brutal_purge,soft_purge,[]}]}, {load_module,emqx_auth_mysql,brutal_purge,soft_purge,[]}]},
{"4.3.0", {"4.3.0",
[{load_module,emqx_auth_mysql_app,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_mysql_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_mysql_app,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_mysql,brutal_purge,soft_purge,[]}, {load_module,emqx_auth_mysql,brutal_purge,soft_purge,[]},
{load_module,emqx_acl_mysql,brutal_purge,soft_purge,[]}]}, {load_module,emqx_acl_mysql,brutal_purge,soft_purge,[]}]},
{<<".*">>,[]}], {<<".*">>,[]}],
[{<<"4\\.3\\.[1-2]">>, [{"4.3.3",
[{load_module,emqx_auth_mysql_app,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_mysql,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_mysql_cli,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[1-2]">>,
[{load_module,emqx_auth_mysql_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_mysql_app,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_mysql,brutal_purge,soft_purge,[]}]}, {load_module,emqx_auth_mysql,brutal_purge,soft_purge,[]}]},
{"4.3.0", {"4.3.0",
[{load_module,emqx_auth_mysql_app,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_mysql_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_mysql_app,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_mysql,brutal_purge,soft_purge,[]}, {load_module,emqx_auth_mysql,brutal_purge,soft_purge,[]},
{load_module,emqx_acl_mysql,brutal_purge,soft_purge,[]}]}, {load_module,emqx_acl_mysql,brutal_purge,soft_purge,[]}]},
{<<".*">>,[]}] {<<".*">>,[]}]

View File

@ -41,7 +41,7 @@ check(ClientInfo = #{password := Password}, AuthResult,
{ok, _Columns, []} -> {ok, _Columns, []} ->
{error, not_found}; {error, not_found};
{error, Reason} -> {error, Reason} ->
?LOG(error, "[MySQL] query '~p' failed: ~p", [AuthSql, Reason]), ?LOG_SENSITIVE(error, "[MySQL] query '~p' failed: ~p", [AuthSql, Reason]),
{error, Reason} {error, Reason}
end, end,
case CheckPass of case CheckPass of
@ -52,7 +52,7 @@ check(ClientInfo = #{password := Password}, AuthResult,
{error, not_found} -> {error, not_found} ->
ok; ok;
{error, ResultCode} -> {error, ResultCode} ->
?LOG(error, "[MySQL] Auth from mysql failed: ~p", [ResultCode]), ?LOG_SENSITIVE(error, "[MySQL] Auth from mysql failed: ~p", [ResultCode]),
{stop, AuthResult#{auth_result => ResultCode, anonymous => false}} {stop, AuthResult#{auth_result => ResultCode, anonymous => false}}
end. end.

View File

@ -54,10 +54,10 @@ connect(Options) ->
?LOG(error, "[MySQL] Can't connect to MySQL server: Connection refused."), ?LOG(error, "[MySQL] Can't connect to MySQL server: Connection refused."),
{error, Reason}; {error, Reason};
{error, Reason = {ErrorCode, _, Error}} -> {error, Reason = {ErrorCode, _, Error}} ->
?LOG(error, "[MySQL] Can't connect to MySQL server: ~p - ~p", [ErrorCode, Error]), ?LOG_SENSITIVE(error, "[MySQL] Can't connect to MySQL server: ~p - ~p", [ErrorCode, Error]),
{error, Reason}; {error, Reason};
{error, Reason} -> {error, Reason} ->
?LOG(error, "[MySQL] Can't connect to MySQL server: ~p", [Reason]), ?LOG_SENSITIVE(error, "[MySQL] Can't connect to MySQL server: ~p", [Reason]),
{error, Reason} {error, Reason}
end. end.

View File

@ -1,6 +1,6 @@
{application, emqx_auth_pgsql, {application, emqx_auth_pgsql,
[{description, "EMQ X Authentication/ACL with PostgreSQL"}, [{description, "EMQ X Authentication/ACL with PostgreSQL"},
{vsn, "4.3.3"}, % strict semver, bump manually! {vsn, "4.3.4"}, % strict semver, bump manually!
{modules, []}, {modules, []},
{registered, [emqx_auth_pgsql_sup]}, {registered, [emqx_auth_pgsql_sup]},
{applications, [kernel,stdlib,epgsql,ecpool]}, {applications, [kernel,stdlib,epgsql,ecpool]},

View File

@ -1,11 +1,17 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
%% Unless you know what you are doing, DO NOT edit manually!! %% Unless you know what you are doing, DO NOT edit manually!!
{VSN, {VSN,
[{<<"4\\.3\\.[0-2]">>, [{"4.3.3",
[{load_module,emqx_auth_pgsql,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_pgsql_cli,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[0-2]">>,
%% restart it due to epgsql upgraded from 4.4.0 to 4.6.0 %% restart it due to epgsql upgraded from 4.4.0 to 4.6.0
%% in emqx_auth_pgsql:v4.3.3 %% in emqx_auth_pgsql:v4.3.3
[{restart_application,emqx_auth_pgsql}]}, [{restart_application,emqx_auth_pgsql}]},
{<<".*">>,[]}], {<<".*">>,[]}],
[{<<"4\\.3\\.[0-2]">>, [{"4.3.3",
[{load_module,emqx_auth_pgsql,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_pgsql_cli,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[0-2]">>,
[{restart_application,emqx_auth_pgsql}]}, [{restart_application,emqx_auth_pgsql}]},
{<<".*">>,[]}]}. {<<".*">>,[]}]}.

View File

@ -40,7 +40,7 @@ check(ClientInfo = #{password := Password}, AuthResult,
{ok, _, []} -> {ok, _, []} ->
{error, not_found}; {error, not_found};
{error, Reason} -> {error, Reason} ->
?LOG(error, "[Postgres] query '~p' failed: ~p", [AuthSql, Reason]), ?LOG_SENSITIVE(error, "[Postgres] query '~p' failed: ~p", [AuthSql, Reason]),
{error, not_found} {error, not_found}
end, end,
case CheckPass of case CheckPass of
@ -51,7 +51,7 @@ check(ClientInfo = #{password := Password}, AuthResult,
{error, not_found} -> {error, not_found} ->
ok; ok;
{error, ResultCode} -> {error, ResultCode} ->
?LOG(error, "[Postgres] Auth from pgsql failed: ~p", [ResultCode]), ?LOG_SENSITIVE(error, "[Postgres] Auth from pgsql failed: ~p", [ResultCode]),
{stop, AuthResult#{auth_result => ResultCode, anonymous => false}} {stop, AuthResult#{auth_result => ResultCode, anonymous => false}}
end. end.

View File

@ -82,7 +82,7 @@ connect(Opts) ->
?LOG(error, "[Postgres] Can't connect to Postgres server: Invalid password."), ?LOG(error, "[Postgres] Can't connect to Postgres server: Invalid password."),
{error, Reason}; {error, Reason};
{error, Reason} -> {error, Reason} ->
?LOG(error, "[Postgres] Can't connect to Postgres server: ~p", [Reason]), ?LOG_SENSITIVE(error, "[Postgres] Can't connect to Postgres server: ~p", [Reason]),
{error, Reason} {error, Reason}
end. end.

View File

@ -1,6 +1,6 @@
{application, emqx_auth_redis, {application, emqx_auth_redis,
[{description, "EMQ X Authentication/ACL with Redis"}, [{description, "EMQ X Authentication/ACL with Redis"},
{vsn, "4.3.3"}, % strict semver, bump manually! {vsn, "4.3.4"}, % strict semver, bump manually!
{modules, []}, {modules, []},
{registered, [emqx_auth_redis_sup]}, {registered, [emqx_auth_redis_sup]},
{applications, [kernel,stdlib,eredis,eredis_cluster,ecpool]}, {applications, [kernel,stdlib,eredis,eredis_cluster,ecpool]},

View File

@ -1,19 +1,29 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
%% Unless you know what you are doing, DO NOT edit manually!!
{VSN, {VSN,
[{<<"4\\.3\\.[1-2]">>, [{"4.3.3",
[{load_module,emqx_auth_redis_app,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_redis,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_redis_cli,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[1-2]">>,
[{load_module,emqx_auth_redis_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_redis_app,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_redis,brutal_purge,soft_purge,[]}]}, {load_module,emqx_auth_redis,brutal_purge,soft_purge,[]}]},
{"4.3.0", {"4.3.0",
[{load_module,emqx_auth_redis_app,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_redis_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_redis_app,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_redis,brutal_purge,soft_purge,[]}, {load_module,emqx_auth_redis,brutal_purge,soft_purge,[]},
{load_module,emqx_acl_redis,brutal_purge,soft_purge,[]}]}, {load_module,emqx_acl_redis,brutal_purge,soft_purge,[]}]},
{<<".*">>,[]}], {<<".*">>,[]}],
[{<<"4\\.3\\.[1-2]">>, [{"4.3.3",
[{load_module,emqx_auth_redis_app,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_redis,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_redis_cli,brutal_purge,soft_purge,[]}]},
{<<"4\\.3\\.[1-2]">>,
[{load_module,emqx_auth_redis_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_redis_app,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_redis,brutal_purge,soft_purge,[]}]}, {load_module,emqx_auth_redis,brutal_purge,soft_purge,[]}]},
{"4.3.0", {"4.3.0",
[{load_module,emqx_auth_redis_app,brutal_purge,soft_purge,[]}, [{load_module,emqx_auth_redis_cli,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_redis_app,brutal_purge,soft_purge,[]},
{load_module,emqx_auth_redis,brutal_purge,soft_purge,[]}, {load_module,emqx_auth_redis,brutal_purge,soft_purge,[]},
{load_module,emqx_acl_redis,brutal_purge,soft_purge,[]}]}, {load_module,emqx_acl_redis,brutal_purge,soft_purge,[]}]},
{<<".*">>,[]}] {<<".*">>,[]}]}.
}.

View File

@ -42,7 +42,7 @@ check(ClientInfo = #{password := Password}, AuthResult,
{ok, [PassHash, Salt|_]} -> {ok, [PassHash, Salt|_]} ->
check_pass({PassHash, Salt, Password}, HashType); check_pass({PassHash, Salt, Password}, HashType);
{error, Reason} -> {error, Reason} ->
?LOG(error, "[Redis] Command: ~p failed: ~p", [AuthCmd, Reason]), ?LOG_SENSITIVE(error, "[Redis] Command: ~p failed: ~p", [AuthCmd, Reason]),
{error, not_found} {error, not_found}
end, end,
case CheckPass of case CheckPass of
@ -54,7 +54,7 @@ check(ClientInfo = #{password := Password}, AuthResult,
{error, not_found} -> {error, not_found} ->
ok; ok;
{error, ResultCode} -> {error, ResultCode} ->
?LOG(error, "[Redis] Auth from redis failed: ~p", [ResultCode]), ?LOG_SENSITIVE(error, "[Redis] Auth from redis failed: ~p", [ResultCode]),
{stop, AuthResult#{auth_result => ResultCode, anonymous => false}} {stop, AuthResult#{auth_result => ResultCode, anonymous => false}}
end. end.

View File

@ -56,7 +56,7 @@ connect(Opts) ->
?LOG(error, "[Redis] Can't connect to Redis server: Authentication failed."), ?LOG(error, "[Redis] Can't connect to Redis server: Authentication failed."),
{error, Reason}; {error, Reason};
{error, Reason} -> {error, Reason} ->
?LOG(error, "[Redis] Can't connect to Redis server: ~p", [Reason]), ?LOG_SENSITIVE(error, "[Redis] Can't connect to Redis server: ~p", [Reason]),
{error, Reason} {error, Reason}
end. end.
@ -86,4 +86,3 @@ repl(S, _Var, undefined) ->
repl(S, Var, Val) -> repl(S, Var, Val) ->
NVal = re:replace(Val, "&", "\\\\&", [global, {return, list}]), NVal = re:replace(Val, "&", "\\\\&", [global, {return, list}]),
re:replace(S, Var, NVal, [{return, list}]). re:replace(S, Var, NVal, [{return, list}]).

View File

@ -1,6 +1,6 @@
{application, emqx_coap, {application, emqx_coap,
[{description, "EMQ X CoAP Gateway"}, [{description, "EMQ X CoAP Gateway"},
{vsn, "4.3.1"}, % strict semver, bump manually! {vsn, "4.3.2"}, % strict semver, bump manually!
{modules, []}, {modules, []},
{registered, []}, {registered, []},
{applications, [kernel,stdlib,gen_coap]}, {applications, [kernel,stdlib,gen_coap]},

View File

@ -1,9 +1,15 @@
%% -*-: erlang -*- %% -*-: erlang -*-
{VSN, {VSN,
[{"4.3.0",[ [{<<"4\\.3\\.[0-1]">>,[
{load_module, emqx_coap_mqtt_adapter, brutal_purge, soft_purge, []}]}, {load_module, emqx_coap_mqtt_adapter, brutal_purge, soft_purge, []},
{load_module, emqx_coap_pubsub_resource, brutal_purge, soft_purge, []},
{load_module, emqx_coap_resource, brutal_purge, soft_purge, []}
]},
{<<".*">>, []}], {<<".*">>, []}],
[{"4.3.0",[ [{<<"4\\.3\\.[0-1]">>,[
{load_module, emqx_coap_mqtt_adapter, brutal_purge, soft_purge, []}]}, {load_module, emqx_coap_mqtt_adapter, brutal_purge, soft_purge, []},
{load_module, emqx_coap_pubsub_resource, brutal_purge, soft_purge, []},
{load_module, emqx_coap_resource, brutal_purge, soft_purge, []}
]},
{<<".*">>, []}] {<<".*">>, []}]
}. }.

View File

@ -31,6 +31,9 @@
-export([ subscribe/2 -export([ subscribe/2
, unsubscribe/2 , unsubscribe/2
, publish/3 , publish/3
, received_puback/2
, message_payload/1
, message_topic/1
]). ]).
-export([ client_pid/4 -export([ client_pid/4
@ -95,6 +98,15 @@ unsubscribe(Pid, Topic) ->
publish(Pid, Topic, Payload) -> publish(Pid, Topic, Payload) ->
gen_server:call(Pid, {publish, Topic, Payload}). gen_server:call(Pid, {publish, Topic, Payload}).
received_puback(Pid, Msg) ->
gen_server:cast(Pid, {received_puback, Msg}).
message_payload(#message{payload = Payload}) ->
Payload.
message_topic(#message{topic = Topic}) ->
Topic.
%% For emqx_management plugin %% For emqx_management plugin
call(Pid, Msg) -> call(Pid, Msg) ->
call(Pid, Msg, infinity). call(Pid, Msg, infinity).
@ -172,13 +184,19 @@ handle_call(Request, _From, State) ->
?LOG(error, "adapter unexpected call ~p", [Request]), ?LOG(error, "adapter unexpected call ~p", [Request]),
{reply, ignored, State, hibernate}. {reply, ignored, State, hibernate}.
handle_cast({received_puback, Msg}, State) ->
%% NOTE: the counter named 'messages.acked', but the hook named 'message.acked'!
ok = emqx_metrics:inc('messages.acked'),
_ = emqx_hooks:run('message.acked', [conninfo(State), Msg]),
{noreply, State, hibernate};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?LOG(error, "broker_api unexpected cast ~p", [Msg]), ?LOG(error, "broker_api unexpected cast ~p", [Msg]),
{noreply, State, hibernate}. {noreply, State, hibernate}.
handle_info({deliver, _Topic, #message{topic = Topic, payload = Payload}}, handle_info({deliver, _Topic, #message{} = Msg},
State = #state{sub_topics = Subscribers}) -> State = #state{sub_topics = Subscribers}) ->
deliver([{Topic, Payload}], Subscribers), deliver([Msg], Subscribers),
{noreply, State, hibernate}; {noreply, State, hibernate};
handle_info(check_alive, State = #state{sub_topics = []}) -> handle_info(check_alive, State = #state{sub_topics = []}) ->
@ -271,27 +289,25 @@ packet_to_message(Topic, Payload,
%% Deliver %% Deliver
deliver([], _) -> ok; deliver([], _) -> ok;
deliver([Pub | More], Subscribers) -> deliver([Msg | More], Subscribers) ->
ok = do_deliver(Pub, Subscribers), ok = do_deliver(Msg, Subscribers),
deliver(More, Subscribers). deliver(More, Subscribers).
do_deliver({Topic, Payload}, Subscribers) -> do_deliver(Msg, Subscribers) ->
%% handle PUBLISH packet from broker %% handle PUBLISH packet from broker
?LOG(debug, "deliver message from broker Topic=~p, Payload=~p", [Topic, Payload]), ?LOG(debug, "deliver message from broker, msg: ~p", [Msg]),
deliver_to_coap(Topic, Payload, Subscribers), deliver_to_coap(Msg, Subscribers),
ok. ok.
deliver_to_coap(_TopicName, _Payload, []) -> deliver_to_coap(_Msg, []) ->
ok; ok;
deliver_to_coap(TopicName, Payload, [{TopicFilter, {IsWild, CoapPid}} | T]) -> deliver_to_coap(#message{topic = TopicName} = Msg, [{TopicFilter, {IsWild, CoapPid}} | T]) ->
Matched = case IsWild of Matched = case IsWild of
true -> emqx_topic:match(TopicName, TopicFilter); true -> emqx_topic:match(TopicName, TopicFilter);
false -> TopicName =:= TopicFilter false -> TopicName =:= TopicFilter
end, end,
%?LOG(debug, "deliver_to_coap Matched=~p, CoapPid=~p, TopicName=~p, Payload=~p, T=~p", Matched andalso (CoapPid ! {dispatch, Msg}),
% [Matched, CoapPid, TopicName, Payload, T]), deliver_to_coap(Msg, T).
Matched andalso (CoapPid ! {dispatch, TopicName, Payload}),
deliver_to_coap(TopicName, Payload, T).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Helper funcs %% Helper funcs
@ -328,12 +344,13 @@ chann_info(State) ->
will_msg => undefined will_msg => undefined
}. }.
conninfo(#state{peername = Peername, conninfo(#state{peername = {PeerHost, _} = Peername,
clientid = ClientId, clientid = ClientId,
connected_at = ConnectedAt}) -> connected_at = ConnectedAt}) ->
#{socktype => udp, #{socktype => udp,
sockname => {{127, 0, 0, 1}, 5683}, sockname => {{127, 0, 0, 1}, 5683},
peername => Peername, peername => Peername,
peerhost => PeerHost,
peercert => nossl, %% TODO: dtls peercert => nossl, %% TODO: dtls
conn_mod => ?MODULE, conn_mod => ?MODULE,
proto_name => <<"CoAP">>, proto_name => <<"CoAP">>,

View File

@ -138,16 +138,32 @@ coap_unobserve({state, ChId, Prefix, TopicPath}) ->
ok. ok.
handle_info({dispatch, Topic, Payload}, State) -> handle_info({dispatch, Topic, Payload}, State) ->
%% This clause should never be matched any more. We keep it here to handle
%% the old format messages during the release upgrade.
%% In this case the second function clause of `coap_ack/2` will be called,
%% and the ACKs is discarded.
?LOG(debug, "dispatch Topic=~p, Payload=~p", [Topic, Payload]), ?LOG(debug, "dispatch Topic=~p, Payload=~p", [Topic, Payload]),
{ok, Ret} = emqx_coap_pubsub_topics:reset_topic_info(Topic, Payload), {ok, Ret} = emqx_coap_pubsub_topics:reset_topic_info(Topic, Payload),
?LOG(debug, "Updated publish info of topic=~p, the Ret is ~p", [Topic, Ret]), ?LOG(debug, "Updated publish info of topic=~p, the Ret is ~p", [Topic, Ret]),
{notify, [], #coap_content{format = <<"application/octet-stream">>, payload = Payload}, State}; {notify, [], #coap_content{format = <<"application/octet-stream">>, payload = Payload}, State};
handle_info({dispatch, Msg}, State) ->
Payload = emqx_coap_mqtt_adapter:message_payload(Msg),
Topic = emqx_coap_mqtt_adapter:message_topic(Msg),
{ok, Ret} = emqx_coap_pubsub_topics:reset_topic_info(Topic, Payload),
?LOG(debug, "Updated publish info of topic=~p, the Ret is ~p", [Topic, Ret]),
{notify, {pub, Msg}, #coap_content{format = <<"application/octet-stream">>, payload = Payload}, State};
handle_info(Message, State) -> handle_info(Message, State) ->
?LOG(error, "Unknown Message ~p", [Message]), ?LOG(error, "Unknown Message ~p", [Message]),
{noreply, State}. {noreply, State}.
coap_ack(_Ref, State) -> {ok, State}. coap_ack({pub, Msg}, State) ->
?LOG(debug, "received coap ack for publish msg: ~p", [Msg]),
Pid = get(mqtt_client_pid),
emqx_coap_mqtt_adapter:received_puback(Pid, Msg),
{ok, State};
coap_ack(_Ref, State) ->
?LOG(debug, "received coap ack: ~p", [_Ref]),
{ok, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal Functions %% Internal Functions

View File

@ -104,12 +104,26 @@ coap_unobserve({state, ChId, Prefix, Topic}) ->
ok. ok.
handle_info({dispatch, Topic, Payload}, State) -> handle_info({dispatch, Topic, Payload}, State) ->
%% This clause should never be matched any more. We keep it here to handle
%% the old format messages during the release upgrade.
%% In this case the second function clause of `coap_ack/2` will be called,
%% and the ACKs is discarded.
?LOG(debug, "dispatch Topic=~p, Payload=~p", [Topic, Payload]), ?LOG(debug, "dispatch Topic=~p, Payload=~p", [Topic, Payload]),
{notify, [], #coap_content{format = <<"application/octet-stream">>, payload = Payload}, State}; {notify, [], #coap_content{format = <<"application/octet-stream">>, payload = Payload}, State};
handle_info({dispatch, Msg}, State) ->
Payload = emqx_coap_mqtt_adapter:message_payload(Msg),
{notify, {pub, Msg}, #coap_content{format = <<"application/octet-stream">>, payload = Payload}, State};
handle_info(Message, State) -> handle_info(Message, State) ->
emqx_coap_mqtt_adapter:handle_info(Message, State). emqx_coap_mqtt_adapter:handle_info(Message, State).
coap_ack(_Ref, State) -> {ok, State}. coap_ack({pub, Msg}, State) ->
?LOG(debug, "received coap ack for publish msg: ~p", [Msg]),
Pid = get(mqtt_client_pid),
emqx_coap_mqtt_adapter:received_puback(Pid, Msg),
{ok, State};
coap_ack(_Ref, State) ->
?LOG(debug, "received coap ack: ~p", [_Ref]),
{ok, State}.
get_auth(Query) -> get_auth(Query) ->
get_auth(Query, #coap_mqtt_auth{}). get_auth(Query, #coap_mqtt_auth{}).

View File

@ -91,7 +91,7 @@ t_observe(_Config) ->
Topic = <<"abc">>, TopicStr = binary_to_list(Topic), Topic = <<"abc">>, TopicStr = binary_to_list(Topic),
Payload = <<"123">>, Payload = <<"123">>,
Uri = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", Uri = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret",
{ok, Pid, N, Code, Content} = er_coap_observer:observe(Uri), {ok, Pid, N, Code, Content} = er_coap_observer:observe(Uri),
?LOGT("observer Pid=~p, N=~p, Code=~p, Content=~p", [Pid, N, Code, Content]), ?LOGT("observer Pid=~p, N=~p, Code=~p, Content=~p", [Pid, N, Code, Content]),
[SubPid] = emqx:subscribers(Topic), [SubPid] = emqx:subscribers(Topic),
@ -195,12 +195,16 @@ t_one_clientid_sub_2_topics(_Config) ->
[SubPid] = emqx:subscribers(Topic2), [SubPid] = emqx:subscribers(Topic2),
?assert(is_pid(SubPid)), ?assert(is_pid(SubPid)),
CntrAcked1 = emqx_metrics:val('messages.acked'),
emqx:publish(emqx_message:make(Topic1, Payload1)), emqx:publish(emqx_message:make(Topic1, Payload1)),
Notif1 = receive_notification(), Notif1 = receive_notification(),
?LOGT("observer 1 get Notif=~p", [Notif1]), ?LOGT("observer 1 get Notif=~p", [Notif1]),
{coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv1}} = Notif1, {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv1}} = Notif1,
?assertEqual(Payload1, PayloadRecv1), ?assertEqual(Payload1, PayloadRecv1),
timer:sleep(100),
CntrAcked2 = emqx_metrics:val('messages.acked'),
?assertEqual(CntrAcked2, CntrAcked1 + 1),
emqx:publish(emqx_message:make(Topic2, Payload2)), emqx:publish(emqx_message:make(Topic2, Payload2)),
@ -208,6 +212,9 @@ t_one_clientid_sub_2_topics(_Config) ->
?LOGT("observer 2 get Notif=~p", [Notif2]), ?LOGT("observer 2 get Notif=~p", [Notif2]),
{coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv2}} = Notif2, {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv2}} = Notif2,
?assertEqual(Payload2, PayloadRecv2), ?assertEqual(Payload2, PayloadRecv2),
timer:sleep(100),
CntrAcked3 = emqx_metrics:val('messages.acked'),
?assertEqual(CntrAcked3, CntrAcked2 + 1),
er_coap_observer:stop(Pid1), er_coap_observer:stop(Pid1),
er_coap_observer:stop(Pid2). er_coap_observer:stop(Pid2).

View File

@ -20,6 +20,16 @@ management.default_application.id = admin
## Value: String ## Value: String
management.default_application.secret = public management.default_application.secret = public
## Initialize apps file
## Is used to add administrative app/secrets when EMQX is launched for the first time.
## This config will not take any effect once EMQX database is populated with the provided apps.
## The file content format is as below:
## ```
##819e5db182cf:l9C5suZClIF3FvdzWqmINrVU61WNfIjcglxw9CVM7y1VI
##bb5a6cf1c06a:WuNRRgcRTGiNcuyrE49Bpwz4PGPrRnP4hUMi647kNSbN
## ```
# management.bootstrap_apps_file = {{ platform_etc_dir }}/bootstrap_apps.txt
##-------------------------------------------------------------------- ##--------------------------------------------------------------------
## HTTP Listener ## HTTP Listener

View File

@ -6,6 +6,11 @@
{datatype, integer} {datatype, integer}
]}. ]}.
{mapping, "management.bootstrap_apps_file", "emqx_management.bootstrap_apps_file", [
{datatype, string},
hidden
]}.
{mapping, "management.default_application.id", "emqx_management.default_application_id", [ {mapping, "management.default_application.id", "emqx_management.default_application_id", [
{default, undefined}, {default, undefined},
{datatype, string} {datatype, string}

View File

@ -25,11 +25,16 @@
]). ]).
start(_Type, _Args) -> start(_Type, _Args) ->
{ok, Sup} = emqx_mgmt_sup:start_link(), case emqx_mgmt_auth:init_bootstrap_apps() of
_ = emqx_mgmt_auth:add_default_app(), ok ->
emqx_mgmt_http:start_listeners(), {ok, Sup} = emqx_mgmt_sup:start_link(),
emqx_mgmt_cli:load(), _ = emqx_mgmt_auth:add_default_app(),
{ok, Sup}. emqx_mgmt_http:start_listeners(),
emqx_mgmt_cli:load(),
{ok, Sup};
{error, _Reason} = Error ->
Error
end.
stop(_State) -> stop(_State) ->
emqx_mgmt_http:stop_listeners(). emqx_mgmt_http:stop_listeners().

View File

@ -35,6 +35,7 @@
, update_app/5 , update_app/5
, del_app/1 , del_app/1
, list_apps/0 , list_apps/0
, init_bootstrap_apps/0
]). ]).
%% APP Auth/ACL API %% APP Auth/ACL API
@ -44,6 +45,8 @@
-record(mqtt_app, {id, secret, name, desc, status, expired}). -record(mqtt_app, {id, secret, name, desc, status, expired}).
-define(BOOTSTRAP_TAG, <<"Bootstrapped From File">>).
-type(appid() :: binary()). -type(appid() :: binary()).
-type(appsecret() :: binary()). -type(appsecret() :: binary()).
@ -77,6 +80,68 @@ add_default_app() ->
add_app(AppId1, <<"Default">>, AppSecret1, <<"Application user">>, true, undefined) add_app(AppId1, <<"Default">>, AppSecret1, <<"Application user">>, true, undefined)
end. end.
init_bootstrap_apps() ->
Bootstrap = application:get_env(emqx_management, bootstrap_apps_file, undefined),
Size = mnesia:table_info(mqtt_app, size),
init_bootstrap_apps(Bootstrap, Size).
init_bootstrap_apps(undefined, _) -> ok;
init_bootstrap_apps(_File, Size)when Size > 0 -> ok;
init_bootstrap_apps(File, 0) ->
case file:open(File, [read, binary]) of
{ok, Dev} ->
{ok, MP} = re:compile(<<"(\.+):(\.+$)">>, [ungreedy]),
case init_bootstrap_apps(File, Dev, MP) of
ok -> ok;
Error ->
%% if failed add bootstrap users, we should clear all bootstrap apps
{atomic, ok} = mnesia:clear_table(mqtt_app),
Error
end;
{error, Reason} = Error ->
?LOG(error,
"failed to open the mgmt bootstrap apps file(~s) for ~p",
[File, Reason]
),
Error
end.
init_bootstrap_apps(File, Dev, MP) ->
try
add_bootstrap_app(File, Dev, MP, 1)
catch
throw:Error -> {error, Error};
Type:Reason:Stacktrace ->
{error, {Type, Reason, Stacktrace}}
after
file:close(Dev)
end.
add_bootstrap_app(File, Dev, MP, Line) ->
case file:read_line(Dev) of
{ok, Bin} ->
case re:run(Bin, MP, [global, {capture, all_but_first, binary}]) of
{match, [[AppId, AppSecret]]} ->
Name = <<"bootstraped">>,
case add_app(AppId, Name, AppSecret, ?BOOTSTRAP_TAG, true, undefined) of
{ok, _} ->
add_bootstrap_app(File, Dev, MP, Line + 1);
{error, Reason} ->
throw(#{file => File, line => Line, content => Bin, reason => Reason})
end;
_ ->
?LOG(error,
"failed to bootstrap apps file(~s) for Line(~w): ~ts",
[File, Line, Bin]
),
throw(#{file => File, line => Line, content => Bin, reason => "invalid format"})
end;
eof ->
ok;
{error, Error} ->
throw(#{file => File, line => Line, reason => Error})
end.
-spec(add_app(appid(), binary()) -> {ok, appsecret()} | {error, term()}). -spec(add_app(appid(), binary()) -> {ok, appsecret()} | {error, term()}).
add_app(AppId, Name) when is_binary(AppId) -> add_app(AppId, Name) when is_binary(AppId) ->
add_app(AppId, Name, <<"Application user">>, true, undefined). add_app(AppId, Name, <<"Application user">>, true, undefined).

View File

@ -0,0 +1,89 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_mgmt_bootstrap_app_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("emqx/include/emqx.hrl").
%%--------------------------------------------------------------------
%% Setups
%%--------------------------------------------------------------------
all() ->
emqx_ct:all(?MODULE).
init_per_suite(Config) ->
emqx_ct_helpers:boot_modules(all),
application:load(emqx_modules),
application:load(emqx_modules_spec),
application:load(emqx_management),
application:stop(emqx_rule_engine),
ekka_mnesia:start(),
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_) ->
emqx_ct_helpers:stop_apps([]),
ok.
%%--------------------------------------------------------------------
%% Test cases
%%--------------------------------------------------------------------
t_load_ok(_) ->
application:stop(emqx_management),
Bin = <<"test-1:secret-1\ntest-2:secret-2">>,
File = "./bootstrap_apps.txt",
ok = file:write_file(File, Bin),
_ = mnesia:clear_table(mqtt_app),
application:set_env(emqx_management, bootstrap_apps_file, File),
{ok, _} = application:ensure_all_started(emqx_management),
?assert(emqx_mgmt_auth:is_authorized(<<"test-1">>, <<"secret-1">>)),
?assert(emqx_mgmt_auth:is_authorized(<<"test-2">>, <<"secret-2">>)),
?assertNot(emqx_mgmt_auth:is_authorized(<<"test-2">>, <<"secret-1">>)),
application:stop(emqx_management).
t_bootstrap_user_file_not_found(_) ->
File = "./bootstrap_apps_not_exist.txt",
check_load_failed(File),
ok.
t_load_invalid_username_failed(_) ->
Bin = <<"test-1:password-1\ntest&2:password-2">>,
File = "./bootstrap_apps.txt",
ok = file:write_file(File, Bin),
check_load_failed(File),
ok.
t_load_invalid_format_failed(_) ->
Bin = <<"test-1:password-1\ntest-2password-2">>,
File = "./bootstrap_apps.txt",
ok = file:write_file(File, Bin),
check_load_failed(File),
ok.
check_load_failed(File) ->
_ = mnesia:clear_table(mqtt_app),
application:stop(emqx_management),
application:set_env(emqx_management, bootstrap_apps_file, File),
?assertMatch({error, _}, application:ensure_all_started(emqx_management)),
?assertNot(lists:member(emqx_management, application:which_applications())),
?assertEqual(0, mnesia:table_info(mqtt_app, size)).

View File

@ -250,7 +250,8 @@ do_create_rule2(ParsedParams) ->
return({error, 400, ?ERR_BADARGS(Reason)}) return({error, 400, ?ERR_BADARGS(Reason)})
end. end.
update_rule(#{id := Id}, Params) -> update_rule(#{id := Id0}, Params) ->
Id = urldecode(Id0),
case parse_rule_params(Params, #{id => Id}) of case parse_rule_params(Params, #{id => Id}) of
{ok, ParsedParams} -> {ok, ParsedParams} ->
case emqx_rule_engine:update_rule(ParsedParams) of case emqx_rule_engine:update_rule(ParsedParams) of
@ -275,16 +276,21 @@ list_rules(_Bindings, Params) ->
return_all(emqx_rule_registry:get_rules_ordered_by_ts()) return_all(emqx_rule_registry:get_rules_ordered_by_ts())
end. end.
show_rule(#{id := Id}, _Params) -> show_rule(#{id := Id0}, _Params) ->
Id = urldecode(Id0),
reply_with(fun emqx_rule_registry:get_rule/1, Id). reply_with(fun emqx_rule_registry:get_rule/1, Id).
delete_rule(#{id := Id}, _Params) -> delete_rule(#{id := Id0}, _Params) ->
Id = urldecode(Id0),
ok = emqx_rule_engine:delete_rule(Id), ok = emqx_rule_engine:delete_rule(Id),
return(ok). return(ok).
reset_metrics_local(Id) -> emqx_rule_metrics:reset_metrics(Id). reset_metrics_local(Id0) ->
Id = urldecode(Id0),
emqx_rule_metrics:reset_metrics(Id).
reset_metrics(#{id := Id}, _Params) -> reset_metrics(#{id := Id0}, _Params) ->
Id = urldecode(Id0),
_ = ?CLUSTER_CALL(reset_metrics_local, [Id]), _ = ?CLUSTER_CALL(reset_metrics_local, [Id]),
return(ok). return(ok).
@ -350,7 +356,8 @@ list_resources(#{}, _Params) ->
list_resources_by_type(#{type := Type}, _Params) -> list_resources_by_type(#{type := Type}, _Params) ->
return_all(emqx_rule_registry:get_resources_by_type(Type)). return_all(emqx_rule_registry:get_resources_by_type(Type)).
show_resource(#{id := Id}, _Params) -> show_resource(#{id := Id0}, _Params) ->
Id = urldecode(Id0),
case emqx_rule_registry:find_resource(Id) of case emqx_rule_registry:find_resource(Id) of
{ok, R} -> {ok, R} ->
StatusFun = StatusFun =
@ -366,7 +373,8 @@ show_resource(#{id := Id}, _Params) ->
return({error, 404, <<"Not Found">>}) return({error, 404, <<"Not Found">>})
end. end.
get_resource_status(#{id := Id}, _Params) -> get_resource_status(#{id := Id0}, _Params) ->
Id = urldecode(Id0),
case emqx_rule_engine:get_resource_status(Id) of case emqx_rule_engine:get_resource_status(Id) of
{ok, Status} -> {ok, Status} ->
return({ok, Status}); return({ok, Status});
@ -374,7 +382,8 @@ get_resource_status(#{id := Id}, _Params) ->
return({error, 400, ?ERR_NO_RESOURCE(Id)}) return({error, 400, ?ERR_NO_RESOURCE(Id)})
end. end.
start_resource(#{id := Id}, _Params) -> start_resource(#{id := Id0}, _Params) ->
Id = urldecode(Id0),
case emqx_rule_engine:start_resource(Id) of case emqx_rule_engine:start_resource(Id) of
ok -> ok ->
return(ok); return(ok);
@ -385,7 +394,8 @@ start_resource(#{id := Id}, _Params) ->
return({error, 400, ?ERR_BADARGS(Reason)}) return({error, 400, ?ERR_BADARGS(Reason)})
end. end.
update_resource(#{id := Id}, NewParams) -> update_resource(#{id := Id0}, NewParams) ->
Id = urldecode(Id0),
P1 = case proplists:get_value(<<"description">>, NewParams) of P1 = case proplists:get_value(<<"description">>, NewParams) of
undefined -> #{}; undefined -> #{};
Value -> #{<<"description">> => Value} Value -> #{<<"description">> => Value}
@ -409,7 +419,8 @@ update_resource(#{id := Id}, NewParams) ->
return({error, 400, ?ERR_BADARGS(Reason)}) return({error, 400, ?ERR_BADARGS(Reason)})
end. end.
delete_resource(#{id := Id}, _Params) -> delete_resource(#{id := Id0}, _Params) ->
Id = urldecode(Id0),
case emqx_rule_engine:delete_resource(Id) of case emqx_rule_engine:delete_resource(Id) of
ok -> return(ok); ok -> return(ok);
{error, not_found} -> return(ok); {error, not_found} -> return(ok);
@ -660,3 +671,6 @@ run_fuzzy_match(E = #rule{for = Topics}, [{for, like, Pattern}|Fuzzy]) ->
lists:any(fun(For) -> binary:match(For, Pattern) /= nomatch end, Topics) lists:any(fun(For) -> binary:match(For, Pattern) /= nomatch end, Topics)
andalso run_fuzzy_match(E, Fuzzy); andalso run_fuzzy_match(E, Fuzzy);
run_fuzzy_match(_E, [{_Key, like, _SubStr}| _Fuzzy]) -> false. run_fuzzy_match(_E, [{_Key, like, _SubStr}| _Fuzzy]) -> false.
urldecode(S) ->
emqx_http_lib:uri_decode(S).

View File

@ -28,6 +28,14 @@
-include("emqx_rule_test.hrl"). -include("emqx_rule_test.hrl").
-import(emqx_rule_test_lib, [make_simple_resource_type/1]). -import(emqx_rule_test_lib, [make_simple_resource_type/1]).
%% API request funcs
-import(emqx_rule_test_lib,
[ request_api/4
, request_api/5
, auth_header_/0
, api_path/1
]).
%%-define(PROPTEST(M,F), true = proper:quickcheck(M:F())). %%-define(PROPTEST(M,F), true = proper:quickcheck(M:F())).
all() -> all() ->
@ -62,6 +70,7 @@ groups() ->
]}, ]},
{api, [], {api, [],
[t_crud_rule_api, [t_crud_rule_api,
t_rule_api_unicode_ids,
t_list_actions_api, t_list_actions_api,
t_show_action_api, t_show_action_api,
t_crud_resources_api, t_crud_resources_api,
@ -227,6 +236,10 @@ init_per_testcase(Test, Config)
{conn_event, TriggerConnEvent}, {conn_event, TriggerConnEvent},
{connsql, SQL} {connsql, SQL}
| Config]; | Config];
init_per_testcase(t_rule_api_unicode_ids, Config) ->
ok = emqx_dashboard_admin:mnesia(boot),
emqx_ct_helpers:start_apps([emqx_management, emqx_dashboard]),
Config;
init_per_testcase(_TestCase, Config) -> init_per_testcase(_TestCase, Config) ->
ok = emqx_rule_registry:register_resource_types( ok = emqx_rule_registry:register_resource_types(
[make_simple_debug_resource_type()]), [make_simple_debug_resource_type()]),
@ -249,6 +262,10 @@ end_per_testcase(Test, Config)
emqtt:stop(?config(subclient, Config)), emqtt:stop(?config(subclient, Config)),
emqtt:stop(?config(connclient, Config)), emqtt:stop(?config(connclient, Config)),
Config; Config;
end_per_testcase(t_rule_api_unicode_ids, _Config) ->
application:stop(emqx_dashboard),
application:stop(emqx_management),
ok;
end_per_testcase(_TestCase, _Config) -> end_per_testcase(_TestCase, _Config) ->
ok. ok.
@ -523,6 +540,46 @@ t_crud_rule_api(_Config) ->
?assertMatch({ok, #{code := 404, message := _Message}}, NotFound), ?assertMatch({ok, #{code := 404, message := _Message}}, NotFound),
ok. ok.
-define(PRED(Elem), fun(Elem) -> true; (_) -> false end).
t_rule_api_unicode_ids(_Config) ->
UData =
fun(Description) ->
#{<<"name">> => <<"debug-rule">>,
<<"rawsql">> => <<"select * from \"t/a\"">>,
<<"actions">> => [#{<<"name">> => <<"do_nothing">>,
<<"params">> => []}
],
<<"description">> => Description}
end,
CData = fun(Id, Description) -> maps:put(<<"id">>, Id, UData(Description)) end,
CDes = <<"Creating rules description">>,
UDes = <<"Updating rules description">>,
%% create rule
CFun = fun(Id) -> {ok, Return} = request_api(post, api_path(["rules"]), [], auth_header_(), CData(Id, CDes)), Return end,
%% update rule
UFun = fun(Id) -> {ok, Return} = request_api(put, api_path(["rules", cow_uri:urlencode(Id)]), [], auth_header_(), UData(UDes)), Return end,
%% show rule
SFun = fun(Id) -> {ok, Return} = request_api(get, api_path(["rules", cow_uri:urlencode(Id)]), [], auth_header_()), Return end,
%% delete rule
DFun = fun(Id) -> {ok, Return} = request_api(delete, api_path(["rules", cow_uri:urlencode(Id)]), [], auth_header_()), Return end,
Ids = [unicode:characters_to_binary([Char]) || Char <- lists:seq(0, 1000) -- [46]] ++ [<<"%2e">>],
Ress = [begin
{?assertMatch(#{<<"code">> := 0, <<"data">> := #{<<"description">> := CDes}}, decode_to_map(CFun(Id))),
?assertMatch(#{<<"code">> := 0}, decode_to_map(UFun(Id))),
?assertMatch(#{<<"code">> := 0, <<"data">> := #{<<"description">> := UDes}}, decode_to_map(SFun(Id))),
?assertMatch(#{<<"code">> := 0}, decode_to_map(DFun(Id)))}
end || Id <- Ids],
?assertEqual(true, lists:all(?PRED(true), [?PRED({ok, ok, ok, ok})(Res) || Res <- Ress ])).
decode_to_map(ResponseBody) ->
jiffy:decode(list_to_binary(ResponseBody), [return_maps]).
t_list_rule_api(_Config) -> t_list_rule_api(_Config) ->
AddIds = AddIds =
lists:map(fun(Seq) -> lists:map(fun(Seq) ->

View File

@ -128,6 +128,67 @@ make_simple_resource_type(ResTypeName) ->
init_events_counters() -> init_events_counters() ->
ets:new(events_record_tab, [named_table, bag, public]). ets:new(events_record_tab, [named_table, bag, public]).
%%------------------------------------------------------------------------------
%% rule test helper funcs
%%------------------------------------------------------------------------------
-define(HOST, "http://127.0.0.1:18083/").
-define(API_VERSION, "v4").
-define(BASE_PATH, "api").
request_api(Method, Url, Auth) ->
request_api(Method, Url, [], Auth, []).
request_api(Method, Url, QueryParams, Auth) ->
request_api(Method, Url, QueryParams, Auth, []).
request_api(Method, Url, QueryParams, Auth, []) ->
NewUrl = case QueryParams of
"" -> Url;
_ -> Url ++ "?" ++ QueryParams
end,
Headers = case Auth of
no_auth -> [];
Header -> [Header]
end,
do_request_api(Method, {NewUrl, Headers});
request_api(Method, Url, QueryParams, Auth, Body) ->
NewUrl = case QueryParams of
"" -> Url;
_ -> Url ++ "?" ++ QueryParams
end,
Headers = case Auth of
no_auth -> [];
Header -> [Header]
end,
do_request_api(Method, {NewUrl, Headers, "application/json", emqx_json:encode(Body)}).
do_request_api(Method, Request)->
%% ct:pal("Method: ~p, Request: ~p", [Method, Request]),
case httpc:request(Method, Request, [], []) of
{error, socket_closed_remotely} ->
{error, socket_closed_remotely};
{ok, {{"HTTP/1.1", Code, _}, _, Return} }
when Code =:= 200 orelse Code =:= 201 ->
{ok, Return};
{ok, {Reason, _, _}} ->
{error, Reason}
end.
auth_header_() ->
AppId = <<"admin">>,
AppSecret = <<"public">>,
auth_header_(binary_to_list(AppId), binary_to_list(AppSecret)).
auth_header_(User, Pass) ->
Encoded = base64:encode_to_string(lists:append([User,":",Pass])),
{"Authorization","Basic " ++ Encoded}.
api_path(Parts)->
?HOST ++ filename:join([?BASE_PATH, ?API_VERSION] ++ Parts).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Internal helper funcs %% Internal helper funcs
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------

View File

@ -2,8 +2,14 @@
## Enhancements ## Enhancements
- Remove useless information from the dashboard listener failure log [#9260](https://github.com/emqx/emqx/pull/9260). - Remove useless information from the dashboard listener failure log [#9260](https://github.com/emqx/emqx/pull/9260).
- We now trigger the `'message.acked'` hook after the CoAP gateway sends a message to the device and receives the ACK from the device [#9264](https://github.com/emqx/emqx/pull/9264).
With this change, the CoAP gateway can be combined with the offline message caching function (in the
emqx enterprise), so that CoAP devices are able to read the missed messages from the database when
it is online again.
- Support to use placeholders like `${var}` in the HTTP `Headers` of rule-engine's Webhook actions [#9239](https://github.com/emqx/emqx/pull/9239). - Support to use placeholders like `${var}` in the HTTP `Headers` of rule-engine's Webhook actions [#9239](https://github.com/emqx/emqx/pull/9239).
- Asynchronously refresh the resources and rules during emqx boot-up [#9199](https://github.com/emqx/emqx/pull/9199). - Asynchronously refresh the resources and rules during emqx boot-up [#9199](https://github.com/emqx/emqx/pull/9199).
@ -17,6 +23,10 @@
- Added a log censor to avoid logging sensitive data [#9189](https://github.com/emqx/emqx/pull/9189). - Added a log censor to avoid logging sensitive data [#9189](https://github.com/emqx/emqx/pull/9189).
If the data to be logged is a map or key-value list which contains sensitive key words such as `password`, the value is obfuscated as `******`. If the data to be logged is a map or key-value list which contains sensitive key words such as `password`, the value is obfuscated as `******`.
- Enhanced log security in ACL modules, sensitive data will be obscured. [#9242](https://github.com/emqx/emqx/pull/9242).
- Add `management.bootstrap_apps_file` configuration to bulk import default app/secret when EMQX initializes the database [#9273](https://github.com/emqx/emqx/pull/9273).
## Bug fixes ## Bug fixes
- Fix that after uploading a backup file with an UTF8 filename, HTTP API `GET /data/export` fails with status code 500 [#9224](https://github.com/emqx/emqx/pull/9224). - Fix that after uploading a backup file with an UTF8 filename, HTTP API `GET /data/export` fails with status code 500 [#9224](https://github.com/emqx/emqx/pull/9224).
@ -39,3 +49,7 @@
For rule-engine's input events like `$events/message_delivered`, and `$events/message_dropped`, For rule-engine's input events like `$events/message_delivered`, and `$events/message_dropped`,
if the message was delivered to a shared-subscription, the encoding (to JSON) of the event will fail. if the message was delivered to a shared-subscription, the encoding (to JSON) of the event will fail.
Affected versions: `v4.3.21`, `v4.4.10`, `e4.3.16` and `e4.4.10`. Affected versions: `v4.3.21`, `v4.4.10`, `e4.3.16` and `e4.4.10`.
- Make sure Rule-Engine API supports Percent-encoding `rule_id` and `resource_id` in HTTP request path [#9190](https://github.com/emqx/emqx/pull/9190).
Note that the `id` in `POST /api/v4/rules` should be literals (not encoded) when creating a `rule` or `resource`.
See docs [Create Rule](https://www.emqx.io/docs/zh/v4.3/advanced/http-api.html#post-api-v4-rules) [Create Resource](https://www.emqx.io/docs/zh/v4.3/advanced/http-api.html#post-api-v4-resources).

View File

@ -4,6 +4,9 @@
- 删除 Dashboard 监听器失败时日志中的无用信息 [#9260](https://github.com/emqx/emqx/pull/9260). - 删除 Dashboard 监听器失败时日志中的无用信息 [#9260](https://github.com/emqx/emqx/pull/9260).
- 当 CoAP 网关给设备投递消息并收到设备发来的确认之后,回调 `'message.acked'` 钩子 [#9264](https://github.com/emqx/emqx/pull/9264)。
有了这个改动CoAP 网关可以配合 EMQX (企业版)的离线消息缓存功能,让 CoAP 设备重新上线之后,从数据库读取其离线状态下错过的消息。
- 支持在规则引擎的 Webhook 动作的 HTTP Headers 里使用 `${var}` 格式的占位符 [#9239](https://github.com/emqx/emqx/pull/9239)。 - 支持在规则引擎的 Webhook 动作的 HTTP Headers 里使用 `${var}` 格式的占位符 [#9239](https://github.com/emqx/emqx/pull/9239)。
- 在 emqx 启动时,异步地刷新资源和规则 [#9199](https://github.com/emqx/emqx/pull/9199)。 - 在 emqx 启动时,异步地刷新资源和规则 [#9199](https://github.com/emqx/emqx/pull/9199)。
@ -17,6 +20,10 @@
- 增强包含敏感数据的日志的安全性 [#9189](https://github.com/emqx/emqx/pull/9189)。 - 增强包含敏感数据的日志的安全性 [#9189](https://github.com/emqx/emqx/pull/9189)。
如果日志中包含敏感关键词,例如 `password`,那么关联的数据回被模糊化处理,替换成 `******` 如果日志中包含敏感关键词,例如 `password`,那么关联的数据回被模糊化处理,替换成 `******`
- 增强 ACL 模块中的日志安全性,敏感数据将被模糊化。[#9242](https://github.com/emqx/emqx/pull/9242)。
- 增加 `management.bootstrap_apps_file` 配置,可以让 EMQX 初始化数据库时,从该文件批量导入一些 APP / Secret [#9273](https://github.com/emqx/emqx/pull/9273)。
## 修复 ## 修复
- 修复若上传的备份文件名中包含 UTF8 字符,`GET /data/export` HTTP 接口返回 500 错误 [#9224](https://github.com/emqx/emqx/pull/9224)。 - 修复若上传的备份文件名中包含 UTF8 字符,`GET /data/export` HTTP 接口返回 500 错误 [#9224](https://github.com/emqx/emqx/pull/9224)。
@ -39,3 +46,7 @@
带消息的规则引擎事件,例如 `$events/message_delivered``$events/message_dropped`, 带消息的规则引擎事件,例如 `$events/message_delivered``$events/message_dropped`,
如果消息事件是共享订阅产生的,在编码(到 JSON 格式)过程中会失败。 如果消息事件是共享订阅产生的,在编码(到 JSON 格式)过程中会失败。
影响到的版本:`v4.3.21`, `v4.4.10`, `e4.3.16``e4.4.10` 影响到的版本:`v4.3.21`, `v4.4.10`, `e4.3.16``e4.4.10`
- 使规则引擎 API 在 HTTP 请求路径中支持百分号编码的 `rule_id``resource_id` [#9190](https://github.com/emqx/emqx/pull/9190)。
注意在创建规则或资源时HTTP body 中的 `id` 字段仍为字面值,而不是编码之后的值。
详情请参考 [创建规则](https://www.emqx.io/docs/zh/v4.3/advanced/http-api.html#post-api-v4-rules) 和 [创建资源](https://www.emqx.io/docs/zh/v4.3/advanced/http-api.html#post-api-v4-resources)。

View File

@ -63,8 +63,8 @@
]). ]).
-export([all_channels/0, -export([all_channels/0,
channel_with_session_table/0, channel_with_session_table/1,
live_connection_table/0]). live_connection_table/1]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([ init/1
@ -433,7 +433,7 @@ all_channels() ->
ets:select(?CHAN_TAB, Pat). ets:select(?CHAN_TAB, Pat).
%% @doc Get clientinfo for all clients with sessions %% @doc Get clientinfo for all clients with sessions
channel_with_session_table() -> channel_with_session_table(ConnModules) ->
Ms = ets:fun2ms( Ms = ets:fun2ms(
fun({{ClientId, _ChanPid}, fun({{ClientId, _ChanPid},
Info, Info,
@ -441,22 +441,25 @@ channel_with_session_table() ->
{ClientId, Info} {ClientId, Info}
end), end),
Table = ets:table(?CHAN_INFO_TAB, [{traverse, {select, Ms}}]), Table = ets:table(?CHAN_INFO_TAB, [{traverse, {select, Ms}}]),
ConnModuleMap = maps:from_list([{Mod, true} || Mod <- ConnModules]),
qlc:q([ {ClientId, ConnState, ConnInfo, ClientInfo} qlc:q([ {ClientId, ConnState, ConnInfo, ClientInfo}
|| {ClientId, || {ClientId,
#{conn_state := ConnState, #{conn_state := ConnState,
clientinfo := ClientInfo, clientinfo := ClientInfo,
conninfo := #{clean_start := false} = ConnInfo}} <- Table conninfo := #{clean_start := false, conn_mod := ConnModule} = ConnInfo}}
<- Table,
maps:is_key(ConnModule, ConnModuleMap)
]). ]).
%% @doc Get all local connection query handle %% @doc Get all local connection query handle
live_connection_table() -> live_connection_table(ConnModules) ->
Ms = ets:fun2ms( Ms = lists:map(fun live_connection_ms/1, ConnModules),
fun({{ClientId, ChanPid}, _}) ->
{ClientId, ChanPid}
end),
Table = ets:table(?CHAN_CONN_TAB, [{traverse, {select, Ms}}]), Table = ets:table(?CHAN_CONN_TAB, [{traverse, {select, Ms}}]),
qlc:q([{ClientId, ChanPid} || {ClientId, ChanPid} <- Table, is_channel_connected(ClientId, ChanPid)]). qlc:q([{ClientId, ChanPid} || {ClientId, ChanPid} <- Table, is_channel_connected(ClientId, ChanPid)]).
live_connection_ms(ConnModule) ->
{{{'$1','$2'},ConnModule},[],[{{'$1','$2'}}]}.
is_channel_connected(ClientId, ChanPid) when node(ChanPid) =:= node() -> is_channel_connected(ClientId, ChanPid) when node(ChanPid) =:= node() ->
case get_chan_info(ClientId, ChanPid) of case get_chan_info(ClientId, ChanPid) of
#{conn_state := disconnected} -> false; #{conn_state := disconnected} -> false;