1: -module(domain_isolation_SUITE).
    2: 
    3: -include_lib("common_test/include/ct.hrl").
    4: -include_lib("eunit/include/eunit.hrl").
    5: 
    6: -compile([export_all, nowarn_export_all]).
    7: -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4, subhost_pattern/1]).
    8: -import(domain_helper, [host_type/0, secondary_host_type/0]).
    9: -import(config_parser_helper, [mod_config/2]).
   10: 
   11: suite() ->
   12:     require_rpc_nodes([mim]).
   13: 
   14: all() ->
   15:     [{group, two_domains}].
   16: 
   17: groups() ->
   18:     [{two_domains, [parallel], cases()}].
   19: 
   20: cases() ->
   21:     [routing_one2one_message_inside_one_domain_works,
   22:      routing_one2one_message_to_another_domain_gets_dropped,
   23:      routing_one2one_message_to_another_domain_results_in_service_unavailable,
   24:      routing_to_yours_subdomain_gets_passed_to_muc_module,
   25:      routing_to_foreign_subdomain_results_in_service_unavailable].
   26: 
   27: host_types() ->
   28:     %% This suite tests domain isolation.
   29:     %% But two domains could be on the same host type, and still should be isolated.
   30:     %% So, we could need to init modules only once.
   31:     lists:usort([host_type(), secondary_host_type()]).
   32: 
   33: %%--------------------------------------------------------------------
   34: %% Init & teardown
   35: %%--------------------------------------------------------------------
   36: 
   37: init_per_suite(Config) ->
   38:     escalus:init_per_suite(Config).
   39: 
   40: end_per_suite(Config) ->
   41:     escalus:end_per_suite(Config).
   42: 
   43: modules() ->
   44:     MucHost = subhost_pattern(muc_helper:muc_host_pattern()),
   45:     Backend = mongoose_helper:mnesia_or_rdbms_backend(),
   46:     [{mod_domain_isolation, []},
   47:      {mod_muc_light, mod_config(mod_muc_light, #{host => MucHost, backend => Backend})}].
   48: 
   49: init_per_group(two_domains, Config) ->
   50:     Config2 = dynamic_modules:save_modules(host_types(), Config),
   51:     [dynamic_modules:ensure_modules(HostType, modules()) || HostType <- host_types()],
   52:     Config2.
   53: 
   54: end_per_group(two_domains, Config) ->
   55:     escalus_fresh:clean(),
   56:     dynamic_modules:restore_modules(Config),
   57:     Config.
   58: 
   59: init_per_testcase(Testcase, Config) ->
   60:     escalus:init_per_testcase(Testcase, Config).
   61: 
   62: end_per_testcase(Testcase, Config) ->
   63:     escalus:end_per_testcase(Testcase, Config).
   64: 
   65: %%--------------------------------------------------------------------
   66: %% Tests
   67: %%--------------------------------------------------------------------
   68: 
   69: routing_one2one_message_inside_one_domain_works(Config) ->
   70:     F = fun(Alice, Bob) ->
   71:           %% WHEN Routed inside the same domain
   72:           escalus_client:send(Bob, escalus_stanza:chat_to(Alice, <<"Hello">>)),
   73:           %% THEN Message gets delivered
   74:           Stanza = escalus:wait_for_stanza(Alice),
   75:           escalus:assert(is_chat_message, [<<"Hello">>], Stanza)
   76:         end,
   77:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], F).
   78: 
   79: routing_one2one_message_to_another_domain_gets_dropped(Config) ->
   80:     F = fun(Alice, Bob, Bis) ->
   81:           %% GIVEN Alice and Bis are on different domains
   82:           %% WHEN A stanza is sent to another domain
   83:           escalus_client:send(Bis, escalus_stanza:chat_to(Alice, <<"Hello">>)),
   84:           %% THEN Receiver does not receive a message
   85:           verify_alice_has_no_pending_messages(Alice, Bob)
   86:         end,
   87:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {alice_bis, 1}], F).
   88: 
   89: routing_one2one_message_to_another_domain_results_in_service_unavailable(Config) ->
   90:     F = fun(Alice, Bis) ->
   91:           %% GIVEN Alice and Bis are on different domains
   92:           %% WHEN A stanza is sent to another domain
   93:           escalus_client:send(Bis, escalus_stanza:chat_to(Alice, <<"Hello">>)),
   94:           %% THEN Sender receives an error
   95:           receives_service_unavailable(Bis)
   96:         end,
   97:     escalus:fresh_story(Config, [{alice, 1}, {alice_bis, 1}], F).
   98: 
   99: routing_to_yours_subdomain_gets_passed_to_muc_module(Config) ->
  100:     F = fun(Alice) ->
  101:           %% GIVEN Alice is on the same domain
  102:           %% WHEN Alice routes a stanza
  103:           escalus_client:send(Alice, invalid_muc_stanza()),
  104:           %% THEN Alice receives an error from mod_muc,
  105:           %%      like if there is no mod_domain_isolation.
  106:           receives_muc_bad_request(Alice)
  107:         end,
  108:     escalus:fresh_story(Config, [{alice, 1}], F).
  109: 
  110: routing_to_foreign_subdomain_results_in_service_unavailable(Config) ->
  111:     F = fun(Alice) ->
  112:           %% GIVEN Alice is on another domain
  113:           %% WHEN Alice routes a stanza
  114:           escalus_client:send(Alice, invalid_muc_stanza()),
  115:           %% THEN Sender receives an error about the drop
  116:           receives_service_unavailable(Alice)
  117:         end,
  118:     escalus:fresh_story(Config, [{alice_bis, 1}], F).
  119: 
  120: %%--------------------------------------------------------------------
  121: %% Helpers
  122: %%--------------------------------------------------------------------
  123: 
  124: get_error_text(Err) ->
  125:     exml_query:path(Err, [{element, <<"error">>}, {element, <<"text">>}, cdata]).
  126: 
  127: invalid_muc_address() ->
  128:     MucHost = muc_helper:muc_host(),
  129:     <<MucHost/binary, "/wow_resource_not_so_empty">>.
  130: 
  131: invalid_muc_stanza() ->
  132:     escalus_stanza:chat_to(invalid_muc_address(), <<"Hi muc!">>).
  133: 
  134: receives_service_unavailable(Alice) ->
  135:     Err = escalus:wait_for_stanza(Alice),
  136:     escalus:assert(is_error, [<<"cancel">>, <<"service-unavailable">>], Err),
  137:     <<"Filtered by the domain isolation">> = get_error_text(Err).
  138: 
  139: receives_muc_bad_request(Alice) ->
  140:     Err = escalus:wait_for_stanza(Alice),
  141:     escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], Err),
  142:     %% This error is generated by mod_muc:
  143:     <<"Resource expected to be empty">> = get_error_text(Err).
  144: 
  145: %% Verify than there is no unreceived messages by Alice by routing a message from Bob.
  146: %% Bob should be able to send messages to Alice.
  147: %% If the Bob's message gets received - there is no pending messages.
  148: verify_alice_has_no_pending_messages(Alice, Bob) ->
  149:     escalus_client:send(Bob, escalus_stanza:chat_to(Alice, <<"Forces to flush">>)),
  150:     Stanza = escalus:wait_for_stanza(Alice),
  151:     escalus:assert(is_chat_message, [<<"Forces to flush">>], Stanza).