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