1: -module(graphql_account_SUITE). 2: 3: -include_lib("common_test/include/ct.hrl"). 4: -include_lib("eunit/include/eunit.hrl"). 5: 6: -compile([export_all, nowarn_export_all]). 7: 8: -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]). 9: -import(graphql_helper, [execute/3, execute_auth/2, get_listener_port/1, 10: get_listener_config/1, get_ok_value/2, get_err_msg/1]). 11: 12: -define(NOT_EXISTING_JID, <<"unknown987@unknown">>). 13: 14: suite() -> 15: require_rpc_nodes([mim]) ++ escalus:suite(). 16: 17: all() -> 18: [{group, user_account_handler}, 19: {group, admin_account_handler}]. 20: 21: groups() -> 22: [{user_account_handler, [parallel], user_account_handler()}, 23: {admin_account_handler, [], admin_account_handler()}]. 24: 25: user_account_handler() -> 26: [user_unregister, 27: user_change_password]. 28: 29: admin_account_handler() -> 30: [admin_list_users, 31: admin_count_users, 32: admin_check_password, 33: admin_check_password_hash, 34: admin_check_plain_password_hash, 35: admin_check_user, 36: admin_register_user, 37: admin_register_random_user, 38: admin_remove_non_existing_user, 39: admin_remove_existing_user, 40: admin_ban_user, 41: admin_change_user_password]. 42: 43: init_per_suite(Config) -> 44: Config1 = [{ctl_auth_mods, mongoose_helper:auth_modules()} | Config], 45: Config2 = escalus:init_per_suite(Config1), 46: dynamic_modules:save_modules(domain_helper:host_type(), Config2). 47: 48: end_per_suite(Config) -> 49: dynamic_modules:restore_modules(Config), 50: escalus:end_per_suite(Config). 51: 52: init_per_group(admin_account_handler, Config) -> 53: Config1 = escalus:create_users(Config, escalus:get_users([alice])), 54: graphql_helper:init_admin_handler(Config1); 55: init_per_group(user_account_handler, Config) -> 56: [{schema_endpoint, user} | Config]; 57: init_per_group(_, Config) -> 58: Config. 59: 60: end_per_group(admin_account_handler, Config) -> 61: escalus_fresh:clean(), 62: escalus:delete_users(Config, escalus:get_users([alice])); 63: end_per_group(user_account_handler, _Config) -> 64: escalus_fresh:clean(); 65: end_per_group(_, _Config) -> 66: ok. 67: 68: init_per_testcase(admin_register_user = C, Config) -> 69: Config1 = [{user, {<<"gql_admin_registration_test">>, domain_helper:domain()}} | Config], 70: escalus:init_per_testcase(C, Config1); 71: init_per_testcase(admin_check_plain_password_hash = C, Config) -> 72: {_, AuthMods} = lists:keyfind(ctl_auth_mods, 1, Config), 73: case lists:member(ejabberd_auth_ldap, AuthMods) of 74: true -> 75: {skip, not_fully_supported_with_ldap}; 76: false -> 77: AuthOpts = mongoose_helper:auth_opts_with_password_format(plain), 78: Config1 = mongoose_helper:backup_and_set_config_option( 79: Config, {auth, domain_helper:host_type()}, AuthOpts), 80: Config2 = escalus:create_users(Config1, escalus:get_users([carol])), 81: escalus:init_per_testcase(C, Config2) 82: end; 83: init_per_testcase(CaseName, Config) -> 84: escalus:init_per_testcase(CaseName, Config). 85: 86: end_per_testcase(admin_register_user = C, Config) -> 87: {Username, Domain} = proplists:get_value(user, Config), 88: rpc(mim(), mongoose_account_api, unregister_user, [Username, Domain]), 89: escalus:end_per_testcase(C, Config); 90: end_per_testcase(admin_check_plain_password_hash, Config) -> 91: mongoose_helper:restore_config(Config), 92: escalus:delete_users(Config, escalus:get_users([carol])); 93: end_per_testcase(CaseName, Config) -> 94: escalus:end_per_testcase(CaseName, Config). 95: 96: user_unregister(Config) -> 97: escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_unregister_story/2). 98: 99: user_unregister_story(Config, Alice) -> 100: Ep = ?config(schema_endpoint, Config), 101: Password = lists:last(escalus_users:get_usp(Config, alice)), 102: BinJID = escalus_client:full_jid(Alice), 103: Creds = {BinJID, Password}, 104: Query = <<"mutation { account { unregister } }">>, 105: 106: Path = [data, account, unregister], 107: Resp = execute(Ep, #{query => Query}, Creds), 108: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp), <<"successfully unregistered">>)), 109: % Ensure the user is removed 110: AllUsers = rpc(mim(), mongoose_account_api, list_users, [domain_helper:domain()]), 111: LAliceJID = jid:to_binary(jid:to_lower((jid:binary_to_bare(BinJID)))), 112: ?assertNot(lists:member(LAliceJID, AllUsers)). 113: 114: user_change_password(Config) -> 115: escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_change_password_story/2). 116: 117: user_change_password_story(Config, Alice) -> 118: Ep = ?config(schema_endpoint, Config), 119: Password = lists:last(escalus_users:get_usp(Config, alice)), 120: Creds = {escalus_client:full_jid(Alice), Password}, 121: % Set an empty password 122: Resp1 = execute(Ep, user_change_password_body(<<>>), Creds), 123: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"Empty password">>)), 124: % Set a correct password 125: Path = [data, account, changePassword], 126: Resp2 = execute(Ep, user_change_password_body(<<"kaczka">>), Creds), 127: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp2), <<"Password changed">>)). 128: 129: admin_list_users(Config) -> 130: % An unknown domain 131: Resp = execute_auth(list_users_body(<<"unknown-domain">>), Config), 132: ?assertEqual([], get_ok_value([data, account, listUsers], Resp)), 133: % A domain with users 134: Domain = domain_helper:domain(), 135: Username = jid:nameprep(escalus_users:get_username(Config, alice)), 136: JID = <<Username/binary, "@", Domain/binary>>, 137: Resp2 = execute_auth(list_users_body(Domain), Config), 138: Users = get_ok_value([data, account, listUsers], Resp2), 139: ?assert(lists:member(JID, Users)). 140: 141: admin_count_users(Config) -> 142: % An unknown domain 143: Resp = execute_auth(count_users_body(<<"unknown-domain">>), Config), 144: ?assertEqual(0, get_ok_value([data, account, countUsers], Resp)), 145: % A domain with at least one user 146: Domain = domain_helper:domain(), 147: Resp2 = execute_auth(count_users_body(Domain), Config), 148: ?assert(0 < get_ok_value([data, account, countUsers], Resp2)). 149: 150: admin_check_password(Config) -> 151: Password = lists:last(escalus_users:get_usp(Config, alice)), 152: BinJID = escalus_users:get_jid(Config, alice), 153: Path = [data, account, checkPassword], 154: % A correct password 155: Resp1 = execute_auth(check_password_body(BinJID, Password), Config), 156: ?assertMatch(#{<<"correct">> := true, <<"message">> := _}, get_ok_value(Path, Resp1)), 157: % An incorrect password 158: Resp2 = execute_auth(check_password_body(BinJID, <<"incorrect_pw">>), Config), 159: ?assertMatch(#{<<"correct">> := false, <<"message">> := _}, get_ok_value(Path, Resp2)), 160: % A non-existing user 161: Resp3 = execute_auth(check_password_body(?NOT_EXISTING_JID, Password), Config), 162: ?assertEqual(null, get_ok_value(Path, Resp3)). 163: 164: admin_check_password_hash(Config) -> 165: UserSCRAM = escalus_users:get_jid(Config, alice), 166: EmptyHash = list_to_binary(get_md5(<<>>)), 167: Method = <<"md5">>, 168: % SCRAM password user 169: Resp1 = execute_auth(check_password_hash_body(UserSCRAM, EmptyHash, Method), Config), 170: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"SCRAM password">>)), 171: % A non-existing user 172: Resp2 = execute_auth(check_password_hash_body(?NOT_EXISTING_JID, EmptyHash, Method), Config), 173: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"not exist">>)). 174: 175: admin_check_plain_password_hash(Config) -> 176: UserJID = escalus_users:get_jid(Config, carol), 177: Password = lists:last(escalus_users:get_usp(Config, carol)), 178: Method = <<"md5">>, 179: Hash = list_to_binary(get_md5(Password)), 180: WrongHash = list_to_binary(get_md5(<<"wrong password">>)), 181: Path = [data, account, checkPasswordHash], 182: % A correct hash 183: Resp = execute_auth(check_password_hash_body(UserJID, Hash, Method), Config), 184: ?assertMatch(#{<<"correct">> := true, <<"message">> := _}, get_ok_value(Path, Resp)), 185: % An incorrect hash 186: Resp2 = execute_auth(check_password_hash_body(UserJID, WrongHash, Method), Config), 187: ?assertMatch(#{<<"correct">> := false, <<"message">> := _}, get_ok_value(Path, Resp2)), 188: % A not-supported hash method 189: Resp3 = execute_auth(check_password_hash_body(UserJID, Hash, <<"a">>), Config), 190: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp3), <<"not supported">>)). 191: 192: admin_check_user(Config) -> 193: BinJID = escalus_users:get_jid(Config, alice), 194: Path = [data, account, checkUser], 195: % An existing user 196: Resp1 = execute_auth(check_user_body(BinJID), Config), 197: ?assertMatch(#{<<"exist">> := true, <<"message">> := _}, get_ok_value(Path, Resp1)), 198: % A non-existing user 199: Resp2 = execute_auth(check_user_body(?NOT_EXISTING_JID), Config), 200: ?assertMatch(#{<<"exist">> := false, <<"message">> := _}, get_ok_value(Path, Resp2)). 201: 202: admin_register_user(Config) -> 203: Password = <<"my_password">>, 204: {Username, Domain} = proplists:get_value(user, Config), 205: Path = [data, account, registerUser, message], 206: % Register a new user 207: Resp1 = execute_auth(register_user_body(Domain, Username, Password), Config), 208: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp1), <<"successfully registered">>)), 209: % Try to register a user with existing name 210: Resp2 = execute_auth(register_user_body(Domain, Username, Password), Config), 211: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"already registered">>)). 212: 213: admin_register_random_user(Config) -> 214: Password = <<"my_password">>, 215: Domain = domain_helper:domain(), 216: Path = [data, account, registerUser], 217: % Register a new user 218: Resp1 = execute_auth(register_user_body(Domain, null, Password), Config), 219: #{<<"message">> := Msg, <<"jid">> := JID} = get_ok_value(Path, Resp1), 220: {Username, Server} = jid:to_lus(jid:from_binary(JID)), 221: 222: ?assertNotEqual(nomatch, binary:match(Msg, <<"successfully registered">>)), 223: {ok, _} = rpc(mim(), mongoose_account_api, unregister_user, [Username, Server]). 224: 225: admin_remove_non_existing_user(Config) -> 226: Resp = execute_auth(remove_user_body(?NOT_EXISTING_JID), Config), 227: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp), <<"not exist">>)). 228: 229: admin_remove_existing_user(Config) -> 230: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 231: Path = [data, account, removeUser, message], 232: BinJID = escalus_client:full_jid(Alice), 233: Resp4 = execute_auth(remove_user_body(BinJID), Config), 234: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp4), 235: <<"successfully unregister">>)) 236: end). 237: 238: admin_ban_user(Config) -> 239: Path = [data, account, banUser, message], 240: Reason = <<"annoying">>, 241: % Ban not existing user 242: Resp1 = execute_auth(ban_user_body(?NOT_EXISTING_JID, Reason), Config), 243: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"not allowed">>)), 244: % Ban an existing user 245: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 246: BinJID = escalus_client:full_jid(Alice), 247: Resp2 = execute_auth(ban_user_body(BinJID, Reason), Config), 248: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp2), <<"successfully banned">>)) 249: end). 250: 251: admin_change_user_password(Config) -> 252: Path = [data, account, changeUserPassword, message], 253: NewPassword = <<"new password">>, 254: % Change password of not existing user 255: Resp1 = execute_auth(change_user_password_body(?NOT_EXISTING_JID, NewPassword), Config), 256: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"not allowed">>)), 257: % Set an empty password 258: Resp2 = execute_auth(change_user_password_body(?NOT_EXISTING_JID, <<>>), Config), 259: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"Empty password">>)), 260: % Change password of an existing user 261: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 262: BinJID = escalus_client:full_jid(Alice), 263: Resp3 = execute_auth(change_user_password_body(BinJID, NewPassword), Config), 264: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp3), <<"Password changed">>)) 265: end). 266: 267: %% Helpers 268: 269: get_md5(AccountPass) -> 270: lists:flatten([io_lib:format("~.16B", [X]) 271: || X <- binary_to_list(crypto:hash(md5, AccountPass))]). 272: 273: %% Request bodies 274: 275: list_users_body(Domain) -> 276: Query = <<"query Q1($domain: String!) { account { listUsers(domain: $domain) } }">>, 277: OpName = <<"Q1">>, 278: Vars = #{<<"domain">> => Domain}, 279: #{query => Query, operationName => OpName, variables => Vars}. 280: 281: count_users_body(Domain) -> 282: Query = <<"query Q1($domain: String!) { account { countUsers(domain: $domain) } }">>, 283: OpName = <<"Q1">>, 284: Vars = #{<<"domain">> => Domain}, 285: #{query => Query, operationName => OpName, variables => Vars}. 286: 287: check_password_body(User, Password) -> 288: Query = <<"query Q1($user: JID!, $password: String!) 289: { account { checkPassword(user: $user, password: $password) {correct message} } }">>, 290: OpName = <<"Q1">>, 291: Vars = #{<<"user">> => User, <<"password">> => Password}, 292: #{query => Query, operationName => OpName, variables => Vars}. 293: 294: check_password_hash_body(User, PasswordHash, HashMethod) -> 295: Query = <<"query Q1($user: JID!, $hash: String!, $method: String!) 296: { account { checkPasswordHash(user: $user, passwordHash: $hash, hashMethod: $method) 297: {correct message} } }">>, 298: OpName = <<"Q1">>, 299: Vars = #{<<"user">> => User, <<"hash">> => PasswordHash, <<"method">> => HashMethod}, 300: #{query => Query, operationName => OpName, variables => Vars}. 301: 302: check_user_body(User) -> 303: Query = <<"query Q1($user: JID!) 304: { account { checkUser(user: $user) {exist message} } }">>, 305: OpName = <<"Q1">>, 306: Vars = #{<<"user">> => User}, 307: #{query => Query, operationName => OpName, variables => Vars}. 308: 309: register_user_body(Domain, Username, Password) -> 310: Query = <<"mutation M1($domain: String!, $username: String, $password: String!) 311: { account { registerUser(domain: $domain, username: $username, password: $password) 312: { jid message } } }">>, 313: OpName = <<"M1">>, 314: Vars = #{<<"domain">> => Domain, <<"username">> => Username, <<"password">> => Password}, 315: #{query => Query, operationName => OpName, variables => Vars}. 316: 317: remove_user_body(User) -> 318: Query = <<"mutation M1($user: JID!) 319: { account { removeUser(user: $user) { jid message } } }">>, 320: OpName = <<"M1">>, 321: Vars = #{<<"user">> => User}, 322: #{query => Query, operationName => OpName, variables => Vars}. 323: 324: ban_user_body(JID, Reason) -> 325: Query = <<"mutation M1($user: JID!, $reason: String!) 326: { account { banUser(user: $user, reason: $reason) { jid message } } }">>, 327: OpName = <<"M1">>, 328: Vars = #{<<"user">> => JID, <<"reason">> => Reason}, 329: #{query => Query, operationName => OpName, variables => Vars}. 330: 331: change_user_password_body(JID, NewPassword) -> 332: Query = <<"mutation M1($user: JID!, $newPassword: String!) 333: { account { changeUserPassword(user: $user, newPassword: $newPassword) { jid message } } }">>, 334: OpName = <<"M1">>, 335: Vars = #{<<"user">> => JID, <<"newPassword">> => NewPassword}, 336: #{query => Query, operationName => OpName, variables => Vars}. 337: 338: user_change_password_body(NewPassword) -> 339: Query = <<"mutation M1($newPassword: String!) 340: { account { changePassword(newPassword: $newPassword) } }">>, 341: OpName = <<"M1">>, 342: Vars = #{<<"newPassword">> => NewPassword}, 343: #{query => Query, operationName => OpName, variables => Vars}.