1: -module(disco_and_caps_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("eunit/include/eunit.hrl").
    5: -include_lib("escalus/include/escalus_xmlns.hrl").
    6: 
    7: -import(domain_helper, [host_type/0, domain/0]).
    8: -import(config_parser_helper, [default_mod_config/1, mod_config/2]).
    9: 
   10: all() ->
   11:     [{group, disco_with_caps},
   12:      {group, disco_with_extra_features}].
   13: 
   14: groups() ->
   15:     G = [{disco_with_caps, [parallel], basic_test_cases() ++ caps_test_cases()},
   16:          {disco_with_extra_features, [parallel], basic_test_cases() ++ extra_feature_test_cases()}],
   17:     ct_helper:repeat_all_until_all_ok(G).
   18: 
   19: basic_test_cases() ->
   20:     [user_cannot_query_stranger_resources,
   21:      user_cannot_query_stranger_features,
   22:      user_can_query_friend_resources,
   23:      user_can_query_friend_features,
   24:      user_cannot_query_own_resources_with_unknown_node,
   25:      user_cannot_query_friend_resources_with_unknown_node,
   26:      user_can_query_server_features].
   27: 
   28: caps_test_cases() ->
   29:     [caps_feature_is_advertised,
   30:      user_can_query_server_caps_via_disco].
   31: 
   32: extra_feature_test_cases() ->
   33:     [user_can_query_extra_domains,
   34:      user_can_query_server_info].
   35: 
   36: init_per_suite(C) ->
   37:     C.
   38: 
   39: end_per_suite(C) ->
   40:     escalus_fresh:clean(),
   41:     escalus:end_per_suite(C).
   42: 
   43: init_per_group(Name, C) ->
   44:     C2 = escalus:init_per_suite(dynamic_modules:save_modules(host_type(), C)),
   45:     dynamic_modules:ensure_modules(host_type(), required_modules(Name)),
   46:     C2.
   47: 
   48: end_per_group(_Name, C) ->
   49:     dynamic_modules:restore_modules(C).
   50: 
   51: init_per_testcase(Name, C) ->
   52:     escalus:init_per_testcase(Name, C).
   53: 
   54: end_per_testcase(Name, C) ->
   55:     escalus:end_per_testcase(Name, C).
   56: 
   57: caps_feature_is_advertised(Config) ->
   58:     Spec = escalus_users:get_userspec(Config, alice),
   59:     {ok, Connection, Features} = escalus_connection:start(Spec, [start_stream, stream_features]),
   60:     true = is_map(proplists:get_value(caps, Features)),
   61:     escalus_connection:stop(Connection).
   62: 
   63: user_can_query_server_caps_via_disco(Config) ->
   64:     NewConfig = escalus_fresh:create_users(Config, [{alice, 1}]),
   65:     Spec = escalus_users:get_userspec(NewConfig, alice),
   66:     {ok, Alice, Features} = escalus_connection:start(Spec),
   67:     #{<<"node">> := Node,
   68:       <<"ver">> := Ver} = proplists:get_value(caps, Features),
   69:     NodeVer = <<Node/binary, $#, Ver/binary>>,
   70:     Server = proplists:get_value(server, Spec),
   71:     Disco = escalus_stanza:disco_info(Server, NodeVer),
   72:     escalus:send(Alice, Disco),
   73:     DiscoResp = escalus:wait_for_stanza(Alice),
   74:     escalus:assert(is_iq_result, [Disco], DiscoResp),
   75:     Identity  = exml_query:path(DiscoResp, [{element, <<"query">>},
   76:                                             {element, <<"identity">>},
   77:                                             {attr, <<"name">>}]),
   78:     <<"MongooseIM">> = Identity,
   79:     escalus_connection:stop(Alice).
   80: 
   81: user_cannot_query_stranger_resources(Config) ->
   82:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
   83:         BobJid = escalus_client:short_jid(Bob),
   84:         Request = escalus_stanza:disco_items(BobJid),
   85:         escalus:send(Alice, Request),
   86:         Stanza = escalus:wait_for_stanza(Alice),
   87:         escalus:assert(is_iq_error, [Request], Stanza),
   88:         escalus:assert(is_error, [<<"cancel">>, <<"service-unavailable">>], Stanza),
   89:         escalus:assert(is_stanza_from, [BobJid], Stanza)
   90:     end).
   91: 
   92: user_cannot_query_stranger_features(Config) ->
   93:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
   94:         BobJid = escalus_client:short_jid(Bob),
   95:         Request = escalus_stanza:disco_info(BobJid),
   96:         escalus:send(Alice, Request),
   97:         Stanza = escalus:wait_for_stanza(Alice),
   98:         escalus:assert(is_iq_error, [Request], Stanza),
   99:         escalus:assert(is_error, [<<"cancel">>, <<"service-unavailable">>], Stanza),
  100:         escalus:assert(is_stanza_from, [BobJid], Stanza)
  101:     end).
  102: 
  103: user_can_query_friend_resources(Config) ->
  104:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  105:         escalus_story:make_all_clients_friends([Alice, Bob]),
  106:         BobJid = escalus_client:short_jid(Bob),
  107:         escalus:send(Alice, escalus_stanza:disco_items(BobJid)),
  108:         Stanza = escalus:wait_for_stanza(Alice),
  109:         Query = exml_query:subelement(Stanza, <<"query">>),
  110:         BobFullJid = escalus_client:full_jid(Bob),
  111:         BobName = escalus_client:username(Bob),
  112:         Item = exml_query:subelement_with_attr(Query, <<"jid">>, BobFullJid),
  113:         ?assertEqual(BobName, exml_query:attr(Item, <<"name">>)),
  114:         escalus:assert(is_stanza_from, [BobJid], Stanza)
  115:     end).
  116: 
  117: user_can_query_friend_features(Config) ->
  118:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  119:         escalus_story:make_all_clients_friends([Alice, Bob]),
  120:         BobJid = escalus_client:short_jid(Bob),
  121:         escalus:send(Alice, escalus_stanza:disco_info(BobJid)),
  122:         Stanza = escalus:wait_for_stanza(Alice),
  123:         escalus:assert(has_identity, [<<"account">>, <<"registered">>], Stanza),
  124:         escalus:assert(is_stanza_from, [BobJid], Stanza)
  125:     end).
  126: 
  127: user_cannot_query_own_resources_with_unknown_node(Config) ->
  128:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  129:         AliceJid = escalus_client:short_jid(Alice),
  130:         Request = escalus_stanza:disco_items(AliceJid, <<"unknown-node">>),
  131:         escalus:send(Alice, Request),
  132:         Stanza = escalus:wait_for_stanza(Alice),
  133:         escalus:assert(is_iq_error, [Request], Stanza),
  134:         escalus:assert(is_error, [<<"cancel">>, <<"item-not-found">>], Stanza),
  135:         escalus:assert(is_stanza_from, [AliceJid], Stanza)
  136:     end).
  137: 
  138: user_cannot_query_friend_resources_with_unknown_node(Config) ->
  139:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  140:         escalus_story:make_all_clients_friends([Alice, Bob]),
  141:         BobJid = escalus_client:short_jid(Bob),
  142:         Request = escalus_stanza:disco_items(BobJid, <<"unknown-node">>),
  143:         escalus:send(Alice, Request),
  144:         Stanza = escalus:wait_for_stanza(Alice),
  145:         escalus:assert(is_iq_error, [Request], Stanza),
  146:         escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>], Stanza),
  147:         escalus:assert(is_stanza_from, [BobJid], Stanza)
  148:     end).
  149: 
  150: user_can_query_extra_domains(Config) ->
  151:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  152:         Server = escalus_client:server(Alice),
  153:         escalus:send(Alice, escalus_stanza:service_discovery(Server)),
  154:         Stanza = escalus:wait_for_stanza(Alice),
  155:         escalus:assert(has_service, [extra_domain()], Stanza),
  156:         escalus:assert(is_stanza_from, [domain()], Stanza)
  157:     end).
  158: 
  159: user_can_query_server_features(Config) ->
  160:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  161:         Server = escalus_client:server(Alice),
  162:         escalus:send(Alice, escalus_stanza:disco_info(Server)),
  163:         Stanza = escalus:wait_for_stanza(Alice),
  164:         escalus:assert(has_identity, [<<"server">>, <<"im">>], Stanza),
  165:         escalus:assert(has_feature, [<<"iq">>], Stanza),
  166:         escalus:assert(has_feature, [<<"presence">>], Stanza),
  167:         escalus:assert(has_feature, [<<"presence-invisible">>], Stanza),
  168:         escalus:assert(is_stanza_from, [domain()], Stanza)
  169:     end).
  170: 
  171: %% XEP-0157: Contact Addresses for XMPP Services
  172: user_can_query_server_info(Config) ->
  173:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  174:         Server = escalus_client:server(Alice),
  175:         escalus:send(Alice, escalus_stanza:disco_info(Server)),
  176:         Stanza = escalus:wait_for_stanza(Alice),
  177:         escalus:assert(is_stanza_from, [domain()], Stanza),
  178: 
  179:         %% 'sales' is hidden for mod_disco, so only 'abuse' and 'admin' are expected
  180:         [HiddenField, AbuseField, AdminField] = get_form_fields(Stanza),
  181:         ?assertEqual(<<"FORM_TYPE">>, exml_query:attr(HiddenField, <<"var">>)),
  182:         ?assertEqual(<<"hidden">>, exml_query:attr(HiddenField, <<"type">>)),
  183:         ?assertEqual([?NS_SERVERINFO], exml_query:paths(HiddenField, [{element, <<"value">>},
  184:                                                                       cdata])),
  185:         ?assertEqual(name(abuse), exml_query:attr(AbuseField, <<"var">>)),
  186:         ?assertEqual(urls(abuse), exml_query:paths(AbuseField, [{element, <<"value">>},
  187:                                                                               cdata])),
  188:         ?assertEqual(name(admin), exml_query:attr(AdminField, <<"var">>)),
  189:         ?assertEqual(urls(admin), exml_query:paths(AdminField, [{element, <<"value">>},
  190:                                                                 cdata]))
  191:     end).
  192: 
  193: %% Helpers
  194: 
  195: required_modules(disco_with_caps) ->
  196:     [{mod_caps, config_parser_helper:default_mod_config(mod_caps)},
  197:      {mod_disco, default_mod_config(mod_disco)}];
  198: required_modules(disco_with_extra_features) ->
  199:     [{mod_disco, mod_config(mod_disco, extra_disco_opts())}].
  200: 
  201: extra_disco_opts() ->
  202:     #{extra_domains => [extra_domain()],
  203:       server_info => [server_info(abuse, #{}),
  204:                       server_info(admin, #{modules => [mod_disco]}),
  205:                       server_info(sales, #{modules => [mod_pubsub]})]}.
  206: 
  207: get_form_fields(Stanza) ->
  208:      exml_query:paths(Stanza, [{element_with_ns, <<"query">>, ?NS_DISCO_INFO},
  209:                                {element_with_ns, <<"x">>, ?NS_DATA_FORMS},
  210:                                {element, <<"field">>}]).
  211: 
  212: extra_domain() ->
  213:     <<"eXtra.example.com">>.
  214: 
  215: server_info(Type, Extra) ->
  216:     maps:merge(#{name => name(Type), urls => urls(Type)}, Extra).
  217: 
  218: name(abuse) -> <<"abuse-addresses">>;
  219: name(admin) -> <<"admin-addresses">>;
  220: name(sales) -> <<"sales-addresses">>.
  221: 
  222: urls(abuse) -> [<<"abuse@example.com">>];
  223: urls(admin) -> [<<"admin@example.com">>, <<"operations@example.com">>];
  224: urls(sales) -> [<<"sales@example.com">>].