Merge branch 'release-v43' into bump-minirest-0.3.10
This commit is contained in:
commit
704bf3eade
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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}) ->
|
||||||
|
|
|
@ -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,[]}]},
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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]},
|
||||||
|
|
|
@ -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]">>,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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}}.
|
||||||
|
|
||||||
|
|
|
@ -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"}
|
||||||
|
|
|
@ -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]},
|
||||||
|
|
|
@ -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]">>,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]},
|
||||||
|
|
|
@ -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,[]}]},
|
||||||
{<<".*">>,[]}]
|
{<<".*">>,[]}]
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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]},
|
||||||
|
|
|
@ -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}]},
|
||||||
{<<".*">>,[]}]}.
|
{<<".*">>,[]}]}.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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]},
|
||||||
|
|
|
@ -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,[]}]},
|
||||||
{<<".*">>,[]}]
|
{<<".*">>,[]}]}.
|
||||||
}.
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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}]).
|
||||||
|
|
||||||
|
|
|
@ -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]},
|
||||||
|
|
|
@ -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, []}
|
||||||
|
]},
|
||||||
{<<".*">>, []}]
|
{<<".*">>, []}]
|
||||||
}.
|
}.
|
||||||
|
|
|
@ -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">>,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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{}).
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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().
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -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)).
|
|
@ -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).
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
|
@ -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
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -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)。
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue