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_get_active_users_number, 33: admin_check_password, 34: admin_check_password_hash, 35: admin_check_plain_password_hash, 36: admin_check_user, 37: admin_register_user, 38: admin_register_random_user, 39: admin_remove_non_existing_user, 40: admin_remove_existing_user, 41: admin_ban_user, 42: admin_change_user_password, 43: admin_list_old_users_domain, 44: admin_list_old_users_all, 45: admin_remove_old_users_domain, 46: admin_remove_old_users_all]. 47: 48: init_per_suite(Config) -> 49: Config1 = [{ctl_auth_mods, mongoose_helper:auth_modules()} | Config], 50: Config2 = escalus:init_per_suite(Config1), 51: dynamic_modules:save_modules(domain_helper:host_type(), Config2). 52: 53: end_per_suite(Config) -> 54: dynamic_modules:restore_modules(Config), 55: escalus:end_per_suite(Config). 56: 57: init_per_group(admin_account_handler, Config) -> 58: Mods = [{mod_last, config_parser_helper:default_mod_config(mod_last)}], 59: dynamic_modules:ensure_modules(domain_helper:host_type(), Mods), 60: dynamic_modules:ensure_modules(domain_helper:secondary_host_type(), Mods), 61: Config1 = escalus:create_users(Config, escalus:get_users([alice])), 62: graphql_helper:init_admin_handler(Config1); 63: init_per_group(user_account_handler, Config) -> 64: [{schema_endpoint, user} | Config]; 65: init_per_group(_, Config) -> 66: Config. 67: 68: end_per_group(admin_account_handler, Config) -> 69: escalus_fresh:clean(), 70: escalus:delete_users(Config, escalus:get_users([alice])), 71: dynamic_modules:restore_modules(Config); 72: end_per_group(user_account_handler, _Config) -> 73: escalus_fresh:clean(); 74: end_per_group(_, _Config) -> 75: ok. 76: 77: init_per_testcase(C, Config) when C =:= admin_list_old_users_all; 78: C =:= admin_list_old_users_domain; 79: C =:= admin_remove_old_users_all; 80: C =:= admin_remove_old_users_domain -> 81: {_, AuthMods} = lists:keyfind(ctl_auth_mods, 1, Config), 82: case lists:member(ejabberd_auth_ldap, AuthMods) of 83: true -> {skip, not_fully_supported_with_ldap}; 84: false -> 85: Config1 = escalus:create_users(Config, escalus:get_users([alice, bob, alice_bis])), 86: escalus:init_per_testcase(C, Config1) 87: end; 88: init_per_testcase(admin_register_user = C, Config) -> 89: Config1 = [{user, {<<"gql_admin_registration_test">>, domain_helper:domain()}} | Config], 90: escalus:init_per_testcase(C, Config1); 91: init_per_testcase(admin_check_plain_password_hash = C, Config) -> 92: {_, AuthMods} = lists:keyfind(ctl_auth_mods, 1, Config), 93: case lists:member(ejabberd_auth_ldap, AuthMods) of 94: true -> 95: {skip, not_fully_supported_with_ldap}; 96: false -> 97: AuthOpts = mongoose_helper:auth_opts_with_password_format(plain), 98: Config1 = mongoose_helper:backup_and_set_config_option( 99: Config, {auth, domain_helper:host_type()}, AuthOpts), 100: Config2 = escalus:create_users(Config1, escalus:get_users([carol])), 101: escalus:init_per_testcase(C, Config2) 102: end; 103: init_per_testcase(CaseName, Config) -> 104: escalus:init_per_testcase(CaseName, Config). 105: 106: end_per_testcase(C, Config) when C =:= admin_list_old_users_all; 107: C =:= admin_list_old_users_domain; 108: C =:= admin_remove_old_users_all; 109: C =:= admin_remove_old_users_domain -> 110: escalus:delete_users(Config, escalus:get_users([alice, bob, alice_bis])); 111: end_per_testcase(admin_register_user = C, Config) -> 112: {Username, Domain} = proplists:get_value(user, Config), 113: rpc(mim(), mongoose_account_api, unregister_user, [Username, Domain]), 114: escalus:end_per_testcase(C, Config); 115: end_per_testcase(admin_check_plain_password_hash, Config) -> 116: mongoose_helper:restore_config(Config), 117: escalus:delete_users(Config, escalus:get_users([carol])); 118: end_per_testcase(CaseName, Config) -> 119: escalus:end_per_testcase(CaseName, Config). 120: 121: user_unregister(Config) -> 122: escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_unregister_story/2). 123: 124: user_unregister_story(Config, Alice) -> 125: Ep = ?config(schema_endpoint, Config), 126: Password = lists:last(escalus_users:get_usp(Config, alice)), 127: BinJID = escalus_client:full_jid(Alice), 128: Creds = {BinJID, Password}, 129: Query = <<"mutation { account { unregister } }">>, 130: 131: Path = [data, account, unregister], 132: Resp = execute(Ep, #{query => Query}, Creds), 133: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp), <<"successfully unregistered">>)), 134: % Ensure the user is removed 135: AllUsers = rpc(mim(), mongoose_account_api, list_users, [domain_helper:domain()]), 136: LAliceJID = jid:to_binary(jid:to_lower((jid:binary_to_bare(BinJID)))), 137: ?assertNot(lists:member(LAliceJID, AllUsers)). 138: 139: user_change_password(Config) -> 140: escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_change_password_story/2). 141: 142: user_change_password_story(Config, Alice) -> 143: Ep = ?config(schema_endpoint, Config), 144: Password = lists:last(escalus_users:get_usp(Config, alice)), 145: Creds = {escalus_client:full_jid(Alice), Password}, 146: % Set an empty password 147: Resp1 = execute(Ep, user_change_password_body(<<>>), Creds), 148: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"Empty password">>)), 149: % Set a correct password 150: Path = [data, account, changePassword], 151: Resp2 = execute(Ep, user_change_password_body(<<"kaczka">>), Creds), 152: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp2), <<"Password changed">>)). 153: 154: admin_list_users(Config) -> 155: % An unknown domain 156: Resp = execute_auth(list_users_body(<<"unknown-domain">>), Config), 157: ?assertEqual([], get_ok_value([data, account, listUsers], Resp)), 158: % A domain with users 159: Domain = domain_helper:domain(), 160: Username = jid:nameprep(escalus_users:get_username(Config, alice)), 161: JID = <<Username/binary, "@", Domain/binary>>, 162: Resp2 = execute_auth(list_users_body(Domain), Config), 163: Users = get_ok_value([data, account, listUsers], Resp2), 164: ?assert(lists:member(JID, Users)). 165: 166: admin_count_users(Config) -> 167: % An unknown domain 168: Resp = execute_auth(count_users_body(<<"unknown-domain">>), Config), 169: ?assertEqual(0, get_ok_value([data, account, countUsers], Resp)), 170: % A domain with at least one user 171: Domain = domain_helper:domain(), 172: Resp2 = execute_auth(count_users_body(Domain), Config), 173: ?assert(0 < get_ok_value([data, account, countUsers], Resp2)). 174: 175: admin_get_active_users_number(Config) -> 176: % Check non-existing domain 177: Resp = execute_auth(get_active_users_number_body(<<"unknown-domain">>, 5), Config), 178: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp), <<"Cannot count">>)), 179: % Check an existing domain without active users 180: Resp2 = execute_auth(get_active_users_number_body(domain_helper:domain(), 5), Config), 181: ?assertEqual(0, get_ok_value([data, account, countActiveUsers], Resp2)). 182: 183: admin_check_password(Config) -> 184: Password = lists:last(escalus_users:get_usp(Config, alice)), 185: BinJID = escalus_users:get_jid(Config, alice), 186: Path = [data, account, checkPassword], 187: % A correct password 188: Resp1 = execute_auth(check_password_body(BinJID, Password), Config), 189: ?assertMatch(#{<<"correct">> := true, <<"message">> := _}, get_ok_value(Path, Resp1)), 190: % An incorrect password 191: Resp2 = execute_auth(check_password_body(BinJID, <<"incorrect_pw">>), Config), 192: ?assertMatch(#{<<"correct">> := false, <<"message">> := _}, get_ok_value(Path, Resp2)), 193: % A non-existing user 194: Resp3 = execute_auth(check_password_body(?NOT_EXISTING_JID, Password), Config), 195: ?assertEqual(null, get_ok_value(Path, Resp3)). 196: 197: admin_check_password_hash(Config) -> 198: UserSCRAM = escalus_users:get_jid(Config, alice), 199: EmptyHash = list_to_binary(get_md5(<<>>)), 200: Method = <<"md5">>, 201: % SCRAM password user 202: Resp1 = execute_auth(check_password_hash_body(UserSCRAM, EmptyHash, Method), Config), 203: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"SCRAM password">>)), 204: % A non-existing user 205: Resp2 = execute_auth(check_password_hash_body(?NOT_EXISTING_JID, EmptyHash, Method), Config), 206: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"not exist">>)). 207: 208: admin_check_plain_password_hash(Config) -> 209: UserJID = escalus_users:get_jid(Config, carol), 210: Password = lists:last(escalus_users:get_usp(Config, carol)), 211: Method = <<"md5">>, 212: Hash = list_to_binary(get_md5(Password)), 213: WrongHash = list_to_binary(get_md5(<<"wrong password">>)), 214: Path = [data, account, checkPasswordHash], 215: % A correct hash 216: Resp = execute_auth(check_password_hash_body(UserJID, Hash, Method), Config), 217: ?assertMatch(#{<<"correct">> := true, <<"message">> := _}, get_ok_value(Path, Resp)), 218: % An incorrect hash 219: Resp2 = execute_auth(check_password_hash_body(UserJID, WrongHash, Method), Config), 220: ?assertMatch(#{<<"correct">> := false, <<"message">> := _}, get_ok_value(Path, Resp2)), 221: % A not-supported hash method 222: Resp3 = execute_auth(check_password_hash_body(UserJID, Hash, <<"a">>), Config), 223: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp3), <<"not supported">>)). 224: 225: admin_check_user(Config) -> 226: BinJID = escalus_users:get_jid(Config, alice), 227: Path = [data, account, checkUser], 228: % An existing user 229: Resp1 = execute_auth(check_user_body(BinJID), Config), 230: ?assertMatch(#{<<"exist">> := true, <<"message">> := _}, get_ok_value(Path, Resp1)), 231: % A non-existing user 232: Resp2 = execute_auth(check_user_body(?NOT_EXISTING_JID), Config), 233: ?assertMatch(#{<<"exist">> := false, <<"message">> := _}, get_ok_value(Path, Resp2)). 234: 235: admin_register_user(Config) -> 236: Password = <<"my_password">>, 237: {Username, Domain} = proplists:get_value(user, Config), 238: Path = [data, account, registerUser, message], 239: % Register a new user 240: Resp1 = execute_auth(register_user_body(Domain, Username, Password), Config), 241: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp1), <<"successfully registered">>)), 242: % Try to register a user with existing name 243: Resp2 = execute_auth(register_user_body(Domain, Username, Password), Config), 244: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"already registered">>)). 245: 246: admin_register_random_user(Config) -> 247: Password = <<"my_password">>, 248: Domain = domain_helper:domain(), 249: Path = [data, account, registerUser], 250: % Register a new user 251: Resp1 = execute_auth(register_user_body(Domain, null, Password), Config), 252: #{<<"message">> := Msg, <<"jid">> := JID} = get_ok_value(Path, Resp1), 253: {Username, Server} = jid:to_lus(jid:from_binary(JID)), 254: 255: ?assertNotEqual(nomatch, binary:match(Msg, <<"successfully registered">>)), 256: {ok, _} = rpc(mim(), mongoose_account_api, unregister_user, [Username, Server]). 257: 258: admin_remove_non_existing_user(Config) -> 259: Resp = execute_auth(remove_user_body(?NOT_EXISTING_JID), Config), 260: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp), <<"not exist">>)). 261: 262: admin_remove_existing_user(Config) -> 263: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 264: Path = [data, account, removeUser, message], 265: BinJID = escalus_client:full_jid(Alice), 266: Resp4 = execute_auth(remove_user_body(BinJID), Config), 267: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp4), 268: <<"successfully unregister">>)) 269: end). 270: 271: admin_ban_user(Config) -> 272: Path = [data, account, banUser, message], 273: Reason = <<"annoying">>, 274: % Ban not existing user 275: Resp1 = execute_auth(ban_user_body(?NOT_EXISTING_JID, Reason), Config), 276: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"not allowed">>)), 277: % Ban an existing user 278: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 279: BinJID = escalus_client:full_jid(Alice), 280: Resp2 = execute_auth(ban_user_body(BinJID, Reason), Config), 281: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp2), <<"successfully banned">>)) 282: end). 283: 284: admin_change_user_password(Config) -> 285: Path = [data, account, changeUserPassword, message], 286: NewPassword = <<"new password">>, 287: % Change password of not existing user 288: Resp1 = execute_auth(change_user_password_body(?NOT_EXISTING_JID, NewPassword), Config), 289: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp1), <<"not allowed">>)), 290: % Set an empty password 291: Resp2 = execute_auth(change_user_password_body(?NOT_EXISTING_JID, <<>>), Config), 292: ?assertNotEqual(nomatch, binary:match(get_err_msg(Resp2), <<"Empty password">>)), 293: % Change password of an existing user 294: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 295: BinJID = escalus_client:full_jid(Alice), 296: Resp3 = execute_auth(change_user_password_body(BinJID, NewPassword), Config), 297: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Resp3), <<"Password changed">>)) 298: end). 299: 300: admin_remove_old_users_domain(Config) -> 301: [AliceName, Domain, _] = escalus_users:get_usp(Config, alice), 302: [BobName, Domain, _] = escalus_users:get_usp(Config, bob), 303: [AliceBisName, BisDomain, _] = escalus_users:get_usp(Config, alice_bis), 304: 305: Now = erlang:system_time(seconds), 306: set_last(AliceName, Domain, Now), 307: set_last(BobName, Domain, Now - 86400 * 30), 308: 309: Path = [data, account, removeOldUsers, users], 310: Resp = execute_auth(remove_old_users_body(Domain, 10), Config), 311: ?assertEqual(1, length(get_ok_value(Path, Resp))), 312: ?assertMatch({user_does_not_exist, _}, check_account(BobName, Domain)), 313: ?assertMatch({ok, _}, check_account(AliceName, Domain)), 314: ?assertMatch({ok, _}, check_account(AliceBisName, BisDomain)). 315: 316: admin_remove_old_users_all(Config) -> 317: [AliceName, Domain, _] = escalus_users:get_usp(Config, alice), 318: [BobName, Domain, _] = escalus_users:get_usp(Config, bob), 319: [AliceBisName, BisDomain, _] = escalus_users:get_usp(Config, alice_bis), 320: 321: Now = erlang:system_time(seconds), 322: OldTime = Now - 86400 * 30, 323: set_last(AliceName, Domain, Now), 324: set_last(BobName, Domain, OldTime), 325: set_last(AliceBisName, BisDomain, OldTime), 326: 327: Path = [data, account, removeOldUsers, users], 328: Resp = execute_auth(remove_old_users_body(null, 10), Config), 329: ?assertEqual(2, length(get_ok_value(Path, Resp))), 330: ?assertMatch({user_does_not_exist, _}, check_account(BobName, Domain)), 331: ?assertMatch({ok, _}, check_account(AliceName, Domain)), 332: ?assertMatch({user_does_not_exist, _}, check_account(AliceBisName, BisDomain)). 333: 334: admin_list_old_users_domain(Config) -> 335: [AliceName, Domain, _] = escalus_users:get_usp(Config, alice), 336: [BobName, Domain, _] = escalus_users:get_usp(Config, bob), 337: 338: Now = erlang:system_time(seconds), 339: set_last(AliceName, Domain, Now), 340: set_last(BobName, Domain, Now - 86400 * 30), 341: 342: LBob = escalus_utils:jid_to_lower(escalus_users:get_jid(Config, bob)), 343: 344: Path = [data, account, listOldUsers], 345: Resp = execute_auth(list_old_users_body(Domain, 10), Config), 346: Users = get_ok_value(Path, Resp), 347: ?assertEqual(1, length(Users)), 348: ?assert(lists:member(LBob, Users)). 349: 350: admin_list_old_users_all(Config) -> 351: [AliceName, Domain, _] = escalus_users:get_usp(Config, alice), 352: [BobName, Domain, _] = escalus_users:get_usp(Config, bob), 353: [AliceBisName, BisDomain, _] = escalus_users:get_usp(Config, alice_bis), 354: 355: Now = erlang:system_time(seconds), 356: OldTime = Now - 86400 * 30, 357: set_last(AliceName, Domain, Now), 358: set_last(BobName, Domain, OldTime), 359: set_last(AliceBisName, BisDomain, OldTime), 360: 361: LBob = escalus_utils:jid_to_lower(escalus_users:get_jid(Config, bob)), 362: LAliceBis = escalus_utils:jid_to_lower(escalus_users:get_jid(Config, alice_bis)), 363: 364: Path = [data, account, listOldUsers], 365: Resp = execute_auth(list_old_users_body(null, 10), Config), 366: Users = get_ok_value(Path, Resp), 367: ?assertEqual(2, length(Users)), 368: ?assert(lists:member(LBob, Users)), 369: ?assert(lists:member(LAliceBis, Users)). 370: 371: %% Helpers 372: 373: get_md5(AccountPass) -> 374: lists:flatten([io_lib:format("~.16B", [X]) 375: || X <- binary_to_list(crypto:hash(md5, AccountPass))]). 376: 377: set_last(User, Domain, TStamp) -> 378: rpc(mim(), mod_last, store_last_info, 379: [domain_helper:host_type(), escalus_utils:jid_to_lower(User), Domain, TStamp, <<>>]). 380: 381: check_account(Username, Domain) -> 382: rpc(mim(), mongoose_account_api, check_account, [Username, Domain]). 383: 384: %% Request bodies 385: 386: list_users_body(Domain) -> 387: Query = <<"query Q1($domain: String!) { account { listUsers(domain: $domain) } }">>, 388: OpName = <<"Q1">>, 389: Vars = #{<<"domain">> => Domain}, 390: #{query => Query, operationName => OpName, variables => Vars}. 391: 392: count_users_body(Domain) -> 393: Query = <<"query Q1($domain: String!) { account { countUsers(domain: $domain) } }">>, 394: OpName = <<"Q1">>, 395: Vars = #{<<"domain">> => Domain}, 396: #{query => Query, operationName => OpName, variables => Vars}. 397: 398: get_active_users_number_body(Domain, Days) -> 399: Query = <<"query Q1($domain: String!, $days: Int!) 400: { account { countActiveUsers(domain: $domain, days: $days) } }">>, 401: OpName = <<"Q1">>, 402: Vars = #{<<"domain">> => Domain, <<"days">> => Days}, 403: #{query => Query, operationName => OpName, variables => Vars}. 404: 405: check_password_body(User, Password) -> 406: Query = <<"query Q1($user: JID!, $password: String!) 407: { account { checkPassword(user: $user, password: $password) {correct message} } }">>, 408: OpName = <<"Q1">>, 409: Vars = #{<<"user">> => User, <<"password">> => Password}, 410: #{query => Query, operationName => OpName, variables => Vars}. 411: 412: check_password_hash_body(User, PasswordHash, HashMethod) -> 413: Query = <<"query Q1($user: JID!, $hash: String!, $method: String!) 414: { account { checkPasswordHash(user: $user, passwordHash: $hash, hashMethod: $method) 415: {correct message} } }">>, 416: OpName = <<"Q1">>, 417: Vars = #{<<"user">> => User, <<"hash">> => PasswordHash, <<"method">> => HashMethod}, 418: #{query => Query, operationName => OpName, variables => Vars}. 419: 420: check_user_body(User) -> 421: Query = <<"query Q1($user: JID!) 422: { account { checkUser(user: $user) {exist message} } }">>, 423: OpName = <<"Q1">>, 424: Vars = #{<<"user">> => User}, 425: #{query => Query, operationName => OpName, variables => Vars}. 426: 427: register_user_body(Domain, Username, Password) -> 428: Query = <<"mutation M1($domain: String!, $username: String, $password: String!) 429: { account { registerUser(domain: $domain, username: $username, password: $password) 430: { jid message } } }">>, 431: OpName = <<"M1">>, 432: Vars = #{<<"domain">> => Domain, <<"username">> => Username, <<"password">> => Password}, 433: #{query => Query, operationName => OpName, variables => Vars}. 434: 435: remove_user_body(User) -> 436: Query = <<"mutation M1($user: JID!) 437: { account { removeUser(user: $user) { jid message } } }">>, 438: OpName = <<"M1">>, 439: Vars = #{<<"user">> => User}, 440: #{query => Query, operationName => OpName, variables => Vars}. 441: 442: remove_old_users_body(Domain, Days) -> 443: Query = <<"mutation M1($domain: String, $days: Int!) 444: { account { removeOldUsers(domain: $domain, days: $days) { message users } } }">>, 445: OpName = <<"M1">>, 446: Vars = #{<<"domain">> => Domain, <<"days">> => Days}, 447: #{query => Query, operationName => OpName, variables => Vars}. 448: 449: list_old_users_body(Domain, Days) -> 450: Query = <<"query Q1($domain: String, $days: Int!) 451: { account { listOldUsers(domain: $domain, days: $days) } }">>, 452: OpName = <<"Q1">>, 453: Vars = #{<<"domain">> => Domain, <<"days">> => Days}, 454: #{query => Query, operationName => OpName, variables => Vars}. 455: 456: ban_user_body(JID, Reason) -> 457: Query = <<"mutation M1($user: JID!, $reason: String!) 458: { account { banUser(user: $user, reason: $reason) { jid message } } }">>, 459: OpName = <<"M1">>, 460: Vars = #{<<"user">> => JID, <<"reason">> => Reason}, 461: #{query => Query, operationName => OpName, variables => Vars}. 462: 463: change_user_password_body(JID, NewPassword) -> 464: Query = <<"mutation M1($user: JID!, $newPassword: String!) 465: { account { changeUserPassword(user: $user, newPassword: $newPassword) { jid message } } }">>, 466: OpName = <<"M1">>, 467: Vars = #{<<"user">> => JID, <<"newPassword">> => NewPassword}, 468: #{query => Query, operationName => OpName, variables => Vars}. 469: 470: user_change_password_body(NewPassword) -> 471: Query = <<"mutation M1($newPassword: String!) 472: { account { changePassword(newPassword: $newPassword) } }">>, 473: OpName = <<"M1">>, 474: Vars = #{<<"newPassword">> => NewPassword}, 475: #{query => Query, operationName => OpName, variables => Vars}.