1: -module(graphql_account_SUITE).
    2: 
    3: -include_lib("eunit/include/eunit.hrl").
    4: -include_lib("common_test/include/ct.hrl").
    5: 
    6: -compile([export_all, nowarn_export_all]).
    7: 
    8: -import(common_helper, [unprep/1]).
    9: -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]).
   10: -import(graphql_helper, [execute_command/4, execute_user_command/5, get_listener_port/1,
   11:                          get_listener_config/1, get_ok_value/2, get_err_msg/1,
   12:                          execute_domain_admin_command/4, get_unauthorized/1,
   13:                          get_coercion_err_msg/1]).
   14: 
   15: -define(NOT_EXISTING_JID, <<"unknown987@unknown">>).
   16: -define(NOT_EXISTING_NAME, <<"unknown987@", (domain_helper:domain())/binary>>).
   17: -define(EMPTY_NAME_JID, <<"@", (domain_helper:domain())/binary>>).
   18: 
   19: suite() ->
   20:     require_rpc_nodes([mim]) ++ escalus:suite().
   21: 
   22: all() ->
   23:     [{group, user_account},
   24:      {group, admin_account_http},
   25:      {group, admin_account_cli},
   26:      {group, domain_admin_account}].
   27: 
   28: groups() ->
   29:     [{user_account, [parallel], user_account_tests()},
   30:      {admin_account_http, [], admin_account_tests() ++ admin_account_http_tests()},
   31:      {admin_account_cli, [], admin_account_tests() ++ admin_account_cli_tests()},
   32:      {domain_admin_account, [], domain_admin_tests()}].
   33: 
   34: user_account_tests() ->
   35:     [user_unregister,
   36:      user_change_password].
   37: 
   38: admin_account_tests() ->
   39:     [admin_list_users,
   40:      admin_list_users_unknown_domain,
   41:      admin_count_users,
   42:      admin_count_users_unknown_domain,
   43:      admin_check_password,
   44:      admin_check_password_non_existing_user,
   45:      admin_check_password_hash,
   46:      admin_check_password_hash_non_existing_user,
   47:      admin_check_plain_password_hash_md5,
   48:      admin_check_plain_password_hash_sha,
   49:      admin_check_user,
   50:      admin_check_non_existing_user,
   51:      admin_register_user,
   52:      admin_register_random_user,
   53:      admin_register_user_non_existing_domain,
   54:      admin_register_user_limit_error,
   55:      admin_remove_non_existing_user,
   56:      admin_remove_existing_user,
   57:      admin_ban_user,
   58:      admin_ban_non_existing_user,
   59:      admin_change_user_password,
   60:      admin_change_non_existing_user_password].
   61: 
   62: admin_account_http_tests() ->
   63:     [admin_import_users_http].
   64: 
   65: admin_account_cli_tests() ->
   66:     [admin_import_users_cli].
   67: 
   68: domain_admin_tests() ->
   69:     [admin_list_users,
   70:      domain_admin_list_users_no_permission,
   71:      admin_count_users,
   72:      domain_admin_count_users_no_permission,
   73:      admin_check_password,
   74:      domain_admin_check_password_no_permission,
   75:      admin_check_password_hash,
   76:      domain_admin_check_password_hash_no_permission,
   77:      domain_admin_check_plain_password_hash_no_permission,
   78:      admin_check_user,
   79:      domain_admin_check_user_no_permission,
   80:      admin_register_user,
   81:      domain_admin_register_user_no_permission,
   82:      admin_register_random_user,
   83:      domain_admin_register_random_user_no_permission,
   84:      admin_register_user_limit_error,
   85:      admin_remove_existing_user,
   86:      domain_admin_remove_user_no_permission,
   87:      admin_ban_user,
   88:      domain_admin_ban_user_no_permission,
   89:      admin_change_user_password,
   90:      domain_admin_change_user_password_no_permission].
   91: 
   92: init_per_suite(Config) ->
   93:     Config1 = [{ctl_auth_mods, mongoose_helper:auth_modules()} | Config],
   94:     Config2 = escalus:init_per_suite(Config1),
   95:     Config3 = ejabberd_node_utils:init(mim(), Config2),
   96:     dynamic_modules:save_modules(domain_helper:host_type(), Config3).
   97: 
   98: end_per_suite(Config) ->
   99:     file:delete(filename:join(?config(mim_data_dir, Config), "users.csv.tmp")),
  100:     dynamic_modules:restore_modules(Config),
  101:     escalus:end_per_suite(Config).
  102: 
  103: init_per_group(admin_account_http, Config) ->
  104:     graphql_helper:init_admin_handler(init_users(Config));
  105: init_per_group(admin_account_cli, Config) ->
  106:     graphql_helper:init_admin_cli(init_users(Config));
  107: init_per_group(domain_admin_account, Config) ->
  108:     graphql_helper:init_domain_admin_handler(domain_admin_init_users(Config));
  109: init_per_group(user_account, Config) ->
  110:     graphql_helper:init_user(Config).
  111: 
  112: end_per_group(user_account, _Config) ->
  113:     graphql_helper:clean(),
  114:     escalus_fresh:clean();
  115: end_per_group(domain_admin_account, Config) ->
  116:     graphql_helper:clean(),
  117:     domain_admin_clean_users(Config);
  118: end_per_group(_GroupName, Config) ->
  119:     graphql_helper:clean(),
  120:     clean_users(Config).
  121: 
  122: init_users(Config) ->
  123:     escalus:create_users(Config, escalus:get_users([alice])).
  124: 
  125: domain_admin_init_users(Config) ->
  126:     escalus:create_users(Config, escalus:get_users([alice, alice_bis])).
  127: 
  128: clean_users(Config) ->
  129:     escalus_fresh:clean(),
  130:     escalus:delete_users(Config, escalus:get_users([alice])).
  131: 
  132: domain_admin_clean_users(Config) ->
  133:     escalus_fresh:clean(),
  134:     escalus:delete_users(Config, escalus:get_users([alice, alice_bis])).
  135: 
  136: init_per_testcase(admin_register_user = C, Config) ->
  137:     Config1 = [{user, {<<"gql_admin_registration_test">>, domain_helper:domain()}} | Config],
  138:     escalus:init_per_testcase(C, Config1);
  139: init_per_testcase(C, Config) when C =:= admin_check_plain_password_hash_md5;
  140:                                   C =:= admin_check_plain_password_hash_sha ->
  141:     {_, AuthMods} = lists:keyfind(ctl_auth_mods, 1, Config),
  142:     case lists:member(ejabberd_auth_ldap, AuthMods) of
  143:         true ->
  144:             {skip, not_fully_supported_with_ldap};
  145:         false ->
  146:             AuthOpts = mongoose_helper:auth_opts_with_password_format(plain),
  147:             Config1 = mongoose_helper:backup_and_set_config_option(
  148:                         Config, {auth, domain_helper:host_type()}, AuthOpts),
  149:             Config2 = escalus:create_users(Config1, escalus:get_users([carol])),
  150:             escalus:init_per_testcase(C, Config2)
  151:     end;
  152: init_per_testcase(admin_register_user_limit_error = C, Config) ->
  153:     Domain = domain_helper:domain(),
  154:     {ok, HostType} = rpc(mim(), mongoose_domain_api, get_domain_host_type, [Domain]),
  155:     OptKey = [{auth, HostType}, max_users_per_domain],
  156:     Config1 = mongoose_helper:backup_and_set_config_option(Config, OptKey, 3),
  157:     Config2 = [{bob, <<"bob">>}, {kate, <<"kate">>}, {john, <<"john">>} | Config1],
  158:     escalus:init_per_testcase(C, Config2);
  159: init_per_testcase(domain_admin_check_plain_password_hash_no_permission = C, Config) ->
  160:     {_, AuthMods} = lists:keyfind(ctl_auth_mods, 1, Config),
  161:     case lists:member(ejabberd_auth_ldap, AuthMods) of
  162:         true ->
  163:             {skip, not_fully_supported_with_ldap};
  164:         false ->
  165:             AuthOpts = mongoose_helper:auth_opts_with_password_format(plain),
  166:             Config1 = mongoose_helper:backup_and_set_config_option(
  167:                         Config, {auth, domain_helper:host_type()}, AuthOpts),
  168:             Config2 = escalus:create_users(Config1, escalus:get_users([alice_bis])),
  169:             escalus:init_per_testcase(C, Config2)
  170:     end;
  171: init_per_testcase(domain_admin_register_user = C, Config) ->
  172:     Config1 = [{user, {<<"gql_domain_admin_registration_test">>, domain_helper:domain()}} | Config],
  173:     escalus:init_per_testcase(C, Config1);
  174: init_per_testcase(CaseName, Config) ->
  175:     escalus:init_per_testcase(CaseName, Config).
  176: 
  177: end_per_testcase(admin_register_user = C, Config) ->
  178:     {Username, Domain} = proplists:get_value(user, Config),
  179:     rpc(mim(), mongoose_account_api, unregister_user, [Username, Domain]),
  180:     escalus:end_per_testcase(C, Config);
  181: end_per_testcase(C, Config) when C =:= admin_check_plain_password_hash_md5;
  182:                                  C =:= admin_check_plain_password_hash_sha ->
  183:     mongoose_helper:restore_config(Config),
  184:     escalus:delete_users(Config, escalus:get_users([carol]));
  185: end_per_testcase(admin_register_user_limit_error = C, Config) ->
  186:     Domain = domain_helper:domain(),
  187:     rpc(mim(), mongoose_account_api, unregister_user, [proplists:get_value(bob, Config), Domain]),
  188:     rpc(mim(), mongoose_account_api, unregister_user, [proplists:get_value(kate, Config), Domain]),
  189:     rpc(mim(), mongoose_account_api, unregister_user, [proplists:get_value(john, Config), Domain]),
  190:     mongoose_helper:restore_config(Config),
  191:     escalus:end_per_testcase(C, Config);
  192: end_per_testcase(domain_admin_check_plain_password_hash_no_permission, Config) ->
  193:     mongoose_helper:restore_config(Config),
  194:     escalus:delete_users(Config, escalus:get_users([carol, alice_bis]));
  195: end_per_testcase(domain_admin_register_user = C, Config) ->
  196:     {Username, Domain} = proplists:get_value(user, Config),
  197:     rpc(mim(), mongoose_account_api, unregister_user, [Username, Domain]),
  198:     escalus:end_per_testcase(C, Config);
  199: end_per_testcase(CaseName, Config)
  200:       when CaseName == admin_import_users_http; CaseName == admin_import_users_cli ->
  201:     Domain = domain_helper:domain(),
  202:     rpc(mim(), mongoose_account_api, unregister_user, [<<"john">>, Domain]),
  203:     escalus:end_per_testcase(CaseName, Config);
  204: end_per_testcase(CaseName, Config) ->
  205:     escalus:end_per_testcase(CaseName, Config).
  206: 
  207: user_unregister(Config) ->
  208:     escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_unregister_story/2).
  209: 
  210: user_unregister_story(Config, Alice) ->
  211:     Resp = user_unregister(Alice, Config),
  212:     Path = [data, account, unregister],
  213:     ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp), <<"successfully unregistered">>)),
  214:     % Ensure the user is removed
  215:     AllUsers = rpc(mim(), mongoose_account_api, list_users, [domain_helper:domain()]),
  216:     {_, AllUsersList} = AllUsers,
  217:     LAliceJID = jid:to_binary(jid:from_binary(escalus_client:short_jid(Alice))),
  218:     ?assertNot(lists:member(LAliceJID, AllUsersList)).
  219: 
  220: user_change_password(Config) ->
  221:     escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_change_password_story/2).
  222: 
  223: user_change_password_story(Config, Alice) ->
  224:     % Set an empty password
  225:     Resp1 = user_change_password(Alice, <<>>, Config),
  226:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"Empty password">>)),
  227:     % Set a correct password
  228:     Resp2 = user_change_password(Alice, <<"kaczka">>, Config),
  229:     Path = [data, account, changePassword],
  230:     ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp2), <<"Password changed">>)).
  231: 
  232: admin_list_users(Config) ->
  233:     Domain = domain_helper:domain(),
  234:     Username = jid:nameprep(escalus_users:get_username(Config, alice)),
  235:     JID = <<Username/binary, "@", Domain/binary>>,
  236:     Resp1 = list_users(Domain, Config),
  237:     Users = get_ok_value([data, account, listUsers], Resp1),
  238:     ?assert(lists:member(JID, Users)),
  239:     Resp2 = list_users(unprep(Domain), Config),
  240:     ?assertEqual(Users, get_ok_value([data, account, listUsers], Resp2)).
  241: 
  242: admin_list_users_unknown_domain(Config) ->
  243:     Resp = list_users(<<"unknown-domain">>, Config),
  244:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp), <<"Domain does not exist">>)).
  245: 
  246: admin_count_users(Config) ->
  247:     % A domain with at least one user
  248:     Domain = domain_helper:domain(),
  249:     Resp1 = count_users(Domain, Config),
  250:     Count = get_ok_value([data, account, countUsers], Resp1),
  251:     ?assert(0 < Count),
  252:     Resp2 = count_users(unprep(Domain), Config),
  253:     ?assertEqual(Count, get_ok_value([data, account, countUsers], Resp2)).
  254: 
  255: admin_count_users_unknown_domain(Config) ->
  256:     Resp = count_users(<<"unknown-domain">>, Config),
  257:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp), <<"Domain does not exist">>)).
  258: 
  259: admin_check_password(Config) ->
  260:     Password = lists:last(escalus_users:get_usp(Config, alice)),
  261:     BinJID = escalus_users:get_jid(Config, alice),
  262:     Path = [data, account, checkPassword],
  263:     % A correct password
  264:     Resp1 = check_password(BinJID, Password, Config),
  265:     ?assertMatch(#{<<"correct">> := true, <<"message">> := _}, get_ok_value(Path, Resp1)),
  266:     % An incorrect password
  267:     Resp2 = check_password(BinJID, <<"incorrect_pw">>, Config),
  268:     ?assertMatch(#{<<"correct">> := false, <<"message">> := _}, get_ok_value(Path, Resp2)).
  269: 
  270: admin_check_password_non_existing_user(Config) ->
  271:     Password = lists:last(escalus_users:get_usp(Config, alice)),
  272:     % Non-existing user, non-existing domain
  273:     Resp = check_password(?NOT_EXISTING_JID, Password, Config),
  274:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp), <<"not exist">>)),
  275:     % Non-existing user, existing domain
  276:     Resp2 = check_password(?NOT_EXISTING_NAME, Password, Config),
  277:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"not exist">>)),
  278:     % Empty username
  279:     Resp3 = check_password(?EMPTY_NAME_JID, Password, Config),
  280:     get_coercion_err_msg(Resp3).
  281: 
  282: admin_check_password_hash(Config) ->
  283:     UserSCRAM = escalus_users:get_jid(Config, alice),
  284:     EmptyHash = list_to_binary(get_md5(<<>>)),
  285:     Method = <<"md5">>,
  286:     % SCRAM password user
  287:     Resp1 = check_password_hash(UserSCRAM, EmptyHash, Method, Config),
  288:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"SCRAM password">>)).
  289: 
  290: admin_check_password_hash_non_existing_user(Config) ->
  291:     EmptyHash = list_to_binary(get_md5(<<>>)),
  292:     Method = <<"md5">>,
  293:     % Non-existing user, non-existing domain
  294:     Resp = check_password_hash(?NOT_EXISTING_JID, EmptyHash, Method, Config),
  295:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp), <<"not exist">>)),
  296:     % Non-existing user, existing domain
  297:     Resp2 = check_password_hash(?NOT_EXISTING_NAME, EmptyHash, Method, Config),
  298:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"not exist">>)),
  299:     % Empty username
  300:     Resp3 = check_password_hash(?EMPTY_NAME_JID, EmptyHash, Method, Config),
  301:     get_coercion_err_msg(Resp3).
  302: 
  303: admin_check_plain_password_hash_md5(Config) ->
  304:     admin_check_password_hash(Config, <<"md5">>, fun get_md5/1).
  305: 
  306: admin_check_plain_password_hash_sha(Config) ->
  307:     admin_check_password_hash(Config, <<"sha">>, fun get_sha/1).
  308: 
  309: admin_check_password_hash(Config, Method, HashFun) ->
  310:     UserJID = escalus_users:get_jid(Config, carol),
  311:     Password = lists:last(escalus_users:get_usp(Config, carol)),
  312:     Hash = list_to_binary(HashFun(Password)),
  313:     WrongHash = list_to_binary(HashFun(<<"wrong password">>)),
  314:     Path = [data, account, checkPasswordHash],
  315:     % A correct hash
  316:     Resp = check_password_hash(UserJID, Hash, Method, Config),
  317:     ?assertMatch(#{<<"correct">> := true, <<"message">> := _}, get_ok_value(Path, Resp)),
  318:     % An incorrect hash
  319:     Resp2 = check_password_hash(UserJID, WrongHash, Method, Config),
  320:     ?assertMatch(#{<<"correct">> := false, <<"message">> := _}, get_ok_value(Path, Resp2)),
  321:     % A not-supported hash method
  322:     Resp3 = check_password_hash(UserJID, Hash, <<"a">>, Config),
  323:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp3), <<"not supported">>)).
  324: 
  325: admin_check_user(Config) ->
  326:     BinJID = escalus_users:get_jid(Config, alice),
  327:     Path = [data, account, checkUser],
  328:     Resp = check_user(BinJID, Config),
  329:     ?assertMatch(#{<<"exist">> := true, <<"message">> := _}, get_ok_value(Path, Resp)).
  330: 
  331: admin_check_non_existing_user(Config) ->
  332:     Path = [data, account, checkUser],
  333:     % Non-existing user, non-existing domain
  334:     Resp = check_user(?NOT_EXISTING_JID, Config),
  335:     ?assertMatch(#{<<"exist">> := false, <<"message">> := _}, get_ok_value(Path, Resp)),
  336:     % Non-existing user, existing domain
  337:     Resp2 = check_user(?NOT_EXISTING_NAME, Config),
  338:     ?assertMatch(#{<<"exist">> := false, <<"message">> := _}, get_ok_value(Path, Resp2)),
  339:     % Empty username
  340:     Resp3 = check_user(?EMPTY_NAME_JID, Config),
  341:     get_coercion_err_msg(Resp3).
  342: 
  343: admin_register_user(Config) ->
  344:     Password = <<"my_password">>,
  345:     {Username, Domain} = proplists:get_value(user, Config),
  346:     Path = [data, account, registerUser, message],
  347:     % Register a new user
  348:     Resp1 = register_user(Domain, Username, Password, Config),
  349:     ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp1), <<"successfully registered">>)),
  350:     % Try to register a user with existing name
  351:     Resp2 = register_user(Domain, Username, Password, Config),
  352:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"already registered">>)),
  353:     % Try again, this time with a name that is not stringprepped
  354:     Resp3 = register_user(unprep(Domain), unprep(Username), Password, Config),
  355:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp3), <<"already registered">>)),
  356:     % Try to register a user without any name
  357:     Resp4 = register_user(Domain, <<>>, Password, Config),
  358:     ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Resp4), <<"empty_user_name">>)),
  359:     % Try to register a user with an invalid name
  360:     Resp5 = register_user(Domain, <<"@invalid">>, Password, Config),
  361:     ?assertMatch({_, _}, binary:match(get_coercion_err_msg(Resp5), <<"failed_to_parse_user_name">>)).
  362: 
  363: admin_register_random_user(Config) ->
  364:     Password = <<"my_password">>,
  365:     Domain = domain_helper:domain(),
  366:     Path = [data, account, registerUser],
  367:     % Register a new user
  368:     Resp1 = register_random_user(Domain, Password, Config),
  369:     #{<<"message">> := Msg, <<"jid">> := JID} = get_ok_value(Path, Resp1),
  370:     {Username, Server} = jid:to_lus(jid:from_binary(JID)),
  371: 
  372:     ?assertNotEqual(nomatch, binary:match(Msg, <<"successfully registered">>)),
  373:     {ok, _} = rpc(mim(), mongoose_account_api, unregister_user, [Username, Server]).
  374: 
  375: admin_register_user_non_existing_domain(Config) ->
  376:     % Try to register a user with a non-existing domain
  377:     Resp = register_user(<<"unknown">>, <<"alice">>, <<"test_password">>, Config),
  378:     ?assertMatch({_, _}, binary:match(get_err_msg(Resp), <<"not_allowed">>)).
  379: 
  380: admin_register_user_limit_error(Config) ->
  381:     Password = <<"password">>,
  382:     Domain = domain_helper:domain(),
  383:     Path = [data, account, registerUser, message],
  384:     Resp1 = register_user(Domain, proplists:get_value(bob, Config), Password, Config),
  385:     ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp1), <<"successfully registered">>)),
  386:     Resp2 = register_user(Domain, proplists:get_value(kate, Config), Password, Config),
  387:     ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp2), <<"successfully registered">>)),
  388:     %% One user was registered in the init_per_group, and two more were registered in this test case
  389:     %% There are three registered users at this moment
  390:     %% The next (fourth) registration should exceed the limit of three
  391:     JohnNick = proplists:get_value(john, Config),
  392:     Resp3 = register_user(Domain, JohnNick, Password, Config),
  393:     ?assertMatch({_, _}, binary:match(get_err_msg(Resp3), <<"limit has been exceeded">>)),
  394:     %% Make sure the fourth account wasn't created
  395:     CheckUserPath = [data, account, checkUser],
  396:     Resp4 = check_user(<<JohnNick/binary, "@", Domain/binary>>, Config),
  397:     ?assertMatch(#{<<"exist">> := false, <<"message">> := _}, get_ok_value(CheckUserPath, Resp4)).
  398: 
  399: admin_remove_non_existing_user(Config) ->
  400:     % Non-existing user, non-existing domain
  401:     Resp = remove_user(?NOT_EXISTING_JID, Config),
  402:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp), <<"not exist">>)),
  403:     % Non-existing user, existing domain
  404:     Resp2 = remove_user(?NOT_EXISTING_NAME, Config),
  405:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"not exist">>)),
  406:     % Empty username
  407:     Resp3 = remove_user(?EMPTY_NAME_JID, Config),
  408:     get_coercion_err_msg(Resp3).
  409: 
  410: admin_remove_existing_user(Config) ->
  411:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  412:         Path = [data, account, removeUser, message],
  413:         BinJID = escalus_client:full_jid(Alice),
  414:         Resp4 = remove_user(BinJID, Config),
  415:         ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp4),
  416:                                               <<"successfully unregister">>))
  417:     end).
  418: 
  419: admin_ban_user(Config) ->
  420:     Path = [data, account, banUser, message],
  421:     Reason = <<"annoying">>,
  422:     % Ban an existing user
  423:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  424:         BinJID = escalus_client:full_jid(Alice),
  425:         Resp1 = ban_user(BinJID, Reason, Config),
  426:         ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp1), <<"successfully banned">>))
  427:     end).
  428: 
  429: admin_ban_non_existing_user(Config) ->
  430:     Reason = <<"annoying">>,
  431:     % Non-existing name, non-existing domain
  432:     Resp = ban_user(?NOT_EXISTING_JID, Reason, Config),
  433:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp), <<"not exist">>)),
  434:     % Non-existing name, existing domain
  435:     Resp2 = ban_user(?NOT_EXISTING_NAME, Reason, Config),
  436:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"not exist">>)),
  437:     % Empty username
  438:     Resp3 = ban_user(?EMPTY_NAME_JID, Reason, Config),
  439:     get_coercion_err_msg(Resp3).
  440: 
  441: admin_change_user_password(Config) ->
  442:     Path = [data, account, changeUserPassword, message],
  443:     NewPassword = <<"new password">>,
  444:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  445:         BinJID = escalus_client:full_jid(Alice),
  446:         % Set an empty password
  447:         Resp1 = change_user_password(BinJID, <<>>, Config),
  448:         ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"Empty password">>)),
  449:         % Set non-empty password
  450:         Resp2 = change_user_password(BinJID, NewPassword, Config),
  451:         ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp2), <<"Password changed">>))
  452:     end).
  453: 
  454: admin_change_non_existing_user_password(Config) ->
  455:     NewPassword = <<"new password">>,
  456:     % Non-existing name, non-existing domain
  457:     Resp = change_user_password(?NOT_EXISTING_JID, NewPassword, Config),
  458:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp), <<"does not exist">>)),
  459:     % Non-existing name, existing domain
  460:     Resp2 = change_user_password(?NOT_EXISTING_NAME, NewPassword, Config),
  461:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"does not exist">>)),
  462:     % Empty username
  463:     Resp3 = ban_user(?EMPTY_NAME_JID, NewPassword, Config),
  464:     get_coercion_err_msg(Resp3).
  465: 
  466: admin_import_users_cli(Config) ->
  467:     escalus:fresh_story(Config, [{alice, 1}], fun(_Alice) ->
  468:         % Non-existing file
  469:         Resp = import_users(<<"nonexisting.csv">>, Config),
  470:         ?assertEqual(<<"File not found">>, get_err_msg(Resp)),
  471:         % Summary
  472:         Path = filename:join(?config(mim_data_dir, Config), "users.csv"),
  473:         Path2 = replace_hosts_in_file(Path),
  474:         Resp2 = import_users(list_to_binary(Path2), Config),
  475:         Domain = domain_helper:domain(),
  476:         ?assertEqual(#{<<"status">> => <<"Completed">>,
  477:                        <<"created">> => [<<"john@", Domain/binary>>],
  478:                        <<"emptyPassword">> => [<<"elise@", Domain/binary>>],
  479:                        <<"existing">> => [<<"alice@", Domain/binary>>],
  480:                        <<"invalidJID">> => [<<",", Domain/binary, ",password">>],
  481:                        <<"invalidRecord">> => [<<"elise,elise,", Domain/binary, ",esile">>],
  482:                        <<"notAllowed">> => null},
  483:                      get_ok_value([data, account, importUsers], Resp2))
  484:     end).
  485: 
  486: admin_import_users_http(Config) ->
  487:     escalus:fresh_story(Config, [{alice, 1}], fun(_Alice) ->
  488:         % Summary
  489:         Path = filename:join(?config(mim_data_dir, Config), "users.csv"),
  490:         Path2 = replace_hosts_in_file(Path),
  491:         Resp2 = import_users(list_to_binary(Path2), Config),
  492:         ?assertEqual(#{<<"status">> => <<"ImportUsers scheduled">>,
  493:                        <<"created">> => null,
  494:                        <<"emptyPassword">> => null,
  495:                        <<"existing">> => null,
  496:                        <<"invalidJID">> => null,
  497:                        <<"invalidRecord">> => null,
  498:                        <<"notAllowed">> => null},
  499:                      get_ok_value([data, account, importUsers], Resp2)),
  500:         Domain = domain_helper:domain(),
  501:         JID = mongoose_helper:make_jid(<<"john">>, Domain),
  502:         mongoose_helper:wait_until(fun() ->
  503:                                        rpc(mim(), mongoose_account_api, check_account, [JID])
  504:                                    end,
  505:                                    {ok, io_lib:format("User ~s exists", [<<"john@", Domain/binary>>])},
  506:                                    #{time_left => timer:seconds(20),
  507:                                      sleep_time => 1000,
  508:                                      name => verify_account_created})
  509:     end).
  510: 
  511: replace_hosts_in_file(Path) ->
  512:     {ok, Content} = file:read_file(Path),
  513:     Content2 = binary:replace(Content, <<"$host$">>, domain_helper:domain(), [global]),
  514:     Path2 = Path ++ ".tmp",
  515:     ok = file:write_file(Path2, Content2),
  516:     Path2.
  517: 
  518: domain_admin_list_users_no_permission(Config) ->
  519:     % An unknown domain
  520:     Resp1 = list_users(<<"unknown-domain">>, Config),
  521:     get_unauthorized(Resp1),
  522:     % An external domain
  523:     Resp2 = list_users(domain_helper:secondary_domain(), Config),
  524:     get_unauthorized(Resp2).
  525: 
  526: domain_admin_count_users_no_permission(Config) ->
  527:     % An unknown domain
  528:     Resp1 = count_users(<<"unknown-domain">>, Config),
  529:     get_unauthorized(Resp1),
  530:     % An external domain
  531:     Resp2 = count_users(domain_helper:secondary_domain(), Config),
  532:     get_unauthorized(Resp2).
  533: 
  534: domain_admin_check_password_no_permission(Config) ->
  535:     Password = lists:last(escalus_users:get_usp(Config, alice)),
  536:     PasswordOutside = lists:last(escalus_users:get_usp(Config, alice_bis)),
  537:     BinOutsideJID = escalus_users:get_jid(Config, alice_bis),
  538:     % An external domain user
  539:     Resp3 = check_password(BinOutsideJID, PasswordOutside, Config),
  540:     get_unauthorized(Resp3),
  541:     % A non-existing user
  542:     Resp4 = check_password(?NOT_EXISTING_JID, Password, Config),
  543:     get_unauthorized(Resp4).
  544: 
  545: domain_admin_check_password_hash_no_permission(Config) ->
  546:     ExternalUserSCRAM = escalus_users:get_jid(Config, alice_bis),
  547:     EmptyHash = list_to_binary(get_md5(<<>>)),
  548:     Method = <<"md5">>,
  549:     % An external domain user
  550:     Resp1 = check_password_hash(ExternalUserSCRAM, EmptyHash, Method, Config),
  551:     get_unauthorized(Resp1),
  552:     % A non-existing user
  553:     Resp2 = check_password_hash(?NOT_EXISTING_JID, EmptyHash, Method, Config),
  554:     get_unauthorized(Resp2).
  555: 
  556: domain_admin_check_plain_password_hash_no_permission(Config) ->
  557:     Method = <<"md5">>,
  558:     ExternalUserJID = escalus_users:get_jid(Config, alice_bis),
  559:     ExternalPassword = lists:last(escalus_users:get_usp(Config, alice_bis)),
  560:     ExternalHash = list_to_binary(get_md5(ExternalPassword)),
  561:     get_unauthorized(check_password_hash(ExternalUserJID, ExternalHash, Method, Config)).
  562: 
  563: domain_admin_check_user_no_permission(Config) ->
  564:     ExternalBinJID = escalus_users:get_jid(Config, alice_bis),
  565:     % An external domain user
  566:     Resp1 = check_user(ExternalBinJID, Config),
  567:     get_unauthorized(Resp1),
  568:     % A non-existing user
  569:     Resp2 = check_user(?NOT_EXISTING_JID, Config),
  570:     get_unauthorized(Resp2).
  571: 
  572: domain_admin_register_user_no_permission(Config) ->
  573:     Password = <<"my_password">>,
  574:     Domain = <<"unknown-domain">>,
  575:     get_unauthorized(register_user(Domain, external_user, Password, Config)).
  576: 
  577: domain_admin_register_random_user_no_permission(Config) ->
  578:     Password = <<"my_password">>,
  579:     Domain = domain_helper:secondary_domain(),
  580:     Resp = register_random_user(Domain, Password, Config),
  581:     get_unauthorized(Resp).
  582: 
  583: domain_admin_remove_user_no_permission(Config) ->
  584:     get_unauthorized(remove_user(?NOT_EXISTING_JID, Config)),
  585:     escalus:fresh_story(Config, [{alice_bis, 1}], fun(AliceBis) ->
  586:         BinJID = escalus_client:full_jid(AliceBis),
  587:         get_unauthorized(remove_user(BinJID, Config))
  588:     end).
  589: 
  590: domain_admin_ban_user_no_permission(Config) ->
  591:     Reason = <<"annoying">>,
  592:     % Ban not existing user
  593:     Resp1 = ban_user(?NOT_EXISTING_JID, Reason, Config),
  594:     get_unauthorized(Resp1),
  595:     % Ban an external domain user
  596:     escalus:fresh_story(Config, [{alice_bis, 1}], fun(AliceBis) ->
  597:         BinJID = escalus_client:full_jid(AliceBis),
  598:         Resp2 = ban_user(BinJID, Reason, Config),
  599:         get_unauthorized(Resp2)
  600:     end).
  601: 
  602: domain_admin_change_user_password_no_permission(Config) ->
  603:     NewPassword = <<"new password">>,
  604:     % Change password of not existing user
  605:     Resp1 = change_user_password(?NOT_EXISTING_JID, NewPassword, Config),
  606:     get_unauthorized(Resp1),
  607:     % Change external domain user password
  608:     escalus:fresh_story(Config, [{alice_bis, 1}], fun(AliceBis) ->
  609:         BinJID = escalus_client:full_jid(AliceBis),
  610:         Resp2 = change_user_password(BinJID, NewPassword, Config),
  611:         get_unauthorized(Resp2)
  612:     end).
  613: 
  614: %% Helpers
  615: 
  616: get_md5(AccountPass) ->
  617:     lists:flatten([io_lib:format("~.16B", [X])
  618:                    || X <- binary_to_list(crypto:hash(md5, AccountPass))]).
  619: 
  620: get_sha(AccountPass) ->
  621:     lists:flatten([io_lib:format("~.16B", [X])
  622:                    || X <- binary_to_list(crypto:hash(sha, AccountPass))]).
  623: 
  624: %% Commands
  625: 
  626: user_unregister(User, Config) ->
  627:     execute_user_command(<<"account">>, <<"unregister">>, User, #{}, Config).
  628: 
  629: user_change_password(User, Password, Config) ->
  630:     Vars = #{<<"newPassword">> => Password},
  631:     execute_user_command(<<"account">>, <<"changePassword">>, User, Vars, Config).
  632: 
  633: list_users(Domain, Config) ->
  634:     Vars = #{<<"domain">> => Domain},
  635:     execute_command(<<"account">>, <<"listUsers">>, Vars, Config).
  636: 
  637: count_users(Domain, Config) ->
  638:     Vars = #{<<"domain">> => Domain},
  639:     execute_command(<<"account">>, <<"countUsers">>, Vars, Config).
  640: 
  641: check_password(User, Password, Config) ->
  642:     Vars = #{<<"user">> => User, <<"password">> => Password},
  643:     execute_command(<<"account">>, <<"checkPassword">>, Vars, Config).
  644: 
  645: check_password_hash(User, PasswordHash, HashMethod, Config) ->
  646:     Vars = #{<<"user">> => User, <<"passwordHash">> => PasswordHash, <<"hashMethod">> => HashMethod},
  647:     execute_command(<<"account">>, <<"checkPasswordHash">>, Vars, Config).
  648: 
  649: check_user(User, Config) ->
  650:     Vars = #{<<"user">> => User},
  651:     execute_command(<<"account">>, <<"checkUser">>, Vars, Config).
  652: 
  653: register_user(Domain, Username, Password, Config) ->
  654:     Vars = #{<<"domain">> => Domain, <<"username">> => Username, <<"password">> => Password},
  655:     execute_command(<<"account">>, <<"registerUser">>, Vars, Config).
  656: 
  657: register_random_user(Domain, Password, Config) ->
  658:     Vars = #{<<"domain">> => Domain, <<"password">> => Password},
  659:     execute_command(<<"account">>, <<"registerUser">>, Vars, Config).
  660: 
  661: remove_user(User, Config) ->
  662:     Vars = #{<<"user">> => User},
  663:     execute_command(<<"account">>, <<"removeUser">>, Vars, Config).
  664: 
  665: ban_user(JID, Reason, Config) ->
  666:     Vars = #{<<"user">> => JID, <<"reason">> => Reason},
  667:     execute_command(<<"account">>, <<"banUser">>, Vars, Config).
  668: 
  669: change_user_password(JID, NewPassword, Config) ->
  670:     Vars = #{<<"user">> => JID, <<"newPassword">> => NewPassword},
  671:     execute_command(<<"account">>, <<"changeUserPassword">>, Vars, Config).
  672: 
  673: import_users(Filename, Config) ->
  674:     Vars = #{<<"filename">> => Filename},
  675:     execute_command(<<"account">>, <<"importUsers">>, Vars, Config).