1: -module(accounts_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("escalus/include/escalus.hrl").
    5: -include_lib("escalus/include/escalus_xmlns.hrl").
    6: 
    7: -include_lib("common_test/include/ct.hrl").
    8: -include_lib("eunit/include/eunit.hrl").
    9: 
   10: -include_lib("exml/include/exml.hrl").
   11: 
   12: -import(distributed_helper, [mim/0,
   13:                              require_rpc_nodes/1,
   14:                              rpc/4]).
   15: 
   16: -import(mongoose_helper, [wait_for_user/3]).
   17: 
   18: -import(domain_helper, [domain/0, host_type/0]).
   19: 
   20: %%--------------------------------------------------------------------
   21: %% Suite configuration
   22: %%--------------------------------------------------------------------
   23: 
   24: -define(REGISTRATION_TIMEOUT, 2).  %% seconds
   25: 
   26: all() ->
   27:     [
   28:      {group, register},
   29:      {group, registration_watchers},
   30:      {group, bad_registration},
   31:      {group, bad_cancelation},
   32:      {group, registration_timeout},
   33:      {group, change_account_details},
   34:      {group, change_account_details_store_plain},
   35:      {group, utilities}
   36:     ].
   37: 
   38: groups() ->
   39:     [{register, [parallel], [register,
   40:                              already_registered,
   41:                              registration_conflict,
   42:                              check_unregistered]},
   43:      {registration_watchers, [sequence], [admin_notify]},
   44:      {bad_registration, [sequence], [null_password]},
   45:      {bad_cancelation, [sequence], [bad_request_registration_cancelation,
   46:                                     not_allowed_registration_cancelation]},
   47:      {registration_timeout, [sequence], [registration_timeout,
   48:                                          registration_failure_timeout]},
   49:      {change_account_details, [parallel], change_password_tests()},
   50:      {change_account_details_store_plain, [parallel], change_password_tests()},
   51:      {utilities, [{group, user_info},
   52:                   {group, users_number_estimate}]},
   53:      {user_info, [parallel], [list_users,
   54:                               list_selected_users,
   55:                               count_users,
   56:                               count_selected_users]},
   57:      {users_number_estimate, [], [count_users_estimate]}
   58:     ].
   59: 
   60: suite() ->
   61:     require_rpc_nodes([mim]) ++ escalus:suite().
   62: 
   63: change_password_tests() ->
   64:     [change_password,
   65:      change_password_to_null].
   66: %%--------------------------------------------------------------------
   67: %% Init & teardown
   68: %%--------------------------------------------------------------------
   69: 
   70: init_per_suite(Config1) ->
   71:     ok = dynamic_modules:ensure_modules(host_type(), required_modules()),
   72:     Config2 = [{mod_register_options, mod_register_options()} | Config1],
   73:     escalus:init_per_suite([{escalus_user_db, xmpp} | Config2]).
   74: 
   75: end_per_suite(Config) ->
   76:     escalus_fresh:clean(),
   77:     escalus:end_per_suite(Config).
   78: 
   79: required_modules() ->
   80:     [{mod_register, mod_register_options()}].
   81: 
   82: mod_register_options() ->
   83:     config_parser_helper:mod_config(mod_register, #{ip_access => [{allow, "127.0.0.0/8"},
   84:                                                                   {deny, "0.0.0.0/0"}],
   85:                                                     access => register}).
   86: 
   87: init_per_group(bad_cancelation, Config) ->
   88:     escalus:create_users(Config, escalus:get_users([alice]));
   89: init_per_group(change_account_details, Config) ->
   90:     [{escalus_user_db,  {module, escalus_ejabberd}} |Config];
   91: init_per_group(change_account_details_store_plain, Config) ->
   92:     AuthOpts = mongoose_helper:auth_opts_with_password_format(plain),
   93:     Config1 = mongoose_helper:backup_and_set_config_option(Config, auth, AuthOpts),
   94:     [{escalus_user_db,  {module, escalus_ejabberd}} |Config1];
   95: init_per_group(registration_timeout, Config) ->
   96:     set_registration_timeout(Config);
   97: init_per_group(utilities, Config) ->
   98:     escalus:create_users(Config, escalus:get_users([alice, bob]));
   99: init_per_group(users_number_estimate, Config) ->
  100:     AuthOpts = get_auth_opts(),
  101:     NewAuthOpts = AuthOpts#{rdbms => #{users_number_estimate => true}},
  102:     set_auth_opts(Config, NewAuthOpts);
  103: init_per_group(_GroupName, Config) ->
  104:     Config.
  105: 
  106: end_per_group(change_account_details, Config) ->
  107:     escalus_fresh:clean(),
  108:     [{escalus_user_db, xmpp} | Config];
  109: end_per_group(change_account_details_store_plain, Config) ->
  110:     escalus_fresh:clean(),
  111:     mongoose_helper:restore_config(Config),
  112:     [{escalus_user_db, xmpp} | Config];
  113: end_per_group(bad_cancelation, Config) ->
  114:     escalus:delete_users(Config, escalus:get_users([alice]));
  115: end_per_group(registration_timeout, Config) ->
  116:     restore_registration_timeout(Config);
  117: end_per_group(utilities, Config) ->
  118:     escalus:delete_users(Config, escalus:get_users([alice, bob]));
  119: end_per_group(users_number_estimate, Config) ->
  120:     mongoose_helper:restore_config(Config);
  121: end_per_group(_GroupName, Config) ->
  122:     Config.
  123: 
  124: get_auth_opts() ->
  125:     rpc(mim(), mongoose_config, get_opt, [{auth, host_type()}]).
  126: 
  127: set_auth_opts(Config, AuthOpts) ->
  128:     rpc(mim(), ejabberd_auth, stop, [host_type()]),
  129:     Config1 = mongoose_helper:backup_and_set_config_option(Config, {auth, host_type()}, AuthOpts),
  130:     rpc(mim(), ejabberd_auth, start, [host_type()]),
  131:     Config1.
  132: 
  133: init_per_testcase(admin_notify, Config) ->
  134:     [{_, AdminSpec}] = escalus_users:get_users([admin]),
  135:     [AdminU, AdminS, _AdminP] = escalus_users:get_usp(Config, AdminSpec),
  136:     AdminJid = <<AdminU/binary, "@", AdminS/binary>>,
  137:     enable_watcher(Config, AdminJid),
  138:     escalus:init_per_testcase(admin_notify, Config);
  139: init_per_testcase(not_allowed_registration_cancelation, Config) ->
  140:     %% Use a configuration that will not allow inband cancelation (and registration).
  141:     reload_mod_register_option(Config, access, none),
  142:     escalus:init_per_testcase(not_allowed_registration_cancelation, Config);
  143: init_per_testcase(registration_failure_timeout, Config) ->
  144:     Config1 = deny_everyone_registration(Config),
  145:     escalus:init_per_testcase(registration_failure_timeout, Config1);
  146: init_per_testcase(CaseName, Config) when CaseName =:= list_selected_users;
  147:                                          CaseName =:= count_selected_users ->
  148:     case mongoose_helper:auth_modules() of
  149:         [Mod | _] when Mod =:= ejabberd_auth_rdbms;
  150:                        Mod =:= ejabberd_auth_internal ->
  151:             escalus:init_per_testcase(CaseName, Config);
  152:         Modules ->
  153:             {skip, {"Queries for selected users not supported", Modules}}
  154:     end;
  155: init_per_testcase(CaseName, Config) ->
  156:     escalus:init_per_testcase(CaseName, Config).
  157: 
  158: end_per_testcase(register, Config) ->
  159:     escalus:end_per_testcase(register, Config);
  160: end_per_testcase(admin_notify, Config) ->
  161:     disable_watcher(Config),
  162:     escalus:delete_users(Config, escalus:get_users([alice, bob, admin])),
  163:     escalus:end_per_testcase(admin_notify, Config);
  164: end_per_testcase(registration_conflict, Config) ->
  165:     escalus_users:delete_users(Config, escalus:get_users([alice])),
  166:     escalus:end_per_testcase(registration_conflict, Config);
  167: end_per_testcase(not_allowed_registration_cancelation, Config) ->
  168:     restore_mod_register_options(Config),
  169:     escalus:end_per_testcase(not_allowed_registration_cancelation, Config);
  170: end_per_testcase(registration_timeout, Config) ->
  171:     escalus:delete_users(Config, escalus:get_users([alice, bob])),
  172:     escalus:end_per_testcase(registration_timeout, Config);
  173: end_per_testcase(registration_failure_timeout, Config) ->
  174:     mongoose_helper:restore_config_option(Config, [{access, host_type()}, register]),
  175:     escalus:end_per_testcase(registration_failure_timeout, Config);
  176: end_per_testcase(CaseName, Config) ->
  177:     escalus:end_per_testcase(CaseName, Config).
  178: 
  179: register(Config) ->
  180:     [{Name1, _UserSpec1}, {Name2, _UserSpec2}] = escalus_users:get_users([alice, bob]),
  181:     escalus_fresh:create_users(Config, escalus:get_users([Name1, Name2])).
  182: 
  183: already_registered(Config) ->
  184:     escalus_fresh:story(Config, [{alice, 1}], fun(Alice) ->
  185:         escalus:send(Alice, escalus_stanza:get_registration_fields()),
  186:         Stanza = escalus:wait_for_stanza(Alice),
  187:         escalus:assert(is_iq_result, Stanza),
  188:         true = has_registered_element(Stanza)
  189: 
  190:                                               end).
  191: registration_conflict(Config) ->
  192:     [Alice] = escalus_users:get_users([alice]),
  193:     {ok, result, _Stanza} = escalus_users:create_user(Config, Alice),
  194:     {ok, conflict, _Raw} = escalus_users:create_user(Config, Alice).
  195: 
  196: 
  197: 
  198: admin_notify(Config) ->
  199:     [{Name1, UserSpec1}, {Name2, UserSpec2}] = escalus_users:get_users([alice, bob]),
  200:     [{_, AdminSpec}] = escalus_users:get_users([admin]),
  201:     Username1 = jid:str_tolower(escalus_users:get_username(Config, UserSpec1)),
  202:     Username2 = jid:str_tolower(escalus_users:get_username(Config, UserSpec2)),
  203:     [AdminU, AdminS, AdminP] = escalus_users:get_usp(Config, AdminSpec),
  204: 
  205:     rpc(mim(), ejabberd_auth, try_register, [mongoose_helper:make_jid(AdminU, AdminS), AdminP]),
  206:     escalus:story(Config, [{admin, 1}], fun(Admin) ->
  207:         escalus:create_users(Config, escalus:get_users([Name1, Name2])),
  208: 
  209:             Predicates = [
  210:                           fun(Stanza) ->
  211:                                   Body = exml_query:path(Stanza, [{element, <<"body">>}, cdata]),
  212:                                   escalus_pred:is_chat_message(Stanza)
  213:                                   andalso
  214:                                   re:run(Body, <<"registered">>, []) =/= nomatch
  215:                                   andalso
  216:                                   re:run(Body, Username, []) =/= nomatch
  217:                           end
  218:                           || Username <- [Username1, Username2]
  219:                          ],
  220:             escalus:assert_many(Predicates, escalus:wait_for_stanzas(Admin, 2))
  221:         end).
  222: 
  223: 
  224: 
  225: null_password(Config) ->
  226:     Details = escalus_fresh:freshen_spec(Config, alice),
  227:     Alice = {alice, lists:keyreplace(password, 1, Details, {password, <<>>})},
  228:     {error, _, Response} = escalus_users:create_user(Config, Alice),
  229:     escalus:assert(is_iq_error, Response),
  230:     %% This error response means there was no character data,
  231:     %% i.e. elements `<password\>' or `<password></password>' where
  232:     %% indeed present.
  233:     {username, Name} = lists:keyfind(username, 1, Details),
  234:     {server, Server} = lists:keyfind(server, 1, Details),
  235:     escalus:assert(is_error, [<<"modify">>, <<"not-acceptable">>], Response),
  236:     false = rpc(mim(), ejabberd_auth, does_user_exist, [mongoose_helper:make_jid(Name, Server)]).
  237: 
  238: check_unregistered(Config) ->
  239:     [{_, UserSpec}] = escalus_users:get_users([bob]),
  240:     [Username, Server, _Pass] = escalus_users:get_usp(Config, UserSpec),
  241:     false = rpc(mim(), ejabberd_auth, does_user_exist, [mongoose_helper:make_jid(Username, Server)]).
  242: 
  243: bad_request_registration_cancelation(Config) ->
  244: 
  245:     %% To quote XEP 0077, section 3.2, table 1 (unregister error
  246:     %% cases): "The <remove/> element [is] not the only child element
  247:     %% of the <query/> element."
  248:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  249: 
  250:         %% Alice sends bad cancelation request
  251:         escalus:send(Alice, bad_cancelation_stanza()),
  252: 
  253:         %% Alice receives failure response
  254:         Stanza = escalus:wait_for_stanza(Alice),
  255:         escalus:assert(is_iq_error, Stanza),
  256:         escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], Stanza)
  257: 
  258:     end).
  259: 
  260: not_allowed_registration_cancelation(Config) ->
  261: 
  262:     %% To quote XEP 0077, section 3.2, table 1 (unregister error
  263:     %% cases): "No sender is allowed to cancel registrations in-band."
  264: 
  265:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  266: 
  267:         %% Alice sends cancelation request
  268:         escalus:send(Alice, escalus_stanza:remove_account()),
  269: 
  270:         %% Alice receives failure response
  271:         Stanza = escalus:wait_for_stanza(Alice),
  272:         escalus:assert(is_iq_error, Stanza),
  273:         escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>], Stanza)
  274: 
  275:     end).
  276: 
  277: registration_timeout(Config) ->
  278:     [Alice, Bob] = escalus_users:get_users([alice, bob]),
  279: 
  280: 	%% The first user should be created successfully
  281: 	wait_for_user(Config, Alice, ?REGISTRATION_TIMEOUT),
  282: 
  283:     %% Creation of the second one should err because of not timing out yet
  284:     {error, failed_to_register, Stanza} = escalus_users:create_user(Config, Bob),
  285:     escalus:assert(is_iq_error, Stanza),
  286:     %% Something else may be more acceptable for the assertion
  287:     %% below... 2nd paragraph, section 3.1.1, XEP 0077: [...] a server
  288:     %% MAY return a `<not-acceptable/>' stanza error if [...] an
  289:     %% entity attempts to register a second identity after
  290:     %% successfully completing the registration use case.
  291:     escalus:assert(is_error, [<<"wait">>, <<"resource-constraint">>], Stanza),
  292: 
  293:     %% After timeout, the user should be registered successfully
  294:     wait_for_user(Config, Bob, erlang:round(?REGISTRATION_TIMEOUT * 1.5 * 1000)).
  295: 
  296: registration_failure_timeout(Config) ->
  297:     timer:sleep(timer:seconds(?REGISTRATION_TIMEOUT + 1)),
  298:     [Alice] = escalus_users:get_users([alice]),
  299: 
  300:     %% Registration of the first user should fail because of access denial
  301:     {error,failed_to_register,R} = escalus_users:create_user(Config, Alice),
  302:     escalus:assert(is_iq_error, R),
  303:     escalus:assert(is_error, [<<"auth">>, <<"forbidden">>], R),
  304: 
  305:     %% Registration of a second one should fail because requests were
  306:     %% made in quick succession
  307:     {error,failed_to_register,S} = escalus_users:create_user(Config, Alice),
  308:     escalus:assert(is_iq_error, S),
  309:     escalus:assert(is_error, [<<"wait">>, <<"resource-constraint">>], S).
  310: 
  311: change_password(Config) ->
  312: 
  313:     escalus_fresh:story(Config, [{alice, 1}], fun(Alice) ->
  314:         Username = escalus_client:username(Alice),
  315:         escalus:send(Alice,
  316:             Q = escalus_stanza:iq_set(?NS_INBAND_REGISTER,
  317:                 [#xmlel{name = <<"username">>,
  318:                         children = [#xmlcdata{content = Username}]},
  319:                  #xmlel{name = <<"password">>,
  320:                         children = [#xmlcdata{content = strong_pwd()}]}])),
  321: 
  322:         R = escalus:wait_for_stanza(Alice),
  323: 
  324:         escalus:assert(is_iq_result, [Q], R)
  325: 
  326:     end).
  327: 
  328: change_password_to_null(Config) ->
  329: 
  330:     %% Section 3.3, XEP 0077: If the user provides an empty password
  331:     %% element or a password element that contains no XML character
  332:     %% data (i.e., either <password/> or <password></password>), the
  333:     %% server or service MUST NOT change the password to a null value,
  334:     %% but instead MUST maintain the existing password.
  335: 
  336:     %% By the above, `end_per_testcase' should succeed. XEP 0077
  337:     %% doesn't say how how an XMPP sever should respond, but since
  338:     %% this is in IQ, it must: so we choose to require a `not-allowed'
  339:     %% response.
  340: 
  341:     escalus_fresh:story(Config, [{alice, 1}], fun(Alice) ->
  342:         Username = escalus_client:username(Alice),
  343:         escalus:send(Alice,
  344:             escalus_stanza:iq_set(?NS_INBAND_REGISTER,
  345:                 [#xmlel{name = <<"username">>,
  346:                         children = [#xmlcdata{content = Username}]},
  347:                  #xmlel{name = <<"password">>,
  348:                         children = [#xmlcdata{content = <<"">>}]}])),
  349: 
  350:         R = escalus:wait_for_stanza(Alice),
  351: 
  352:         escalus:assert(is_iq_error, R),
  353:         escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], R)
  354: 
  355:     end).
  356: 
  357: %% Tests for utility functions currently accessible only from the Erlang shell
  358: 
  359: list_users(_Config) ->
  360:     Users = [{<<"alice">>, domain()}, {<<"bob">>, domain()}],
  361:     ?assertEqual(Users, lists:sort(rpc(mim(), ejabberd_auth, get_vh_registered_users, [domain()]))).
  362: 
  363: list_selected_users(_Config) ->
  364:     Alice = {<<"alice">>, domain()},
  365:     Bob = {<<"bob">>, domain()},
  366:     ?assertEqual([Alice], rpc(mim(), ejabberd_auth, get_vh_registered_users,
  367:                               [domain(), [{from, 1}, {to, 1}]])),
  368:     ?assertEqual([Bob], rpc(mim(), ejabberd_auth, get_vh_registered_users,
  369:                             [domain(), [{from, 2}, {to, 10}]])),
  370:     ?assertEqual([Alice], rpc(mim(), ejabberd_auth, get_vh_registered_users,
  371:                               [domain(), [{prefix, <<"a">>}]])),
  372:     ?assertEqual([Alice], rpc(mim(), ejabberd_auth, get_vh_registered_users,
  373:                               [domain(), [{prefix, <<"a">>}, {from, 1}, {to, 10}]])),
  374:     ?assertEqual([Bob], rpc(mim(), ejabberd_auth, get_vh_registered_users,
  375:                             [domain(), [{prefix, <<>>}, {from, 2}, {to, 10}]])).
  376: 
  377: count_users(_Config) ->
  378:     ?assertEqual(2, rpc(mim(), ejabberd_auth, get_vh_registered_users_number, [domain()])).
  379: 
  380: count_users_estimate(_Config) ->
  381:     Count = rpc(mim(), ejabberd_auth, get_vh_registered_users_number, [domain()]),
  382:     ?assert(is_integer(Count) andalso Count >= 0).
  383: 
  384: count_selected_users(_Config) ->
  385:     ?assertEqual(1, rpc(mim(), ejabberd_auth, get_vh_registered_users_number,
  386:                         [domain(), [{prefix, <<"a">>}]])).
  387: 
  388: %%--------------------------------------------------------------------
  389: %% Helpers
  390: %%--------------------------------------------------------------------
  391: 
  392: skip_if_mod_register_not_enabled(Config) ->
  393:     case escalus_users:is_mod_register_enabled(Config) of
  394:         true ->
  395:             Config; % will create users inside test case
  396:         _ ->
  397:             {skip, mod_register_disabled}
  398:     end.
  399: 
  400: strong_pwd() ->
  401:     <<"Sup3r","c4li","fr4g1","l1571c","3xp1","4l1","d0c10u5">>.
  402: 
  403: set_registration_timeout(Config) ->
  404:     mongoose_helper:backup_and_set_config_option(Config, registration_timeout,
  405:                                                  ?REGISTRATION_TIMEOUT).
  406: 
  407: restore_registration_timeout(Config) ->
  408:     mongoose_helper:restore_config_option(Config, registration_timeout).
  409: 
  410: deny_everyone_registration(Config) ->
  411:     mongoose_helper:backup_and_set_config_option(Config, [{access, host_type()}, register],
  412:                                                  [#{acl => all, value => deny}]).
  413: 
  414: has_registered_element(Stanza) ->
  415:         [#xmlel{name = <<"registered">>}] =:= exml_query:paths(Stanza,
  416:             [{element, <<"query">>}, {element, <<"registered">>}]).
  417: 
  418: bad_cancelation_stanza() ->
  419:     escalus_stanza:iq(<<"set">>, [#xmlel{name = <<"query">>,
  420:         attrs = [{<<"xmlns">>, <<"jabber:iq:register">>}],
  421:         children = [#xmlel{name = <<"remove">>},
  422:                     %% The <remove/> element is not the only child element of the
  423:                     %% <query/> element.
  424:                     #xmlel{name = <<"foo">>}]}]).
  425: 
  426: user_exists(Name, Config) ->
  427:     {Name, Client} = escalus_users:get_user_by_name(Name),
  428:     [Username, Server, _Pass] = escalus_users:get_usp(Config, Client),
  429:     rpc(mim(), ejabberd_auth, does_user_exist, [mongoose_helper:make_jid(Username, Server)]).
  430: 
  431: reload_mod_register_option(Config, Key, Value) ->
  432:     Host = host_type(),
  433:     Args = proplists:get_value(mod_register_options, Config),
  434:     Args1 = maps:put(Key, Value, Args),
  435:     dynamic_modules:restart(Host, mod_register, Args1).
  436: 
  437: restore_mod_register_options(Config) ->
  438:     Host = host_type(),
  439:     Args = proplists:get_value(mod_register_options, Config),
  440:     dynamic_modules:restart(Host, mod_register, Args).
  441: 
  442: enable_watcher(Config, Watcher) ->
  443:     reload_mod_register_option(Config, registration_watchers, [Watcher]).
  444: 
  445: disable_watcher(Config) ->
  446:     restore_mod_register_options(Config).