Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xeps/bind2 inlines #4114

Merged
merged 16 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 136 additions & 26 deletions big_tests/tests/bind2_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
-include_lib("escalus/include/escalus.hrl").
-include_lib("escalus/include/escalus_xmlns.hrl").

-define(NS_CSI, <<"urn:xmpp:csi:0">>).
-define(NS_SASL_2, <<"urn:xmpp:sasl:2">>).
-define(NS_BIND_2, <<"urn:xmpp:bind:0">>).
-define(BAD_TAG, <<"\x{EFBB}"/utf8>>).
Expand All @@ -24,12 +25,16 @@ groups() ->
[
{basic, [parallel],
[
server_announces_bind2,
server_announces_bind2_with_sm,
server_announces_bind2_with_features,
auth_and_bind_ignores_invalid_resource_and_generates_a_new_one,
auth_and_bind_to_random_resource,
auth_and_bind_do_not_expose_user_agent_id_in_client,
auth_and_bind_contains_client_tag,
carbons_are_enabled_with_bind_inline_request,
csi_is_active_with_bind_inline_request,
csi_is_inactive_with_bind_inline_request,
stream_resumption_enable_sm_on_bind,
stream_resumption_enable_sm_on_bind_with_resume,
stream_resumption_failing_does_bind_and_contains_sm_status,
stream_resumption_overrides_bind_request
]}
Expand Down Expand Up @@ -70,34 +75,26 @@ load_modules(Config) ->
%% tests
%%--------------------------------------------------------------------

server_announces_bind2(Config) ->
server_announces_bind2_with_features(Config) ->
Steps = [create_connect_tls, start_stream_get_features],
#{features := Features} = sasl2_helper:apply_steps(Steps, Config),
Bind2 = exml_query:path(Features, [{element_with_ns, <<"authentication">>, ?NS_SASL_2},
{element, <<"inline">>},
{element_with_ns, <<"bind">>, ?NS_BIND_2}]),
?assertNotEqual(undefined, Bind2).

server_announces_bind2_with_sm(Config) ->
Steps = [create_connect_tls, start_stream_get_features],
#{features := Features} = sasl2_helper:apply_steps(Steps, Config),
SM = exml_query:path(Features, [{element_with_ns, <<"authentication">>, ?NS_SASL_2},
{element, <<"inline">>},
{element_with_ns, <<"bind">>, ?NS_BIND_2},
{element, <<"inline">>},
{element_with_attr, <<"var">>, ?NS_STREAM_MGNT_3}]),
?assertNotEqual(undefined, SM).
?assertNotEqual(undefined, Bind2),
InlineFeatures = exml_query:path(Bind2, [{element, <<"inline">>}]),
check_var(InlineFeatures, ?NS_STREAM_MGNT_3),
check_var(InlineFeatures, ?NS_CARBONS_2),
check_var(InlineFeatures, ?NS_CSI).

auth_and_bind_ignores_invalid_resource_and_generates_a_new_one(Config) ->
Steps = [create_connect_tls, start_stream_get_features,
{?MODULE, auth_and_bind_wrong_resource}, has_no_more_stanzas],
Steps = [start_new_user, {?MODULE, auth_and_bind_wrong_resource}, receive_features, has_no_more_stanzas],
#{answer := Response} = sasl2_helper:apply_steps(Steps, Config),
Success = exml_query:path(Response, [{element_with_ns, <<"bound">>, ?NS_BIND_2}]),
?assertNotEqual(undefined, Success).

auth_and_bind_to_random_resource(Config) ->
Steps = [create_connect_tls, start_stream_get_features,
{?MODULE, auth_and_bind}, has_no_more_stanzas],
Steps = [start_new_user, {?MODULE, auth_and_bind}, receive_features, has_no_more_stanzas],
#{answer := Success} = sasl2_helper:apply_steps(Steps, Config),
?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
Bound = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2}]),
Expand All @@ -107,8 +104,7 @@ auth_and_bind_to_random_resource(Config) ->
?assert(0 =< byte_size(LResource), LResource).

auth_and_bind_do_not_expose_user_agent_id_in_client(Config) ->
Steps = [create_connect_tls, start_stream_get_features,
{?MODULE, auth_and_bind_with_user_agent_uuid}, has_no_more_stanzas],
Steps = [start_new_user, {?MODULE, auth_and_bind_with_user_agent_uuid}, receive_features, has_no_more_stanzas],
#{answer := Success, uuid := Uuid} = sasl2_helper:apply_steps(Steps, Config),
?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
Bound = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2}]),
Expand All @@ -118,8 +114,7 @@ auth_and_bind_do_not_expose_user_agent_id_in_client(Config) ->
?assertNotEqual(Uuid, LResource).

