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