Merge pull request #6248 from HJianBo/gw-improve-apis-1

Add authentication data management APIs for gateway
This commit is contained in:
JianBo He 2021-11-23 09:12:04 +08:00 committed by GitHub
commit 50542ec441
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 930 additions and 336 deletions

View File

@ -65,6 +65,17 @@
, response_users_example/0 , 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
]).
-elvis([{elvis_style, god_modules, disable}]).
api_spec() -> api_spec() ->
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
@ -785,11 +796,12 @@ add_user(_, _, #{<<"user_id">> := _}) ->
add_user(_, _, _) -> add_user(_, _, _) ->
serialize_error({missing_parameter, user_id}). serialize_error({missing_parameter, user_id}).
update_user(ChainName, AuthenticatorID, UserID, UserInfo) -> update_user(ChainName, AuthenticatorID, UserID, UserInfo0) ->
case maps:with([<<"password">>, <<"is_superuser">>], UserInfo) =:= #{} of case maps:with([<<"password">>, <<"is_superuser">>], UserInfo0) =:= #{} of
true -> true ->
serialize_error({missing_parameter, password}); serialize_error({missing_parameter, password});
false -> false ->
UserInfo = emqx_map_lib:safe_atom_key_map(UserInfo0),
case emqx_authentication:update_user(ChainName, AuthenticatorID, UserID, UserInfo) of case emqx_authentication:update_user(ChainName, AuthenticatorID, UserID, UserInfo) of
{ok, User} -> {ok, User} ->
{200, User}; {200, User};

View File

@ -18,21 +18,34 @@
-behaviour(minirest_api). -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, -import(emqx_gateway_http,
[ return_http_error/2 [ return_http_error/2
, schema_bad_request/0
, schema_not_found/0
, schema_internal_error/0
, schema_no_content/0
, with_gateway/2 , with_gateway/2
, with_authn/2
, checks/2 , checks/2
]). ]).
%% minirest behaviour callbacks %% minirest/dashbaord_swagger behaviour callbacks
-export([api_spec/0]). -export([ api_spec/0
, paths/0
, schema/1
]).
%% http handlers %% http handlers
-export([authn/2]). -export([ authn/2
, users/2
, users_insta/2
, import_users/2
]).
%% internal export for emqx_gateway_api_listeners module %% internal export for emqx_gateway_api_listeners module
-export([schema_authn/0]). -export([schema_authn/0]).
@ -42,10 +55,13 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
api_spec() -> api_spec() ->
{metadata(apis()), []}. emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
apis() -> paths() ->
[ {"/gateway/:name/authentication", authn} [ "/gateway/:name/authentication"
, "/gateway/:name/authentication/users"
, "/gateway/:name/authentication/users/:uid"
, "/gateway/:name/authentication/import_users"
]. ].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -66,6 +82,7 @@ authn(get, #{bindings := #{name := Name0}}) ->
authn(put, #{bindings := #{name := Name0}, authn(put, #{bindings := #{name := Name0},
body := Body}) -> body := Body}) ->
with_gateway(Name0, fun(GwName, _) -> with_gateway(Name0, fun(GwName, _) ->
%% TODO: return the authn instances?
ok = emqx_gateway_http:update_authn(GwName, Body), ok = emqx_gateway_http:update_authn(GwName, Body),
{204} {204}
end); end);
@ -73,6 +90,7 @@ authn(put, #{bindings := #{name := Name0},
authn(post, #{bindings := #{name := Name0}, authn(post, #{bindings := #{name := Name0},
body := Body}) -> body := Body}) ->
with_gateway(Name0, fun(GwName, _) -> with_gateway(Name0, fun(GwName, _) ->
%% TODO: return the authn instances?
ok = emqx_gateway_http:add_authn(GwName, Body), ok = emqx_gateway_http:add_authn(GwName, Body),
{204} {204}
end); end);
@ -83,87 +101,251 @@ authn(delete, #{bindings := #{name := Name0}}) ->
{204} {204}
end). 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 %% 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) -> schema("/gateway/:name/authentication") ->
#{ description => <<"Get the gateway authentication">> #{ 'operationId' => authn,
, parameters => params_gateway_name_in_path() get =>
, responses => #{ description => <<"Get the gateway authentication">>
#{ <<"400">> => schema_bad_request() , parameters => params_gateway_name_in_path()
, <<"404">> => schema_not_found() , responses =>
, <<"500">> => schema_internal_error() #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
, <<"200">> => schema_authn() , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
, <<"204">> => schema_no_content() , 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) -> schema("/gateway/:name/authentication/users") ->
#{ description => <<"Update authentication for the gateway">> #{ 'operationId' => users
, parameters => params_gateway_name_in_path() , get =>
, requestBody => schema_authn() #{ description => <<"Get the users for the authentication">>
, responses => , parameters => params_gateway_name_in_path() ++
#{ <<"400">> => schema_bad_request() params_paging_in_qs()
, <<"404">> => schema_not_found() , responses =>
, <<"500">> => schema_internal_error() #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
, <<"204">> => schema_no_content() , 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()
, 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
ref(emqx_authn_api, request_user_create),
emqx_authn_api:request_user_create_examples())
, 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) -> schema("/gateway/:name/authentication/users/:uid") ->
#{ description => <<"Add authentication for the gateway">> #{ 'operationId' => users_insta
, parameters => params_gateway_name_in_path() , get =>
, requestBody => schema_authn() #{ description => <<"Get user info from the gateway "
, responses => "authentication">>
#{ <<"400">> => schema_bad_request() , parameters => params_gateway_name_in_path() ++
, <<"404">> => schema_not_found() params_userid_in_path()
, <<"500">> => schema_internal_error() , responses =>
, <<"204">> => schema_no_content() #{ 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()
, 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
ref(emqx_authn_api, request_user_update),
emqx_authn_api:request_user_update_examples())
, 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()
, responses =>
#{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
, 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
, 500 => error_codes([?INTERNAL_ERROR],
<<"Ineternal Server Error">>)
, 204 => <<"User Deleted">>
}
}
}; };
swagger("/gateway/:name/authentication", delete) -> schema("/gateway/:name/authentication/import_users") ->
#{ description => <<"Remove the gateway authentication">> #{ 'operationId' => import_users
, parameters => params_gateway_name_in_path() , post =>
, responses => #{ description => <<"Import users into the gateway authentication">>
#{ <<"400">> => schema_bad_request() , parameters => params_gateway_name_in_path()
, <<"404">> => schema_not_found() , 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
, <<"500">> => schema_internal_error() ref(emqx_authn_api, request_import_users),
, <<"204">> => schema_no_content() 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 defines
params_gateway_name_in_path() -> params_gateway_name_in_path() ->
[#{ name => name [{name,
, in => path mk(binary(),
, schema => #{type => string} #{ in => path
, required => true , desc => <<"Gateway Name">>
}]. })}
].
params_userid_in_path() ->
[{uid, mk(binary(),
#{ in => path
, desc => <<"User ID">>
})}
].
params_paging_in_qs() ->
[{page, mk(integer(),
#{ in => query
, nullable => true
, desc => <<"Page Index">>
})},
{limit, mk(integer(),
#{ in => query
, nullable => true
, desc => <<"Page Limit">>
})}
].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% schemas %% schemas
schema_authn() -> schema_authn() ->
#{ description => <<"OK">> emqx_dashboard_swagger:schema_with_examples(
, content => #{ emqx_authn_schema:authenticator_type(),
'application/json' => #{ emqx_authn_api:authenticator_examples()
schema => minirest:ref(<<"AuthenticatorInstance">>) ).
}}
}.

