diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index 9721dad93..68d9c0d1f 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -53,7 +53,14 @@ , listener_authenticator_user/2 ]). --export([authenticator_examples/0]). +-export([ authenticator_examples/0 + , request_move_examples/0 + , request_import_users_examples/0 + , request_user_create_examples/0 + , request_user_update_examples/0 + , response_user_examples/0 + , response_users_example/0 + ]). api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). @@ -78,13 +85,13 @@ roots() -> [ request_user_create , request_move , request_import_users , response_user + , response_users ]. fields(request_user_create) -> [ - {user_id, binary()}, - {password, binary()}, - {is_superuser, mk(boolean(), #{default => false, nullable => true})} + {user_id, binary()} + | fields(request_user_update) ]; fields(request_user_update) -> @@ -103,11 +110,21 @@ fields(response_user) -> [ {user_id, binary()}, {is_superuser, mk(boolean(), #{default => false, nullable => true})} + ]; + +fields(response_users) -> + paginated_list_type(ref(response_user)); + +fields(pagination_meta) -> + [ + {page, non_neg_integer()}, + {limit, non_neg_integer()}, + {count, non_neg_integer()} ]. schema("/authentication") -> #{ - operationId => authenticators, + 'operationId' => authenticators, get => #{ tags => [<<"authentication">>, <<"global">>], description => <<"List authenticators for global authentication">>, @@ -120,7 +137,7 @@ schema("/authentication") -> post => #{ tags => [<<"authentication">>, <<"global">>], description => <<"Create authenticator for global authentication">>, - requestBody => emqx_dashboard_swagger:schema_with_examples( + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), authenticator_examples()), responses => #{ @@ -135,7 +152,7 @@ schema("/authentication") -> schema("/authentication/:id") -> #{ - operationId => authenticator, + 'operationId' => authenticator, get => #{ tags => [<<"authentication">>, <<"global">>], description => <<"Get authenticator from global authentication chain">>, @@ -151,7 +168,7 @@ schema("/authentication/:id") -> tags => [<<"authentication">>, <<"global">>], description => <<"Update authenticator from global authentication chain">>, parameters => [{id, mk(binary(), #{in => path, desc => <<"Authenticator ID">>})}], - requestBody => emqx_dashboard_swagger:schema_with_examples( + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), authenticator_examples() ), @@ -177,7 +194,7 @@ schema("/authentication/:id") -> schema("/listeners/:listener_id/authentication") -> #{ - operationId => listener_authenticators, + 'operationId' => listener_authenticators, get => #{ tags => [<<"authentication">>, <<"listener">>], description => <<"List authenticators for listener authentication">>, @@ -192,7 +209,7 @@ schema("/listeners/:listener_id/authentication") -> tags => [<<"authentication">>, <<"listener">>], description => <<"Create authenticator for listener authentication">>, parameters => [{listener_id, mk(binary(), #{in => path, desc => <<"Listener ID">>})}], - requestBody => emqx_dashboard_swagger:schema_with_examples( + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), authenticator_examples() ), @@ -208,7 +225,7 @@ schema("/listeners/:listener_id/authentication") -> schema("/listeners/:listener_id/authentication/:id") -> #{ - operationId => listener_authenticator, + 'operationId' => listener_authenticator, get => #{ tags => [<<"authentication">>, <<"listener">>], description => <<"Get authenticator from listener authentication chain">>, @@ -230,7 +247,7 @@ schema("/listeners/:listener_id/authentication/:id") -> {listener_id, mk(binary(), #{in => path, desc => <<"Listener ID">>})}, {id, mk(binary(), #{in => path, desc => <<"Authenticator ID">>})} ], - requestBody => emqx_dashboard_swagger:schema_with_examples( + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( emqx_authn_schema:authenticator_type(), authenticator_examples()), responses => #{ @@ -259,12 +276,14 @@ schema("/listeners/:listener_id/authentication/:id") -> schema("/authentication/:id/move") -> #{ - operationId => authenticator_move, + 'operationId' => authenticator_move, post => #{ tags => [<<"authentication">>, <<"global">>], description => <<"Move authenticator in global authentication chain">>, parameters => [{id, mk(binary(), #{in => path, desc => <<"Authenticator ID">>})}], - requestBody => ref(request_move), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(request_move), + request_move_examples()), responses => #{ 204 => <<"Authenticator moved">>, 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), @@ -275,7 +294,7 @@ schema("/authentication/:id/move") -> schema("/listeners/:listener_id/authentication/:id/move") -> #{ - operationId => listener_authenticator_move, + 'operationId' => listener_authenticator_move, post => #{ tags => [<<"authentication">>, <<"listener">>], description => <<"Move authenticator in listener authentication chain">>, @@ -283,7 +302,9 @@ schema("/listeners/:listener_id/authentication/:id/move") -> {listener_id, mk(binary(), #{in => path, desc => <<"Listener ID">>})}, {id, mk(binary(), #{in => path, desc => <<"Authenticator ID">>})} ], - requestBody => ref(request_move), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(request_move), + request_move_examples()), responses => #{ 204 => <<"Authenticator moved">>, 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), @@ -294,12 +315,14 @@ schema("/listeners/:listener_id/authentication/:id/move") -> schema("/authentication/:id/import_users") -> #{ - operationId => authenticator_import_users, + 'operationId' => authenticator_import_users, post => #{ tags => [<<"authentication">>, <<"global">>], description => <<"Import users into authenticator in global authentication chain">>, parameters => [{id, mk(binary(), #{in => path, desc => <<"Authenticator ID">>})}], - requestBody => ref(request_import_users), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(request_import_users), + request_import_users_examples()), responses => #{ 204 => <<"Users imported">>, 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), @@ -310,7 +333,7 @@ schema("/authentication/:id/import_users") -> schema("/listeners/:listener_id/authentication/:id/import_users") -> #{ - operationId => listener_authenticator_import_users, + 'operationId' => listener_authenticator_import_users, post => #{ tags => [<<"authentication">>, <<"listener">>], description => <<"Import users into authenticator in listener authentication chain">>, @@ -318,7 +341,9 @@ schema("/listeners/:listener_id/authentication/:id/import_users") -> {listener_id, mk(binary(), #{in => path, desc => <<"Listener ID">>})}, {id, mk(binary(), #{in => path, desc => <<"Authenticator ID">>})} ], - requestBody => ref(request_import_users), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(request_import_users), + request_import_users_examples()), responses => #{ 204 => <<"Users imported">>, 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), @@ -329,14 +354,18 @@ schema("/listeners/:listener_id/authentication/:id/import_users") -> schema("/authentication/:id/users") -> #{ - operationId => authenticator_users, + 'operationId' => authenticator_users, post => #{ tags => [<<"authentication">>, <<"global">>], description => <<"Create users for authenticator in global authentication chain">>, parameters => [{id, mk(binary(), #{in => path, desc => <<"Authenticator ID">>})}], - requestBody => ref(request_user_create), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(request_user_create), + request_user_create_examples()), responses => #{ - 201 => ref(response_user), + 201 => emqx_dashboard_swagger:schema_with_examples( + ref(response_user), + response_user_examples()), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } @@ -350,7 +379,9 @@ schema("/authentication/:id/users") -> {limit, mk(integer(), #{in => query, desc => <<"Page Limit">>, nullable => true})} ], responses => #{ - 200 => mk(hoconsc:array(ref(response_user)), #{}), + 200 => emqx_dashboard_swagger:schema_with_example( + ref(response_users), + response_users_example()), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } @@ -359,7 +390,7 @@ schema("/authentication/:id/users") -> schema("/listeners/:listener_id/authentication/:id/users") -> #{ - operationId => listener_authenticator_users, + 'operationId' => listener_authenticator_users, post => #{ tags => [<<"authentication">>, <<"listener">>], description => <<"Create users for authenticator in global authentication chain">>, @@ -367,9 +398,13 @@ schema("/listeners/:listener_id/authentication/:id/users") -> {listener_id, mk(binary(), #{in => path, desc => <<"Listener ID">>})}, {id, mk(binary(), #{in => path, desc => <<"Authenticator ID">>})} ], - requestBody => ref(request_user_create), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(request_user_create), + request_user_create_examples()), responses => #{ - 201 => ref(response_user), + 201 => emqx_dashboard_swagger:schema_with_examples( + ref(response_user), + response_user_examples()), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } @@ -384,7 +419,9 @@ schema("/listeners/:listener_id/authentication/:id/users") -> {limit, mk(integer(), #{in => query, desc => <<"Page Limit">>, nullable => true})} ], responses => #{ - 200 => mk(hoconsc:array(ref(response_user)), #{}), + 200 => emqx_dashboard_swagger:schema_with_example( + ref(response_users), + response_users_example()), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } @@ -393,7 +430,7 @@ schema("/listeners/:listener_id/authentication/:id/users") -> schema("/authentication/:id/users/:user_id") -> #{ - operationId => authenticator_user, + 'operationId' => authenticator_user, get => #{ tags => [<<"authentication">>, <<"global">>], description => <<"Get user from authenticator in global authentication chain">>, @@ -402,7 +439,9 @@ schema("/authentication/:id/users/:user_id") -> {user_id, mk(binary(), #{in => path, desc => <<"User ID">>})} ], responses => #{ - 200 => ref(response_user), + 200 => emqx_dashboard_swagger:schema_with_examples( + ref(response_user), + response_user_examples()), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } }, @@ -413,9 +452,13 @@ schema("/authentication/:id/users/:user_id") -> {id, mk(binary(), #{in => path, desc => <<"Authenticator ID">>})}, {user_id, mk(binary(), #{in => path, desc => <<"User ID">>})} ], - requestBody => ref(request_user_update), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(request_user_update), + request_user_update_examples()), responses => #{ - 200 => mk(hoconsc:array(ref(response_user)), #{}), + 200 => emqx_dashboard_swagger:schema_with_example( + ref(response_user), + response_user_examples()), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } @@ -436,7 +479,7 @@ schema("/authentication/:id/users/:user_id") -> schema("/listeners/:listener_id/authentication/:id/users/:user_id") -> #{ - operationId => listener_authenticator_user, + 'operationId' => listener_authenticator_user, get => #{ tags => [<<"authentication">>, <<"listener">>], description => <<"Get user from authenticator in listener authentication chain">>, @@ -446,7 +489,9 @@ schema("/listeners/:listener_id/authentication/:id/users/:user_id") -> {user_id, mk(binary(), #{in => path, desc => <<"User ID">>})} ], responses => #{ - 200 => ref(response_user), + 200 => emqx_dashboard_swagger:schema_with_example( + ref(response_user), + response_user_examples()), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } }, @@ -458,9 +503,13 @@ schema("/listeners/:listener_id/authentication/:id/users/:user_id") -> {id, mk(binary(), #{in => path, desc => <<"Authenticator ID">>})}, {user_id, mk(binary(), #{in => path, desc => <<"User ID">>})} ], - requestBody => ref(request_user_update), + 'requestBody' => emqx_dashboard_swagger:schema_with_example( + ref(request_user_update), + request_user_update_examples()), responses => #{ - 200 => mk(hoconsc:array(ref(response_user)), #{}), + 200 => emqx_dashboard_swagger:schema_with_example( + ref(response_user), + response_user_examples()), 400 => error_codes([?BAD_REQUEST], <<"Bad Request">>), 404 => error_codes([?NOT_FOUND], <<"Not Found">>) } @@ -516,7 +565,9 @@ listener_authenticator(get, #{bindings := #{listener_id := ListenerID, id := Aut list_authenticator([listeners, Type, Name, authentication], AuthenticatorID) end); -listener_authenticator(put, #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}, body := Config}) -> +listener_authenticator(put, + #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}, + body := Config}) -> with_listener(ListenerID, fun(Type, Name, ChainName) -> update_authenticator([listeners, Type, Name, authentication], @@ -524,7 +575,8 @@ listener_authenticator(put, #{bindings := #{listener_id := ListenerID, id := Aut AuthenticatorID, Config) end); -listener_authenticator(delete, #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}}) -> +listener_authenticator(delete, + #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}}) -> with_listener(ListenerID, fun(Type, Name, ChainName) -> delete_authenticator([listeners, Type, Name, authentication], @@ -532,12 +584,16 @@ listener_authenticator(delete, #{bindings := #{listener_id := ListenerID, id := AuthenticatorID) end). -authenticator_move(post, #{bindings := #{id := AuthenticatorID}, body := #{<<"position">> := Position}}) -> +authenticator_move(post, + #{bindings := #{id := AuthenticatorID}, + body := #{<<"position">> := Position}}) -> move_authenitcator([authentication], ?GLOBAL, AuthenticatorID, Position); authenticator_move(post, #{bindings := #{id := _}, body := _}) -> serialize_error({missing_parameter, position}). -listener_authenticator_move(post, #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}, body := #{<<"position">> := Position}}) -> +listener_authenticator_move(post, + #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}, + body := #{<<"position">> := Position}}) -> with_listener(ListenerID, fun(Type, Name, ChainName) -> move_authenitcator([listeners, Type, Name, authentication], @@ -548,22 +604,28 @@ listener_authenticator_move(post, #{bindings := #{listener_id := ListenerID, id listener_authenticator_move(post, #{bindings := #{listener_id := _, id := _}, body := _}) -> serialize_error({missing_parameter, position}). -authenticator_import_users(post, #{bindings := #{id := AuthenticatorID}, body := #{<<"filename">> := Filename}}) -> - case ?AUTHN:import_users(?GLOBAL, AuthenticatorID, Filename) of +authenticator_import_users(post, + #{bindings := #{id := AuthenticatorID}, + body := #{<<"filename">> := Filename}}) -> + case emqx_authentication:import_users(?GLOBAL, AuthenticatorID, Filename) of ok -> {204}; {error, Reason} -> serialize_error(Reason) end; authenticator_import_users(post, #{bindings := #{id := _}, body := _}) -> serialize_error({missing_parameter, filename}). -listener_authenticator_import_users(post, #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}, body := #{<<"filename">> := Filename}}) -> - with_chain(ListenerID, - fun(ChainName) -> - case ?AUTHN:import_users(ChainName, AuthenticatorID, Filename) of - ok -> {204}; - {error, Reason} -> serialize_error(Reason) - end - end); +listener_authenticator_import_users( + post, + #{bindings := #{listener_id := ListenerID, id := AuthenticatorID}, + body := #{<<"filename">> := Filename}}) -> + with_chain( + ListenerID, + fun(ChainName) -> + case emqx_authentication:import_users(ChainName, AuthenticatorID, Filename) of + ok -> {204}; + {error, Reason} -> serialize_error(Reason) + end + end); listener_authenticator_import_users(post, #{bindings := #{listener_id := _, id := _}, body := _}) -> serialize_error({missing_parameter, filename}). @@ -644,7 +706,7 @@ find_listener(ListenerID) -> end. with_chain(ListenerID, Fun) -> - {ok, ChainNames} = ?AUTHN:list_chain_names(), + {ok, ChainNames} = emqx_authentication:list_chain_names(), ListenerChainName = [ Name || Name <- ChainNames, atom_to_binary(Name) =:= ListenerID ], case ListenerChainName of @@ -656,7 +718,7 @@ with_chain(ListenerID, Fun) -> create_authenticator(ConfKeyPath, ChainName, Config) -> case update_config(ConfKeyPath, {create_authenticator, ChainName, Config}) of - {ok, #{post_config_update := #{?AUTHN := #{id := ID}}, + {ok, #{post_config_update := #{emqx_authentication := #{id := ID}}, raw_config := AuthenticatorsConfig}} -> {ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig), {200, maps:put(id, ID, convert_certs(fill_defaults(AuthenticatorConfig)))}; @@ -666,7 +728,10 @@ create_authenticator(ConfKeyPath, ChainName, Config) -> list_authenticators(ConfKeyPath) -> AuthenticatorsConfig = get_raw_config_with_defaults(ConfKeyPath), - NAuthenticators = [maps:put(id, ?AUTHN:authenticator_id(AuthenticatorConfig), convert_certs(AuthenticatorConfig)) + NAuthenticators = [ maps:put( + id, + emqx_authentication:authenticator_id(AuthenticatorConfig), + convert_certs(AuthenticatorConfig)) || AuthenticatorConfig <- AuthenticatorsConfig], {200, NAuthenticators}. @@ -681,7 +746,7 @@ list_authenticator(ConfKeyPath, AuthenticatorID) -> update_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Config) -> case update_config(ConfKeyPath, {update_authenticator, ChainName, AuthenticatorID, Config}) of - {ok, #{post_config_update := #{?AUTHN := #{id := ID}}, + {ok, #{post_config_update := #{emqx_authentication := #{id := ID}}, raw_config := AuthenticatorsConfig}} -> {ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig), {200, maps:put(id, ID, convert_certs(fill_defaults(AuthenticatorConfig)))}; @@ -700,7 +765,9 @@ delete_authenticator(ConfKeyPath, ChainName, AuthenticatorID) -> move_authenitcator(ConfKeyPath, ChainName, AuthenticatorID, Position) -> case parse_position(Position) of {ok, NPosition} -> - case update_config(ConfKeyPath, {move_authenticator, ChainName, AuthenticatorID, NPosition}) of + case update_config( + ConfKeyPath, + {move_authenticator, ChainName, AuthenticatorID, NPosition}) of {ok, _} -> {204}; {error, {_, _, Reason}} -> @@ -710,9 +777,11 @@ move_authenitcator(ConfKeyPath, ChainName, AuthenticatorID, Position) -> serialize_error(Reason) end. -add_user(ChainName, AuthenticatorID, #{<<"user_id">> := UserID, <<"password">> := Password} = UserInfo) -> +add_user(ChainName, + AuthenticatorID, + #{<<"user_id">> := UserID, <<"password">> := Password} = UserInfo) -> IsSuperuser = maps:get(<<"is_superuser">>, UserInfo, false), - case ?AUTHN:add_user(ChainName, AuthenticatorID, #{ user_id => UserID + case emqx_authentication:add_user(ChainName, AuthenticatorID, #{ user_id => UserID , password => Password , is_superuser => IsSuperuser}) of {ok, User} -> @@ -730,7 +799,7 @@ update_user(ChainName, AuthenticatorID, UserID, UserInfo) -> true -> serialize_error({missing_parameter, password}); false -> - case ?AUTHN:update_user(ChainName, AuthenticatorID, UserID, UserInfo) of + case emqx_authentication:update_user(ChainName, AuthenticatorID, UserID, UserInfo) of {ok, User} -> {200, User}; {error, Reason} -> @@ -739,7 +808,7 @@ update_user(ChainName, AuthenticatorID, UserID, UserInfo) -> end. find_user(ChainName, AuthenticatorID, UserID) -> - case ?AUTHN:lookup_user(ChainName, AuthenticatorID, UserID) of + case emqx_authentication:lookup_user(ChainName, AuthenticatorID, UserID) of {ok, User} -> {200, User}; {error, Reason} -> @@ -747,7 +816,7 @@ find_user(ChainName, AuthenticatorID, UserID) -> end. delete_user(ChainName, AuthenticatorID, UserID) -> - case ?AUTHN:delete_user(ChainName, AuthenticatorID, UserID) of + case emqx_authentication:delete_user(ChainName, AuthenticatorID, UserID) of ok -> {204}; {error, Reason} -> @@ -755,7 +824,7 @@ delete_user(ChainName, AuthenticatorID, UserID) -> end. list_users(ChainName, AuthenticatorID, PageParams) -> - case ?AUTHN:list_users(ChainName, AuthenticatorID, PageParams) of + case emqx_authentication:list_users(ChainName, AuthenticatorID, PageParams) of {ok, Users} -> {200, Users}; {error, Reason} -> @@ -771,7 +840,11 @@ get_raw_config_with_defaults(ConfKeyPath) -> ensure_list(fill_defaults(RawConfig)). find_config(AuthenticatorID, AuthenticatorsConfig) -> - case [AC || AC <- ensure_list(AuthenticatorsConfig), AuthenticatorID =:= ?AUTHN:authenticator_id(AC)] of + MatchingACs + = [AC + || AC <- ensure_list(AuthenticatorsConfig), + AuthenticatorID =:= emqx_authentication:authenticator_id(AC)], + case MatchingACs of [] -> {error, {not_found, {authenticator, AuthenticatorID}}}; [AuthenticatorConfig] -> {ok, AuthenticatorConfig} end. @@ -860,6 +933,12 @@ ensure_list(L) when is_list(L) -> L. binfmt(Fmt, Args) -> iolist_to_binary(io_lib:format(Fmt, Args)). +paginated_list_type(Type) -> + [ + {data, hoconsc:array(Type)}, + {meta, ref(pagination_meta)} + ]. + authenticator_array_example() -> [Config || #{value := Config} <- maps:values(authenticator_examples())]. @@ -941,3 +1020,112 @@ authenticator_examples() -> } } }. + +request_user_create_examples() -> + #{ + regular_user => #{ + summary => <<"Regular user">>, + value => #{ + user_id => <<"user1">>, + password => <<"secret">> + } + }, + super_user => #{ + summary => <<"Superuser">>, + value => #{ + user_id => <<"user2">>, + password => <<"secret">>, + is_superuser => true + } + } + }. + +request_user_update_examples() -> + #{ + regular_user => #{ + summary => <<"Update regular user">>, + value => #{ + password => <<"newsecret">> + } + }, + super_user => #{ + summary => <<"Update user and promote to superuser">>, + value => #{ + password => <<"newsecret">>, + is_superuser => true + } + } + }. + +request_move_examples() -> + #{ + move_to_top => #{ + summary => <<"Move authenticator to the beginning of the chain">>, + value => #{ + position => <<"top">> + } + }, + move_to_bottom => #{ + summary => <<"Move authenticator to the end of the chain">>, + value => #{ + position => <<"bottom">> + } + }, + 'move_before_password-based:built-in-database' => #{ + summary => <<"Move authenticator to the position preceding some other authenticator">>, + value => #{ + position => <<"before:password-based:built-in-database">> + } + } + }. + +request_import_users_examples() -> + #{ + import_csv => #{ + summary => <<"Import users from CSV file">>, + value => #{ + filename => <<"/path/to/user/data.csv">> + } + }, + import_json => #{ + summary => <<"Import users from JSON file">>, + value => #{ + filename => <<"/path/to/user/data.json">> + } + } + }. + +response_user_examples() -> + #{ + regular_user => #{ + summary => <<"Regular user">>, + value => #{ + user_id => <<"user1">> + } + }, + super_user => #{ + summary => <<"Superuser">>, + value => #{ + user_id => <<"user2">>, + is_superuser => true + } + } + }. + +response_users_example() -> + #{ + data => [ + #{ + user_id => <<"user1">> + }, + #{ + user_id => <<"user2">>, + is_superuser => true + } + ], + meta => #{ + page => 0, + limit => 20, + count => 300 + } + }. diff --git a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl b/apps/emqx_authn/test/emqx_authn_api_SUITE.erl index b2f9bb106..654aa042a 100644 --- a/apps/emqx_authn/test/emqx_authn_api_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_api_SUITE.erl @@ -49,7 +49,9 @@ init_per_testcase(_, Config) -> Config. init_per_suite(Config) -> - ok = emqx_common_test_helpers:start_apps([emqx_authn, emqx_dashboard], fun set_special_configs/1), + ok = emqx_common_test_helpers:start_apps( + [emqx_authn, emqx_dashboard], + fun set_special_configs/1), Config. end_per_suite(_Config) -> @@ -118,309 +120,275 @@ test_authenticators(PathPrefix) -> ValidConfig = emqx_authn_test_lib:http_example(), {ok, 200, _} = request( - post, - uri(PathPrefix ++ ["authentication"]), - ValidConfig), + post, + uri(PathPrefix ++ ["authentication"]), + ValidConfig), InvalidConfig = ValidConfig#{method => <<"delete">>}, {ok, 400, _} = request( - post, - uri(PathPrefix ++ ["authentication"]), - InvalidConfig), + post, + uri(PathPrefix ++ ["authentication"]), + InvalidConfig), ?assertAuthenticatorsMatch( - [#{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}], - PathPrefix ++ ["authentication"]). + [#{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}], + PathPrefix ++ ["authentication"]). test_authenticator(PathPrefix) -> ValidConfig0 = emqx_authn_test_lib:http_example(), {ok, 200, _} = request( - post, - uri(PathPrefix ++ ["authentication"]), - ValidConfig0), + post, + uri(PathPrefix ++ ["authentication"]), + ValidConfig0), {ok, 200, _} = request( - get, - uri(PathPrefix ++ ["authentication", "password-based:http"])), + get, + uri(PathPrefix ++ ["authentication", "password-based:http"])), {ok, 404, _} = request( - get, - uri(PathPrefix ++ ["authentication", "password-based:redis"])), + get, + uri(PathPrefix ++ ["authentication", "password-based:redis"])), {ok, 404, _} = request( - put, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database"]), - emqx_authn_test_lib:built_in_database_example()), + put, + uri(PathPrefix ++ ["authentication", "password-based:built-in-database"]), + emqx_authn_test_lib:built_in_database_example()), InvalidConfig0 = ValidConfig0#{method => <<"delete">>}, {ok, 400, _} = request( - put, - uri(PathPrefix ++ ["authentication", "password-based:http"]), - InvalidConfig0), + put, + uri(PathPrefix ++ ["authentication", "password-based:http"]), + InvalidConfig0), ValidConfig1 = ValidConfig0#{pool_size => 9}, {ok, 200, _} = request( - put, - uri(PathPrefix ++ ["authentication", "password-based:http"]), - ValidConfig1), + put, + uri(PathPrefix ++ ["authentication", "password-based:http"]), + ValidConfig1), {ok, 404, _} = request( - delete, - uri(PathPrefix ++ ["authentication", "password-based:redis"])), + delete, + uri(PathPrefix ++ ["authentication", "password-based:redis"])), {ok, 204, _} = request( - delete, - uri(PathPrefix ++ ["authentication", "password-based:http"])), + delete, + uri(PathPrefix ++ ["authentication", "password-based:http"])), ?assertAuthenticatorsMatch([], PathPrefix ++ ["authentication"]). test_authenticator_users(PathPrefix) -> + UsersUri = uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users"]), + {ok, 200, _} = request( - post, - uri(PathPrefix ++ ["authentication"]), - emqx_authn_test_lib:built_in_database_example()), + post, + uri(PathPrefix ++ ["authentication"]), + emqx_authn_test_lib:built_in_database_example()), InvalidUsers = [ - #{clientid => <<"u1">>, password => <<"p1">>}, - #{user_id => <<"u2">>}, - #{user_id => <<"u3">>, password => <<"p3">>, foobar => <<"foobar">>}], + #{clientid => <<"u1">>, password => <<"p1">>}, + #{user_id => <<"u2">>}, + #{user_id => <<"u3">>, password => <<"p3">>, foobar => <<"foobar">>}], lists:foreach( - fun(User) -> - {ok, 400, _} = request( - post, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users"]), - User) - end, - InvalidUsers), + fun(User) -> {ok, 400, _} = request(post, UsersUri, User) end, + InvalidUsers), ValidUsers = [ - #{user_id => <<"u1">>, password => <<"p1">>}, - #{user_id => <<"u2">>, password => <<"p2">>, is_superuser => true}, - #{user_id => <<"u3">>, password => <<"p3">>}], + #{user_id => <<"u1">>, password => <<"p1">>}, + #{user_id => <<"u2">>, password => <<"p2">>, is_superuser => true}, + #{user_id => <<"u3">>, password => <<"p3">>}], lists:foreach( - fun(User) -> - {ok, 201, _} = request( - post, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users"]), - User) - end, - ValidUsers), + fun(User) -> + {ok, 201, UserData} = request(post, UsersUri, User), + CreatedUser = jiffy:decode(UserData, [return_maps]), + ?assertMatch(#{<<"user_id">> := _}, CreatedUser) + end, + ValidUsers), - {ok, 200, Page1Data} = - request( - get, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users"]) ++ "?page=1&limit=2"), + {ok, 200, Page1Data} = request(get, UsersUri ++ "?page=1&limit=2"), - Page1Users = response_data(Page1Data), + #{<<"data">> := Page1Users, + <<"meta">> := + #{<<"page">> := 1, + <<"limit">> := 2, + <<"count">> := 3}} = + jiffy:decode(Page1Data, [return_maps]), - {ok, 200, Page2Data} = - request( - get, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users"]) ++ "?page=2&limit=2"), + {ok, 200, Page2Data} = request(get, UsersUri ++ "?page=2&limit=2"), - Page2Users = response_data(Page2Data), + #{<<"data">> := Page2Users, + <<"meta">> := + #{<<"page">> := 2, + <<"limit">> := 2, + <<"count">> := 3}} = jiffy:decode(Page2Data, [return_maps]), ?assertEqual(2, length(Page1Users)), ?assertEqual(1, length(Page2Users)), ?assertEqual( - [<<"u1">>, <<"u2">>, <<"u3">>], - lists:usort([ UserId || #{<<"user_id">> := UserId} <- Page1Users ++ Page2Users])). + [<<"u1">>, <<"u2">>, <<"u3">>], + lists:usort([ UserId || #{<<"user_id">> := UserId} <- Page1Users ++ Page2Users])). test_authenticator_user(PathPrefix) -> + UsersUri = uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users"]), + {ok, 200, _} = request( - post, - uri(PathPrefix ++ ["authentication"]), - emqx_authn_test_lib:built_in_database_example()), + post, + uri(PathPrefix ++ ["authentication"]), + emqx_authn_test_lib:built_in_database_example()), User = #{user_id => <<"u1">>, password => <<"p1">>}, - {ok, 201, _} = request( - post, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users"]), - User), + {ok, 201, _} = request(post, UsersUri, User), - {ok, 404, _} = request( - get, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users", "u123"])), + {ok, 404, _} = request(get, UsersUri ++ "/u123"), - {ok, 409, _} = request( - post, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users"]), - User), + {ok, 409, _} = request(post, UsersUri, User), - {ok, 200, UserData} = request( - get, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users", "u1"])), + {ok, 200, UserData} = request(get, UsersUri ++ "/u1"), FetchedUser = jiffy:decode(UserData, [return_maps]), ?assertMatch(#{<<"user_id">> := <<"u1">>}, FetchedUser), ?assertNotMatch(#{<<"password">> := _}, FetchedUser), ValidUserUpdates = [ - #{password => <<"p1">>}, - #{password => <<"p1">>, is_superuser => true}], + #{password => <<"p1">>}, + #{password => <<"p1">>, is_superuser => true}], lists:foreach( - fun(UserUpdate) -> - {ok, 200, _} = request( - put, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users", "u1"]), - UserUpdate) - end, - ValidUserUpdates), + fun(UserUpdate) -> {ok, 200, _} = request(put, UsersUri ++ "/u1", UserUpdate) end, + ValidUserUpdates), InvalidUserUpdates = [ - #{user_id => <<"u1">>, password => <<"p1">>}, - #{is_superuser => true}], + #{user_id => <<"u1">>, password => <<"p1">>}, + #{is_superuser => true}], lists:foreach( - fun(UserUpdate) -> - {ok, 400, _} = request( - put, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users", "u1"]), - UserUpdate) - end, - InvalidUserUpdates), + fun(UserUpdate) -> {ok, 400, _} = request(put, UsersUri ++ "/u1", UserUpdate) end, + InvalidUserUpdates), - {ok, 404, _} = request( - delete, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users", "u123"])), - - {ok, 204, _} = request( - delete, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "users", "u1"])). + {ok, 404, _} = request(delete, UsersUri ++ "/u123"), + {ok, 204, _} = request(delete, UsersUri ++ "/u1"). test_authenticator_move(PathPrefix) -> AuthenticatorConfs = [ - emqx_authn_test_lib:http_example(), - emqx_authn_test_lib:jwt_example(), - emqx_authn_test_lib:built_in_database_example() - ], + emqx_authn_test_lib:http_example(), + emqx_authn_test_lib:jwt_example(), + emqx_authn_test_lib:built_in_database_example() + ], lists:foreach( - fun(Conf) -> - {ok, 200, _} = request( - post, - uri(PathPrefix ++ ["authentication"]), - Conf) - end, - AuthenticatorConfs), + fun(Conf) -> + {ok, 200, _} = request( + post, + uri(PathPrefix ++ ["authentication"]), + Conf) + end, + AuthenticatorConfs), ?assertAuthenticatorsMatch( - [ - #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}, - #{<<"mechanism">> := <<"jwt">>}, - #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>} - ], - PathPrefix ++ ["authentication"]), + [ + #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}, + #{<<"mechanism">> := <<"jwt">>}, + #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>} + ], + PathPrefix ++ ["authentication"]), % Invalid moves {ok, 400, _} = request( - post, - uri(PathPrefix ++ ["authentication", "jwt", "move"]), - #{position => <<"up">>}), + post, + uri(PathPrefix ++ ["authentication", "jwt", "move"]), + #{position => <<"up">>}), {ok, 400, _} = request( - post, - uri(PathPrefix ++ ["authentication", "jwt", "move"]), - #{}), + post, + uri(PathPrefix ++ ["authentication", "jwt", "move"]), + #{}), {ok, 404, _} = request( - post, - uri(PathPrefix ++ ["authentication", "jwt", "move"]), - #{position => <<"before:invalid">>}), + post, + uri(PathPrefix ++ ["authentication", "jwt", "move"]), + #{position => <<"before:invalid">>}), {ok, 404, _} = request( - post, - uri(PathPrefix ++ ["authentication", "jwt", "move"]), - #{position => <<"before:password-based:redis">>}), + post, + uri(PathPrefix ++ ["authentication", "jwt", "move"]), + #{position => <<"before:password-based:redis">>}), {ok, 404, _} = request( - post, - uri(PathPrefix ++ ["authentication", "jwt", "move"]), - #{position => <<"before:password-based:redis">>}), + post, + uri(PathPrefix ++ ["authentication", "jwt", "move"]), + #{position => <<"before:password-based:redis">>}), % Valid moves {ok, 204, _} = request( - post, - uri(PathPrefix ++ ["authentication", "jwt", "move"]), - #{position => <<"top">>}), + post, + uri(PathPrefix ++ ["authentication", "jwt", "move"]), + #{position => <<"top">>}), ?assertAuthenticatorsMatch( - [ - #{<<"mechanism">> := <<"jwt">>}, - #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}, - #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>} - ], - PathPrefix ++ ["authentication"]), + [ + #{<<"mechanism">> := <<"jwt">>}, + #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}, + #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>} + ], + PathPrefix ++ ["authentication"]), {ok, 204, _} = request( - post, - uri(PathPrefix ++ ["authentication", "jwt", "move"]), - #{position => <<"bottom">>}), + post, + uri(PathPrefix ++ ["authentication", "jwt", "move"]), + #{position => <<"bottom">>}), ?assertAuthenticatorsMatch( - [ - #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}, - #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>}, - #{<<"mechanism">> := <<"jwt">>} - ], - PathPrefix ++ ["authentication"]), + [ + #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}, + #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>}, + #{<<"mechanism">> := <<"jwt">>} + ], + PathPrefix ++ ["authentication"]), {ok, 204, _} = request( - post, - uri(PathPrefix ++ ["authentication", "jwt", "move"]), - #{position => <<"before:password-based:built-in-database">>}), + post, + uri(PathPrefix ++ ["authentication", "jwt", "move"]), + #{position => <<"before:password-based:built-in-database">>}), ?assertAuthenticatorsMatch( - [ - #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}, - #{<<"mechanism">> := <<"jwt">>}, - #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>} - ], - PathPrefix ++ ["authentication"]). + [ + #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}, + #{<<"mechanism">> := <<"jwt">>}, + #{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"built-in-database">>} + ], + PathPrefix ++ ["authentication"]). test_authenticator_import_users(PathPrefix) -> + ImportUri = uri( + PathPrefix ++ + ["authentication", "password-based:built-in-database", "import_users"]), + + {ok, 200, _} = request( - post, - uri(PathPrefix ++ ["authentication"]), - emqx_authn_test_lib:built_in_database_example()), + post, + uri(PathPrefix ++ ["authentication"]), + emqx_authn_test_lib:built_in_database_example()), - {ok, 400, _} = request( - post, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "import_users"]), - #{}), + {ok, 400, _} = request(post, ImportUri, #{}), - {ok, 400, _} = request( - post, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "import_users"]), - #{filename => <<"/etc/passwd">>}), + {ok, 400, _} = request(post, ImportUri, #{filename => <<"/etc/passwd">>}), - {ok, 400, _} = request( - post, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "import_users"]), - #{filename => <<"/not_exists.csv">>}), + {ok, 400, _} = request(post, ImportUri, #{filename => <<"/not_exists.csv">>}), Dir = code:lib_dir(emqx_authn, test), JSONFileName = filename:join([Dir, <<"data/user-credentials.json">>]), CSVFileName = filename:join([Dir, <<"data/user-credentials.csv">>]), - {ok, 204, _} = request( - post, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "import_users"]), - #{filename => JSONFileName}), + {ok, 204, _} = request(post, ImportUri, #{filename => JSONFileName}), - {ok, 204, _} = request( - post, - uri(PathPrefix ++ ["authentication", "password-based:built-in-database", "import_users"]), - #{filename => CSVFileName}). + {ok, 204, _} = request(post, ImportUri, #{filename => CSVFileName}). %%------------------------------------------------------------------------------ %% Helpers @@ -440,10 +408,6 @@ delete_authenticators(Path, Chain) -> Authenticators) end. -response_data(Response) -> - #{<<"data">> := Data} = jiffy:decode(Response, [return_maps]), - Data. - request(Method, Url) -> request(Method, Url, []).