From f033fad7b37caaa5e7eb4ee4ecd0ab51ed460037 Mon Sep 17 00:00:00 2001 From: JianBo He Date: Fri, 19 Nov 2021 11:20:48 +0800 Subject: [PATCH] refactor(gw): deps on emqx_dasboard_swagger --- apps/emqx_authn/src/emqx_authn_api.erl | 9 + .../src/emqx_gateway_api_authn.erl | 324 ++++++++--- .../src/emqx_gateway_api_listeners.erl | 513 ++++++++++-------- apps/emqx_gateway/src/emqx_gateway_http.erl | 29 +- .../src/emqx_gateway_insta_sup.erl | 22 +- apps/emqx_gateway/src/emqx_gateway_utils.erl | 19 + .../emqx_gateway/test/emqx_sn_frame_SUITE.erl | 2 +- 7 files changed, 597 insertions(+), 321 deletions(-) diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index bef27f99d..de3e0b5d3 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -65,6 +65,15 @@ , response_users_example/0 ]). +%% export these funcs for gateway +-export([ list_users/3 + , add_user/3 + , delete_user/3 + , find_user/3 + , update_user/4 + , serialize_error/1 + ]). + api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). diff --git a/apps/emqx_gateway/src/emqx_gateway_api_authn.erl b/apps/emqx_gateway/src/emqx_gateway_api_authn.erl index 4cd2d8867..e529bf2ae 100644 --- a/apps/emqx_gateway/src/emqx_gateway_api_authn.erl +++ b/apps/emqx_gateway/src/emqx_gateway_api_authn.erl @@ -18,21 +18,34 @@ -behaviour(minirest_api). +-include_lib("typerefl/include/types.hrl"). + +-define(BAD_REQUEST, 'BAD_REQUEST'). +-define(NOT_FOUND, 'NOT_FOUND'). +-define(INTERNAL_ERROR, 'INTERNAL_SERVER_ERROR'). + +-import(hoconsc, [mk/2, ref/2]). +-import(emqx_dashboard_swagger, [error_codes/2]). + -import(emqx_gateway_http, [ return_http_error/2 - , schema_bad_request/0 - , schema_not_found/0 - , schema_internal_error/0 - , schema_no_content/0 , with_gateway/2 + , with_authn/2 , checks/2 ]). -%% minirest behaviour callbacks --export([api_spec/0]). +%% minirest/dashbaord_swagger behaviour callbacks +-export([ api_spec/0 + , paths/0 + , schema/1 + ]). %% http handlers --export([authn/2]). +-export([ authn/2 + , users/2 + , users_insta/2 + , import_users/2 + ]). %% internal export for emqx_gateway_api_listeners module -export([schema_authn/0]). @@ -42,10 +55,13 @@ %%-------------------------------------------------------------------- api_spec() -> - {metadata(apis()), []}. + emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). -apis() -> - [ {"/gateway/:name/authentication", authn} +paths() -> + [ "/gateway/:name/authentication" + , "/gateway/:name/authentication/users" + , "/gateway/:name/authentication/users/:uid" + , "/gateway/:name/authentication/import_users" ]. %%-------------------------------------------------------------------- @@ -83,87 +99,245 @@ authn(delete, #{bindings := #{name := Name0}}) -> {204} end). +users(get, #{bindings := #{name := Name0}, query_string := Qs}) -> + with_authn(Name0, fun(_GwName, #{id := AuthId, + chain_name := ChainName}) -> + emqx_authn_api:list_users(ChainName, AuthId, page_pramas(Qs)) + end); +users(post, #{bindings := #{name := Name0}, + body := Body}) -> + with_authn(Name0, fun(_GwName, #{id := AuthId, + chain_name := ChainName}) -> + emqx_authn_api:add_user(ChainName, AuthId, Body) + end). + +users_insta(get, #{bindings := #{name := Name0, uid := UserId}}) -> + with_authn(Name0, fun(_GwName, #{id := AuthId, + chain_name := ChainName}) -> + emqx_authn_api:find_user(ChainName, AuthId, UserId) + end); +users_insta(put, #{bindings := #{name := Name0, uid := UserId}, + body := Body}) -> + with_authn(Name0, fun(_GwName, #{id := AuthId, + chain_name := ChainName}) -> + emqx_authn_api:update_user(ChainName, AuthId, UserId, Body) + end); +users_insta(delete, #{bindings := #{name := Name0, uid := UserId}}) -> + with_authn(Name0, fun(_GwName, #{id := AuthId, + chain_name := ChainName}) -> + emqx_authn_api:delete_user(ChainName, AuthId, UserId) + end). + +import_users(post, #{bindings := #{name := Name0}, + body := Body}) -> + with_authn(Name0, fun(_GwName, #{id := AuthId, + chain_name := ChainName}) -> + case maps:get(<<"filename">>, Body, undefined) of + undefined -> + emqx_authn_api:serialize_error({missing_parameter, filename}); + Filename -> + case emqx_authentication:import_users( + ChainName, AuthId, Filename) of + ok -> {204}; + {error, Reason} -> + emqx_authn_api:serialize_error(Reason) + end + end + end). + +%%-------------------------------------------------------------------- +%% Utils + +page_pramas(Qs) -> + maps:with([<<"page">>, <<"limit">>], Qs). + %%-------------------------------------------------------------------- %% Swagger defines %%-------------------------------------------------------------------- -metadata(APIs) -> - metadata(APIs, []). -metadata([], APIAcc) -> - lists:reverse(APIAcc); -metadata([{Path, Fun}|More], APIAcc) -> - Methods = [get, post, put, delete, patch], - Mds = lists:foldl(fun(M, Acc) -> - try - Acc#{M => swagger(Path, M)} - catch - error : function_clause -> - Acc - end - end, #{}, Methods), - metadata(More, [{Path, Mds, Fun} | APIAcc]). -swagger("/gateway/:name/authentication", get) -> - #{ description => <<"Get the gateway authentication">> - , parameters => params_gateway_name_in_path() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"200">> => schema_authn() - , <<"204">> => schema_no_content() - } +schema("/gateway/:name/authentication") -> + #{ 'operationId' => authn, + get => + #{ description => <<"Get the gateway authentication">> + , parameters => params_gateway_name_in_path() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 200 => schema_authn() + , 204 => <<"Authentication does not initiated">> + } + }, + put => + #{ description => <<"Update authentication for the gateway">> + , parameters => params_gateway_name_in_path() + , requestBody => schema_authn() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 204 => <<"Updated">> %% XXX: ??? return the updated object + } + }, + post => + #{ description => <<"Add authentication for the gateway">> + , parameters => params_gateway_name_in_path() + , requestBody => schema_authn() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 204 => <<"Added">> + } + }, + delete => + #{ description => <<"Remove the gateway authentication">> + , parameters => params_gateway_name_in_path() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 204 => <<"Deleted">> + } + } }; -swagger("/gateway/:name/authentication", put) -> - #{ description => <<"Update authentication for the gateway">> - , parameters => params_gateway_name_in_path() - , requestBody => schema_authn() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"204">> => schema_no_content() - } +schema("/gateway/:name/authentication/users") -> + #{ 'operationId' => users + , get => + #{ description => <<"Get the users for the authentication">> + , parameters => params_gateway_name_in_path() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 200 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_user), + emqx_authn_api:response_user_examples()) + } + }, + post => + #{ description => <<"Add user for the authentication">> + , parameters => params_gateway_name_in_path() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 201 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_user), + emqx_authn_api:response_user_examples()) + } + } }; -swagger("/gateway/:name/authentication", post) -> - #{ description => <<"Add authentication for the gateway">> - , parameters => params_gateway_name_in_path() - , requestBody => schema_authn() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"204">> => schema_no_content() - } +schema("/gateway/:name/authentication/users/:uid") -> + #{ 'operationId' => users_insta + , get => + #{ description => <<"Get user info from the gateway " + "authentication">> + , parameters => params_gateway_name_in_path() ++ + params_userid_in_path() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 200 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_user), + emqx_authn_api:response_user_examples()) + } + }, + put => + #{ description => <<"Update the user info for the gateway " + "authentication">> + , parameters => params_gateway_name_in_path() ++ + params_userid_in_path() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 200 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_user), + emqx_authn_api:response_user_examples()) + } + }, + delete => + #{ description => <<"Delete the user for the gateway " + "authentication">> + , parameters => params_gateway_name_in_path() ++ + params_userid_in_path() ++ + params_paging_in_qs() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 200 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_user), + emqx_authn_api:response_user_examples()) + } + } }; -swagger("/gateway/:name/authentication", delete) -> - #{ description => <<"Remove the gateway authentication">> - , parameters => params_gateway_name_in_path() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"204">> => schema_no_content() - } +schema("/gateway/:name/authentication/import_users") -> + #{ 'operationId' => import_users + , post => + #{ description => <<"Import users into the gateway authentication">> + , parameters => params_gateway_name_in_path() + , requestBody => emqx_dashboard_swagger:schema_with_examples( + ref(emqx_authn_api, request_import_users), + emqx_authn_api:request_import_users_examples() + ) + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + %% XXX: Put a hint message into 204 return ? + , 204 => <<"Imported">> + } + } }. %%-------------------------------------------------------------------- %% params defines params_gateway_name_in_path() -> - [#{ name => name - , in => path - , schema => #{type => string} - , required => true - }]. + [{name, + mk(binary(), + #{ in => path + , desc => <<"Gateway Name">> + })} + ]. + +params_userid_in_path() -> + [{uid, mk(binary(), + #{ in => path + , desc => <<"User ID">> + })} + ]. + +params_paging_in_qs() -> + [{page, mk(integer(), + #{ in => query + , desc => <<"Page Number">> + })}, + {limit, mk(integer(), + #{ in => query + , desc => <<"Page Limit">> + })} + ]. %%-------------------------------------------------------------------- %% schemas schema_authn() -> - #{ description => <<"OK">> - , content => #{ - 'application/json' => #{ - schema => minirest:ref(<<"AuthenticatorInstance">>) - }} - }. + emqx_dashboard_swagger:schema_with_examples( + emqx_authn_schema:authenticator_type(), + emqx_authn_api:authenticator_examples() + ). diff --git a/apps/emqx_gateway/src/emqx_gateway_api_listeners.erl b/apps/emqx_gateway/src/emqx_gateway_api_listeners.erl index 0ab054df8..25bfc5b5c 100644 --- a/apps/emqx_gateway/src/emqx_gateway_api_listeners.erl +++ b/apps/emqx_gateway/src/emqx_gateway_api_listeners.erl @@ -18,20 +18,32 @@ -behaviour(minirest_api). +-include_lib("typerefl/include/types.hrl"). + +-define(BAD_REQUEST, 'BAD_REQUEST'). +-define(NOT_FOUND, 'NOT_FOUND'). +-define(INTERNAL_ERROR, 'INTERNAL_SERVER_ERROR'). + +-import(hoconsc, [mk/2, ref/1, ref/2]). +-import(emqx_dashboard_swagger, [error_codes/2]). + -import(emqx_gateway_http, [ return_http_error/2 - , schema_bad_request/0 - , schema_not_found/0 - , schema_internal_error/0 - , schema_no_content/0 , with_gateway/2 , checks/2 ]). -import(emqx_gateway_api_authn, [schema_authn/0]). -%% minirest behaviour callbacks --export([api_spec/0]). +%% minirest/dashbaord_swagger behaviour callbacks +-export([ api_spec/0 + , paths/0 + , schema/1 + ]). + +-export([ roots/0 + , fields/1 + ]). %% http handlers -export([ listeners/2 @@ -44,12 +56,12 @@ %%-------------------------------------------------------------------- api_spec() -> - {metadata(apis()), []}. + emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). -apis() -> - [ {"/gateway/:name/listeners", listeners} - , {"/gateway/:name/listeners/:id", listeners_insta} - , {"/gateway/:name/listeners/:id/authentication", listeners_insta_authn} +paths() -> + [ "/gateway/:name/listeners" + , "/gateway/:name/listeners/:id" + , "/gateway/:name/listeners/:id/authentication" ]. %%-------------------------------------------------------------------- @@ -149,219 +161,228 @@ listeners_insta_authn(delete, #{bindings := #{name := Name0, %% Swagger defines %%-------------------------------------------------------------------- -metadata(APIs) -> - metadata(APIs, []). -metadata([], APIAcc) -> - lists:reverse(APIAcc); -metadata([{Path, Fun}|More], APIAcc) -> - Methods = [get, post, put, delete, patch], - Mds = lists:foldl(fun(M, Acc) -> - try - Acc#{M => swagger(Path, M)} - catch - error : function_clause -> - Acc - end - end, #{}, Methods), - metadata(More, [{Path, Mds, Fun} | APIAcc]). - -swagger("/gateway/:name/listeners", get) -> - #{ description => <<"Get the gateway listeners">> - , parameters => params_gateway_name_in_path() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"200">> => schema_listener_list() - } +schema("/gateway/:name/listeners") -> + #{ 'operationId' => listeners, + get => + #{ description => <<"Get the gateway listeners">> + , parameters => params_gateway_name_in_path() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 200 => emqx_dashboard_swagger:schema_with_examples( + hoconsc:array(ref(listener)), + examples_listener_list()) + } + }, + post => + #{ description => <<"Create the gateway listener">> + , parameters => params_gateway_name_in_path() + , requestBody => emqx_dashboard_swagger:schema_with_examples( + ref(listener), + examples_listener()) + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 204 => <<"Created">> + } + } }; -swagger("/gateway/:name/listeners", post) -> - #{ description => <<"Create the gateway listener">> - , parameters => params_gateway_name_in_path() - , requestBody => schema_listener() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"200">> => schema_listener_list() - } +schema("/gateway/:name/listeners/:id") -> + #{ 'operationId' => listeners_insta, + get => + #{ description => <<"Get the gateway listener configurations">> + , parameters => params_gateway_name_in_path() + ++ params_listener_id_in_path() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 200 => emqx_dashboard_swagger:schema_with_examples( + ref(listener), + examples_listener()) + } + }, + delete => + #{ description => <<"Delete the gateway listener">> + , parameters => params_gateway_name_in_path() + ++ params_listener_id_in_path() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 204 => <<"Deleted">> + } + }, + put => + #{ description => <<"Update the gateway listener">> + , parameters => params_gateway_name_in_path() + ++ params_listener_id_in_path() + , requestBody => emqx_dashboard_swagger:schema_with_examples( + ref(listener), + examples_listener()) + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 200 => <<"Updated">> + } + } }; -swagger("/gateway/:name/listeners/:id", get) -> - #{ description => <<"Get the gateway listener configurations">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"200">> => schema_listener() - } - }; -swagger("/gateway/:name/listeners/:id", delete) -> - #{ description => <<"Delete the gateway listener">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"204">> => schema_no_content() - } - }; -swagger("/gateway/:name/listeners/:id", put) -> - #{ description => <<"Update the gateway listener">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , requestBody => schema_listener() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"200">> => schema_no_content() - } - }; -swagger("/gateway/:name/listeners/:id/authentication", get) -> - #{ description => <<"Get the listener's authentication info">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"200">> => schema_authn() - , <<"204">> => schema_no_content() - } - }; -swagger("/gateway/:name/listeners/:id/authentication", post) -> - #{ description => <<"Add authentication for the listener">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , requestBody => schema_authn() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"204">> => schema_no_content() - } - }; -swagger("/gateway/:name/listeners/:id/authentication", put) -> - #{ description => <<"Update authentication for the listener">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , requestBody => schema_authn() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"204">> => schema_no_content() - } - }; -swagger("/gateway/:name/listeners/:id/authentication", delete) -> - #{ description => <<"Remove authentication for the listener">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , responses => - #{ <<"400">> => schema_bad_request() - , <<"404">> => schema_not_found() - , <<"500">> => schema_internal_error() - , <<"204">> => schema_no_content() - } +schema("/gateway/:name/listeners/:id/authentication") -> + #{ 'operationId' => listeners_insta_authn, + get => + #{ description => <<"Get the listener's authentication info">> + , parameters => params_gateway_name_in_path() + ++ params_listener_id_in_path() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 200 => schema_authn() + , 204 => <<"Authentication does not initiated">> + } + }, + post => + #{ description => <<"Add authentication for the listener">> + , parameters => params_gateway_name_in_path() + ++ params_listener_id_in_path() + , requestBody => schema_authn() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 204 => <<"Added">> + } + }, + put => + #{ description => <<"Update authentication for the listener">> + , parameters => params_gateway_name_in_path() + ++ params_listener_id_in_path() + , requestBody => schema_authn() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 204 => <<"Updated">> + } + }, + delete => + #{ description => <<"Remove authentication for the listener">> + , parameters => params_gateway_name_in_path() + ++ params_listener_id_in_path() + , responses => + #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>) + , 404 => error_codes([?NOT_FOUND], <<"Not Found">>) + , 500 => error_codes([?INTERNAL_ERROR], + <<"Ineternal Server Error">>) + , 204 => <<"Deleted">> + } + } }. %%-------------------------------------------------------------------- %% params defines params_gateway_name_in_path() -> - [#{ name => name - , in => path - , schema => #{type => string} - , required => true - }]. + [{name, + mk(binary(), + #{ in => path + , desc => <<"Gateway Name">> + })} + ]. params_listener_id_in_path() -> - [#{ name => id - , in => path - , schema => #{type => string} - , required => true - }]. + [{id, + mk(binary(), + #{ in => path + , desc => <<"Listener ID">> + })} + ]. %%-------------------------------------------------------------------- %% schemas -schema_listener_list() -> - emqx_mgmt_util:array_schema( - #{ type => object - , properties => properties_listener() - }, - <<"Listener list">> - ). - -schema_listener() -> - emqx_mgmt_util:schema( - #{ type => object - , properties => properties_listener() - } - ). - -%%-------------------------------------------------------------------- -%% properties - -properties_listener() -> - emqx_mgmt_util:properties( - raw_properties_common_listener() ++ - [ {tcp, object, raw_properties_tcp_opts()} - , {ssl, object, raw_properties_ssl_opts()} - , {udp, object, raw_properties_udp_opts()} - , {dtls, object, raw_properties_dtls_opts()} - ]). - -raw_properties_tcp_opts() -> - [ {active_n, integer, <<>>} - , {backlog, integer, <<>>} - , {buffer, string, <<>>} - , {recbuf, string, <<>>} - , {sndbuf, string, <<>>} - , {high_watermark, string, <<>>} - , {nodelay, boolean, <<>>} - , {reuseaddr, boolean, <<>>} - , {send_timeout, string, <<>>} - , {send_timeout_close, boolean, <<>>} +roots() -> + [ listener ]. -raw_properties_ssl_opts() -> - [ {cacertfile, string, <<>>} - , {certfile, string, <<>>} - , {keyfile, string, <<>>} - , {verify, string, <<>>} - , {fail_if_no_peer_cert, boolean, <<>>} - , {server_name_indication, boolean, <<>>} - , {depth, integer, <<>>} - , {password, string, <<>>} - , {handshake_timeout, string, <<>>} - , {versions, {array, string}, <<>>} - , {ciphers, {array, string}, <<>>} - , {user_lookup_fun, string, <<>>} - , {reuse_sessions, boolean, <<>>} - , {secure_renegotiate, boolean, <<>>} - , {honor_cipher_order, boolean, <<>>} - , {dhfile, string, <<>>} - ]. - -raw_properties_udp_opts() -> - [ {active_n, integer, <<>>} - , {buffer, string, <<>>} - , {recbuf, string, <<>>} - , {sndbuf, string, <<>>} - , {reuseaddr, boolean, <<>>} - ]. - -raw_properties_dtls_opts() -> +fields(listener) -> + common_listener_opts() ++ + [ {tcp, + mk(ref(tcp_listener_opts), + #{ nullable => {true, recursively} + , desc => <<"The tcp socket options for tcp or ssl listener">> + })} + , {ssl, + mk(ref(ssl_listener_opts), + #{ nullable => {true, recursively} + , desc => <<"The ssl socket options for ssl listener">> + })} + , {udp, + mk(ref(udp_listener_opts), + #{ nullable => {true, recursively} + , desc => <<"The udp socket options for udp or dtls listener">> + })} + , {dtls, + mk(ref(dtls_listener_opts), + #{ nullable => {true, recursively} + , desc => <<"The dtls socket options for dtls listener">> + })} + ]; +fields(tcp_listener_opts) -> + [ {active_n, mk(integer(), #{})} + , {backlog, mk(integer(), #{})} + , {buffer, mk(binary(), #{})} + , {recbuf, mk(binary(), #{})} + , {sndbuf, mk(binary(), #{})} + , {high_watermark, mk(binary(), #{})} + , {nodelay, mk(boolean(), #{})} + , {reuseaddr, boolean()} + , {send_timeout, binary()} + , {send_timeout_close, boolean()} + ]; +fields(ssl_listener_opts) -> + [ {cacertfile, binary()} + , {certfile, binary()} + , {keyfile, binary()} + , {verify, binary()} + , {fail_if_no_peer_cert, boolean()} + , {server_name_indication, boolean()} + , {depth, integer()} + , {password, binary()} + , {handshake_timeout, binary()} + , {versions, hoconsc:array(binary())} + , {ciphers, hoconsc:array(binary())} + , {user_lookup_fun, binary()} + , {reuse_sessions, boolean()} + , {secure_renegotiate, boolean()} + , {honor_cipher_order, boolean()} + , {dhfile, binary()} + ]; +fields(udp_listener_opts) -> + [ {active_n, integer()} + , {buffer, binary()} + , {recbuf, binary()} + , {sndbuf, binary()} + , {reuseaddr, boolean()} + ]; +fields(dtls_listener_opts) -> Ls = lists_key_without( [versions,ciphers,handshake_timeout], 1, - raw_properties_ssl_opts() + fields(ssl_listener_opts) ), - [ {versions, {array, string}, <<>>} - , {ciphers, {array, string}, <<>>} + [ {versions, hoconsc:array(binary())} + , {ciphers, hoconsc:array(binary())} | Ls]. lists_key_without([], _N, L) -> @@ -369,23 +390,67 @@ lists_key_without([], _N, L) -> lists_key_without([K|Ks], N, L) -> lists_key_without(Ks, N, lists:keydelete(K, N, L)). -raw_properties_common_listener() -> - [ {enable, boolean, <<"Whether to enable this listener">>} - , {id, string, <<"Listener Id">>} - , {name, string, <<"Listener name">>} - , {type, string, - <<"Listener type. Enum: tcp, udp, ssl, dtls">>, - [<<"tcp">>, <<"ssl">>, <<"udp">>, <<"dtls">>]} - , {running, boolean, <<"Listener running status">>} - , {bind, string, <<"Listener bind address or port">>} - , {acceptors, integer, <<"Listener acceptors number">>} - , {access_rules, {array, string}, <<"Listener Access rules for client">>} - , {max_conn_rate, integer, <<"Max connection rate for the listener">>} - , {max_connections, integer, <<"Max connections for the listener">>} - , {mountpoint, string, - <<"The Mounpoint for clients of the listener. " - "The gateway-level mountpoint configuration can be overloaded " - "when it is not null or empty string">>} +common_listener_opts() -> + [ {enable, + mk(boolean(), + #{ nullable => true + , desc => <<"Whether to enable this listener">>})} + , {id, + mk(binary(), + #{ nullable => true + , desc => <<"Listener Id">>})} + , {name, + mk(binary(), + #{ nullable => true + , desc => <<"Listener name">>})} + , {type, + mk(hoconsc:enum([tcp, ssl, udp, dtls]), + #{ nullable => true + , desc => <<"Listener type. Enum: tcp, udp, ssl, dtls">>})} + , {running, + mk(boolean(), + #{ nullable => true + , desc => <<"Listener running status">>})} + , {bind, + mk(binary(), + #{ nullable => true + , desc => <<"Listener bind address or port">>})} + , {acceptors, + mk(integer(), + #{ nullable => true + , desc => <<"Listener acceptors number">>})} + , {access_rules, + mk(hoconsc:array(binary()), + #{ nullable => true + , desc => <<"Listener Access rules for client">>})} + , {max_conn_rate, + mk(integer(), + #{ nullable => true + , desc => <<"Max connection rate for the listener">>})} + , {max_connections, + mk(integer(), + #{ nullable => true + , desc => <<"Max connections for the listener">>})} + , {mountpoint, + mk(binary(), + #{ nullable => true + , desc => +<<"The Mounpoint for clients of the listener. " + "The gateway-level mountpoint configuration can be overloaded " + "when it is not null or empty string">>})} %% FIXME: - , {authentication, string, <<"NOT-SUPPORTED-NOW">>} - ]. + , {authentication, + mk(emqx_authn_schema:authenticator_type(), + #{ nullable => {true, recursively} + , desc => <<"The authenticatior for this listener">> + })} + ]. + +%%-------------------------------------------------------------------- +%% examples + +examples_listener_list() -> + [examples_listener()]. + +examples_listener() -> + #{id => true}. diff --git a/apps/emqx_gateway/src/emqx_gateway_http.erl b/apps/emqx_gateway/src/emqx_gateway_http.erl index da440dba8..41923a66e 100644 --- a/apps/emqx_gateway/src/emqx_gateway_http.erl +++ b/apps/emqx_gateway/src/emqx_gateway_http.erl @@ -53,6 +53,7 @@ %% Utils for http, swagger, etc. -export([ return_http_error/2 , with_gateway/2 + , with_authn/2 , checks/2 , schema_bad_request/0 , schema_not_found/0 @@ -159,14 +160,31 @@ remove_listener(ListenerId) -> -spec authn(gateway_name()) -> map(). authn(GwName) -> + %% XXX: Need append chain-nanme, authenticator-id? Path = [gateway, GwName, authentication], - emqx_map_lib:jsonable_map(emqx:get_config(Path)). + ChainName = emqx_gateway_utils:global_chain(GwName), + wrap_chain_name( + ChainName, + emqx_map_lib:jsonable_map(emqx:get_config(Path)) + ). -spec authn(gateway_name(), binary()) -> map(). authn(GwName, ListenerId) -> {_, Type, Name} = emqx_gateway_utils:parse_listener_id(ListenerId), Path = [gateway, GwName, listeners, Type, Name, authentication], - emqx_map_lib:jsonable_map(emqx:get_config(Path)). + ChainName = emqx_gateway_utils:listener_chain(GwName, Type, Name), + wrap_chain_name( + ChainName, + emqx_map_lib:jsonable_map(emqx:get_config(Path)) + ). + +wrap_chain_name(ChainName, Conf) -> + case emqx_authentication:list_authenticators(ChainName) of + {ok, [#{id := Id} | _]} -> + Conf#{chain_name => ChainName, id => Id}; + _ -> + Conf + end. -spec add_authn(gateway_name(), map()) -> ok. add_authn(GwName, AuthConf) -> @@ -303,6 +321,13 @@ codestr(401) -> 'NOT_SUPPORTED_NOW'; codestr(404) -> 'RESOURCE_NOT_FOUND'; codestr(500) -> 'UNKNOW_ERROR'. +-spec with_authn(binary(), function()) -> any(). +with_authn(GwName0, Fun) -> + with_gateway(GwName0, fun(GwName) -> + Authn = emqx_gateway_http:authn(GwName), + Fun(GwName, Authn) + end). + -spec with_gateway(binary(), function()) -> any(). with_gateway(GwName0, Fun) -> try diff --git a/apps/emqx_gateway/src/emqx_gateway_insta_sup.erl b/apps/emqx_gateway/src/emqx_gateway_insta_sup.erl index 52c23d459..4c0417fa9 100644 --- a/apps/emqx_gateway/src/emqx_gateway_insta_sup.erl +++ b/apps/emqx_gateway/src/emqx_gateway_insta_sup.erl @@ -219,23 +219,6 @@ detailed_gateway_info(State) -> %% Internal funcs %%-------------------------------------------------------------------- -%% same with emqx_authentication:global_chain/1 -global_chain(mqtt) -> - 'mqtt:global'; -global_chain('mqtt-sn') -> - 'mqtt-sn:global'; -global_chain(coap) -> - 'coap:global'; -global_chain(lwm2m) -> - 'lwm2m:global'; -global_chain(stomp) -> - 'stomp:global'; -global_chain(_) -> - 'unknown:global'. - -listener_chain(GwName, Type, LisName) -> - emqx_gateway_utils:listener_id(GwName, Type, LisName). - %% There are two layer authentication configs %% stomp.authn %% / \ @@ -266,10 +249,11 @@ do_init_authn([_BadConf|More], Names) -> authns(GwName, Config) -> Listeners = maps:to_list(maps:get(listeners, Config, #{})), lists:append( - [ [{listener_chain(GwName, LisType, LisName), authn_conf(Opts)} + [ [{emqx_gateway_utils:listener_chain(GwName, LisType, LisName), + authn_conf(Opts)} || {LisName, Opts} <- maps:to_list(LisNames) ] || {LisType, LisNames} <- Listeners]) - ++ [{global_chain(GwName), authn_conf(Config)}]. + ++ [{emqx_gateway_utils:global_chain(GwName), authn_conf(Config)}]. authn_conf(Conf) -> maps:get(authentication, Conf, #{enable => false}). diff --git a/apps/emqx_gateway/src/emqx_gateway_utils.erl b/apps/emqx_gateway/src/emqx_gateway_utils.erl index a497e11d0..8c824a1a1 100644 --- a/apps/emqx_gateway/src/emqx_gateway_utils.erl +++ b/apps/emqx_gateway/src/emqx_gateway_utils.erl @@ -34,6 +34,8 @@ , listener_id/3 , parse_listener_id/1 , is_running/2 + , global_chain/1 + , listener_chain/3 ]). -export([ stringfy/1 @@ -159,6 +161,23 @@ is_running(ListenerId, #{<<"bind">> := ListenOn0}) -> false end. +%% same with emqx_authentication:global_chain/1 +global_chain(mqtt) -> + 'mqtt:global'; +global_chain('mqtt-sn') -> + 'mqtt-sn:global'; +global_chain(coap) -> + 'coap:global'; +global_chain(lwm2m) -> + 'lwm2m:global'; +global_chain(stomp) -> + 'stomp:global'; +global_chain(_) -> + 'unknown:global'. + +listener_chain(GwName, Type, LisName) -> + listener_id(GwName, Type, LisName). + bin(A) when is_atom(A) -> atom_to_binary(A); bin(L) when is_list(L); is_binary(L) -> diff --git a/apps/emqx_gateway/test/emqx_sn_frame_SUITE.erl b/apps/emqx_gateway/test/emqx_sn_frame_SUITE.erl index c02b60dd0..335e00b20 100644 --- a/apps/emqx_gateway/test/emqx_sn_frame_SUITE.erl +++ b/apps/emqx_gateway/test/emqx_sn_frame_SUITE.erl @@ -164,7 +164,7 @@ t_random_test(_) -> random_test_body() -> Data = generate_random_binary(), case catch parse(Data) of - {ok, _Msg} -> ok; + Msg when is_record(Msg, mqtt_sn_message) -> ok; {'EXIT', {Err, _Stack}} when Err =:= unkown_message_type; Err =:= malformed_message_len;