Merge pull request #13278 from savonarola/0617-release-57-sync
Sync release-57
This commit is contained in:
commit
ad993437aa
|
@ -1,3 +1,5 @@
|
||||||
|
简体中文 | [English](./README.md) | [Русский](./README-RU.md)
|
||||||
|
|
||||||
# EMQX
|
# EMQX
|
||||||
|
|
||||||
[](https://github.com/emqx/emqx/releases)
|
[](https://github.com/emqx/emqx/releases)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
Русский | [简体中文](./README-CN.md) | [English](./README.md)
|
||||||
|
|
||||||
# Брокер EMQX
|
# Брокер EMQX
|
||||||
|
|
||||||
[](https://github.com/emqx/emqx/releases)
|
[](https://github.com/emqx/emqx/releases)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
English | [简体中文](./README-CN.md) | [Русский](./README-RU.md)
|
||||||
|
|
||||||
# EMQX
|
# EMQX
|
||||||
|
|
||||||
[](https://github.com/emqx/emqx/releases)
|
[](https://github.com/emqx/emqx/releases)
|
||||||
|
|
|
@ -119,12 +119,12 @@
|
||||||
%% All Messages received
|
%% All Messages received
|
||||||
{counter, 'messages.received', <<
|
{counter, 'messages.received', <<
|
||||||
"Number of messages received from the client, equal to the sum of "
|
"Number of messages received from the client, equal to the sum of "
|
||||||
"messages.qos0.received\fmessages.qos1.received and messages.qos2.received"
|
"messages.qos0.received, messages.qos1.received and messages.qos2.received"
|
||||||
>>},
|
>>},
|
||||||
%% All Messages sent
|
%% All Messages sent
|
||||||
{counter, 'messages.sent', <<
|
{counter, 'messages.sent', <<
|
||||||
"Number of messages sent to the client, equal to the sum of "
|
"Number of messages sent to the client, equal to the sum of "
|
||||||
"messages.qos0.sent\fmessages.qos1.sent and messages.qos2.sent"
|
"messages.qos0.sent, messages.qos1.sent and messages.qos2.sent"
|
||||||
>>},
|
>>},
|
||||||
%% QoS0 Messages received
|
%% QoS0 Messages received
|
||||||
{counter, 'messages.qos0.received', <<"Number of QoS 0 messages received from clients">>},
|
{counter, 'messages.qos0.received', <<"Number of QoS 0 messages received from clients">>},
|
||||||
|
|
|
@ -100,7 +100,7 @@ update_config(Path, ConfigRequest) ->
|
||||||
override_to => cluster
|
override_to => cluster
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-spec parse_http_resp_body(binary(), binary()) -> allow | deny | ignore | error.
|
-spec parse_http_resp_body(binary(), binary()) -> allow | deny | ignore | error | {error, term()}.
|
||||||
parse_http_resp_body(<<"application/x-www-form-urlencoded", _/binary>>, Body) ->
|
parse_http_resp_body(<<"application/x-www-form-urlencoded", _/binary>>, Body) ->
|
||||||
try
|
try
|
||||||
result(maps:from_list(cow_qs:parse_qs(Body)))
|
result(maps:from_list(cow_qs:parse_qs(Body)))
|
||||||
|
@ -112,7 +112,9 @@ parse_http_resp_body(<<"application/json", _/binary>>, Body) ->
|
||||||
result(emqx_utils_json:decode(Body, [return_maps]))
|
result(emqx_utils_json:decode(Body, [return_maps]))
|
||||||
catch
|
catch
|
||||||
_:_ -> error
|
_:_ -> error
|
||||||
end.
|
end;
|
||||||
|
parse_http_resp_body(ContentType = <<_/binary>>, _Body) ->
|
||||||
|
{error, <<"unsupported content-type: ", ContentType/binary>>}.
|
||||||
|
|
||||||
result(#{<<"result">> := <<"allow">>}) -> allow;
|
result(#{<<"result">> := <<"allow">>}) -> allow;
|
||||||
result(#{<<"result">> := <<"deny">>}) -> deny;
|
result(#{<<"result">> := <<"deny">>}) -> deny;
|
||||||
|
|
|
@ -106,6 +106,9 @@ authorize(
|
||||||
body => Body
|
body => Body
|
||||||
}),
|
}),
|
||||||
nomatch;
|
nomatch;
|
||||||
|
{error, Reason} ->
|
||||||
|
?tp(error, bad_authz_http_response, #{reason => Reason}),
|
||||||
|
nomatch;
|
||||||
Result ->
|
Result ->
|
||||||
{matched, Result}
|
{matched, Result}
|
||||||
end;
|
end;
|
||||||
|
|
|
@ -463,6 +463,72 @@ t_placeholder_and_body(_Config) ->
|
||||||
emqx_access_control:authorize(ClientInfo, ?AUTHZ_PUBLISH, <<"t">>)
|
emqx_access_control:authorize(ClientInfo, ?AUTHZ_PUBLISH, <<"t">>)
|
||||||
).
|
).
|
||||||
|
|
||||||
|
%% Checks that we don't crash when receiving an unsupported content-type back.
|
||||||
|
t_bad_response_content_type(_Config) ->
|
||||||
|
ok = setup_handler_and_config(
|
||||||
|
fun(Req0, State) ->
|
||||||
|
?assertEqual(
|
||||||
|
<<"/authz/users/">>,
|
||||||
|
cowboy_req:path(Req0)
|
||||||
|
),
|
||||||
|
|
||||||
|
{ok, _PostVars, Req1} = cowboy_req:read_urlencoded_body(Req0),
|
||||||
|
|
||||||
|
Req = cowboy_req:reply(
|
||||||
|
200,
|
||||||
|
#{<<"content-type">> => <<"text/csv">>},
|
||||||
|
"hi",
|
||||||
|
Req1
|
||||||
|
),
|
||||||
|
{ok, Req, State}
|
||||||
|
end,
|
||||||
|
#{
|
||||||
|
<<"method">> => <<"post">>,
|
||||||
|
<<"body">> => #{
|
||||||
|
<<"username">> => <<"${username}">>,
|
||||||
|
<<"clientid">> => <<"${clientid}">>,
|
||||||
|
<<"peerhost">> => <<"${peerhost}">>,
|
||||||
|
<<"proto_name">> => <<"${proto_name}">>,
|
||||||
|
<<"mountpoint">> => <<"${mountpoint}">>,
|
||||||
|
<<"topic">> => <<"${topic}">>,
|
||||||
|
<<"action">> => <<"${action}">>,
|
||||||
|
<<"access">> => <<"${access}">>,
|
||||||
|
<<"CN">> => ?PH_CERT_CN_NAME,
|
||||||
|
<<"CS">> => ?PH_CERT_SUBJECT
|
||||||
|
},
|
||||||
|
<<"headers">> => #{
|
||||||
|
<<"accept">> => <<"text/plain">>,
|
||||||
|
<<"content-type">> => <<"application/json">>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
ClientInfo = #{
|
||||||
|
clientid => <<"client id">>,
|
||||||
|
username => <<"user name">>,
|
||||||
|
peerhost => {127, 0, 0, 1},
|
||||||
|
protocol => <<"MQTT">>,
|
||||||
|
mountpoint => <<"MOUNTPOINT">>,
|
||||||
|
zone => default,
|
||||||
|
listener => {tcp, default},
|
||||||
|
cn => ?PH_CERT_CN_NAME,
|
||||||
|
dn => ?PH_CERT_SUBJECT
|
||||||
|
},
|
||||||
|
|
||||||
|
?check_trace(
|
||||||
|
?assertEqual(
|
||||||
|
deny,
|
||||||
|
emqx_access_control:authorize(ClientInfo, ?AUTHZ_PUBLISH, <<"t">>)
|
||||||
|
),
|
||||||
|
fun(Trace) ->
|
||||||
|
?assertMatch(
|
||||||
|
[#{reason := <<"unsupported content-type", _/binary>>}],
|
||||||
|
?of_kind(bad_authz_http_response, Trace)
|
||||||
|
),
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
t_no_value_for_placeholder(_Config) ->
|
t_no_value_for_placeholder(_Config) ->
|
||||||
ok = setup_handler_and_config(
|
ok = setup_handler_and_config(
|
||||||
fun(Req0, State) ->
|
fun(Req0, State) ->
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
%% Swagger specs from hocon schema
|
%% Swagger specs from hocon schema
|
||||||
-export([
|
-export([
|
||||||
api_spec/0,
|
api_spec/0,
|
||||||
|
check_api_schema/2,
|
||||||
paths/0,
|
paths/0,
|
||||||
schema/1,
|
schema/1,
|
||||||
namespace/0
|
namespace/0
|
||||||
|
@ -96,7 +97,7 @@
|
||||||
namespace() -> "actions_and_sources".
|
namespace() -> "actions_and_sources".
|
||||||
|
|
||||||
api_spec() ->
|
api_spec() ->
|
||||||
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => fun check_api_schema/2}).
|
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => fun ?MODULE:check_api_schema/2}).
|
||||||
|
|
||||||
paths() ->
|
paths() ->
|
||||||
[
|
[
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{application, emqx_bridge_mongodb, [
|
{application, emqx_bridge_mongodb, [
|
||||||
{description, "EMQX Enterprise MongoDB Bridge"},
|
{description, "EMQX Enterprise MongoDB Bridge"},
|
||||||
{vsn, "0.3.0"},
|
{vsn, "0.3.1"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [
|
{applications, [
|
||||||
kernel,
|
kernel,
|
||||||
|
|
|
@ -100,8 +100,10 @@ fields(mongodb_action) ->
|
||||||
);
|
);
|
||||||
fields(action_parameters) ->
|
fields(action_parameters) ->
|
||||||
[
|
[
|
||||||
{collection, mk(binary(), #{desc => ?DESC("collection"), default => <<"mqtt">>})},
|
{collection,
|
||||||
{payload_template, mk(binary(), #{required => false, desc => ?DESC("payload_template")})}
|
mk(emqx_schema:template(), #{desc => ?DESC("collection"), default => <<"mqtt">>})},
|
||||||
|
{payload_template,
|
||||||
|
mk(emqx_schema:template(), #{required => false, desc => ?DESC("payload_template")})}
|
||||||
];
|
];
|
||||||
fields(connector_resource_opts) ->
|
fields(connector_resource_opts) ->
|
||||||
emqx_connector_schema:resource_opts_fields();
|
emqx_connector_schema:resource_opts_fields();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{application, emqx_bridge_mysql, [
|
{application, emqx_bridge_mysql, [
|
||||||
{description, "EMQX Enterprise MySQL Bridge"},
|
{description, "EMQX Enterprise MySQL Bridge"},
|
||||||
{vsn, "0.1.5"},
|
{vsn, "0.1.6"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [
|
{applications, [
|
||||||
kernel,
|
kernel,
|
||||||
|
|
|
@ -146,7 +146,7 @@ fields(action_parameters) ->
|
||||||
[
|
[
|
||||||
{sql,
|
{sql,
|
||||||
mk(
|
mk(
|
||||||
binary(),
|
emqx_schema:template(),
|
||||||
#{desc => ?DESC("sql_template"), default => ?DEFAULT_SQL, format => <<"sql">>}
|
#{desc => ?DESC("sql_template"), default => ?DEFAULT_SQL, format => <<"sql">>}
|
||||||
)}
|
)}
|
||||||
];
|
];
|
||||||
|
|
|
@ -144,17 +144,27 @@ reset(Node, KeyPath, Opts) ->
|
||||||
%% @doc Called from build script.
|
%% @doc Called from build script.
|
||||||
%% TODO: move to a external escript after all refactoring is done
|
%% TODO: move to a external escript after all refactoring is done
|
||||||
dump_schema(Dir, SchemaModule) ->
|
dump_schema(Dir, SchemaModule) ->
|
||||||
%% TODO: Load all apps instead of only emqx_dashboard
|
%% Load all apps in ERL_LIBS
|
||||||
%% as this will help schemas that searches for apps with
|
%% as this will help schemas that searches for apps with
|
||||||
%% relevant schema definitions
|
%% relevant schema definitions
|
||||||
_ = application:load(emqx_dashboard),
|
lists:foreach(
|
||||||
|
fun(LibPath) ->
|
||||||
|
Lib = list_to_atom(lists:last(filename:split(LibPath))),
|
||||||
|
load(SchemaModule, Lib)
|
||||||
|
end,
|
||||||
|
string:lexemes(os:getenv("ERL_LIBS"), ":;")
|
||||||
|
),
|
||||||
ok = emqx_dashboard_desc_cache:init(),
|
ok = emqx_dashboard_desc_cache:init(),
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(Lang) ->
|
fun(Lang) ->
|
||||||
ok = gen_schema_json(Dir, SchemaModule, Lang)
|
ok = gen_schema_json(Dir, SchemaModule, Lang)
|
||||||
end,
|
end,
|
||||||
["en", "zh"]
|
["en", "zh"]
|
||||||
).
|
),
|
||||||
|
emqx_dashboard:save_dispatch_eterm(SchemaModule).
|
||||||
|
|
||||||
|
load(emqx_enterprise_schema, emqx_telemetry) -> ignore;
|
||||||
|
load(_, Lib) -> ok = application:load(Lib).
|
||||||
|
|
||||||
%% for scripts/spellcheck.
|
%% for scripts/spellcheck.
|
||||||
gen_schema_json(Dir, SchemaModule, Lang) ->
|
gen_schema_json(Dir, SchemaModule, Lang) ->
|
||||||
|
|
|
@ -28,11 +28,15 @@
|
||||||
%% Authorization
|
%% Authorization
|
||||||
-export([authorize/1]).
|
-export([authorize/1]).
|
||||||
|
|
||||||
|
-export([save_dispatch_eterm/1]).
|
||||||
|
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
-include_lib("emqx/include/http_api.hrl").
|
-include_lib("emqx/include/http_api.hrl").
|
||||||
-include_lib("emqx/include/emqx_release.hrl").
|
-include_lib("emqx/include/emqx_release.hrl").
|
||||||
|
-dialyzer({[no_opaque, no_match, no_return], [init_cache_dispatch/2, start_listeners/1]}).
|
||||||
|
|
||||||
-define(EMQX_MIDDLE, emqx_dashboard_middleware).
|
-define(EMQX_MIDDLE, emqx_dashboard_middleware).
|
||||||
|
-define(DISPATCH_FILE, "dispatch.eterm").
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Start/Stop Listeners
|
%% Start/Stop Listeners
|
||||||
|
@ -46,6 +50,42 @@ stop_listeners() ->
|
||||||
|
|
||||||
start_listeners(Listeners) ->
|
start_listeners(Listeners) ->
|
||||||
{ok, _} = application:ensure_all_started(minirest),
|
{ok, _} = application:ensure_all_started(minirest),
|
||||||
|
SwaggerSupport = emqx:get_config([dashboard, swagger_support], true),
|
||||||
|
InitDispatch = dispatch(),
|
||||||
|
{OkListeners, ErrListeners} =
|
||||||
|
lists:foldl(
|
||||||
|
fun({Name, Protocol, Bind, RanchOptions, ProtoOpts}, {OkAcc, ErrAcc}) ->
|
||||||
|
ok = init_cache_dispatch(Name, InitDispatch),
|
||||||
|
Options = #{
|
||||||
|
dispatch => InitDispatch,
|
||||||
|
swagger_support => SwaggerSupport,
|
||||||
|
protocol => Protocol,
|
||||||
|
protocol_options => ProtoOpts
|
||||||
|
},
|
||||||
|
Minirest = minirest_option(Options),
|
||||||
|
case minirest:start(Name, RanchOptions, Minirest) of
|
||||||
|
{ok, _} ->
|
||||||
|
?ULOG("Listener ~ts on ~ts started.~n", [
|
||||||
|
Name, emqx_listeners:format_bind(Bind)
|
||||||
|
]),
|
||||||
|
{[Name | OkAcc], ErrAcc};
|
||||||
|
{error, _Reason} ->
|
||||||
|
%% Don't record the reason because minirest already does(too much logs noise).
|
||||||
|
{OkAcc, [Name | ErrAcc]}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
{[], []},
|
||||||
|
listeners(ensure_ssl_cert(Listeners))
|
||||||
|
),
|
||||||
|
case ErrListeners of
|
||||||
|
[] ->
|
||||||
|
optvar:set(emqx_dashboard_listeners_ready, OkListeners),
|
||||||
|
ok;
|
||||||
|
_ ->
|
||||||
|
{error, ErrListeners}
|
||||||
|
end.
|
||||||
|
|
||||||
|
minirest_option(Options) ->
|
||||||
Authorization = {?MODULE, authorize},
|
Authorization = {?MODULE, authorize},
|
||||||
GlobalSpec = #{
|
GlobalSpec = #{
|
||||||
openapi => "3.0.0",
|
openapi => "3.0.0",
|
||||||
|
@ -68,42 +108,33 @@ start_listeners(Listeners) ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
BaseMinirest = #{
|
Base =
|
||||||
base_path => emqx_dashboard_swagger:base_path(),
|
#{
|
||||||
modules => minirest_api:find_api_modules(apps()),
|
base_path => emqx_dashboard_swagger:base_path(),
|
||||||
authorization => Authorization,
|
modules => minirest_api:find_api_modules(apps()),
|
||||||
log => audit_log_fun(),
|
authorization => Authorization,
|
||||||
security => [#{'basicAuth' => []}, #{'bearerAuth' => []}],
|
log => audit_log_fun(),
|
||||||
swagger_global_spec => GlobalSpec,
|
security => [#{'basicAuth' => []}, #{'bearerAuth' => []}],
|
||||||
dispatch => dispatch(),
|
swagger_global_spec => GlobalSpec,
|
||||||
middlewares => [?EMQX_MIDDLE, cowboy_router, cowboy_handler],
|
dispatch => static_dispatch(),
|
||||||
swagger_support => emqx:get_config([dashboard, swagger_support], true)
|
middlewares => [?EMQX_MIDDLE, cowboy_router, cowboy_handler],
|
||||||
},
|
swagger_support => true
|
||||||
{OkListeners, ErrListeners} =
|
},
|
||||||
lists:foldl(
|
maps:merge(Base, Options).
|
||||||
fun({Name, Protocol, Bind, RanchOptions, ProtoOpts}, {OkAcc, ErrAcc}) ->
|
|
||||||
Minirest = BaseMinirest#{protocol => Protocol, protocol_options => ProtoOpts},
|
%% save dispatch to priv dir.
|
||||||
case minirest:start(Name, RanchOptions, Minirest) of
|
save_dispatch_eterm(SchemaMod) ->
|
||||||
{ok, _} ->
|
Dir = code:priv_dir(emqx_dashboard),
|
||||||
?ULOG("Listener ~ts on ~ts started.~n", [
|
emqx_config:put([dashboard], #{i18n_lang => en, swagger_support => false}),
|
||||||
Name, emqx_listeners:format_bind(Bind)
|
os:putenv("SCHEMA_MOD", atom_to_list(SchemaMod)),
|
||||||
]),
|
DispatchFile = filename:join([Dir, ?DISPATCH_FILE]),
|
||||||
{[Name | OkAcc], ErrAcc};
|
io:format(user, "===< Generating: ~s~n", [DispatchFile]),
|
||||||
{error, _Reason} ->
|
#{dispatch := Dispatch} = generate_dispatch(),
|
||||||
%% Don't record the reason because minirest already does(too much logs noise).
|
IoData = io_lib:format("~p.~n", [Dispatch]),
|
||||||
{OkAcc, [Name | ErrAcc]}
|
ok = file:write_file(DispatchFile, IoData),
|
||||||
end
|
{ok, [SaveDispatch]} = file:consult(DispatchFile),
|
||||||
end,
|
SaveDispatch =/= Dispatch andalso erlang:error("bad dashboard dispatch.eterm file generated"),
|
||||||
{[], []},
|
ok.
|
||||||
listeners(ensure_ssl_cert(Listeners))
|
|
||||||
),
|
|
||||||
case ErrListeners of
|
|
||||||
[] ->
|
|
||||||
optvar:set(emqx_dashboard_listeners_ready, OkListeners),
|
|
||||||
ok;
|
|
||||||
_ ->
|
|
||||||
{error, ErrListeners}
|
|
||||||
end.
|
|
||||||
|
|
||||||
stop_listeners(Listeners) ->
|
stop_listeners(Listeners) ->
|
||||||
optvar:unset(emqx_dashboard_listeners_ready),
|
optvar:unset(emqx_dashboard_listeners_ready),
|
||||||
|
@ -127,6 +158,34 @@ wait_for_listeners() ->
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% internal
|
%% internal
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
init_cache_dispatch(Name, Dispatch0) ->
|
||||||
|
Dispatch1 = [{_, _, Rules}] = trails:single_host_compile(Dispatch0),
|
||||||
|
FileName = filename:join(code:priv_dir(emqx_dashboard), ?DISPATCH_FILE),
|
||||||
|
Dispatch2 =
|
||||||
|
case file:consult(FileName) of
|
||||||
|
{ok, [[{Host, Path, CacheRules}]]} ->
|
||||||
|
Trails = trails:trails([{cowboy_swagger_handler, #{server => 'http:dashboard'}}]),
|
||||||
|
[{_, _, SwaggerRules}] = trails:single_host_compile(Trails),
|
||||||
|
[{Host, Path, CacheRules ++ SwaggerRules ++ Rules}];
|
||||||
|
{error, _} ->
|
||||||
|
Dispatch1
|
||||||
|
end,
|
||||||
|
persistent_term:put(Name, Dispatch2).
|
||||||
|
|
||||||
|
generate_dispatch() ->
|
||||||
|
Options = #{
|
||||||
|
dispatch => [],
|
||||||
|
swagger_support => false,
|
||||||
|
protocol => http,
|
||||||
|
protocol_options => proto_opts(#{})
|
||||||
|
},
|
||||||
|
Minirest = minirest_option(Options),
|
||||||
|
minirest:generate_dispatch(Minirest).
|
||||||
|
|
||||||
|
dispatch() ->
|
||||||
|
static_dispatch() ++ dynamic_dispatch().
|
||||||
|
|
||||||
apps() ->
|
apps() ->
|
||||||
[
|
[
|
||||||
|
@ -287,9 +346,6 @@ ensure_ssl_cert(Listeners = #{https := Https0 = #{ssl_options := SslOpts}}) ->
|
||||||
ensure_ssl_cert(Listeners) ->
|
ensure_ssl_cert(Listeners) ->
|
||||||
Listeners.
|
Listeners.
|
||||||
|
|
||||||
dispatch() ->
|
|
||||||
static_dispatch() ++ dynamic_dispatch().
|
|
||||||
|
|
||||||
static_dispatch() ->
|
static_dispatch() ->
|
||||||
StaticFiles = ["/editor.worker.js", "/json.worker.js", "/version"],
|
StaticFiles = ["/editor.worker.js", "/json.worker.js", "/version"],
|
||||||
[
|
[
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
|
|
||||||
-include("emqx_dashboard.hrl").
|
-include("emqx_dashboard.hrl").
|
||||||
|
|
||||||
|
-dialyzer({nowarn_function, [start/2]}).
|
||||||
|
|
||||||
start(_StartType, _StartArgs) ->
|
start(_StartType, _StartArgs) ->
|
||||||
Tables = lists:append([
|
Tables = lists:append([
|
||||||
emqx_dashboard_admin:create_tables(),
|
emqx_dashboard_admin:create_tables(),
|
||||||
|
|
|
@ -21,28 +21,14 @@
|
||||||
-export([execute/2]).
|
-export([execute/2]).
|
||||||
|
|
||||||
execute(Req, Env) ->
|
execute(Req, Env) ->
|
||||||
case check_dispatch_ready(Env) of
|
add_cors_flag(Req, Env).
|
||||||
true -> add_cors_flag(Req, Env);
|
|
||||||
false -> {stop, cowboy_req:reply(503, #{<<"retry-after">> => <<"15">>}, Req)}
|
|
||||||
end.
|
|
||||||
|
|
||||||
add_cors_flag(Req, Env) ->
|
add_cors_flag(Req, Env) ->
|
||||||
CORS = emqx_conf:get([dashboard, cors], false),
|
CORS = emqx_conf:get([dashboard, cors], false),
|
||||||
Origin = cowboy_req:header(<<"origin">>, Req, undefined),
|
case CORS andalso cowboy_req:header(<<"origin">>, Req, undefined) =/= undefined of
|
||||||
case CORS andalso Origin =/= undefined of
|
|
||||||
false ->
|
false ->
|
||||||
{ok, Req, Env};
|
{ok, Req, Env};
|
||||||
true ->
|
true ->
|
||||||
Req2 = cowboy_req:set_resp_header(<<"Access-Control-Allow-Origin">>, <<"*">>, Req),
|
Req2 = cowboy_req:set_resp_header(<<"Access-Control-Allow-Origin">>, <<"*">>, Req),
|
||||||
{ok, Req2, Env}
|
{ok, Req2, Env}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
check_dispatch_ready(Env) ->
|
|
||||||
case maps:is_key(options, Env) of
|
|
||||||
false ->
|
|
||||||
true;
|
|
||||||
true ->
|
|
||||||
%% dashboard should always ready, if not, is_ready/1 will block until ready.
|
|
||||||
%% if not ready, dashboard will return 503.
|
|
||||||
emqx_dashboard_listener:is_ready(timer:seconds(20))
|
|
||||||
end.
|
|
||||||
|
|
|
@ -348,14 +348,7 @@ compose_filters(undefined, Filter2) ->
|
||||||
compose_filters(Filter1, undefined) ->
|
compose_filters(Filter1, undefined) ->
|
||||||
Filter1;
|
Filter1;
|
||||||
compose_filters(Filter1, Filter2) ->
|
compose_filters(Filter1, Filter2) ->
|
||||||
fun(Request, RequestMeta) ->
|
[Filter1, Filter2].
|
||||||
case Filter1(Request, RequestMeta) of
|
|
||||||
{ok, Request1} ->
|
|
||||||
Filter2(Request1, RequestMeta);
|
|
||||||
Response ->
|
|
||||||
Response
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Private functions
|
%% Private functions
|
||||||
|
|
|
@ -69,6 +69,9 @@ init_per_suite(Config) ->
|
||||||
],
|
],
|
||||||
#{work_dir => emqx_cth_suite:work_dir(Config)}
|
#{work_dir => emqx_cth_suite:work_dir(Config)}
|
||||||
),
|
),
|
||||||
|
_ = emqx_conf_schema:roots(),
|
||||||
|
ok = emqx_dashboard_desc_cache:init(),
|
||||||
|
emqx_dashboard:save_dispatch_eterm(emqx_conf:schema_module()),
|
||||||
emqx_common_test_http:create_default_app(),
|
emqx_common_test_http:create_default_app(),
|
||||||
[{suite_apps, SuiteApps} | Config].
|
[{suite_apps, SuiteApps} | Config].
|
||||||
|
|
||||||
|
@ -87,6 +90,79 @@ t_overview(_) ->
|
||||||
|| Overview <- ?OVERVIEWS
|
|| Overview <- ?OVERVIEWS
|
||||||
].
|
].
|
||||||
|
|
||||||
|
t_dashboard_restart(Config) ->
|
||||||
|
emqx_config:put([dashboard], #{
|
||||||
|
i18n_lang => en,
|
||||||
|
swagger_support => true,
|
||||||
|
listeners =>
|
||||||
|
#{
|
||||||
|
http =>
|
||||||
|
#{
|
||||||
|
inet6 => false,
|
||||||
|
bind => 18083,
|
||||||
|
ipv6_v6only => false,
|
||||||
|
send_timeout => 10000,
|
||||||
|
num_acceptors => 8,
|
||||||
|
max_connections => 512,
|
||||||
|
backlog => 1024,
|
||||||
|
proxy_header => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
application:stop(emqx_dashboard),
|
||||||
|
application:start(emqx_dashboard),
|
||||||
|
Name = 'http:dashboard',
|
||||||
|
t_overview(Config),
|
||||||
|
[{'_', [], Rules}] = Dispatch = persistent_term:get(Name),
|
||||||
|
%% complete dispatch has more than 150 rules.
|
||||||
|
?assertNotMatch([{[], [], cowboy_static, _} | _], Rules),
|
||||||
|
?assert(erlang:length(Rules) > 150),
|
||||||
|
CheckRules = fun(Tag) ->
|
||||||
|
[{'_', [], NewRules}] = persistent_term:get(Name, Tag),
|
||||||
|
?assertEqual(length(Rules), length(NewRules), Tag),
|
||||||
|
?assertEqual(lists:sort(Rules), lists:sort(NewRules), Tag)
|
||||||
|
end,
|
||||||
|
?check_trace(
|
||||||
|
?wait_async_action(
|
||||||
|
begin
|
||||||
|
ok = application:stop(emqx_dashboard),
|
||||||
|
?assertEqual(Dispatch, persistent_term:get(Name)),
|
||||||
|
ok = application:start(emqx_dashboard),
|
||||||
|
%% After we restart the dashboard, the dispatch rules should be the same.
|
||||||
|
CheckRules(step_1)
|
||||||
|
end,
|
||||||
|
#{?snk_kind := regenerate_minirest_dispatch},
|
||||||
|
30_000
|
||||||
|
),
|
||||||
|
fun(Trace) ->
|
||||||
|
?assertMatch([#{i18n_lang := en}], ?of_kind(regenerate_minirest_dispatch, Trace)),
|
||||||
|
%% The dispatch is updated after being regenerated.
|
||||||
|
CheckRules(step_2)
|
||||||
|
end
|
||||||
|
),
|
||||||
|
t_overview(Config),
|
||||||
|
?check_trace(
|
||||||
|
?wait_async_action(
|
||||||
|
begin
|
||||||
|
%% erase to mock the initial dashboard startup.
|
||||||
|
persistent_term:erase(Name),
|
||||||
|
ok = application:stop(emqx_dashboard),
|
||||||
|
ok = application:start(emqx_dashboard),
|
||||||
|
ct:sleep(800),
|
||||||
|
%% regenerate the dispatch rules again
|
||||||
|
CheckRules(step_3)
|
||||||
|
end,
|
||||||
|
#{?snk_kind := regenerate_minirest_dispatch},
|
||||||
|
30_000
|
||||||
|
),
|
||||||
|
fun(Trace) ->
|
||||||
|
?assertMatch([#{i18n_lang := en}], ?of_kind(regenerate_minirest_dispatch, Trace)),
|
||||||
|
CheckRules(step_4)
|
||||||
|
end
|
||||||
|
),
|
||||||
|
t_overview(Config),
|
||||||
|
ok.
|
||||||
|
|
||||||
t_admins_add_delete(_) ->
|
t_admins_add_delete(_) ->
|
||||||
mnesia:clear_table(?ADMIN),
|
mnesia:clear_table(?ADMIN),
|
||||||
Desc = <<"simple description">>,
|
Desc = <<"simple description">>,
|
||||||
|
@ -196,28 +272,41 @@ t_disable_swagger_json(_Config) ->
|
||||||
{ok, {{"HTTP/1.1", 200, "OK"}, __, _}},
|
{ok, {{"HTTP/1.1", 200, "OK"}, __, _}},
|
||||||
httpc:request(get, {Url, []}, [], [{body_format, binary}])
|
httpc:request(get, {Url, []}, [], [{body_format, binary}])
|
||||||
),
|
),
|
||||||
|
|
||||||
DashboardCfg = emqx:get_raw_config([dashboard]),
|
DashboardCfg = emqx:get_raw_config([dashboard]),
|
||||||
DashboardCfg2 = DashboardCfg#{<<"swagger_support">> => false},
|
|
||||||
emqx:update_config([dashboard], DashboardCfg2),
|
|
||||||
?retry(
|
|
||||||
_Sleep = 1000,
|
|
||||||
_Attempts = 5,
|
|
||||||
?assertMatch(
|
|
||||||
{ok, {{"HTTP/1.1", 404, "Not Found"}, _, _}},
|
|
||||||
httpc:request(get, {Url, []}, [], [{body_format, binary}])
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
DashboardCfg3 = DashboardCfg#{<<"swagger_support">> => true},
|
?check_trace(
|
||||||
emqx:update_config([dashboard], DashboardCfg3),
|
?wait_async_action(
|
||||||
?retry(
|
begin
|
||||||
_Sleep0 = 1000,
|
DashboardCfg2 = DashboardCfg#{<<"swagger_support">> => false},
|
||||||
_Attempts0 = 5,
|
emqx:update_config([dashboard], DashboardCfg2)
|
||||||
?assertMatch(
|
end,
|
||||||
{ok, {{"HTTP/1.1", 200, "OK"}, __, _}},
|
#{?snk_kind := regenerate_minirest_dispatch},
|
||||||
httpc:request(get, {Url, []}, [], [{body_format, binary}])
|
30_000
|
||||||
)
|
),
|
||||||
|
fun(Trace) ->
|
||||||
|
?assertMatch([#{i18n_lang := en}], ?of_kind(regenerate_minirest_dispatch, Trace)),
|
||||||
|
?assertMatch(
|
||||||
|
{ok, {{"HTTP/1.1", 404, "Not Found"}, _, _}},
|
||||||
|
httpc:request(get, {Url, []}, [], [{body_format, binary}])
|
||||||
|
)
|
||||||
|
end
|
||||||
|
),
|
||||||
|
?check_trace(
|
||||||
|
?wait_async_action(
|
||||||
|
begin
|
||||||
|
DashboardCfg3 = DashboardCfg#{<<"swagger_support">> => true},
|
||||||
|
emqx:update_config([dashboard], DashboardCfg3)
|
||||||
|
end,
|
||||||
|
#{?snk_kind := regenerate_minirest_dispatch},
|
||||||
|
30_000
|
||||||
|
),
|
||||||
|
fun(Trace) ->
|
||||||
|
?assertMatch([#{i18n_lang := en}], ?of_kind(regenerate_minirest_dispatch, Trace)),
|
||||||
|
?assertMatch(
|
||||||
|
{ok, {{"HTTP/1.1", 200, "OK"}, __, _}},
|
||||||
|
httpc:request(get, {Url, []}, [], [{body_format, binary}])
|
||||||
|
)
|
||||||
|
end
|
||||||
),
|
),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
api_spec/0,
|
api_spec/0,
|
||||||
|
check_api_schema/2,
|
||||||
paths/0,
|
paths/0,
|
||||||
schema/1,
|
schema/1,
|
||||||
namespace/0
|
namespace/0
|
||||||
|
@ -40,11 +41,12 @@ namespace() -> "dashboard_sso".
|
||||||
api_spec() ->
|
api_spec() ->
|
||||||
emqx_dashboard_swagger:spec(?MODULE, #{
|
emqx_dashboard_swagger:spec(?MODULE, #{
|
||||||
translate_body => false,
|
translate_body => false,
|
||||||
check_schema => fun(Params, Meta) ->
|
check_schema => fun ?MODULE:check_api_schema/2
|
||||||
emqx_dashboard_swagger:validate_content_type(Params, Meta, <<"application/xml">>)
|
|
||||||
end
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
check_api_schema(Params, Meta) ->
|
||||||
|
emqx_dashboard_swagger:validate_content_type(Params, Meta, <<"application/xml">>).
|
||||||
|
|
||||||
paths() ->
|
paths() ->
|
||||||
[
|
[
|
||||||
"/sso/saml/acs",
|
"/sso/saml/acs",
|
||||||
|
|
|
@ -815,7 +815,8 @@ fields(mqueue_message) ->
|
||||||
fields(requested_client_fields) ->
|
fields(requested_client_fields) ->
|
||||||
%% NOTE: some Client fields actually returned in response are missing in schema:
|
%% NOTE: some Client fields actually returned in response are missing in schema:
|
||||||
%% enable_authn, is_persistent, listener, peerport
|
%% enable_authn, is_persistent, listener, peerport
|
||||||
ClientFields = [element(1, F) || F <- fields(client)],
|
ClientFields0 = [element(1, F) || F <- fields(client)],
|
||||||
|
ClientFields = [client_attrs | ClientFields0],
|
||||||
[
|
[
|
||||||
{fields,
|
{fields,
|
||||||
hoconsc:mk(
|
hoconsc:mk(
|
||||||
|
|
|
@ -1032,6 +1032,7 @@ t_query_multiple_clients_urlencode(_) ->
|
||||||
t_query_clients_with_fields(_) ->
|
t_query_clients_with_fields(_) ->
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
TCBin = atom_to_binary(?FUNCTION_NAME),
|
TCBin = atom_to_binary(?FUNCTION_NAME),
|
||||||
|
APIPort = 18083,
|
||||||
ClientId = <<TCBin/binary, "_client">>,
|
ClientId = <<TCBin/binary, "_client">>,
|
||||||
Username = <<TCBin/binary, "_user">>,
|
Username = <<TCBin/binary, "_user">>,
|
||||||
{ok, C} = emqtt:start_link(#{clientid => ClientId, username => Username}),
|
{ok, C} = emqtt:start_link(#{clientid => ClientId, username => Username}),
|
||||||
|
@ -1040,6 +1041,13 @@ t_query_clients_with_fields(_) ->
|
||||||
|
|
||||||
Auth = emqx_mgmt_api_test_util:auth_header_(),
|
Auth = emqx_mgmt_api_test_util:auth_header_(),
|
||||||
?assertEqual([#{<<"clientid">> => ClientId}], get_clients_all_fields(Auth, "fields=clientid")),
|
?assertEqual([#{<<"clientid">> => ClientId}], get_clients_all_fields(Auth, "fields=clientid")),
|
||||||
|
?assertMatch(
|
||||||
|
{ok,
|
||||||
|
{{_, 200, _}, _, #{
|
||||||
|
<<"data">> := [#{<<"client_attrs">> := #{}}]
|
||||||
|
}}},
|
||||||
|
list_request(APIPort, "fields=client_attrs")
|
||||||
|
),
|
||||||
?assertEqual(
|
?assertEqual(
|
||||||
[#{<<"clientid">> => ClientId, <<"username">> => Username}],
|
[#{<<"clientid">> => ClientId, <<"username">> => Username}],
|
||||||
get_clients_all_fields(Auth, "fields=clientid,username")
|
get_clients_all_fields(Auth, "fields=clientid,username")
|
||||||
|
@ -1072,6 +1080,7 @@ get_clients(Auth, Qs, ExpectError, ClientIdOnly) ->
|
||||||
Resp = emqx_mgmt_api_test_util:request_api(get, ClientsPath, Qs, Auth),
|
Resp = emqx_mgmt_api_test_util:request_api(get, ClientsPath, Qs, Auth),
|
||||||
case ExpectError of
|
case ExpectError of
|
||||||
false ->
|
false ->
|
||||||
|
ct:pal("get clients response:\n ~p", [Resp]),
|
||||||
{ok, Body} = Resp,
|
{ok, Body} = Resp,
|
||||||
#{<<"data">> := Clients} = emqx_utils_json:decode(Body),
|
#{<<"data">> := Clients} = emqx_utils_json:decode(Body),
|
||||||
case ClientIdOnly of
|
case ClientIdOnly of
|
||||||
|
|
|
@ -155,6 +155,7 @@
|
||||||
replace/4,
|
replace/4,
|
||||||
regex_match/2,
|
regex_match/2,
|
||||||
regex_replace/3,
|
regex_replace/3,
|
||||||
|
regex_extract/2,
|
||||||
ascii/1,
|
ascii/1,
|
||||||
find/2,
|
find/2,
|
||||||
find/3,
|
find/3,
|
||||||
|
@ -805,6 +806,8 @@ regex_match(Str, RE) -> emqx_variform_bif:regex_match(Str, RE).
|
||||||
|
|
||||||
regex_replace(SrcStr, RE, RepStr) -> emqx_variform_bif:regex_replace(SrcStr, RE, RepStr).
|
regex_replace(SrcStr, RE, RepStr) -> emqx_variform_bif:regex_replace(SrcStr, RE, RepStr).
|
||||||
|
|
||||||
|
regex_extract(SrcStr, RE) -> emqx_variform_bif:regex_extract(SrcStr, RE).
|
||||||
|
|
||||||
ascii(Char) -> emqx_variform_bif:ascii(Char).
|
ascii(Char) -> emqx_variform_bif:ascii(Char).
|
||||||
|
|
||||||
find(S, P) -> emqx_variform_bif:find(S, P).
|
find(S, P) -> emqx_variform_bif:find(S, P).
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Significantly increased the startup speed of EMQX dashboard listener.
|
|
@ -1,10 +1,10 @@
|
||||||
Respcet `clientid_prefix` config for MQTT bridges.
|
Respcet `clientid_prefix` config for MQTT bridges.
|
||||||
|
|
||||||
As of version 5.4.1, EMQX limits MQTT Client ID lengths to 23 bytes.
|
As of version 5.4.1, EMQX limits MQTT client ID lengths to 23 bytes.
|
||||||
Previously, the system included the `clientid_prefix` in the hash calculation of the original, excessively long Client ID, thereby impacting the resulting shortened ID.
|
Previously, the system included the `clientid_prefix` in the hash calculation of the original unique, but long client ID, thereby impacting the resulting shortened ID.
|
||||||
|
|
||||||
Change Details:
|
Change Details:
|
||||||
- Without Prefix: Behavior remains unchanged; EMQX will hash the entire Client ID into a 23-byte space (when longer than 23 bytes).
|
- Without Prefix: Behavior remains unchanged; EMQX will hash the long (> 23 bytes) client ID into a 23-byte space.
|
||||||
- With Prefix:
|
- With Prefix:
|
||||||
- Prefix no more than 19 bytes: The prefix is preserved, and the remaining suffix is hashed into a 4-byte space.
|
- Prefix no more than 19 bytes: The prefix is preserved, and the client ID is hashed into a 4-byte space capping the length within 23 bytes.
|
||||||
- Prefix is 20 or more bytes: EMQX no longer attempts to shorten the Client ID, respecting the configured prefix in its entirety.
|
- Prefix is 20 or more bytes: EMQX no longer attempts to shorten the client ID, respecting the configured prefix in its entirety.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Improved the logged error messages when an HTTP authorization request with an unsupported content-type header is returned.
|
2
mix.exs
2
mix.exs
|
@ -58,7 +58,7 @@ defmodule EMQXUmbrella.MixProject do
|
||||||
{:ekka, github: "emqx/ekka", tag: "0.19.4", override: true},
|
{:ekka, github: "emqx/ekka", tag: "0.19.4", override: true},
|
||||||
{:gen_rpc, github: "emqx/gen_rpc", tag: "3.3.1", override: true},
|
{:gen_rpc, github: "emqx/gen_rpc", tag: "3.3.1", override: true},
|
||||||
{:grpc, github: "emqx/grpc-erl", tag: "0.6.12", override: true},
|
{:grpc, github: "emqx/grpc-erl", tag: "0.6.12", override: true},
|
||||||
{:minirest, github: "emqx/minirest", tag: "1.4.1", override: true},
|
{:minirest, github: "emqx/minirest", tag: "1.4.3", override: true},
|
||||||
{:ecpool, github: "emqx/ecpool", tag: "0.5.7", override: true},
|
{:ecpool, github: "emqx/ecpool", tag: "0.5.7", override: true},
|
||||||
{:replayq, github: "emqx/replayq", tag: "0.3.8", override: true},
|
{:replayq, github: "emqx/replayq", tag: "0.3.8", override: true},
|
||||||
{:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},
|
{:pbkdf2, github: "emqx/erlang-pbkdf2", tag: "2.0.4", override: true},
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
{ekka, {git, "https://github.com/emqx/ekka", {tag, "0.19.4"}}},
|
{ekka, {git, "https://github.com/emqx/ekka", {tag, "0.19.4"}}},
|
||||||
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "3.3.1"}}},
|
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "3.3.1"}}},
|
||||||
{grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.12"}}},
|
{grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.12"}}},
|
||||||
{minirest, {git, "https://github.com/emqx/minirest", {tag, "1.4.1"}}},
|
{minirest, {git, "https://github.com/emqx/minirest", {tag, "1.4.3"}}},
|
||||||
{ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.7"}}},
|
{ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.7"}}},
|
||||||
{replayq, {git, "https://github.com/emqx/replayq.git", {tag, "0.3.8"}}},
|
{replayq, {git, "https://github.com/emqx/replayq.git", {tag, "0.3.8"}}},
|
||||||
{pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}},
|
{pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}},
|
||||||
|
|
Loading…
Reference in New Issue