1: -module(mim_c2s_SUITE).
    2: 
    3: -compile([export_all, nowarn_export_all]).
    4: 
    5: -include_lib("common_test/include/ct.hrl").
    6: -include_lib("eunit/include/eunit.hrl").
    7: -include_lib("exml/include/exml_stream.hrl").
    8: -include_lib("escalus/include/escalus.hrl").
    9: -include_lib("escalus/include/escalus_xmlns.hrl").
   10: -define(BAD_RESOURCE, <<"\x{EFBB}"/utf8>>).
   11: -define(MAX_STANZA_SIZE, 1024).
   12: 
   13: -import(distributed_helper, [mim/0]).
   14: 
   15: %%--------------------------------------------------------------------
   16: %% Suite configuration
   17: %%--------------------------------------------------------------------
   18: 
   19: all() ->
   20:     [
   21:      {group, basic},
   22:      {group, backwards_compatible_session}
   23:     ].
   24: 
   25: groups() ->
   26:     [
   27:      {basic, [parallel],
   28:       [
   29:        client_sets_stream_from_server_answers_with_to,
   30:        stream_from_does_not_match_sasl_jid_results_in_stream_error,
   31:        two_users_can_log_and_chat,
   32:        too_big_stanza_is_rejected,
   33:        too_big_opening_tag_is_rejected,
   34:        message_sent_to_malformed_jid_results_in_error,
   35:        verify_session_establishment_is_not_announced,
   36:        invalid_resource_fails_to_log
   37:       ]},
   38:      {backwards_compatible_session, [parallel],
   39:       [
   40:        verify_session_establishment_is_announced
   41:       ]}
   42:     ].
   43: 
   44: %%--------------------------------------------------------------------
   45: %% Init & teardown
   46: %%--------------------------------------------------------------------
   47: init_per_suite(Config) ->
   48:     HostType = domain_helper:host_type(),
   49:     Config1 = dynamic_modules:save_modules(HostType, Config),
   50:     dynamic_modules:ensure_stopped(HostType, [mod_presence]),
   51:     EscalusOverrides = [{initial_activity, fun(_) -> ok end},
   52:                         {start_ready_clients, fun ?MODULE:escalus_start/2}],
   53:     escalus:init_per_suite([{escalus_overrides, EscalusOverrides} | Config1 ]).
   54: 
   55: end_per_suite(Config) ->
   56:     dynamic_modules:restore_modules(Config),
   57:     mongoose_helper:restore_config(Config),
   58:     escalus:end_per_suite(Config).
   59: 
   60: init_per_group(basic, Config) ->
   61:     Steps = [start_stream, stream_features, maybe_use_ssl, authenticate, bind],
   62:     Config1 = save_c2s_listener(Config),
   63:     Config2 = escalus_users:update_userspec(Config1, alice, connection_steps, Steps),
   64:     Config3 = escalus_users:update_userspec(Config2, bob, connection_steps, Steps),
   65:     configure_c2s_listener(Config3, #{backwards_compatible_session => false,
   66:                                       max_stanza_size => ?MAX_STANZA_SIZE}),
   67:     Config3;
   68: init_per_group(backwards_compatible_session, Config) ->
   69:     Config.
   70: 
   71: end_per_group(basic, Config) ->
   72:     escalus_fresh:clean(),
   73:     restore_c2s_listener(Config),
   74:     Config;
   75: end_per_group(backwards_compatible_session, Config) ->
   76:     escalus_fresh:clean(),
   77:     Config.
   78: 
   79: init_per_testcase(Name, Config) ->
   80:     escalus:init_per_testcase(Name, Config).
   81: 
   82: end_per_testcase(Name, Config) ->
   83:     escalus:end_per_testcase(Name, Config).
   84: 
   85: %%--------------------------------------------------------------------
   86: %% tests
   87: %%--------------------------------------------------------------------
   88: client_sets_stream_from_server_answers_with_to(Config) ->
   89:     AliceSpec = escalus_fresh:create_fresh_user(Config, alice),
   90:     Alice = escalus_connection:connect(AliceSpec),
   91:     escalus_client:send(Alice, stream_start(Alice)),
   92:     [StreamStartAnswer, _StreamFeatures] = escalus_client:wait_for_stanzas(Alice, 2, 500),
   93:     #xmlstreamstart{name = <<"stream:stream">>, attrs = Attrs} = StreamStartAnswer,
   94:     FromClient = jid:from_binary(escalus_utils:get_jid(Alice)),
   95:     {_, FromServerBin} = lists:keyfind(<<"to">>, 1, Attrs),
   96:     FromServer = jid:from_binary(FromServerBin),
   97:     ?assert(jid:are_equal(FromClient, FromServer)),
   98:     escalus_connection:stop(Alice).
   99: 
  100: stream_from_does_not_match_sasl_jid_results_in_stream_error(Config) ->
  101:     AliceSpec = escalus_fresh:create_fresh_user(Config, alice),
  102:     Alice = escalus_connection:connect(AliceSpec),
  103:     Server = escalus_utils:get_server(Alice),
  104:     escalus_client:send(Alice, stream_start(Server, <<"not_alice@", Server/binary>>)),
  105:     [_StreamStartAnswer, _StreamFeatures] = escalus_client:wait_for_stanzas(Alice, 2, 500),
  106:     try escalus_auth:auth_plain(Alice, AliceSpec) of
  107:         _ -> error(authentication_with_inconsistent_jid_succeeded)
  108:     catch
  109:         throw:{auth_failed, _User, AuthReply} ->
  110:             escalus:assert(is_stream_error, [<<"invalid-from">>, <<>>], AuthReply),
  111:             escalus:assert(is_stream_end, escalus_client:wait_for_stanza(Alice)),
  112:             true = escalus_connection:wait_for_close(Alice, timer:seconds(1))
  113:     end.
  114: 
  115: two_users_can_log_and_chat(Config) ->
  116:     AliceHost = escalus_users:get_server(Config, alice),
  117:     HostType = domain_helper:domain_to_host_type(mim(), AliceHost),
  118:     HostTypePrefix = domain_helper:make_metrics_prefix(HostType),
  119:     MongooseMetrics = [{[global, data, xmpp, received, xml_stanza_size], changed},
  120:                        {[global, data, xmpp, sent, xml_stanza_size], changed},
  121:                        {[global, data, xmpp, received, c2s, tcp], changed},
  122:                        {[global, data, xmpp, sent, c2s, tcp], changed},
  123:                        {[HostTypePrefix, data, xmpp, c2s, message, processing_time], changed},
  124:                        {[global, data, xmpp, received, c2s, tls], 0},
  125:                        {[global, data, xmpp, sent, c2s, tls], 0}],
  126:     escalus:fresh_story([{mongoose_metrics, MongooseMetrics} | Config],
  127:                         [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  128:         escalus_client:send(Alice, escalus_stanza:chat_to(Bob, <<"Hi!">>)),
  129:         escalus:assert(is_chat_message, [<<"Hi!">>], escalus_client:wait_for_stanza(Bob)),
  130:         escalus_client:send(Bob, escalus_stanza:chat_to(Alice, <<"Hi!">>)),
  131:         escalus:assert(is_chat_message, [<<"Hi!">>], escalus_client:wait_for_stanza(Alice))
  132:     end).
  133: 
  134: too_big_stanza_is_rejected(Config) ->
  135:     AliceSpec = escalus_fresh:create_fresh_user(Config, alice),
  136:     {ok, Alice, _Features} = escalus_connection:start(AliceSpec),
  137:     BigBody = base16:encode(crypto:strong_rand_bytes(?MAX_STANZA_SIZE)),
  138:     escalus_client:send(Alice, escalus_stanza:chat_to(Alice, BigBody)),
  139:     escalus:assert(is_stream_error, [<<"policy-violation">>, <<>>], escalus_client:wait_for_stanza(Alice)),
  140:     escalus:assert(is_stream_end, escalus_client:wait_for_stanza(Alice)),
  141:     true = escalus_connection:wait_for_close(Alice, timer:seconds(1)).
  142: 
  143: too_big_opening_tag_is_rejected(Config) ->
  144:     AliceSpec = escalus_fresh:create_fresh_user(Config, alice),
  145:     {ok, Alice, _Features} = escalus_connection:start(AliceSpec, []),
  146:     BigAttrs = [{<<"bigattr">>,  base16:encode(crypto:strong_rand_bytes(?MAX_STANZA_SIZE))}],
  147:     escalus_client:send(Alice, #xmlel{name = <<"stream:stream">>, attrs = BigAttrs}),
  148:     escalus:assert(is_stream_start, escalus_client:wait_for_stanza(Alice)),
  149:     escalus:assert(is_stream_error, [<<"xml-not-well-formed">>, <<>>],
  150:                    escalus_client:wait_for_stanza(Alice)),
  151:     escalus:assert(is_stream_end, escalus_client:wait_for_stanza(Alice)),
  152:     true = escalus_connection:wait_for_close(Alice, timer:seconds(1)).
  153: 
  154: message_sent_to_malformed_jid_results_in_error(Config) ->
  155:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  156:         % Alice sends message with malformed "to"
  157:         Stanza = escalus_client:send_and_wait(Alice,
  158:                                               escalus_stanza:chat_to(<<"@invalid">>, <<"Hi!">>)),
  159:         % Alice receives error
  160:         escalus_assert:is_error(Stanza, <<"modify">>, <<"jid-malformed">>),
  161:         % Alice resends message with proper "to"
  162:         escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"Hi!">>)),
  163:         % Bob gets the message
  164:         escalus_assert:is_chat_message(<<"Hi!">>, escalus_client:wait_for_stanza(Bob))
  165:     end).
  166: 
  167: invalid_resource_fails_to_log(Config) ->
  168:     AliceSpec = escalus_fresh:create_fresh_user(Config, alice),
  169:     Steps = [start_stream, stream_features, authenticate],
  170:     {ok, Alice, _Features} = escalus_connection:start(AliceSpec, Steps),
  171:     BindStanza = escalus_stanza:bind(?BAD_RESOURCE),
  172:     escalus_connection:send(Alice, BindStanza),
  173:     Response = escalus_client:wait_for_stanza(Alice),
  174:     escalus_assert:is_error(Response, <<"modify">>, <<"bad-request">>),
  175:     escalus_connection:stop(Alice).
  176: 
  177: verify_session_establishment_is_not_announced(Config) ->
  178:     MaybeSessionFeature = start_connection_maybe_get_session_feature(Config),
  179:     ?assertEqual(undefined, MaybeSessionFeature).
  180: 
  181: verify_session_establishment_is_announced(Config) ->
  182:     MaybeSessionFeature = start_connection_maybe_get_session_feature(Config),
  183:     ?assertNotEqual(undefined, MaybeSessionFeature).
  184: 
  185: start_connection_maybe_get_session_feature(Config) ->
  186:     Steps = [start_stream, stream_features],
  187:     AliceSpec = escalus_fresh:create_fresh_user(Config, alice),
  188:     {ok, Client = #client{props = Props}, _} = escalus_connection:start(AliceSpec, Steps),
  189:     ok = escalus_auth:auth_plain(Client, Props),
  190:     escalus_connection:reset_parser(Client),
  191:     Client1 = escalus_session:start_stream(Client),
  192:     Features = escalus_connection:get_stanza(Client1, wait_for_features),
  193:     escalus_connection:stop(Client1),
  194:     exml_query:path(Features, [{element_with_ns, <<"session">>, ?NS_SESSION}]).
  195: 
  196: %%--------------------------------------------------------------------
  197: %% helpers
  198: %%--------------------------------------------------------------------
  199: 
  200: stream_start(Client) ->
  201:     Server = escalus_utils:get_server(Client),
  202:     From = escalus_utils:get_jid(Client),
  203:     stream_start(Server, From).
  204: 
  205: stream_start(Server, From) ->
  206:     #xmlstreamstart{name = <<"stream:stream">>,
  207:                     attrs = [{<<"to">>, Server},
  208:                              {<<"from">>, From},
  209:                              {<<"version">>, <<"1.0">>},
  210:                              {<<"xml:lang">>, <<"en">>},
  211:                              {<<"xmlns">>, <<"jabber:client">>},
  212:                              {<<"xmlns:stream">>, <<"http://etherx.jabber.org/streams">>}]}.
  213: 
  214: save_c2s_listener(Config) ->
  215:     C2SPort = ct:get_config({hosts, mim, c2s_port}),
  216:     [C2SListener] = mongoose_helper:get_listeners(mim(), #{port => C2SPort, module => mongoose_c2s_listener}),
  217:     [{c2s_listener, C2SListener} | Config].
  218: 
  219: restore_c2s_listener(Config) ->
  220:     C2SListener = ?config(c2s_listener, Config),
  221:     mongoose_helper:restart_listener(mim(), C2SListener).
  222: 
  223: configure_c2s_listener(Config, ExtraC2SOpts) ->
  224:     C2SListener = ?config(c2s_listener, Config),
  225:     NewC2SListener = maps:merge(C2SListener, ExtraC2SOpts),
  226:     mongoose_helper:restart_listener(mim(), NewC2SListener).
  227: 
  228: escalus_start(Cfg, FlatCDs) ->
  229:     {_, RClients} = lists:foldl(
  230:         fun({UserSpec, BaseResource}, {N, Acc}) ->
  231:                 Resource = escalus_overridables:do(Cfg, modify_resource, [BaseResource],
  232:                                                    {escalus_utils, identity}),
  233:                 {ok, Client} = escalus_client:start(Cfg, UserSpec, Resource),
  234:                 {N+1, [Client|Acc]}
  235:         end, {1, []}, FlatCDs),
  236:     Clients = lists:reverse(RClients),
  237:     [ escalus_assert:has_no_stanzas(Client) || Client <- Clients ],
  238:     Clients.