View File

@ -18,25 +18,41 @@
-behaviour(minirest_api). -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, -import(emqx_gateway_http,
[ return_http_error/2 [ return_http_error/2
, schema_bad_request/0
, schema_not_found/0
, schema_internal_error/0
, schema_no_content/0
, with_gateway/2 , with_gateway/2
, with_listener_authn/3
, checks/2 , checks/2
]). ]).
-import(emqx_gateway_api_authn, [schema_authn/0]). -import(emqx_gateway_api_authn, [schema_authn/0]).
%% minirest behaviour callbacks %% minirest/dashbaord_swagger behaviour callbacks
-export([api_spec/0]). -export([ api_spec/0
, paths/0
, schema/1
]).
-export([ roots/0
, fields/1
]).
%% http handlers %% http handlers
-export([ listeners/2 -export([ listeners/2
, listeners_insta/2 , listeners_insta/2
, listeners_insta_authn/2 , listeners_insta_authn/2
, users/2
, users_insta/2
, import_users/2
]). ]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -44,12 +60,15 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
api_spec() -> api_spec() ->
{metadata(apis()), []}. emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
apis() -> paths() ->
[ {"/gateway/:name/listeners", listeners} [ "/gateway/:name/listeners"
, {"/gateway/:name/listeners/:id", listeners_insta} , "/gateway/:name/listeners/:id"
, {"/gateway/:name/listeners/:id/authentication", listeners_insta_authn} , "/gateway/:name/listeners/:id/authentication"
, "/gateway/:name/listeners/:id/authentication/users"
, "/gateway/:name/listeners/:id/authentication/users/:uid"
, "/gateway/:name/listeners/:id/authentication/import_users"
]. ].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -145,247 +164,481 @@ listeners_insta_authn(delete, #{bindings := #{name := Name0,
{204} {204}
end). end).
users(get, #{bindings := #{name := Name0, id := Id}, query_string := Qs}) ->
with_listener_authn(Name0, Id,
fun(_GwName, #{id := AuthId, chain_name := ChainName}) ->
emqx_authn_api:list_users(ChainName, AuthId, page_pramas(Qs))
end);
users(post, #{bindings := #{name := Name0, id := Id},
body := Body}) ->
with_listener_authn(Name0, Id,
fun(_GwName, #{id := AuthId, chain_name := ChainName}) ->
emqx_authn_api:add_user(ChainName, AuthId, Body)
end).
users_insta(get, #{bindings := #{name := Name0, id := Id, uid := UserId}}) ->
with_listener_authn(Name0, Id,
fun(_GwName, #{id := AuthId, chain_name := ChainName}) ->
emqx_authn_api:find_user(ChainName, AuthId, UserId)
end);
users_insta(put, #{bindings := #{name := Name0, id := Id, uid := UserId},
body := Body}) ->
with_listener_authn(Name0, Id,
fun(_GwName, #{id := AuthId, chain_name := ChainName}) ->
emqx_authn_api:update_user(ChainName, AuthId, UserId, Body)
end);
users_insta(delete, #{bindings := #{name := Name0, id := Id, uid := UserId}}) ->
with_listener_authn(Name0, Id,
fun(_GwName, #{id := AuthId, chain_name := ChainName}) ->
emqx_authn_api:delete_user(ChainName, AuthId, UserId)
end).
import_users(post, #{bindings := #{name := Name0, id := Id},
body := Body}) ->
with_listener_authn(Name0, Id,
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 %% Swagger defines
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
metadata(APIs) -> schema("/gateway/:name/listeners") ->
metadata(APIs, []). #{ 'operationId' => listeners,
metadata([], APIAcc) -> get =>
lists:reverse(APIAcc); #{ description => <<"Get the gateway listeners">>
metadata([{Path, Fun}|More], APIAcc) -> , parameters => params_gateway_name_in_path()
Methods = [get, post, put, delete, patch], , responses =>
Mds = lists:foldl(fun(M, Acc) -> #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
try , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
Acc#{M => swagger(Path, M)} , 500 => error_codes([?INTERNAL_ERROR],
catch <<"Ineternal Server Error">>)
error : function_clause -> , 200 => emqx_dashboard_swagger:schema_with_examples(
Acc hoconsc:array(ref(listener)),
end examples_listener_list())
end, #{}, Methods), }
metadata(More, [{Path, Mds, Fun} | APIAcc]). },
post =>
swagger("/gateway/:name/listeners", get) -> #{ description => <<"Create the gateway listener">>
#{ description => <<"Get the gateway listeners">> , parameters => params_gateway_name_in_path()
, parameters => params_gateway_name_in_path() , 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
, responses => ref(listener),
#{ <<"400">> => schema_bad_request() examples_listener())
, <<"404">> => schema_not_found() , responses =>
, <<"500">> => schema_internal_error() #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
, <<"200">> => schema_listener_list() , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
} , 500 => error_codes([?INTERNAL_ERROR],
<<"Ineternal Server Error">>)
, 204 => <<"Created">>
}
}
}; };
swagger("/gateway/:name/listeners", post) -> schema("/gateway/:name/listeners/:id") ->
#{ description => <<"Create the gateway listener">> #{ 'operationId' => listeners_insta,
, parameters => params_gateway_name_in_path() get =>
, requestBody => schema_listener() #{ description => <<"Get the gateway listener configurations">>
, responses => , parameters => params_gateway_name_in_path()
#{ <<"400">> => schema_bad_request() ++ params_listener_id_in_path()
, <<"404">> => schema_not_found() , responses =>
, <<"500">> => schema_internal_error() #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
, <<"200">> => schema_listener_list() , 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) -> schema("/gateway/:name/listeners/:id/authentication") ->
#{ description => <<"Get the gateway listener configurations">> #{ 'operationId' => listeners_insta_authn,
, parameters => params_gateway_name_in_path() get =>
++ params_listener_id_in_path() #{ description => <<"Get the listener's authentication info">>
, responses => , parameters => params_gateway_name_in_path()
#{ <<"400">> => schema_bad_request() ++ params_listener_id_in_path()
, <<"404">> => schema_not_found() , responses =>
, <<"500">> => schema_internal_error() #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
, <<"200">> => schema_listener() , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
} , 500 => error_codes([?INTERNAL_ERROR],
}; <<"Ineternal Server Error">>)
swagger("/gateway/:name/listeners/:id", delete) -> , 200 => schema_authn()
#{ description => <<"Delete the gateway listener">> , 204 => <<"Authentication does not initiated">>
, parameters => params_gateway_name_in_path() }
++ params_listener_id_in_path() },
, responses => post =>
#{ <<"400">> => schema_bad_request() #{ description => <<"Add authentication for the listener">>
, <<"404">> => schema_not_found() , parameters => params_gateway_name_in_path()
, <<"500">> => schema_internal_error() ++ params_listener_id_in_path()
, <<"204">> => schema_no_content() , 'requestBody' => schema_authn()
} , responses =>
}; #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
swagger("/gateway/:name/listeners/:id", put) -> , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
#{ description => <<"Update the gateway listener">> , 500 => error_codes([?INTERNAL_ERROR],
, parameters => params_gateway_name_in_path() <<"Ineternal Server Error">>)
++ params_listener_id_in_path() , 204 => <<"Added">>
, requestBody => schema_listener() }
, responses => },
#{ <<"400">> => schema_bad_request() put =>
, <<"404">> => schema_not_found() #{ description => <<"Update authentication for the listener">>
, <<"500">> => schema_internal_error() , parameters => params_gateway_name_in_path()
, <<"200">> => schema_no_content() ++ 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">>
}
}
}; };
swagger("/gateway/:name/listeners/:id/authentication", get) -> schema("/gateway/:name/listeners/:id/authentication/users") ->
#{ description => <<"Get the listener's authentication info">> #{ 'operationId' => users
, parameters => params_gateway_name_in_path() , get =>
++ params_listener_id_in_path() #{ description => <<"Get the users for the authentication">>
, responses => , parameters => params_gateway_name_in_path() ++
#{ <<"400">> => schema_bad_request() params_listener_id_in_path() ++
, <<"404">> => schema_not_found() params_paging_in_qs()
, <<"500">> => schema_internal_error() , responses =>
, <<"200">> => schema_authn() #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
, <<"204">> => schema_no_content() , 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() ++
params_listener_id_in_path()
, 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
ref(emqx_authn_api, request_user_create),
emqx_authn_api:request_user_create_examples())
, 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/listeners/:id/authentication", post) -> schema("/gateway/:name/listeners/:id/authentication/users/:uid") ->
#{ description => <<"Add authentication for the listener">> #{ 'operationId' => users_insta
, parameters => params_gateway_name_in_path() , get =>
++ params_listener_id_in_path() #{ description => <<"Get user info from the gateway "
, requestBody => schema_authn() "authentication">>
, responses => , parameters => params_gateway_name_in_path() ++
#{ <<"400">> => schema_bad_request() params_listener_id_in_path() ++
, <<"404">> => schema_not_found() params_userid_in_path()
, <<"500">> => schema_internal_error() , responses =>
, <<"204">> => schema_no_content() #{ 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_listener_id_in_path() ++
params_userid_in_path()
, 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
ref(emqx_authn_api, request_user_update),
emqx_authn_api:request_user_update_examples())
, 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_listener_id_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())
}
}
}; };
swagger("/gateway/:name/listeners/:id/authentication", put) -> schema("/gateway/:name/listeners/:id/authentication/import_users") ->
#{ description => <<"Update authentication for the listener">> #{ 'operationId' => import_users
, parameters => params_gateway_name_in_path() , post =>
++ params_listener_id_in_path() #{ description => <<"Import users into the gateway authentication">>
, requestBody => schema_authn() , parameters => params_gateway_name_in_path() ++
, responses => params_listener_id_in_path()
#{ <<"400">> => schema_bad_request() , 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
, <<"404">> => schema_not_found() ref(emqx_authn_api, request_import_users),
, <<"500">> => schema_internal_error() emqx_authn_api:request_import_users_examples()
, <<"204">> => schema_no_content() )
} , responses =>
}; #{ 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>)
swagger("/gateway/:name/listeners/:id/authentication", delete) -> , 404 => error_codes([?NOT_FOUND], <<"Not Found">>)
#{ description => <<"Remove authentication for the listener">> , 500 => error_codes([?INTERNAL_ERROR],
, parameters => params_gateway_name_in_path() <<"Ineternal Server Error">>)
++ params_listener_id_in_path() %% XXX: Put a hint message into 204 return ?
, responses => , 204 => <<"Imported">>
#{ <<"400">> => schema_bad_request() }
, <<"404">> => schema_not_found() }
, <<"500">> => schema_internal_error()
, <<"204">> => schema_no_content()
}
}. }.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% params defines %% params defines
params_gateway_name_in_path() -> params_gateway_name_in_path() ->
[#{ name => name [{name,
, in => path mk(binary(),
, schema => #{type => string} #{ in => path
, required => true , desc => <<"Gateway Name">>
}]. })}
].
params_listener_id_in_path() -> params_listener_id_in_path() ->
[#{ name => id [{id,
, in => path mk(binary(),
, schema => #{type => string} #{ in => path
, required => true , desc => <<"Listener ID">>
}]. })}
].
params_userid_in_path() ->
[{uid, mk(binary(),
#{ in => path
, desc => <<"User ID">>
})}
].
params_paging_in_qs() ->
[{page, mk(integer(),
#{ in => query
, nullable => true
, desc => <<"Page Index">>
})},
{limit, mk(integer(),
#{ in => query
, nullable => true
, desc => <<"Page Limit">>
})}
].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% schemas %% schemas
schema_listener_list() -> roots() ->
emqx_mgmt_util:array_schema( [ listener
#{ 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, <<>>}
]. ].
raw_properties_ssl_opts() -> fields(listener) ->
[ {cacertfile, string, <<>>} common_listener_opts() ++
, {certfile, string, <<>>} [ {tcp,
, {keyfile, string, <<>>} mk(ref(tcp_listener_opts),
, {verify, string, <<>>} #{ nullable => {true, recursively}
, {fail_if_no_peer_cert, boolean, <<>>} , desc => <<"The tcp socket options for tcp or ssl listener">>
, {server_name_indication, boolean, <<>>} })}
, {depth, integer, <<>>} , {ssl,
, {password, string, <<>>} mk(ref(ssl_listener_opts),
, {handshake_timeout, string, <<>>} #{ nullable => {true, recursively}
, {versions, {array, string}, <<>>} , desc => <<"The ssl socket options for ssl listener">>
, {ciphers, {array, string}, <<>>} })}
, {user_lookup_fun, string, <<>>} , {udp,
, {reuse_sessions, boolean, <<>>} mk(ref(udp_listener_opts),
, {secure_renegotiate, boolean, <<>>} #{ nullable => {true, recursively}
, {honor_cipher_order, boolean, <<>>} , desc => <<"The udp socket options for udp or dtls listener">>
, {dhfile, string, <<>>} })}
]. , {dtls,
mk(ref(dtls_listener_opts),
raw_properties_udp_opts() -> #{ nullable => {true, recursively}
[ {active_n, integer, <<>>} , desc => <<"The dtls socket options for dtls listener">>
, {buffer, string, <<>>} })}
, {recbuf, string, <<>>} ];
, {sndbuf, string, <<>>} fields(tcp_listener_opts) ->
, {reuseaddr, boolean, <<>>} [ {active_n, mk(integer(), #{})}
]. , {backlog, mk(integer(), #{})}
, {buffer, mk(binary(), #{})}
raw_properties_dtls_opts() -> , {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( Ls = lists_key_without(
[versions,ciphers,handshake_timeout], 1, [versions,ciphers,handshake_timeout], 1,
raw_properties_ssl_opts() fields(ssl_listener_opts)
), ),
[ {versions, {array, string}, <<>>} [ {versions, hoconsc:array(binary())}
, {ciphers, {array, string}, <<>>} , {ciphers, hoconsc:array(binary())}
| Ls]. | Ls].
lists_key_without([], _N, L) -> lists_key_without([], _N, L) ->
L; L;
lists_key_without([K|Ks], N, L) -> lists_key_without([K | Ks], N, L) ->
lists_key_without(Ks, N, lists:keydelete(K, N, L)). lists_key_without(Ks, N, lists:keydelete(K, N, L)).
raw_properties_common_listener() -> common_listener_opts() ->
[ {enable, boolean, <<"Whether to enable this listener">>} [ {enable,
, {id, string, <<"Listener Id">>} mk(boolean(),
, {name, string, <<"Listener name">>} #{ nullable => true
, {type, string, , desc => <<"Whether to enable this listener">>})}
<<"Listener type. Enum: tcp, udp, ssl, dtls">>, , {id,
[<<"tcp">>, <<"ssl">>, <<"udp">>, <<"dtls">>]} mk(binary(),
, {running, boolean, <<"Listener running status">>} #{ nullable => true
, {bind, string, <<"Listener bind address or port">>} , desc => <<"Listener Id">>})}
, {acceptors, integer, <<"Listener acceptors number">>} , {name,
, {access_rules, {array, string}, <<"Listener Access rules for client">>} mk(binary(),
, {max_conn_rate, integer, <<"Max connection rate for the listener">>} #{ nullable => true
, {max_connections, integer, <<"Max connections for the listener">>} , desc => <<"Listener name">>})}
, {mountpoint, string, , {type,
<<"The Mounpoint for clients of the listener. " mk(hoconsc:enum([tcp, ssl, udp, dtls]),
"The gateway-level mountpoint configuration can be overloaded " #{ nullable => true
"when it is not null or empty string">>} , 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: %% 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() ->
#{stomp_listeners => [examples_listener()]}.
examples_listener() ->
#{}.

View File

@ -53,6 +53,8 @@
%% Utils for http, swagger, etc. %% Utils for http, swagger, etc.
-export([ return_http_error/2 -export([ return_http_error/2
, with_gateway/2 , with_gateway/2
, with_authn/2
, with_listener_authn/3
, checks/2 , checks/2
, schema_bad_request/0 , schema_bad_request/0
, schema_not_found/0 , schema_not_found/0
@ -69,6 +71,10 @@
, listeners => [] , listeners => []
}. }.
-elvis([{elvis_style, god_modules, disable}]).
-elvis([{elvis_style, no_nested_try_catch, disable}]).
-define(DEFAULT_CALL_TIMEOUT, 15000). -define(DEFAULT_CALL_TIMEOUT, 15000).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -159,14 +165,31 @@ remove_listener(ListenerId) ->
-spec authn(gateway_name()) -> map(). -spec authn(gateway_name()) -> map().
authn(GwName) -> authn(GwName) ->
%% XXX: Need append chain-nanme, authenticator-id?
Path = [gateway, GwName, authentication], 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(). -spec authn(gateway_name(), binary()) -> map().
authn(GwName, ListenerId) -> authn(GwName, ListenerId) ->
{_, Type, Name} = emqx_gateway_utils:parse_listener_id(ListenerId), {_, Type, Name} = emqx_gateway_utils:parse_listener_id(ListenerId),
Path = [gateway, GwName, listeners, Type, Name, authentication], 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. -spec add_authn(gateway_name(), map()) -> ok.
add_authn(GwName, AuthConf) -> add_authn(GwName, AuthConf) ->
@ -303,6 +326,20 @@ codestr(401) -> 'NOT_SUPPORTED_NOW';
codestr(404) -> 'RESOURCE_NOT_FOUND'; codestr(404) -> 'RESOURCE_NOT_FOUND';
codestr(500) -> 'UNKNOW_ERROR'. codestr(500) -> 'UNKNOW_ERROR'.
-spec with_authn(binary(), function()) -> any().
with_authn(GwName0, Fun) ->
with_gateway(GwName0, fun(GwName, _GwConf) ->
Authn = emqx_gateway_http:authn(GwName),
Fun(GwName, Authn)
end).
-spec with_listener_authn(binary(), binary(), function()) -> any().
with_listener_authn(GwName0, Id, Fun) ->
with_gateway(GwName0, fun(GwName, _GwConf) ->
Authn = emqx_gateway_http:authn(GwName, Id),
Fun(GwName, Authn)
end).
-spec with_gateway(binary(), function()) -> any(). -spec with_gateway(binary(), function()) -> any().
with_gateway(GwName0, Fun) -> with_gateway(GwName0, Fun) ->
try try
@ -346,7 +383,7 @@ with_gateway(GwName0, Fun) ->
-spec checks(list(), map()) -> ok. -spec checks(list(), map()) -> ok.
checks([], _) -> checks([], _) ->
ok; ok;
checks([K|Ks], Map) -> checks([K | Ks], Map) ->
case maps:is_key(K, Map) of case maps:is_key(K, Map) of
true -> checks(Ks, Map); true -> checks(Ks, Map);
false -> false ->

View File

@ -52,6 +52,8 @@
stopped_at :: integer() | undefined stopped_at :: integer() | undefined
}). }).
-elvis([{elvis_style, invalid_dynamic_call, disable}]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -219,23 +221,6 @@ detailed_gateway_info(State) ->
%% Internal funcs %% 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 %% There are two layer authentication configs
%% stomp.authn %% stomp.authn
%% / \ %% / \
@ -254,22 +239,23 @@ init_authn(GwName, Config) ->
do_init_authn([], Names) -> do_init_authn([], Names) ->
Names; Names;
do_init_authn([{_ChainName, _AuthConf = #{enable := false}}|More], Names) -> do_init_authn([{_ChainName, _AuthConf = #{enable := false}} | More], Names) ->
do_init_authn(More, Names); do_init_authn(More, Names);
do_init_authn([{ChainName, AuthConf}|More], Names) when is_map(AuthConf) -> do_init_authn([{ChainName, AuthConf} | More], Names) when is_map(AuthConf) ->
_ = application:ensure_all_started(emqx_authn), _ = application:ensure_all_started(emqx_authn),
do_create_authn_chain(ChainName, AuthConf), do_create_authn_chain(ChainName, AuthConf),
do_init_authn(More, [ChainName|Names]); do_init_authn(More, [ChainName | Names]);
do_init_authn([_BadConf|More], Names) -> do_init_authn([_BadConf | More], Names) ->
do_init_authn(More, Names). do_init_authn(More, Names).
authns(GwName, Config) -> authns(GwName, Config) ->
Listeners = maps:to_list(maps:get(listeners, Config, #{})), Listeners = maps:to_list(maps:get(listeners, Config, #{})),
lists:append( 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) ] || {LisName, Opts} <- maps:to_list(LisNames) ]
|| {LisType, LisNames} <- Listeners]) || {LisType, LisNames} <- Listeners])
++ [{global_chain(GwName), authn_conf(Config)}]. ++ [{emqx_gateway_utils:global_chain(GwName), authn_conf(Config)}].
authn_conf(Conf) -> authn_conf(Conf) ->
maps:get(authentication, Conf, #{enable => false}). maps:get(authentication, Conf, #{enable => false}).
@ -328,13 +314,13 @@ do_update_one_by_one(NCfg, State = #state{
OAuths = authns(GwName, OCfg), OAuths = authns(GwName, OCfg),
NAuths = authns(GwName, NCfg), NAuths = authns(GwName, NCfg),
if case {Status, NEnable} of
Status == stopped, NEnable == true -> {stopped, true} ->
NState = State#state{config = NCfg}, NState = State#state{config = NCfg},
cb_gateway_load(NState); cb_gateway_load(NState);
Status == stopped, NEnable == false -> {stopped, false} ->
{ok, State#state{config = NCfg}}; {ok, State#state{config = NCfg}};
Status == running, NEnable == true -> {running, true} ->
NState = case NAuths == OAuths of NState = case NAuths == OAuths of
true -> State; true -> State;
false -> false ->
@ -345,12 +331,12 @@ do_update_one_by_one(NCfg, State = #state{
end, end,
%% XXX: minimum impact update ??? %% XXX: minimum impact update ???
cb_gateway_update(NCfg, NState); cb_gateway_update(NCfg, NState);
Status == running, NEnable == false -> {running, false} ->
case cb_gateway_unload(State) of case cb_gateway_unload(State) of
{ok, NState} -> {ok, NState#state{config = NCfg}}; {ok, NState} -> {ok, NState#state{config = NCfg}};
{error, Reason} -> {error, Reason} {error, Reason} -> {error, Reason}
end; end;
true -> _ ->
throw(nomatch) throw(nomatch)
end. end.
@ -448,7 +434,7 @@ cb_gateway_update(Config,
end. end.
start_child_process([]) -> []; start_child_process([]) -> [];
start_child_process([Indictor|_] = ChildPidOrSpecs) -> start_child_process([Indictor | _] = ChildPidOrSpecs) ->
case erlang:is_pid(Indictor) of case erlang:is_pid(Indictor) of
true -> true ->
ChildPidOrSpecs; ChildPidOrSpecs;

View File

@ -34,6 +34,8 @@
, listener_id/3 , listener_id/3
, parse_listener_id/1 , parse_listener_id/1
, is_running/2 , is_running/2
, global_chain/1
, listener_chain/3
]). ]).
-export([ stringfy/1 -export([ stringfy/1
@ -64,6 +66,8 @@
-define(DEFAULT_OOM_POLICY, #{max_heap_size => 4194304, -define(DEFAULT_OOM_POLICY, #{max_heap_size => 4194304,
message_queue_len => 32000}). message_queue_len => 32000}).
-elvis([{elvis_style, god_modules, disable}]).
-spec childspec(supervisor:worker(), Mod :: atom()) -spec childspec(supervisor:worker(), Mod :: atom())
-> supervisor:child_spec(). -> supervisor:child_spec().
childspec(Type, Mod) -> childspec(Type, Mod) ->
@ -159,6 +163,23 @@ is_running(ListenerId, #{<<"bind">> := ListenOn0}) ->
false false
end. 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) -> bin(A) when is_atom(A) ->
atom_to_binary(A); atom_to_binary(A);
bin(L) when is_list(L); is_binary(L) -> bin(L) when is_list(L); is_binary(L) ->
@ -183,7 +204,7 @@ stringfy(T) when is_list(T); is_binary(T) ->
stringfy(T) -> stringfy(T) ->
iolist_to_binary(io_lib:format("~0p", [T])). iolist_to_binary(io_lib:format("~0p", [T])).
-spec parse_address(binary()|list()) -> {list(), integer()}. -spec parse_address(binary() | list()) -> {list(), integer()}.
parse_address(S) when is_binary(S); is_list(S) -> parse_address(S) when is_binary(S); is_list(S) ->
S1 = case is_binary(S) of S1 = case is_binary(S) of
true -> lists:reverse(binary_to_list(S)); true -> lists:reverse(binary_to_list(S));
@ -215,9 +236,9 @@ normalize_config(RawConf) ->
[bind, tcp, ssl, udp, dtls] [bind, tcp, ssl, udp, dtls]
++ proplists:get_keys(SocketOpts), Confs), ++ proplists:get_keys(SocketOpts), Confs),
Cfg = maps:merge(Cfg0, RemainCfgs), Cfg = maps:merge(Cfg0, RemainCfgs),
[{Type, Name, ListenOn, SocketOpts, Cfg}|AccIn2] [{Type, Name, ListenOn, SocketOpts, Cfg} | AccIn2]
end, [], Liss), end, [], Liss),
[Listeners|AccIn1] [Listeners | AccIn1]
end, [], LisMap)). end, [], LisMap)).
esockd_opts(Type, Opts0) -> esockd_opts(Type, Opts0) ->

View File

@ -207,6 +207,50 @@ t_authn(_) ->
{204, _} = request(get, "/gateway/stomp/authentication"), {204, _} = request(get, "/gateway/stomp/authentication"),
{204, _} = request(delete, "/gateway/stomp"). {204, _} = request(delete, "/gateway/stomp").
t_authn_data_mgmt(_) ->
GwConf = #{name => <<"stomp">>},
{204, _} = request(post, "/gateway", GwConf),
{204, _} = request(get, "/gateway/stomp/authentication"),
AuthConf = #{mechanism => <<"password-based">>,
backend => <<"built-in-database">>,
user_id_type => <<"clientid">>
},
{204, _} = request(post, "/gateway/stomp/authentication", AuthConf),
{200, ConfResp} = request(get, "/gateway/stomp/authentication"),
assert_confs(AuthConf, ConfResp),
User1 = #{ user_id => <<"test">>
, password => <<"123456">>
, is_superuser => false
},
{201, _} = request(post, "/gateway/stomp/authentication/users", User1),
{200, #{data := [UserRespd1]}} = request(get, "/gateway/stomp/authentication/users"),
assert_confs(UserRespd1, User1),
{200, UserRespd2} = request(get,
"/gateway/stomp/authentication/users/test"),
assert_confs(UserRespd2, User1),
{200, UserRespd3} = request(put,
"/gateway/stomp/authentication/users/test",
#{password => <<"654321">>,
is_superuser => true}),
assert_confs(UserRespd3, User1#{is_superuser => true}),
{200, UserRespd4} = request(get,
"/gateway/stomp/authentication/users/test"),
assert_confs(UserRespd4, User1#{is_superuser => true}),
{204, _} = request(delete, "/gateway/stomp/authentication/users/test"),
{200, #{data := []}} = request(get,
"/gateway/stomp/authentication/users"),
{204, _} = request(delete, "/gateway/stomp/authentication"),
{204, _} = request(get, "/gateway/stomp/authentication"),
{204, _} = request(delete, "/gateway/stomp").
t_listeners(_) -> t_listeners(_) ->
GwConf = #{name => <<"stomp">>}, GwConf = #{name => <<"stomp">>},
{204, _} = request(post, "/gateway", GwConf), {204, _} = request(post, "/gateway", GwConf),
@ -262,6 +306,65 @@ t_listeners_authn(_) ->
assert_confs(AuthConf2, ConfResp3), assert_confs(AuthConf2, ConfResp3),
{204, _} = request(delete, "/gateway/stomp"). {204, _} = request(delete, "/gateway/stomp").
t_listeners_authn_data_mgmt(_) ->
GwConf = #{name => <<"stomp">>,
listeners => [
#{name => <<"def">>,
type => <<"tcp">>,
bind => <<"61613">>
}]},
{204, _} = request(post, "/gateway", GwConf),
{200, ConfResp} = request(get, "/gateway/stomp"),
assert_confs(GwConf, ConfResp),
AuthConf = #{mechanism => <<"password-based">>,
backend => <<"built-in-database">>,
user_id_type => <<"clientid">>
},
Path = "/gateway/stomp/listeners/stomp:tcp:def/authentication",
{204, _} = request(post, Path, AuthConf),
{200, ConfResp2} = request(get, Path),
assert_confs(AuthConf, ConfResp2),
User1 = #{ user_id => <<"test">>
, password => <<"123456">>
, is_superuser => false
},
{201, _} = request(post,
"/gateway/stomp/listeners/stomp:tcp:def/authentication/users",
User1),
{200,
#{data := [UserRespd1]} } = request(
get,
"/gateway/stomp/listeners/stomp:tcp:def/authentication/users"),
assert_confs(UserRespd1, User1),
{200, UserRespd2} = request(
get,
"/gateway/stomp/listeners/stomp:tcp:def/authentication/users/test"),
assert_confs(UserRespd2, User1),
{200, UserRespd3} = request(
put,
"/gateway/stomp/listeners/stomp:tcp:def/authentication/users/test",
#{password => <<"654321">>, is_superuser => true}),
assert_confs(UserRespd3, User1#{is_superuser => true}),
{200, UserRespd4} = request(
get,
"/gateway/stomp/listeners/stomp:tcp:def/authentication/users/test"),
assert_confs(UserRespd4, User1#{is_superuser => true}),
{204, _} = request(
delete,
"/gateway/stomp/listeners/stomp:tcp:def/authentication/users/test"),
{200, #{data := []}} = request(
get,
"/gateway/stomp/listeners/stomp:tcp:def/authentication/users"),
{204, _} = request(delete, "/gateway/stomp").
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Asserts %% Asserts

View File

@ -164,7 +164,7 @@ t_random_test(_) ->
random_test_body() -> random_test_body() ->
Data = generate_random_binary(), Data = generate_random_binary(),
case catch parse(Data) of case catch parse(Data) of
{ok, _Msg} -> ok; Msg when is_record(Msg, mqtt_sn_message) -> ok;
{'EXIT', {Err, _Stack}} {'EXIT', {Err, _Stack}}
when Err =:= unkown_message_type; when Err =:= unkown_message_type;
Err =:= malformed_message_len; Err =:= malformed_message_len;