-
Notifications
You must be signed in to change notification settings - Fork 428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Limit the number of users per domain #4059
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,8 @@ admin_account_tests() -> | |
admin_check_non_existing_user, | ||
admin_register_user, | ||
admin_register_random_user, | ||
admin_register_user_non_existing_domain, | ||
admin_register_user_limit_error, | ||
admin_remove_non_existing_user, | ||
admin_remove_existing_user, | ||
admin_ban_user, | ||
|
@@ -78,6 +80,7 @@ domain_admin_tests() -> | |
domain_admin_register_user_no_permission, | ||
admin_register_random_user, | ||
domain_admin_register_random_user_no_permission, | ||
admin_register_user_limit_error, | ||
admin_remove_existing_user, | ||
domain_admin_remove_user_no_permission, | ||
admin_ban_user, | ||
|
@@ -144,6 +147,13 @@ init_per_testcase(admin_check_plain_password_hash = C, Config) -> | |
Config2 = escalus:create_users(Config1, escalus:get_users([carol])), | ||
escalus:init_per_testcase(C, Config2) | ||
end; | ||
init_per_testcase(admin_register_user_limit_error = C, Config) -> | ||
Domain = domain_helper:domain(), | ||
{ok, HostType} = rpc(mim(), mongoose_domain_api, get_domain_host_type, [Domain]), | ||
OptKey = {max_users_per_domain, HostType}, | ||
Config1 = mongoose_helper:backup_and_set_config_option(Config, OptKey, 3), | ||
Config2 = [{user1, <<"bob">>}, {user2, <<"kate">>}, {user3, <<"john">>} | Config1], | ||
escalus:init_per_testcase(C, Config2); | ||
init_per_testcase(domain_admin_check_plain_password_hash_no_permission = C, Config) -> | ||
{_, AuthMods} = lists:keyfind(ctl_auth_mods, 1, Config), | ||
case lists:member(ejabberd_auth_ldap, AuthMods) of | ||
|
@@ -169,6 +179,12 @@ end_per_testcase(admin_register_user = C, Config) -> | |
end_per_testcase(admin_check_plain_password_hash, Config) -> | ||
mongoose_helper:restore_config(Config), | ||
escalus:delete_users(Config, escalus:get_users([carol])); | ||
end_per_testcase(admin_register_user_limit_error = C, Config) -> | ||
Domain = domain_helper:domain(), | ||
rpc(mim(), mongoose_account_api, unregister_user, [proplists:get_value(user1, Config), Domain]), | ||
rpc(mim(), mongoose_account_api, unregister_user, [proplists:get_value(user2, Config), Domain]), | ||
mongoose_helper:restore_config(Config), | ||
escalus:end_per_testcase(C, Config); | ||
end_per_testcase(domain_admin_check_plain_password_hash_no_permission, Config) -> | ||
mongoose_helper:restore_config(Config), | ||
escalus:delete_users(Config, escalus:get_users([carol, alice_bis])); | ||
|
@@ -347,6 +363,25 @@ admin_register_random_user(Config) -> | |
?assertNotEqual(nomatch, binary:match(Msg, <<"successfully registered">>)), | ||
{ok, _} = rpc(mim(), mongoose_account_api, unregister_user, [Username, Server]). | ||
|
||
admin_register_user_non_existing_domain(Config) -> | ||
% Try to register a user with a non-existing domain | ||
Resp = register_user(<<"unknown">>, <<"alice">>, <<"test_password">>, Config), | ||
?assertMatch({_, _}, binary:match(get_err_msg(Resp), <<"not_allowed">>)). | ||
|
||
admin_register_user_limit_error(Config) -> | ||
Password = <<"password">>, | ||
Domain = domain_helper:domain(), | ||
Path = [data, account, registerUser, message], | ||
list_users(Domain, Config), | ||
Resp1 = register_user(Domain, proplists:get_value(user1, Config), Password, Config), | ||
?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp1), <<"successfully registered">>)), | ||
Resp2 = register_user(Domain, proplists:get_value(user2, Config), Password, Config), | ||
?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp2), <<"successfully registered">>)), | ||
%% One user was registered in the init_per_group, and two more were registered in this test case | ||
%% The next registration should exceed the limit of three | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Three does not exceed the limit of three, to be precise. |
||
Resp3 = register_user(Domain, proplists:get_value(user3, Config), Password, Config), | ||
?assertMatch({_, _}, binary:match(get_err_msg(Resp3), <<"limit has been exceeded">>)). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a note: maybe make sure the third account is not created? |
||
|
||
admin_remove_non_existing_user(Config) -> | ||
% Non-existing user, non-existing domain | ||
Resp = remove_user(?NOT_EXISTING_JID, Config), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -157,6 +157,13 @@ See the section about [redis connection setup](./outgoing-connections.md#redis-s | |
|
||
When a user's session is replaced (due to a full JID conflict) by a new one, this parameter specifies the time MongooseIM waits for the old sessions to close. The default value is sufficient in most cases. If you observe `replaced_wait_timeout` warning in logs, then most probably the old sessions are frozen for some reason and it should be investigated. | ||
|
||
### `general.max_users_per_domain` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is in the wrong section - it is user account management, not session management. Just a question: why not place it in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’ve chosen to include it in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually it only limits certain authentication methods, because some of them ( |
||
* **Syntax:** positive integer or string `"infinity"`, representing maximum amount of users that can be registered in a domain | ||
* **Default:** `"infinity"` | ||
* **Example:** `max_users_per_domain = 10000` | ||
|
||
Limits the number of users that can be registered for each domain. If the option is configured to the value `"infinity"`, no limit is present. | ||
|
||
## Message routing | ||
|
||
The following options influence the way MongooseIM routes incoming messages to their recipients. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -209,7 +209,7 @@ set_password(#jid{luser = LUser, lserver = LServer}, Password) -> | |
end. | ||
|
||
-spec try_register(jid:jid() | error, binary()) -> | ||
ok | {error, exists | not_allowed | invalid_jid | null_password}. | ||
ok | {error, exists | not_allowed | invalid_jid | null_password | limit_per_domain_exceeded}. | ||
try_register(_, <<>>) -> | ||
{error, null_password}; | ||
try_register(#jid{luser = <<>>}, _) -> | ||
|
@@ -221,7 +221,7 @@ try_register(JID, Password) -> | |
do_try_register_if_does_not_exist(Exists, JID, Password). | ||
|
||
-spec do_try_register_if_does_not_exist(boolean(), jid:jid(), binary()) -> | ||
ok | {error, exists | not_allowed | invalid_jid | null_password}. | ||
ok | {error, exists | not_allowed | invalid_jid | null_password | limit_per_domain_exceeded}. | ||
do_try_register_if_does_not_exist(true, _, _) -> | ||
{error, exists}; | ||
do_try_register_if_does_not_exist(_, JID, Password) -> | ||
|
@@ -236,7 +236,12 @@ do_try_register_if_does_not_exist(_, JID, Password) -> | |
end | ||
end, | ||
Opts = #{default => {error, not_allowed}, metric => try_register}, | ||
call_auth_modules_for_domain(LServer, F, Opts). | ||
case is_user_limit_per_domain_exceeded(LServer) of | ||
true -> | ||
{error, limit_per_domain_exceeded}; | ||
false -> | ||
call_auth_modules_for_domain(LServer, F, Opts) | ||
end. | ||
|
||
%% @doc Registered users list do not include anonymous users logged | ||
-spec get_vh_registered_users(Server :: jid:server()) -> [jid:simple_bare_jid()]. | ||
|
@@ -578,3 +583,13 @@ fold_auth_modules([AuthModule | AuthModules], F, CurAcc) -> | |
{stop, Value} -> | ||
Value | ||
end. | ||
|
||
is_user_limit_per_domain_exceeded(Domain) -> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Function name is misleading. If there are exactly as many users as the limit, it is not exceeded, yet it returns |
||
case mongoose_domain_api:get_domain_host_type(Domain) of | ||
{ok, HostType} -> | ||
Limit = mongoose_config:get_opt({max_users_per_domain, HostType}), | ||
Current = get_vh_registered_users_number(Domain), | ||
Current >= Limit; | ||
{error, not_found} -> | ||
false | ||
end. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be good to delete all users here to make sure that if the user creation succeeds, all accounts are deleted and the state is clean.