auth_and_bind_contains_client_tag(Config) ->
Steps = [create_connect_tls, start_stream_get_features,
{?MODULE, auth_and_bind_with_tag}, has_no_more_stanzas],
Steps = [start_new_user, {?MODULE, auth_and_bind_with_tag}, receive_features, has_no_more_stanzas],
#{answer := Success, tag := Tag} = sasl2_helper:apply_steps(Steps, Config),
?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
Bound = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2}]),
Expand All @@ -129,9 +124,55 @@ auth_and_bind_contains_client_tag(Config) ->
ResourceParts = binary:split(LResource, <<"/">>, [global]),
?assertMatch([Tag, _], ResourceParts).

carbons_are_enabled_with_bind_inline_request(Config) ->
Steps = [start_new_user,
{?MODULE, start_peer},
{?MODULE, auth_and_bind_with_carbon_copies}, receive_features,
{?MODULE, receive_message_carbon_arrives}, has_no_more_stanzas],
sasl2_helper:apply_steps(Steps, Config).

csi_is_active_with_bind_inline_request(Config) ->
Steps = [start_new_user,
{?MODULE, start_peer},
{?MODULE, auth_and_bind_with_csi_active}, receive_features,
{?MODULE, inactive_csi_msg_wont_arrive}, has_no_more_stanzas],
sasl2_helper:apply_steps(Steps, Config).

csi_is_inactive_with_bind_inline_request(Config) ->
Steps = [start_new_user,
{?MODULE, start_peer},
{?MODULE, auth_and_bind_with_csi_inactive}, has_no_more_stanzas,
{?MODULE, inactive_csi_msgs_do_not_arrive},
{?MODULE, activate_csi}, receive_features,
{?MODULE, receive_csi_msgs}, has_no_more_stanzas],
sasl2_helper:apply_steps(Steps, Config).

stream_resumption_enable_sm_on_bind(Config) ->
Steps = [start_new_user,
{?MODULE, start_peer},
{?MODULE, auth_and_bind_with_sm_enabled},
receive_features, has_no_more_stanzas],
#{answer := Success} = sasl2_helper:apply_steps(Steps, Config),
?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
Enabled = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2},
{element_with_ns, <<"enabled">>, ?NS_STREAM_MGNT_3}]),
?assertNotEqual(undefined, Enabled).

stream_resumption_enable_sm_on_bind_with_resume(Config) ->
Steps = [start_new_user,
{?MODULE, start_peer},
{?MODULE, auth_and_bind_with_sm_resume_enabled},
receive_features, has_no_more_stanzas],
#{answer := Success} = sasl2_helper:apply_steps(Steps, Config),
?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
Enabled = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2},
{element_with_ns, <<"enabled">>, ?NS_STREAM_MGNT_3}]),
?assertNotEqual(undefined, Enabled).

stream_resumption_failing_does_bind_and_contains_sm_status(Config) ->
Steps = [create_user, buffer_messages_and_die, connect_tls, start_stream_get_features,
{?MODULE, auth_and_bind_with_resumption_unknown_smid}, has_no_more_stanzas],
{?MODULE, auth_and_bind_with_resumption_unknown_smid},
receive_features, has_no_more_stanzas],
#{answer := Success, tag := Tag} = sasl2_helper:apply_steps(Steps, Config),
?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
Bound = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2}]),
Expand All @@ -148,8 +189,6 @@ stream_resumption_overrides_bind_request(Config) ->
{?MODULE, auth_and_bind_with_resumption}, has_no_more_stanzas],
#{answer := Success, smid := SMID} = sasl2_helper:apply_steps(Steps, Config),
?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
Bound = exml_query:path(Success, [{element_with_ns, <<"bound">>, ?NS_BIND_2}]),
?assertNotEqual(undefined, Bound),
Resumed = exml_query:path(Success, [{element_with_ns, <<"resumed">>, ?NS_STREAM_MGNT_3}]),
?assert(escalus_pred:is_sm_resumed(SMID, Resumed)).

