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