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