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