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