1: -module(muc_light_legacy_SUITE). 2: 3: -include_lib("escalus/include/escalus.hrl"). 4: -include_lib("escalus/include/escalus_xmlns.hrl"). 5: -include_lib("common_test/include/ct.hrl"). 6: -include_lib("exml/include/exml.hrl"). 7: 8: -export([ 9: disco_service/1, 10: disco_features/1, 11: disco_features_with_mam/1, 12: disco_info/1, 13: disco_info_with_mam/1, 14: disco_rooms/1, 15: disco_rooms_rsm/1, 16: unauthorized_stanza/1 17: ]). 18: -export([ 19: send_message/1, 20: change_subject/1, 21: all_can_configure/1, 22: set_config_deny/1, 23: get_room_config/1, 24: get_room_occupants/1, 25: leave_room/1, 26: change_other_aff_deny/1 27: ]). 28: -export([ 29: create_room/1, 30: create_room_with_equal_occupants/1, 31: create_existing_room_deny/1, 32: destroy_room/1, 33: set_config/1, 34: assorted_config_doesnt_lead_to_duplication/1, 35: remove_and_add_users/1, 36: explicit_owner_change/1, 37: implicit_owner_change/1, 38: edge_case_owner_change/1 39: ]). 40: -export([ 41: manage_blocklist/1, 42: block_room/1, 43: block_user/1, 44: blocking_disabled/1 45: ]). 46: 47: -export([all/0, groups/0, suite/0, 48: init_per_suite/1, end_per_suite/1, 49: init_per_group/2, end_per_group/2, 50: init_per_testcase/2, end_per_testcase/2]). 51: 52: -import(escalus_ejabberd, [rpc/3]). 53: -import(muc_helper, [foreach_occupant/3, foreach_recipient/2]). 54: -import(distributed_helper, [subhost_pattern/1]). 55: -import(domain_helper, [host_type/0, domain/0]). 56: -import(muc_light_helper, [ 57: bin_aff_users/1, 58: to_lus/2, 59: lbin/1, 60: create_room/6, 61: default_config/1, 62: default_schema/0 63: ]). 64: 65: -define(ROOM, <<"testroom">>). 66: -define(ROOM2, <<"testroom2">>). 67: 68: -define(NS_MUC_LIGHT, <<"urn:xmpp:muclight:0">>). 69: -define(NS_MUC_ROOMCONFIG, <<"http://jabber.org/protocol/muc#roomconfig">>). 70: 71: -define(MUCHOST, (muc_helper:muc_host())). 72: 73: -type ct_aff_user() :: {EscalusClient :: escalus:client(), Aff :: atom()}. 74: -type ct_aff_users() :: [ct_aff_user()]. 75: -type ct_block_item() :: {What :: atom(), Action :: atom(), Who :: binary()}. 76: -type verify_fun() :: muc_helper:verify_fun(). 77: -type xmlel() :: exml:element(). 78: 79: -define(DEFAULT_AFF_USERS, [{Alice, owner}, {Bob, member}, {Kate, member}]). 80: 81: %%-------------------------------------------------------------------- 82: %% Suite configuration 83: %%-------------------------------------------------------------------- 84: 85: all() -> 86: [ 87: {group, entity}, 88: {group, occupant}, 89: {group, owner}, 90: {group, blocking} 91: ]. 92: 93: groups() -> 94: G = [ 95: {entity, [sequence], [ 96: disco_service, 97: disco_features, 98: disco_features_with_mam, 99: disco_info, 100: disco_info_with_mam, 101: disco_rooms, 102: disco_rooms_rsm, 103: unauthorized_stanza 104: ]}, 105: {occupant, [sequence], [ 106: send_message, 107: change_subject, 108: all_can_configure, 109: set_config_deny, 110: get_room_config, 111: get_room_occupants, 112: leave_room, 113: change_other_aff_deny 114: ]}, 115: {owner, [sequence], [ 116: create_room, 117: create_room_with_equal_occupants, 118: create_existing_room_deny, 119: destroy_room, 120: set_config, 121: assorted_config_doesnt_lead_to_duplication, 122: remove_and_add_users, 123: explicit_owner_change, 124: implicit_owner_change, 125: edge_case_owner_change 126: ]}, 127: {blocking, [sequence], [ 128: manage_blocklist, 129: block_room, 130: block_user, 131: blocking_disabled 132: ]} 133: ], 134: ct_helper:repeat_all_until_all_ok(G). 135: 136: suite() -> 137: escalus:suite(). 138: 139: %%-------------------------------------------------------------------- 140: %% Init & teardown 141: %%-------------------------------------------------------------------- 142: 143: init_per_suite(Config) -> 144: Config1 = dynamic_modules:save_modules(host_type(), Config), 145: Config2 = escalus:init_per_suite(Config1), 146: escalus:create_users(Config2, escalus:get_users([alice, bob, kate, mike])). 147: 148: end_per_suite(Config) -> 149: clear_db(), 150: Config1 = escalus:delete_users(Config, escalus:get_users([alice, bob, kate, mike])), 151: dynamic_modules:restore_modules(Config), 152: escalus:end_per_suite(Config1). 153: 154: init_per_group(_GroupName, Config) -> 155: Config. 156: 157: end_per_group(_GroupName, Config) -> 158: Config. 159: 160: init_per_testcase(CaseName, Config) when CaseName =:= disco_rooms_rsm; 161: CaseName =:= block_room; 162: CaseName =:= block_user -> 163: dynamic_modules:ensure_modules(host_type(), required_modules(CaseName)), 164: create_room(?ROOM, ?MUCHOST, alice, [bob, kate], Config), 165: create_room(?ROOM2, ?MUCHOST, alice, [kate], Config), 166: escalus:init_per_testcase(CaseName, Config); 167: init_per_testcase(create_existing_room_deny = CaseName, Config) -> 168: dynamic_modules:ensure_modules(host_type(), required_modules(CaseName)), 169: create_room(?ROOM, ?MUCHOST, alice, [], Config), 170: escalus:init_per_testcase(CaseName, Config); 171: init_per_testcase(CaseName, Config) -> 172: dynamic_modules:ensure_modules(host_type(), required_modules(CaseName)), 173: create_room(?ROOM, ?MUCHOST, alice, [bob, kate], Config), 174: escalus:init_per_testcase(CaseName, Config). 175: 176: end_per_testcase(CaseName, Config) -> 177: clear_db(), 178: escalus:end_per_testcase(CaseName, Config). 179: 180: %% Module configuration per test case 181: 182: required_modules(CaseName) -> 183: [{mod_muc_light, common_muc_light_opts() ++ muc_light_opts(CaseName)}, 184: {mod_mam_muc, mam_muc_config(CaseName)}]. 185: 186: muc_light_opts(disco_rooms_rsm) -> 187: [{rooms_per_page, 1}]; 188: muc_light_opts(all_can_configure) -> 189: [{all_can_configure, true}]; 190: muc_light_opts(create_room_with_equal_occupants) -> 191: [{equal_occupants, true}]; 192: muc_light_opts(block_user) -> 193: [{all_can_invite, true}]; 194: muc_light_opts(blocking_disabled) -> 195: [{blocking, false}]; 196: muc_light_opts(_) -> 197: []. 198: 199: common_muc_light_opts() -> 200: [{host, subhost_pattern(muc_helper:muc_host_pattern())}, 201: {backend, mongoose_helper:mnesia_or_rdbms_backend()}, 202: {legacy_mode, true}]. 203: 204: mam_muc_config(CaseName) when CaseName =:= disco_features_with_mam; 205: CaseName =:= disco_info_with_mam -> 206: [{backend, rdbms}, 207: {host, subhost_pattern(?MUCHOST)}]; 208: mam_muc_config(_CaseName) -> 209: stopped. 210: 211: %% ---------------------- Helpers ---------------------- 212: 213: create_room(RoomU, MUCHost, Owner, Members, Config) -> 214: create_room(RoomU, MUCHost, Owner, Members, Config, <<"-">>). 215: 216: clear_db() -> 217: muc_light_helper:clear_db(host_type()). 218: 219: %%-------------------------------------------------------------------- 220: %% MUC light tests 221: %%-------------------------------------------------------------------- 222: 223: %% ---------------------- Disco ---------------------- 224: 225: disco_service(Config) -> 226: muc_helper:disco_service_story(Config). 227: 228: disco_features(Config) -> 229: muc_helper:disco_features_story(Config, [?NS_MUC]). 230: 231: disco_features_with_mam(Config) -> 232: muc_helper:disco_features_story(Config, [?NS_MUC] ++ mam_helper:namespaces()). 233: 234: disco_info(Config) -> 235: muc_helper:disco_info_story(Config, [?NS_MUC]). 236: 237: disco_info_with_mam(Config) -> 238: muc_helper:disco_info_story(Config, [?NS_MUC] ++ mam_helper:namespaces()). 239: 240: disco_rooms(Config) -> 241: escalus:story(Config, [{alice, 1}], fun(Alice) -> 242: {ok, {?ROOM2, _}} = create_room(?ROOM2, ?MUCHOST, kate, [], Config), 243: DiscoStanza = escalus_stanza:to(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), ?MUCHOST), 244: escalus:send(Alice, DiscoStanza), 245: %% we should get 1 room, Alice is not in the second one 246: Stanza = escalus:wait_for_stanza(Alice), 247: [Item] = exml_query:paths(Stanza, [{element, <<"query">>}, {element, <<"item">>}]), 248: ProperJID = room_bin_jid(?ROOM), 249: ProperJID = exml_query:attr(Item, <<"jid">>), 250: escalus:assert(is_stanza_from, [?MUCHOST], Stanza) 251: end). 252: 253: disco_rooms_rsm(Config) -> 254: escalus:story(Config, [{alice, 1}], fun(Alice) -> 255: DiscoStanza = escalus_stanza:to(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), ?MUCHOST), 256: escalus:send(Alice, DiscoStanza), 257: %% we should get 1 room with RSM info 258: Stanza = escalus:wait_for_stanza(Alice), 259: [Item] = exml_query:paths(Stanza, [{element, <<"query">>}, {element, <<"item">>}]), 260: ProperJID = room_bin_jid(?ROOM), 261: ProperJID = exml_query:attr(Item, <<"jid">>), 262: 263: RSM = #xmlel{ name = <<"set">>, 264: attrs = [{<<"xmlns">>, ?NS_RSM}], 265: children = [ #xmlel{ name = <<"max">>, 266: children = [#xmlcdata{ content = <<"10">> }] }, 267: #xmlel{ name = <<"before">> } ] }, 268: DiscoStanza2 = escalus_stanza:to( 269: escalus_stanza:iq_get(?NS_DISCO_ITEMS, [RSM]), ?MUCHOST), 270: escalus:send(Alice, DiscoStanza2), 271: %% we should get second room 272: Stanza2 = escalus:wait_for_stanza(Alice), 273: [Item2] = exml_query:paths(Stanza2, [{element, <<"query">>}, {element, <<"item">>}]), 274: ProperJID2 = room_bin_jid(?ROOM2), 275: ProperJID2 = exml_query:attr(Item2, <<"jid">>), 276: 277: BadAfter = #xmlel{ name = <<"after">>, 278: children = [#xmlcdata{ content = <<"oops@", (?MUCHOST)/binary>> }] }, 279: RSM2 = #xmlel{ name = <<"set">>, 280: attrs = [{<<"xmlns">>, ?NS_RSM}], 281: children = [ #xmlel{ name = <<"max">>, 282: children = [#xmlcdata{ content = <<"10">> }] }, 283: BadAfter ] }, 284: DiscoStanza3 = escalus_stanza:to( 285: escalus_stanza:iq_get(?NS_DISCO_ITEMS, [RSM2]), ?MUCHOST), 286: escalus:send(Alice, DiscoStanza3), 287: escalus:assert(is_error, [<<"cancel">>, <<"item-not-found">>], 288: escalus:wait_for_stanza(Alice)) 289: end). 290: 291: unauthorized_stanza(Config) -> 292: escalus:story(Config, [{alice, 1}, {kate, 1}], fun(Alice, Kate) -> 293: {ok, {?ROOM2, _}} = create_room(?ROOM2, ?MUCHOST, kate, [], Config), 294: MsgStanza = escalus_stanza:groupchat_to(room_bin_jid(?ROOM2), <<"malicious">>), 295: escalus:send(Alice, MsgStanza), 296: escalus:assert(is_error, [<<"cancel">>, <<"item-not-found">>], 297: escalus:wait_for_stanza(Alice)), 298: verify_no_stanzas([Alice, Kate]) 299: end). 300: 301: %% ---------------------- Occupant ---------------------- 302: 303: send_message(Config) -> 304: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 305: Msg = <<"Heyah!">>, 306: Id = <<"LegacyId">>, 307: Stanza = escalus_stanza:set_id( 308: escalus_stanza:groupchat_to(room_bin_jid(?ROOM), Msg), Id), 309: foreach_occupant([Alice, Bob, Kate], Stanza, gc_message_verify_fun(?ROOM, Msg, Id)) 310: end). 311: 312: change_subject(Config) -> 313: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 314: Subject = <<"new subject">>, 315: SubjectStanza = #xmlel{name = <<"message">>, 316: attrs = [{<<"type">>, <<"groupchat">>}], 317: children = [#xmlel{ 318: name = <<"subject">>, 319: children = [#xmlcdata{content = Subject}] 320: }] 321: }, 322: foreach_occupant([Alice, Bob, Kate], 323: escalus_stanza:to(SubjectStanza, room_bin_jid(?ROOM)), 324: subject_message_verify_fun(?ROOM, Subject)) 325: end). 326: 327: all_can_configure(Config) -> 328: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 329: ConfigChange = [{<<"roomname">>, <<"new subject">>}], 330: Stanza = stanza_config_set(?ROOM, ConfigChange), 331: foreach_occupant([Alice, Bob, Kate], Stanza, config_msg_verify_fun()) 332: end). 333: 334: set_config_deny(Config) -> 335: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 336: ConfigChange = [{<<"roomname">>, <<"new subject">>}], 337: Stanza = stanza_config_set(?ROOM, ConfigChange), 338: escalus:send(Kate, Stanza), 339: escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>], 340: escalus:wait_for_stanza(Kate)), 341: verify_no_stanzas([Alice, Bob, Kate]) 342: end). 343: 344: get_room_config(Config) -> 345: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 346: Stanza = stanza_config_get(?ROOM), 347: ConfigKV = default_config(default_schema()), 348: foreach_occupant([Alice, Bob, Kate], Stanza, config_iq_verify_fun(ConfigKV)) 349: end). 350: 351: get_room_occupants(Config) -> 352: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 353: AffUsers = [{Alice, owner}, {Bob, member}, {Kate, member}], 354: foreach_occupant([Alice, Bob, Kate], stanza_aff_get(?ROOM), aff_iq_verify_fun(AffUsers)) 355: end). 356: 357: leave_room(Config) -> 358: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 359: % Users will leave one by one, owner last 360: lists:foldr( 361: fun(User, {Occupants, Outsiders}) -> 362: NewOccupants = lists:keydelete(User, 1, Occupants), 363: user_leave(User, [ U || {U, _} <- NewOccupants]), 364: verify_no_stanzas(Outsiders), 365: {NewOccupants, [User | Outsiders]} 366: end, {?DEFAULT_AFF_USERS, []}, [Alice, Bob, Kate]) 367: end). 368: 369: change_other_aff_deny(Config) -> 370: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}], 371: fun(Alice, Bob, Kate, Mike) -> 372: AffUsersChanges1 = [{Bob, none}], 373: escalus:send(Kate, stanza_aff_set(?ROOM, AffUsersChanges1)), 374: escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>], 375: escalus:wait_for_stanza(Kate)), 376: 377: AffUsersChanges2 = [{Alice, member}, {Kate, owner}], 378: escalus:send(Kate, stanza_aff_set(?ROOM, AffUsersChanges2)), 379: escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>], 380: escalus:wait_for_stanza(Kate)), 381: 382: AffUsersChanges3 = [{Mike, member}], 383: escalus:send(Kate, stanza_aff_set(?ROOM, AffUsersChanges3)), 384: escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>], 385: escalus:wait_for_stanza(Kate)), 386: 387: verify_no_stanzas([Alice, Bob, Kate, Mike]) 388: end). 389: 390: %% ---------------------- owner ---------------------- 391: 392: create_room(Config) -> 393: escalus:story(Config, [{bob, 1}], fun(Bob) -> 394: escalus:send(Bob, stanza_create_room(<<"bobroom">>, Bob)), 395: Result = escalus:wait_for_stanza(Bob), 396: presence_verify(Bob, owner, Result) 397: end). 398: 399: create_room_with_equal_occupants(Config) -> 400: escalus:story(Config, [{bob, 1}], fun(Bob) -> 401: escalus:send(Bob, stanza_create_room(<<"bobroom">>, Bob)), 402: Result = escalus:wait_for_stanza(Bob), 403: presence_verify(Bob, member, Result) 404: end). 405: 406: create_existing_room_deny(Config) -> 407: escalus:story(Config, [{bob, 1}], fun(Bob) -> 408: escalus:send(Bob, stanza_create_room(?ROOM, Bob)), 409: Result = escalus:wait_for_stanza(Bob), 410: escalus:assert(is_presence_with_type, [<<"error">>], Result), 411: escalus:assert(is_error, [<<"auth">>, <<"registration-required">>], Result), 412: X = exml_query:subelement(Result, <<"x">>), 413: ?NS_MUC = exml_query:attr(X, <<"xmlns">>) 414: end). 415: 416: destroy_room(Config) -> 417: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 418: escalus:send(Alice, stanza_destroy_room(?ROOM)), 419: AffUsersChanges = [{Alice, none}, {Bob, none}, {Kate, none}], 420: verify_aff_bcast([], AffUsersChanges, [], Alice), 421: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)) 422: end). 423: 424: set_config(Config) -> 425: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 426: ConfigChange = [{<<"roomname">>, <<"The Coven">>}], 427: escalus:send(Alice, stanza_config_set(?ROOM, ConfigChange)), 428: foreach_recipient([Alice, Bob, Kate], config_msg_verify_fun()), 429: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 430: Stanza = stanza_config_get(?ROOM), 431: foreach_occupant([Alice, Bob, Kate], Stanza, config_iq_verify_fun(ConfigChange)) 432: end). 433: 434: assorted_config_doesnt_lead_to_duplication(Config) -> 435: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 436: ConfigChange = [{<<"subject">>, <<"Elixirs">>}, 437: {<<"roomname">>, <<"The Coven">>}, 438: {<<"subject">>, <<"Elixirs">>}], 439: escalus:send(Alice, stanza_config_set(?ROOM, ConfigChange)), 440: foreach_recipient([Alice, Bob, Kate], config_msg_verify_fun()), 441: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 442: 443: Stanza = stanza_config_get(?ROOM), 444: VerifyFun = fun(Incoming) -> 445: Fields = exml_query:paths(Incoming, [{element, <<"query">>}, 446: {element, <<"x">>}, 447: {element, <<"field">>}]), 448: ConfigKV = [{exml_query:attr(F, <<"var">>), 449: exml_query:path(F, [{element, <<"value">>}, cdata])} 450: || F <- Fields], 451: Length = length(ConfigKV), 452: Length = length(lists:ukeysort(1, ConfigKV)) 453: end, 454: foreach_occupant([Alice, Bob, Kate], Stanza, VerifyFun) 455: end). 456: 457: remove_and_add_users(Config) -> 458: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 459: AffUsersChanges1 = [{Bob, none}, {Kate, none}], 460: escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges1)), 461: verify_aff_bcast([Alice], AffUsersChanges1, [], Alice), 462: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 463: AffUsersChanges2 = [{Bob, member}, {Kate, member}], 464: escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges2)), 465: verify_aff_bcast([Alice, Bob, Kate], AffUsersChanges2, [Bob, Kate], Alice), 466: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)) 467: end). 468: 469: explicit_owner_change(Config) -> 470: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 471: AffUsersChanges1 = [{Bob, none}, {Alice, none}, {Kate, owner}], 472: escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges1)), 473: verify_aff_bcast([Kate], AffUsersChanges1, [], Alice), 474: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)) 475: end). 476: 477: implicit_owner_change(Config) -> 478: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 479: AffUsersChanges1 = [{Bob, none}, {Alice, member}], 480: escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges1)), 481: verify_aff_bcast([Kate, Alice], [{Kate, owner} | AffUsersChanges1], [], Alice), 482: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)) 483: end). 484: 485: edge_case_owner_change(Config) -> 486: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 487: AffUsersChanges1 = [{Alice, member}, {Bob, none}, {Kate, none}], 488: escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges1)), 489: verify_aff_bcast([Alice], [{Kate, none}, {Bob, none}], [], Alice), 490: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)) 491: end). 492: 493: %% ---------------------- blocking ---------------------- 494: 495: manage_blocklist(Config) -> 496: escalus:story(Config, [{alice, 1}], fun(Alice) -> 497: escalus:send(Alice, stanza_blocking_get()), 498: GetResult1 = escalus:wait_for_stanza(Alice), 499: escalus:assert(is_iq_result, GetResult1), 500: QueryEl1 = exml_query:subelement(GetResult1, <<"query">>), 501: verify_blocklist(QueryEl1, []), 502: Domain = domain(), 503: BlocklistChange1 = [{user, deny, <<"user@", Domain/binary>>}, 504: {room, deny, room_bin_jid(?ROOM)}], 505: escalus:send(Alice, stanza_blocking_set(BlocklistChange1)), 506: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 507: escalus:send(Alice, stanza_blocking_get()), 508: GetResult2 = escalus:wait_for_stanza(Alice), 509: escalus:assert(is_iq_result, GetResult2), 510: QueryEl2 = exml_query:subelement(GetResult2, <<"query">>), 511: verify_blocklist(QueryEl2, BlocklistChange1), 512: 513: BlocklistChange2 = [{user, allow, <<"user@", Domain/binary>>}, 514: {room, allow, room_bin_jid(?ROOM)}], 515: escalus:send(Alice, stanza_blocking_set(BlocklistChange2)), 516: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 517: escalus:send(Alice, stanza_blocking_get()), 518: GetResult3 = escalus:wait_for_stanza(Alice), 519: escalus:assert(is_iq_result, GetResult3), 520: % Match below checks for empty list 521: QueryEl1 = exml_query:subelement(GetResult3, <<"query">>) 522: end). 523: 524: block_room(Config) -> 525: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 526: BlocklistChange = [{room, deny, room_bin_jid(?ROOM)}], 527: escalus:send(Bob, stanza_blocking_set(BlocklistChange)), 528: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 529: user_leave(Bob, [Alice, Kate]), 530: 531: % Alice tries to readd Bob to the room but fails 532: BobReadd = [{Bob, member}], 533: escalus:send(Alice, stanza_aff_set(?ROOM, BobReadd)), 534: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 535: verify_no_stanzas([Alice, Bob, Kate]), 536: 537: % But Alice can add Bob to another room! 538: escalus:send(Alice, stanza_aff_set(?ROOM2, BobReadd)), 539: verify_aff_bcast([Alice, Bob, Kate], BobReadd, [Bob], Alice), 540: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)) 541: end). 542: 543: block_user(Config) -> 544: escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 545: AliceJIDBin = lbin(escalus_client:short_jid(Alice)), 546: BlocklistChange = [{user, deny, AliceJIDBin}], 547: escalus:send(Bob, stanza_blocking_set(BlocklistChange)), 548: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 549: 550: % Alice tries to add Bob to the room but fails 551: BobAdd = [{Bob, member}], 552: escalus:send(Alice, stanza_aff_set(?ROOM2, BobAdd)), 553: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 554: verify_no_stanzas([Alice, Bob, Kate]), 555: 556: % But Kate can add Bob to the room! 557: escalus:send(Kate, stanza_aff_set(?ROOM2, BobAdd)), 558: verify_aff_bcast([Alice, Bob, Kate], BobAdd, [Bob], Kate), 559: escalus:assert(is_iq_result, escalus:wait_for_stanza(Kate)), 560: verify_no_stanzas([Alice, Bob, Kate]) 561: end). 562: 563: blocking_disabled(Config) -> 564: escalus:story(Config, [{alice, 1}], fun(Alice) -> 565: escalus:send(Alice, stanza_blocking_get()), 566: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 567: escalus:wait_for_stanza(Alice)), 568: Domain = domain(), 569: BlocklistChange1 = [{user, deny, <<"user@", Domain/binary>>}, 570: {room, deny, room_bin_jid(?ROOM)}], 571: escalus:send(Alice, stanza_blocking_set(BlocklistChange1)), 572: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 573: escalus:wait_for_stanza(Alice)) 574: end). 575: 576: %%-------------------------------------------------------------------- 577: %% Subroutines 578: %%-------------------------------------------------------------------- 579: 580: -spec user_leave(User :: escalus:client(), RemainingOccupants :: [escalus:client()]) -> ok. 581: user_leave(User, RemainingOccupants) -> 582: AffUsersChanges = [{User, none}], 583: Stanza = stanza_aff_set(?ROOM, AffUsersChanges), 584: escalus:send(User, Stanza), 585: verify_aff_bcast(RemainingOccupants, AffUsersChanges, [], User), 586: escalus:assert(is_iq_result, escalus:wait_for_stanza(User)). 587: 588: %%-------------------------------------------------------------------- 589: %% IQ getters 590: %%-------------------------------------------------------------------- 591: 592: -spec stanza_blocking_get() -> xmlel(). 593: stanza_blocking_get() -> 594: escalus_stanza:privacy_get_lists([?NS_MUC_LIGHT]). 595: 596: -spec stanza_config_get(Room :: binary()) -> xmlel(). 597: stanza_config_get(Room) -> 598: escalus_stanza:to( 599: escalus_stanza:iq_get(?NS_MUC_OWNER, []), room_bin_jid(Room)). 600: 601: -spec stanza_aff_get(Room :: binary()) -> xmlel(). 602: stanza_aff_get(Room) -> 603: escalus_stanza:to( 604: escalus_stanza:iq_get(?NS_MUC_ADMIN, []), room_bin_jid(Room)). 605: 606: %%-------------------------------------------------------------------- 607: %% IQ setters 608: %%-------------------------------------------------------------------- 609: 610: -spec stanza_blocking_set(BlocklistChanges :: [ct_block_item()]) -> xmlel(). 611: stanza_blocking_set(BlocklistChanges) -> 612: Items = [ encode_privacy_item(What, Action, Who) || {What, Action, Who} <- BlocklistChanges ], 613: escalus_stanza:privacy_set_list(escalus_stanza:privacy_list(?NS_MUC_LIGHT, Items)). 614: 615: encode_privacy_item(What, Action, Who) -> 616: Value = case What of 617: room -> Who; 618: user -> <<(?MUCHOST)/binary, $/, Who/binary>> 619: end, 620: ActionBin = atom_to_binary(Action, utf8), 621: escalus_stanza:privacy_list_item(<<"1">>, ActionBin, <<"jid">>, Value, []). 622: 623: -spec stanza_create_room(RoomNode :: binary(), Creator :: escalus:client()) -> xmlel(). 624: stanza_create_room(RoomNode, Creator) -> 625: ToBinJID = <<(room_bin_jid(RoomNode))/binary, $/, 626: (lbin(escalus_client:short_jid(Creator)))/binary>>, 627: X = #xmlel{ name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_MUC}] }, 628: escalus_stanza:to(escalus_stanza:presence(<<"available">>, [X]), ToBinJID). 629: 630: -spec stanza_destroy_room(Room :: binary()) -> xmlel(). 631: stanza_destroy_room(Room) -> 632: escalus_stanza:to(escalus_stanza:iq_set(?NS_MUC_OWNER, [#xmlel{ name = <<"destroy">> }]), 633: room_bin_jid(Room)). 634: 635: -spec stanza_config_set(Room :: binary(), ConfigChanges :: [muc_light_helper:config_item()]) -> xmlel(). 636: stanza_config_set(Room, ConfigChanges) -> 637: IQ = escalus_stanza:iq_set(?NS_MUC_OWNER, [form_x_el(ConfigChanges)]), 638: escalus_stanza:to(IQ, room_bin_jid(Room)). 639: 640: -spec form_x_el(Fields :: [xmlel()]) -> xmlel(). 641: form_x_el(Fields) -> 642: #xmlel{ 643: name = <<"x">>, 644: attrs = [{<<"xmlns">>, <<"jabber:x:data">>}, {<<"type">>, <<"submit">>}], 645: children = [form_field(<<"FORM_TYPE">>, ?NS_MUC_ROOMCONFIG, <<"hidden">>) 646: | [form_field(K, V, <<"text-single">>) || {K, V} <- Fields ]] 647: }. 648: 649: -spec form_field(Var :: binary(), Value :: binary(), Type :: binary()) -> xmlel(). 650: form_field(Var, Value, Type) -> 651: #xmlel{ name = <<"field">>, 652: attrs = [{<<"type">>, Type}, {<<"var">>, Var}], 653: children = [#xmlel{name = <<"value">>, 654: children = [#xmlcdata{content = Value}] }] }. 655: 656: -spec stanza_aff_set(Room :: binary(), AffUsers :: ct_aff_users()) -> xmlel(). 657: stanza_aff_set(Room, AffUsers) -> 658: Items = [#xmlel{ name = <<"item">>, attrs = [{<<"affiliation">>, AffBin}, 659: {<<"jid">>, UserBin}] } 660: || {UserBin, AffBin} <- bin_aff_users(AffUsers)], 661: escalus_stanza:to(escalus_stanza:iq_set(?NS_MUC_ADMIN, Items), room_bin_jid(Room)). 662: 663: %%-------------------------------------------------------------------- 664: %% Verifiers 665: %%-------------------------------------------------------------------- 666: 667: -spec verify_blocklist(Query :: xmlel(), ProperBlocklist :: [ct_block_item()]) -> []. 668: verify_blocklist(Query, ProperBlocklist) -> 669: ?NS_PRIVACY = exml_query:attr(Query, <<"xmlns">>), 670: RawItems = exml_query:paths(Query, [{element, <<"list">>}, {element, <<"item">>}]), 671: Blocklist = [ parse_blocked_item(Item) || Item <- RawItems ], 672: ProperBlocklistLen = length(ProperBlocklist), 673: ProperBlocklistLen = length(Blocklist), 674: [] = lists:foldl(fun lists:delete/2, Blocklist, ProperBlocklist). 675: 676: -spec parse_blocked_item(Item :: xmlel()) -> ct_block_item(). 677: parse_blocked_item(Item) -> 678: <<"deny">> = exml_query:attr(Item, <<"action">>), 679: <<"jid">> = exml_query:attr(Item, <<"type">>), 680: Value = exml_query:attr(Item, <<"value">>), 681: MucHost = ?MUCHOST, 682: case binary:split(Value, <<"/">>) of 683: [MucHost, User] -> {user, deny, User}; 684: [Room] -> {room, deny, Room}; 685: Other -> 686: CfgHost = rpc(gen_mod, get_module_opt, [host_type(), mod_muc_light, host, undefined]), 687: ct:fail(#{what => parse_blocked_item_failed, 688: muc_host => MucHost, other => Other, 689: cfg_host => CfgHost}) 690: end. 691: 692: -spec verify_aff_bcast(CurrentOccupants :: [escalus:client()], AffUsersChanges :: ct_aff_users(), 693: Newcomers :: [escalus:client()], Changer :: escalus:client()) -> ok. 694: verify_aff_bcast(CurrentOccupants, AffUsersChanges, Newcomers, Changer) -> 695: MucHost = ?MUCHOST, 696: PredList = [ presence_verify_fun(AffUser) || AffUser <- AffUsersChanges ], 697: lists:foreach( 698: fun(Occupant) -> 699: case lists:member(Occupant, Newcomers) of 700: false -> 701: Stanzas = escalus:wait_for_stanzas(Occupant, length(PredList)), 702: escalus_new_assert:mix_match(PredList, Stanzas); 703: true -> 704: ok 705: end 706: end, CurrentOccupants), 707: lists:foreach( 708: fun(Newcomer) -> 709: #xmlel{ name = <<"message">> } = Incoming = escalus:wait_for_stanza(Newcomer), 710: RoomBareJIDBin = exml_query:attr(Incoming, <<"from">>), 711: [_, MucHost] = binary:split(RoomBareJIDBin, <<"@">>), 712: X = exml_query:subelement(Incoming, <<"x">>), 713: ?NS_MUC_USER = exml_query:attr(X, <<"xmlns">>), 714: [Invite] = exml_query:subelements(X, <<"invite">>), 715: ChangerBareJIDBin = lbin(escalus_client:short_jid(Changer)), 716: ChangerBareJIDBin = exml_query:attr(Invite, <<"from">>) 717: end, Newcomers), 718: lists:foreach( 719: fun({Leaver, none}) -> 720: presence_verify(Leaver, none, escalus:wait_for_stanza(Leaver)); 721: (_) -> 722: ignore 723: end, AffUsersChanges). 724: 725: -spec verify_no_stanzas(Users :: [escalus:client()]) -> ok. 726: verify_no_stanzas(Users) -> 727: lists:foreach( 728: fun(User) -> 729: {false, _} = {escalus_client:has_stanzas(User), User} 730: end, Users). 731: 732: -spec verify_config(ConfigFields :: [xmlel()], Config :: [muc_light_helper:config_item()]) -> ok. 733: verify_config(ConfigFields, Config) -> 734: [] = lists:foldl( 735: fun(Field, ConfigAcc) -> 736: Key = exml_query:attr(Field, <<"var">>), 737: Val = exml_query:path(Field, [{element, <<"value">>}, cdata]), 738: case lists:keytake(Key, 1, ConfigAcc) of 739: {value, {_, ProperVal}, NewConfig} when Val =:= ProperVal -> NewConfig; 740: false -> ConfigAcc 741: end 742: end, Config, ConfigFields). 743: 744: -spec verify_aff_users(Items :: [xmlel()], BinAffUsers :: [{binary(), binary()}]) -> []. 745: verify_aff_users(Items, BinAffUsers) -> 746: true = (length(Items) == length(BinAffUsers)), 747: [] = lists:foldl( 748: fun(Item, AffAcc) -> 749: JID = exml_query:attr(Item, <<"jid">>), 750: JID = exml_query:attr(Item, <<"nick">>), 751: Aff = exml_query:attr(Item, <<"affiliation">>), 752: verify_keytake(lists:keytake(JID, 1, AffAcc), JID, Aff, AffAcc) 753: end, BinAffUsers, Items). 754: 755: -spec verify_keytake(Result :: {value, Item :: tuple(), Acc :: list()}, JID :: binary(), 756: Aff :: binary(), AffAcc :: list()) -> list(). 757: verify_keytake({value, {_, Aff}, NewAffAcc}, _JID, Aff, _AffAcc) -> NewAffAcc. 758: 759: %%-------------------------------------------------------------------- 760: %% Verification funs generators 761: %%-------------------------------------------------------------------- 762: 763: -spec gc_message_verify_fun(Room :: binary(), MsgText :: binary(), Id :: binary()) -> verify_fun(). 764: gc_message_verify_fun(Room, MsgText, Id) -> 765: MucHost = ?MUCHOST, 766: fun(Incoming) -> 767: escalus:assert(is_groupchat_message, [MsgText], Incoming), 768: [RoomBareJID, FromNick] = binary:split(exml_query:attr(Incoming, <<"from">>), <<"/">>), 769: [Room, MucHost] = binary:split(RoomBareJID, <<"@">>), 770: [_] = binary:split(FromNick, <<"/">>), % nick is bare JID 771: Id = exml_query:attr(Incoming, <<"id">>) 772: end. 773: 774: -spec subject_message_verify_fun(Room :: binary(), Subject :: binary()) -> verify_fun(). 775: subject_message_verify_fun(Room, Subject) -> 776: MucHost = ?MUCHOST, 777: fun(Incoming) -> 778: escalus:assert(is_groupchat_message, Incoming), 779: Subject = exml_query:path(Incoming, [{element, <<"subject">>}, cdata]), 780: RoomBareJID = exml_query:attr(Incoming, <<"from">>), 781: [Room, MucHost] = binary:split(RoomBareJID, <<"@">>) 782: end. 783: 784: -spec config_msg_verify_fun() -> verify_fun(). 785: config_msg_verify_fun() -> 786: fun(Incoming) -> 787: escalus:assert(is_groupchat_message, Incoming), 788: [X] = exml_query:subelements(Incoming, <<"x">>), 789: ?NS_MUC_USER = exml_query:attr(X, <<"xmlns">>), 790: <<"104">> = exml_query:path(X, [{element, <<"status">>}, {attr, <<"code">>}]) 791: end. 792: 793: -spec config_iq_verify_fun(RoomConfig :: [muc_light_helper:config_item()]) -> verify_fun(). 794: config_iq_verify_fun(RoomConfig) -> 795: fun(Incoming) -> 796: Fields = exml_query:paths(Incoming, [{element, <<"query">>}, {element, <<"x">>}, 797: {element, <<"field">>}]), 798: ?NS_MUC_OWNER = exml_query:path(Incoming, [{element, <<"query">>}, 799: {attr, <<"xmlns">>}]), 800: verify_config(Fields, RoomConfig) 801: end. 802: 803: -spec aff_iq_verify_fun(AffUsers :: ct_aff_users()) -> verify_fun(). 804: aff_iq_verify_fun(AffUsers) -> 805: BinAffUsers = bin_aff_users(AffUsers), 806: fun(Incoming) -> 807: [Query] = exml_query:subelements(Incoming, <<"query">>), 808: ?NS_MUC_ADMIN = exml_query:attr(Query, <<"xmlns">>), 809: Items = exml_query:subelements(Query, <<"item">>), 810: verify_aff_users(Items, BinAffUsers) 811: end. 812: 813: -spec presence_verify_fun(AffUser :: ct_aff_user()) -> verify_fun(). 814: presence_verify_fun({User, UserAff}) -> 815: fun(Incoming) -> 816: true == (catch presence_verify(User, UserAff, Incoming)) 817: end. 818: 819: -spec presence_verify(User :: escalus:client(), UserAff :: none | member | owner, 820: Incoming :: xmlel()) -> true. 821: presence_verify(User, UserAff, #xmlel{ name = <<"presence">> } = Incoming) -> 822: MucHost = ?MUCHOST, 823: UserJIDBin = lbin(escalus_client:short_jid(User)), 824: [RoomBareJIDBin, UserJIDBin] = binary:split(exml_query:attr(Incoming, <<"from">>), <<"/">>), 825: [_, MucHost] = binary:split(RoomBareJIDBin, <<"@">>), 826: X = exml_query:subelement(Incoming, <<"x">>), 827: HasDestroy = exml_query:subelement(X, <<"destroy">>) =/= undefined, 828: {ProperAff, ProperRole} 829: = case {UserAff, exml_query:attr(Incoming, <<"type">>)} of 830: {none, _} -> 831: <<"unavailable">> = exml_query:attr(Incoming, <<"type">>), 832: case HasDestroy of 833: false -> 834: <<"321">> = exml_query:path(X, [{element, <<"status">>}, {attr, <<"code">>}]); 835: true -> 836: ok 837: end, 838: {<<"none">>, <<"none">>}; 839: {_, Type} when Type =/= <<"unavailable">> -> 840: case UserAff of 841: member -> {<<"member">>, <<"participant">>}; 842: owner -> {<<"owner">>, <<"moderator">>} 843: end 844: end, 845: ?NS_MUC_USER = exml_query:attr(X, <<"xmlns">>), 846: [Item] = exml_query:subelements(X, <<"item">>), 847: ProperAff = exml_query:attr(Item, <<"affiliation">>), 848: ProperRole = exml_query:attr(Item, <<"role">>), 849: case exml_query:subelements(X, <<"status">>) of 850: [_, _] -> % room create request 851: [<<"110">>, <<"201">>] 852: = lists:sort(exml_query:paths(X, [{element, <<"status">>}, {attr, <<"code">>}])); 853: _ -> 854: case HasDestroy of 855: false -> UserJIDBin = exml_query:attr(Item, <<"jid">>); 856: true -> ok 857: end 858: end, 859: true. 860: 861: %%-------------------------------------------------------------------- 862: %% Other helpers 863: %%-------------------------------------------------------------------- 864: 865: -spec room_bin_jid(Room :: binary()) -> binary(). 866: room_bin_jid(Room) -> 867: <<Room/binary, $@, (muc_helper:muc_host())/binary>>.