1: -module(bind2_SUITE).
    2: 
    3: -compile([export_all, nowarn_export_all]).
    4: 
    5: -include_lib("stdlib/include/assert.hrl").
    6: -include_lib("exml/include/exml.hrl").
    7: -include_lib("escalus/include/escalus.hrl").
    8: -include_lib("escalus/include/escalus_xmlns.hrl").
    9: 
   10: -define(NS_CSI, <<"urn:xmpp:csi:0">>).
   11: -define(NS_SASL_2, <<"urn:xmpp:sasl:2">>).
   12: -define(NS_BIND_2, <<"urn:xmpp:bind:0">>).
   13: -define(BAD_TAG, <<"\x{EFBB}"/utf8>>).
   14: 
   15: %%--------------------------------------------------------------------
   16: %% Suite configuration
   17: %%--------------------------------------------------------------------
   18: 
   19: all() ->
   20:     [
   21:      {group, basic}
   22:     ].
   23: 
   24: groups() ->
   25:     [
   26:      {basic, [parallel],
   27:       [
   28:        server_announces_bind2_with_features,
   29:        auth_and_bind_ignores_invalid_resource_and_generates_a_new_one,
   30:        auth_and_bind_to_random_resource,
   31:        auth_and_bind_do_not_expose_user_agent_id_in_client,
   32:        auth_and_bind_contains_client_tag,
   33:        carbons_are_enabled_with_bind_inline_request,
   34:        csi_is_active_with_bind_inline_request,
   35:        csi_is_inactive_with_bind_inline_request,
   36:        stream_resumption_enable_sm_on_bind,
   37:        stream_resumption_enable_sm_on_bind_with_resume,
   38:        stream_resumption_failing_does_bind_and_contains_sm_status,
   39:        stream_resumption_overrides_bind_request
   40:       ]}
   41:     ].
   42: 
   43: %%--------------------------------------------------------------------
   44: %% Init & teardown
   45: %%--------------------------------------------------------------------
   46: 
   47: init_per_suite(Config) ->
   48:     Config1 = load_modules(Config),
   49:     escalus:init_per_suite(Config1).
   50: 
   51: end_per_suite(Config) ->
   52:     escalus_fresh:clean(),
   53:     dynamic_modules:restore_modules(Config),
   54:     escalus:end_per_suite(Config).
   55: 
   56: init_per_group(_GroupName, Config) ->
   57:     Config.
   58: 
   59: end_per_group(_GroupName, Config) ->
   60:     Config.
   61: 
   62: init_per_testcase(Name, Config) ->
   63:     escalus:init_per_testcase(Name, Config).
   64: 
   65: end_per_testcase(Name, Config) ->
   66:     escalus:end_per_testcase(Name, Config).
   67: 
   68: load_modules(Config) ->
   69:     HostType = domain_helper:host_type(),
   70:     Config1 = dynamic_modules:save_modules(HostType, Config),
   71:     sasl2_helper:load_all_sasl2_modules(HostType),
   72:     Config1.
   73: 
   74: %%--------------------------------------------------------------------
   75: %% tests
   76: %%--------------------------------------------------------------------
   77: 
   78: server_announces_bind2_with_features(Config) ->
   79:     Steps = [create_connect_tls, start_stream_get_features],
   80:     #{features := Features} = sasl2_helper:apply_steps(Steps, Config),
   81:     Bind2 = exml_query:path(Features, [{element_with_ns, <<"authentication">>, ?NS_SASL_2},
   82:                                        {element, <<"inline">>},
   83:                                        {element_with_ns, <<"bind">>, ?NS_BIND_2}]),
   84:     ?assertNotEqual(undefined, Bind2),
   85:     InlineFeatures = exml_query:path(Bind2, [{element, <<"inline">>}]),
   86:     check_var(InlineFeatures, ?NS_STREAM_MGNT_3),
   87:     check_var(InlineFeatures, ?NS_CARBONS_2),
   88:     check_var(InlineFeatures, ?NS_CSI).
   89: 
   90: auth_and_bind_ignores_invalid_resource_and_generates_a_new_one(Config) ->
   91:     Steps = [start_new_user, {?MODULE, auth_and_bind_wrong_resource}, receive_features, has_no_more_stanzas],
   92:     #{answer := Response} = sasl2_helper:apply_steps(Steps, Config),
   93:     Success = exml_query:path(Response, [{element_with_ns, <<"bound">>, ?NS_BIND_2}]),
   94:     ?assertNotEqual(undefined, Success).
   95: 
   96: auth_and_bind_to_random_resource(Config) ->
   97:     Steps = [start_new_user, {?MODULE, auth_and_bind}, receive_features, has_no_more_stanzas],
   98:     #{answer := Success} = sasl2_helper:apply_steps(Steps, Config),
   99:     ?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
  100:     Bound = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2}]),
  101:     ?assertNotEqual(undefined, Bound),
  102:     Identifier = exml_query:path(Success, [{element, <<"authorization-identifier">>}, cdata]),
  103:     #jid{resource = LResource} = jid:from_binary(Identifier),
  104:     ?assert(0 =< byte_size(LResource), LResource).
  105: 
  106: auth_and_bind_do_not_expose_user_agent_id_in_client(Config) ->
  107:     Steps = [start_new_user, {?MODULE, auth_and_bind_with_user_agent_uuid}, receive_features, has_no_more_stanzas],
  108:     #{answer := Success, uuid := Uuid} = sasl2_helper:apply_steps(Steps, Config),
  109:     ?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
  110:     Bound = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2}]),
  111:     ?assertNotEqual(undefined, Bound),
  112:     Identifier = exml_query:path(Success, [{element, <<"authorization-identifier">>}, cdata]),
  113:     #jid{resource = LResource} = jid:from_binary(Identifier),
  114:     ?assertNotEqual(Uuid, LResource).
  115: 
  116: auth_and_bind_contains_client_tag(Config) ->
  117:     Steps = [start_new_user, {?MODULE, auth_and_bind_with_tag}, receive_features, has_no_more_stanzas],
  118:     #{answer := Success, tag := Tag} = sasl2_helper:apply_steps(Steps, Config),
  119:     ?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
  120:     Bound = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2}]),
  121:     ?assertNotEqual(undefined, Bound),
  122:     Identifier = exml_query:path(Success, [{element, <<"authorization-identifier">>}, cdata]),
  123:     #jid{resource = LResource} = jid:from_binary(Identifier),
  124:     ResourceParts = binary:split(LResource, <<"/">>, [global]),
  125:     ?assertMatch([Tag, _], ResourceParts).
  126: 
  127: carbons_are_enabled_with_bind_inline_request(Config) ->
  128:     Steps = [start_new_user,
  129:              {?MODULE, start_peer},
  130:              {?MODULE, auth_and_bind_with_carbon_copies}, receive_features,
  131:              {?MODULE, receive_message_carbon_arrives}, has_no_more_stanzas],
  132:     sasl2_helper:apply_steps(Steps, Config).
  133: 
  134: csi_is_active_with_bind_inline_request(Config) ->
  135:     Steps = [start_new_user,
  136:              {?MODULE, start_peer},
  137:              {?MODULE, auth_and_bind_with_csi_active}, receive_features,
  138:              {?MODULE, inactive_csi_msg_wont_arrive}, has_no_more_stanzas],
  139:     sasl2_helper:apply_steps(Steps, Config).
  140: 
  141: csi_is_inactive_with_bind_inline_request(Config) ->
  142:     Steps = [start_new_user,
  143:              {?MODULE, start_peer},
  144:              {?MODULE, auth_and_bind_with_csi_inactive}, has_no_more_stanzas,
  145:              {?MODULE, inactive_csi_msgs_do_not_arrive},
  146:              {?MODULE, activate_csi}, receive_features,
  147:              {?MODULE, receive_csi_msgs}, has_no_more_stanzas],
  148:     sasl2_helper:apply_steps(Steps, Config).
  149: 
  150: stream_resumption_enable_sm_on_bind(Config) ->
  151:     Steps = [start_new_user,
  152:              {?MODULE, start_peer},
  153:              {?MODULE, auth_and_bind_with_sm_enabled},
  154:              receive_features, has_no_more_stanzas],
  155:     #{answer := Success} = sasl2_helper:apply_steps(Steps, Config),
  156:     ?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
  157:     Enabled = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2},
  158:                                         {element_with_ns, <<"enabled">>, ?NS_STREAM_MGNT_3}]),
  159:     ?assertNotEqual(undefined, Enabled).
  160: 
  161: stream_resumption_enable_sm_on_bind_with_resume(Config) ->
  162:     Steps = [start_new_user,
  163:              {?MODULE, start_peer},
  164:              {?MODULE, auth_and_bind_with_sm_resume_enabled},
  165:              receive_features, has_no_more_stanzas],
  166:     #{answer := Success} = sasl2_helper:apply_steps(Steps, Config),
  167:     ?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
  168:     Enabled = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2},
  169:                                         {element_with_ns, <<"enabled">>, ?NS_STREAM_MGNT_3}]),
  170:     ?assertNotEqual(undefined, Enabled).
  171: 
  172: stream_resumption_failing_does_bind_and_contains_sm_status(Config) ->
  173:     Steps = [create_user, buffer_messages_and_die, connect_tls, start_stream_get_features,
  174:              {?MODULE, auth_and_bind_with_resumption_unknown_smid},
  175:              receive_features, has_no_more_stanzas],
  176:     #{answer := Success, tag := Tag} = sasl2_helper:apply_steps(Steps, Config),
  177:     ?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
  178:     Bound = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2}]),
  179:     ?assertNotEqual(undefined, Bound),
  180:     Resumed = exml_query:path(Success, [{element_with_ns, <<"failed">>, ?NS_STREAM_MGNT_3}]),
  181:     escalus:assert(is_sm_failed, [<<"item-not-found">>], Resumed),
  182:     Identifier = exml_query:path(Success, [{element, <<"authorization-identifier">>}, cdata]),
  183:     #jid{resource = LResource} = jid:from_binary(Identifier),
  184:     ResourceParts = binary:split(LResource, <<"/">>, [global]),
  185:     ?assertMatch([Tag, _], ResourceParts).
  186: 
  187: stream_resumption_overrides_bind_request(Config) ->
  188:     Steps = [create_user, buffer_messages_and_die, connect_tls, start_stream_get_features,
  189:              {?MODULE, auth_and_bind_with_resumption}, has_no_more_stanzas],
  190:     #{answer := Success, smid := SMID} = sasl2_helper:apply_steps(Steps, Config),
  191:     ?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
  192:     Resumed = exml_query:path(Success, [{element_with_ns, <<"resumed">>, ?NS_STREAM_MGNT_3}]),
  193:     ?assert(escalus_pred:is_sm_resumed(SMID, Resumed)).
  194: 
  195: 
  196: %% Step helpers
  197: auth_and_bind(Config, Client, Data) ->
  198:     plain_auth(Config, Client, Data, [], []).
  199: 
  200: auth_and_bind_wrong_resource(Config, Client, Data) ->
  201:     Tag = ?BAD_TAG,
  202:     plain_auth(Config, Client, Data#{tag => Tag}, [bind_tag(Tag)], []).
  203: 
  204: auth_and_bind_with_user_agent_uuid(Config, Client, Data) ->
  205:     Uuid = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
  206:     plain_auth(Config, Client, Data#{uuid => Uuid}, [], [good_user_agent_elem(Uuid)]).
  207: 
  208: auth_and_bind_with_tag(Config, Client, Data) ->
  209:     Tag = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
  210:     plain_auth(Config, Client, Data#{tag => Tag}, [bind_tag(Tag)], []).
  211: 
  212: auth_and_bind_with_resumption_unknown_smid(Config, Client, Data) ->
  213:     Tag = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
  214:     Resume = escalus_stanza:resume(<<"123456">>, 1),
  215:     plain_auth(Config, Client, Data#{tag => Tag}, [bind_tag(Tag)], [Resume]).
  216: 
  217: auth_and_bind_with_resumption(Config, Client, #{smid := SMID, texts := Texts} = Data) ->
  218:     Tag = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
  219:     Resume = escalus_stanza:resume(SMID, 1),
  220:     {Client1, Data1} = plain_auth(Config, Client, Data#{tag => Tag}, [bind_tag(Tag)], [Resume]),
  221:     Msgs = sm_helper:wait_for_messages(Client, Texts),
  222:     {Client1, Data1#{sm_storage => Msgs}}.
  223: 
  224: auth_and_bind_with_carbon_copies(Config, Client, #{spec := Spec} = Data) ->
  225:     CarbonEnable = enable_carbons_el(),
  226:     {Client1, Data1} = plain_auth(Config, Client, Data, [CarbonEnable], []),
  227:     Resource = <<"second_resource">>,
  228:     {ok, Client2, _} = escalus_connection:start(
  229:                          [{carbons, true}, {resource, Resource} | Spec]),
  230:     Jid = <<(escalus_client:short_jid(Client2))/binary, "/", Resource/binary>>,
  231:     {Client1, Data1#{client_2 => Client2, client_2_jid => Jid}}.
  232: 
  233: auth_and_bind_with_csi_active(Config, Client, Data) ->
  234:     CsiActive = csi_helper:csi_stanza(<<"active">>),
  235:     plain_auth(Config, Client, Data, [CsiActive], []).
  236: 
  237: inactive_csi_msg_wont_arrive(_Config, Client, #{peer := Bob} = Data) ->
  238:     escalus_client:send(Bob, escalus_stanza:chat_to(Client, <<"hello 1">>)),
  239:     AliceReceived = escalus_client:wait_for_stanza(Client),
  240:     escalus:assert(is_message, AliceReceived),
  241:     csi_helper:given_client_is_inactive_and_no_messages_arrive(Client),
  242:     csi_helper:given_messages_are_sent(Client, Bob, 1),
  243:     csi_helper:then_client_does_not_receive_any_message(Client),
  244:     {Client, Data}.
  245: 
  246: auth_and_bind_with_csi_inactive(Config, Client, Data) ->
  247:     CsiInactive = csi_helper:csi_stanza(<<"inactive">>),
  248:     plain_auth(Config, Client, Data, [CsiInactive], []).
  249: 
  250: inactive_csi_msgs_do_not_arrive(_Config, Client, #{peer := Bob} = Data) ->
  251:     Msgs = csi_helper:given_messages_are_sent(Client, Bob, 2),
  252:     csi_helper:then_client_does_not_receive_any_message(Client),
  253:     {Client, Data#{msgs => Msgs}}.
  254: 
  255: activate_csi(_Config, Client, Data) ->
  256:     csi_helper:given_client_is_active(Client),
  257:     {Client, Data}.
  258: 
  259: receive_csi_msgs(_Config, Client, #{msgs := Msgs} = Data) ->
  260:     csi_helper:then_client_receives_message(Client, Msgs),
  261:     {Client, Data}.
  262: 
  263: auth_and_bind_with_sm_enabled(Config, Client, Data) ->
  264:     SmEnable = escalus_stanza:enable_sm(),
  265:     plain_auth(Config, Client, Data, [SmEnable], []).
  266: 
  267: auth_and_bind_with_sm_resume_enabled(Config, Client, Data) ->
  268:     SmEnable = escalus_stanza:enable_sm([{resume, true}]),
  269:     plain_auth(Config, Client, Data, [SmEnable], []).
  270: 
  271: receive_message_carbon_arrives(
  272:   _Config, Client1, #{client_1_jid := Jid1, client_2_jid := Jid2,
  273:                       client_2 := Client2, peer := Bob} = Data) ->
  274:     escalus_client:send(Bob, escalus_stanza:chat_to(Jid1, <<"hello 1">>)),
  275:     Answers1 = [ escalus_client:wait_for_stanza(C) || C <- [Client1, Client2]],
  276:     escalus_client:send(Bob, escalus_stanza:chat_to(Jid2, <<"hello 2">>)),
  277:     Answers2 = [ escalus_client:wait_for_stanza(C) || C <- [Client1, Client2]],
  278:     {Client1, Data#{answers_1 => Answers1, answers_2 => Answers2}}.
  279: 
  280: plain_auth(_Config, Client, Data, BindElems, Extra) ->
  281:     InitEl = sasl2_helper:plain_auth_initial_response(Client),
  282:     BindEl = #xmlel{name = <<"bind">>,
  283:                   attrs = [{<<"xmlns">>, ?NS_BIND_2}],
  284:                   children = BindElems},
  285:     Authenticate = auth_elem(<<"PLAIN">>, [InitEl, BindEl | Extra]),
  286:     escalus:send(Client, Authenticate),
  287:     Answer = escalus_client:wait_for_stanza(Client),
  288:     Identifier = exml_query:path(Answer, [{element, <<"authorization-identifier">>}, cdata]),
  289:     #jid{resource = LResource} = jid:from_binary(Identifier),
  290:     {Client, Data#{answer => Answer, client_1_jid => Identifier, bind2_resource => LResource}}.
  291: 
  292: start_peer(Config, Client, Data) ->
  293:     BobSpec = escalus_fresh:create_fresh_user(Config, bob),
  294:     {ok, Bob, _} = escalus_connection:start(BobSpec),
  295:     {Client, Data#{peer => Bob}}.
  296: 
  297: %% XML helpers
  298: auth_elem(Mech, Children) ->
  299:     #xmlel{name = <<"authenticate">>,
  300:            attrs = [{<<"xmlns">>, ?NS_SASL_2}, {<<"mechanism">>, Mech}],
  301:            children = Children}.
  302: 
  303: bind_tag(Tag) ->
  304:     #xmlel{name = <<"tag">>, children = [#xmlcdata{content = Tag}]}.
  305: 
  306: enable_carbons_el() ->
  307:     #xmlel{name = <<"enable">>,
  308:            attrs = [{<<"xmlns">>, ?NS_CARBONS_2}]}.
  309: 
  310: good_user_agent_elem(Uuid) ->
  311:     user_agent_elem(Uuid, <<"cool-xmpp-client">>, <<"latest-and-greatest-device">>).
  312: 
  313: user_agent_elem(Id, Software, Device) ->
  314:     SoftEl = [#xmlel{name = <<"software">>, children = [#xmlcdata{content = Value}]}
  315:               || Value <- [Software], Value =/= undefined ],
  316:     DeviEl = [#xmlel{name = <<"device">>, children = [#xmlcdata{content = Value}]}
  317:               || Value <- [Device], Value =/= undefined ],
  318:     Attrs = [ {<<"id">>, Value} || Value <- [Id], Value =/= undefined ],
  319:     #xmlel{name = <<"user-agent">>, attrs = Attrs, children = SoftEl ++ DeviEl}.
  320: 
  321: check_var(InlineFeatures, NS) ->
  322:     Var = exml_query:subelement_with_attr(InlineFeatures, <<"var">>, NS),
  323:     ?assertNotEqual(undefined, Var).