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