Expand Down Expand Up @@ -182,6 +221,62 @@ auth_and_bind_with_resumption(Config, Client, #{smid := SMID, texts := Texts} =
Msgs = sm_helper:wait_for_messages(Client, Texts),
{Client1, Data1#{sm_storage => Msgs}}.

auth_and_bind_with_carbon_copies(Config, Client, #{spec := Spec} = Data) ->
CarbonEnable = enable_carbons_el(),
{Client1, Data1} = plain_auth(Config, Client, Data, [CarbonEnable], []),
Resource = <<"second_resource">>,
{ok, Client2, _} = escalus_connection:start(
[{carbons, true}, {resource, Resource} | Spec]),
Jid = <<(escalus_client:short_jid(Client2))/binary, "/", Resource/binary>>,
{Client1, Data1#{client_2 => Client2, client_2_jid => Jid}}.

auth_and_bind_with_csi_active(Config, Client, Data) ->
CsiActive = csi_helper:csi_stanza(<<"active">>),
plain_auth(Config, Client, Data, [CsiActive], []).

inactive_csi_msg_wont_arrive(_Config, Client, #{peer := Bob} = Data) ->
escalus_client:send(Bob, escalus_stanza:chat_to(Client, <<"hello 1">>)),
AliceReceived = escalus_client:wait_for_stanza(Client),
escalus:assert(is_message, AliceReceived),
csi_helper:given_client_is_inactive_and_no_messages_arrive(Client),
csi_helper:given_messages_are_sent(Client, Bob, 1),
csi_helper:then_client_does_not_receive_any_message(Client),
{Client, Data}.

auth_and_bind_with_csi_inactive(Config, Client, Data) ->
CsiInactive = csi_helper:csi_stanza(<<"inactive">>),
plain_auth(Config, Client, Data, [CsiInactive], []).

inactive_csi_msgs_do_not_arrive(_Config, Client, #{peer := Bob} = Data) ->
Msgs = csi_helper:given_messages_are_sent(Client, Bob, 2),
csi_helper:then_client_does_not_receive_any_message(Client),
{Client, Data#{msgs => Msgs}}.

activate_csi(_Config, Client, Data) ->
csi_helper:given_client_is_active(Client),
{Client, Data}.

receive_csi_msgs(_Config, Client, #{msgs := Msgs} = Data) ->
csi_helper:then_client_receives_message(Client, Msgs),
{Client, Data}.

auth_and_bind_with_sm_enabled(Config, Client, Data) ->
SmEnable = escalus_stanza:enable_sm(),
plain_auth(Config, Client, Data, [SmEnable], []).

auth_and_bind_with_sm_resume_enabled(Config, Client, Data) ->
SmEnable = escalus_stanza:enable_sm([{resume, true}]),
plain_auth(Config, Client, Data, [SmEnable], []).

receive_message_carbon_arrives(
_Config, Client1, #{client_1_jid := Jid1, client_2_jid := Jid2,
client_2 := Client2, peer := Bob} = Data) ->
escalus_client:send(Bob, escalus_stanza:chat_to(Jid1, <<"hello 1">>)),
Answers1 = [ escalus_client:wait_for_stanza(C) || C <- [Client1, Client2]],
escalus_client:send(Bob, escalus_stanza:chat_to(Jid2, <<"hello 2">>)),
Answers2 = [ escalus_client:wait_for_stanza(C) || C <- [Client1, Client2]],
{Client1, Data#{answers_1 => Answers1, answers_2 => Answers2}}.

plain_auth(_Config, Client, Data, BindElems, Extra) ->
InitEl = sasl2_helper:plain_auth_initial_response(Client),
BindEl = #xmlel{name = <<"bind">>,
Expand All @@ -190,7 +285,14 @@ plain_auth(_Config, Client, Data, BindElems, Extra) ->
Authenticate = auth_elem(<<"PLAIN">>, [InitEl, BindEl | Extra]),
escalus:send(Client, Authenticate),
Answer = escalus_client:wait_for_stanza(Client),
{Client, Data#{answer => Answer}}.
Identifier = exml_query:path(Answer, [{element, <<"authorization-identifier">>}, cdata]),
#jid{resource = LResource} = jid:from_binary(Identifier),
{Client, Data#{answer => Answer, client_1_jid => Identifier, bind2_resource => LResource}}.

start_peer(Config, Client, Data) ->
BobSpec = escalus_fresh:create_fresh_user(Config, bob),
{ok, Bob, _} = escalus_connection:start(BobSpec),
{Client, Data#{peer => Bob}}.

%% XML helpers
auth_elem(Mech, Children) ->
Expand All @@ -201,6 +303,10 @@ auth_elem(Mech, Children) ->
bind_tag(Tag) ->
#xmlel{name = <<"tag">>, children = [#xmlcdata{content = Tag}]}.

enable_carbons_el() ->
#xmlel{name = <<"enable">>,
attrs = [{<<"xmlns">>, ?NS_CARBONS_2}]}.

good_user_agent_elem(Uuid) ->
user_agent_elem(Uuid, <<"cool-xmpp-client">>, <<"latest-and-greatest-device">>).

Expand All @@ -211,3 +317,7 @@ user_agent_elem(Id, Software, Device) ->
|| Value <- [Device], Value =/= undefined ],
Attrs = [ {<<"id">>, Value} || Value <- [Id], Value =/= undefined ],
#xmlel{name = <<"user-agent">>, attrs = Attrs, children = SoftEl ++ DeviEl}.

check_var(InlineFeatures, NS) ->
Var = exml_query:subelement_with_attr(InlineFeatures, <<"var">>, NS),
?assertNotEqual(undefined, Var).
36 changes: 36 additions & 0 deletions big_tests/tests/csi_helper.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-module(csi_helper).
-compile([export_all, nowarn_export_all]).

-include_lib("exml/include/exml.hrl").

-define(NS_CSI, <<"urn:xmpp:csi:0">>).

given_client_is_active(Alice) ->
escalus:send(Alice, csi_helper:csi_stanza(<<"active">>)).

given_client_is_inactive_and_no_messages_arrive(Alice) ->
escalus:send(Alice, csi_stanza(<<"inactive">>)),
then_client_does_not_receive_any_message(Alice).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that given/when/then prefixes in helpers are confusing, but it can be a matter of preference. For me they hinder code reuse, e.g. here we have a then in given, which already breaks the pattern. If the helper function was called assert_client_does_not_receive_any_message, it would look cleaner.

So we could get rid of the given/then prefixes, but I see how this code was moved from a suite, so it can stay as it is.


given_messages_are_sent(Alice, Bob, N) ->
Msgs = gen_msgs(<<"Hi, Alice">>, N),
send_msgs(Bob, Alice, Msgs),
Msgs.

then_client_does_not_receive_any_message(Alice) ->
[] = escalus:wait_for_stanzas(Alice, 1, 100),
escalus_assert:has_no_stanzas(Alice).

then_client_receives_message(Alice, Msgs) ->
[escalus:assert(is_chat_message, [Msg], escalus:wait_for_stanza(Alice)) ||
Msg <- Msgs].

gen_msgs(Prefix, N) ->
[<<Prefix/binary, ": ", (integer_to_binary(I))/binary>> || I <- lists:seq(1, N)].

send_msgs(From, To, Msgs) ->
[escalus:send(From, escalus_stanza:chat_to(To, Msg)) || Msg <- Msgs].

csi_stanza(Name) ->
#xmlel{name = Name,
attrs = [{<<"xmlns">>, ?NS_CSI}]}.
35 changes: 21 additions & 14 deletions big_tests/tests/sasl2_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ groups() ->
{basic, [parallel],
[
server_does_not_announce_if_not_tls,
server_announces_sasl2_with_some_mechanism,
server_announces_sasl2_with_some_mechanism_and_inline_sm,
authenticate_stanza_has_invalid_mechanism,
user_agent_is_invalid,
user_agent_is_invalid_uuid_but_not_v4,
authenticate_with_plain,
authenticate_with_plain_and_user_agent_without_id,
authenticate_again_results_in_stream_error
Expand Down Expand Up @@ -100,30 +101,38 @@ server_does_not_announce_if_not_tls(Config) ->
Sasl2 = exml_query:path(Features, [{element_with_ns, <<"authentication">>, ?NS_SASL_2}]),
?assertEqual(undefined, Sasl2).

server_announces_sasl2_with_some_mechanism(Config) ->
server_announces_sasl2_with_some_mechanism_and_inline_sm(Config) ->
Steps = [create_connect_tls, start_stream_get_features],
#{features := Features} = sasl2_helper:apply_steps(Steps, Config),
Sasl2 = exml_query:path(Features, [{element_with_ns, <<"authentication">>, ?NS_SASL_2}]),
?assertNotEqual(undefined, Sasl2),
Mechs = exml_query:paths(Sasl2, [{element, <<"mechanism">>}]),
?assertNotEqual([], Mechs).
?assertNotEqual([], Mechs),
Sm = exml_query:path(Sasl2, [{element, <<"inline">>},
{element_with_ns, <<"sm">>, ?NS_STREAM_MGNT_3}]),
?assertNotEqual(undefined, Sm).

authenticate_stanza_has_invalid_mechanism(Config) ->
Steps = [create_connect_tls, start_stream_get_features, send_invalid_mech_auth_stanza],
Steps = [start_new_user, send_invalid_mech_auth_stanza],
#{answer := Response} = sasl2_helper:apply_steps(Steps, Config),
?assertMatch(#xmlel{name = <<"failure">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Response).

user_agent_is_invalid(Config) ->
Steps = [create_connect_tls, start_stream_get_features, send_bad_user_agent],
Steps = [start_new_user, send_bad_user_agent],
#{answer := Response} = sasl2_helper:apply_steps(Steps, Config),
escalus:assert(is_stream_error, [<<"policy-violation">>, <<>>], Response).

user_agent_is_invalid_uuid_but_not_v4(Config) ->
Steps = [start_new_user, send_bad_user_agent_uuid],
#{answer := Response} = sasl2_helper:apply_steps(Steps, Config),
escalus:assert(is_stream_error, [<<"policy-violation">>, <<>>], Response).

authenticate_with_plain(Config) ->
Steps = [create_connect_tls, start_stream_get_features, plain_authentication, receive_features],
Steps = [start_new_user, plain_authentication, receive_features],
auth_with_plain(Steps, Config).

authenticate_with_plain_and_user_agent_without_id(Config) ->
Steps = [create_connect_tls, start_stream_get_features, plain_auth_user_agent_without_id, receive_features],
Steps = [start_new_user, plain_auth_user_agent_without_id, receive_features],
auth_with_plain(Steps, Config).

auth_with_plain(Steps, Config) ->
Expand All @@ -136,25 +145,24 @@ auth_with_plain(Steps, Config) ->
?assertMatch(#xmlel{name = <<"stream:features">>}, Features).

authenticate_with_scram_abort(Config) ->
Steps = [create_connect_tls, start_stream_get_features, scram_step_1, scram_abort],
Steps = [start_new_user, scram_step_1, scram_abort],
#{answer := Response} = sasl2_helper:apply_steps(Steps, Config),
?assertMatch(#xmlel{name = <<"failure">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Response),
Aborted = exml_query:path(Response, [{element_with_ns, <<"aborted">>, ?NS_SASL}]),
?assertNotEqual(undefined, Aborted).

authenticate_with_scram_bad_abort(Config) ->
Steps = [create_connect_tls, start_stream_get_features, scram_step_1, scram_bad_abort],
Steps = [start_new_user, scram_step_1, scram_bad_abort],
#{answer := Response} = sasl2_helper:apply_steps(Steps, Config),
escalus:assert(is_stream_error, [<<"invalid-namespace">>, <<>>], Response).

authenticate_with_scram_bad_response(Config) ->
Steps = [create_connect_tls, start_stream_get_features, scram_step_1, scram_bad_ns_response],
Steps = [start_new_user, scram_step_1, scram_bad_ns_response],
#{answer := Response} = sasl2_helper:apply_steps(Steps, Config),
escalus:assert(is_stream_error, [<<"invalid-namespace">>, <<>>], Response).

authenticate_with_scram(Config) ->
Steps = [create_connect_tls, start_stream_get_features,
scram_step_1, scram_step_2, receive_features],
Steps = [start_new_user, scram_step_1, scram_step_2, receive_features],
#{answer := Success, features := Features} = sasl2_helper:apply_steps(Steps, Config),
?assertMatch(#xmlel{name = <<"success">>, attrs = [{<<"xmlns">>, ?NS_SASL_2}]}, Success),
CData = exml_query:path(Success, [{element, <<"additional-data">>}, cdata], <<>>),
Expand All @@ -164,8 +172,7 @@ authenticate_with_scram(Config) ->
?assertMatch(#xmlel{name = <<"stream:features">>}, Features).

authenticate_again_results_in_stream_error(Config) ->
Steps = [create_connect_tls, start_stream_get_features,
plain_authentication, receive_features, plain_authentication],
Steps = [start_new_user, plain_authentication, receive_features, plain_authentication],
#{answer := Response} = sasl2_helper:apply_steps(Steps, Config),
escalus:assert(is_stream_error, [<<"policy-violation">>, <<>>], Response).

Expand Down
Loading