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