Skip to content

Commit

Permalink
Bind2: Add initial module, test suite, and document
Browse files Browse the repository at this point in the history
  • Loading branch information
NelsonVides committed Sep 5, 2023
1 parent eb51e35 commit 9ee0ac3
Show file tree
Hide file tree
Showing 12 changed files with 426 additions and 14 deletions.
1 change: 1 addition & 0 deletions big_tests/default.spec
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
{suites, "tests", amp_big_SUITE}.
{suites, "tests", anonymous_SUITE}.
{suites, "tests", auth_methods_for_c2s_SUITE}.
{suites, "tests", bind2_SUITE}.
{suites, "tests", bosh_SUITE}.
{suites, "tests", carboncopy_SUITE}.
{suites, "tests", cluster_commands_SUITE}.
Expand Down
2 changes: 2 additions & 0 deletions big_tests/dynamic_domains.spec
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

{suites, "tests", auth_methods_for_c2s_SUITE}.

{suites, "tests", bind2_SUITE}.

{suites, "tests", bosh_SUITE}.

{suites, "tests", carboncopy_SUITE}.
Expand Down
213 changes: 213 additions & 0 deletions big_tests/tests/bind2_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
-module(bind2_SUITE).

-compile([export_all, nowarn_export_all]).

-include_lib("stdlib/include/assert.hrl").
-include_lib("exml/include/exml.hrl").
-include_lib("escalus/include/escalus.hrl").
-include_lib("escalus/include/escalus_xmlns.hrl").

-define(NS_SASL_2, <<"urn:xmpp:sasl:2">>).
-define(NS_BIND_2, <<"urn:xmpp:bind:0">>).
-define(BAD_TAG, <<"\x{EFBB}"/utf8>>).

%%--------------------------------------------------------------------
%% Suite configuration
%%--------------------------------------------------------------------

all() ->
[
{group, basic}
].

groups() ->
[
{basic, [parallel],
[
server_announces_bind2,
server_announces_bind2_with_sm,
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,
stream_resumption_failing_does_bind_and_contains_sm_status,
stream_resumption_overrides_bind_request
]}
].

%%--------------------------------------------------------------------
%% Init & teardown
%%--------------------------------------------------------------------

init_per_suite(Config) ->
Config1 = load_modules(Config),
escalus:init_per_suite(Config1).

end_per_suite(Config) ->
escalus_fresh:clean(),
dynamic_modules:restore_modules(Config),
escalus:end_per_suite(Config).

init_per_group(_GroupName, Config) ->
Config.

end_per_group(_GroupName, Config) ->
Config.

init_per_testcase(Name, Config) ->
escalus:init_per_testcase(Name, Config).

end_per_testcase(Name, Config) ->
escalus:end_per_testcase(Name, Config).

load_modules(Config) ->
HostType = domain_helper:host_type(),
Config1 = dynamic_modules:save_modules(HostType, Config),
sasl2_helper:load_all_sasl2_modules(HostType),
Config1.

%%--------------------------------------------------------------------
%% tests
%%--------------------------------------------------------------------

server_announces_bind2(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).

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],
#{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],
#{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}]),
?assertNotEqual(undefined, Bound),
Identifier = exml_query:path(Success, [{element, <<"authorization-identifier">>}, cdata]),
#jid{resource = LResource} = jid:from_binary(Identifier),
?assert(0 =< byte_size(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],
#{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}]),
?assertNotEqual(undefined, Bound),
Identifier = exml_query:path(Success, [{element, <<"authorization-identifier">>}, cdata]),
#jid{resource = LResource} = jid:from_binary(Identifier),
?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],
#{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}]),
?assertNotEqual(undefined, Bound),
Identifier = exml_query:path(Success, [{element, <<"authorization-identifier">>}, cdata]),
#jid{resource = LResource} = jid:from_binary(Identifier),
ResourceParts = binary:split(LResource, <<"/">>, [global]),
?assertMatch([Tag, _], ResourceParts).

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],
#{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}]),
?assertNotEqual(undefined, Bound),
Resumed = exml_query:path(Success, [{element_with_ns, <<"failed">>, ?NS_STREAM_MGNT_3}]),
escalus:assert(is_sm_failed, [<<"item-not-found">>], Resumed),
Identifier = exml_query:path(Success, [{element, <<"authorization-identifier">>}, cdata]),
#jid{resource = LResource} = jid:from_binary(Identifier),
ResourceParts = binary:split(LResource, <<"/">>, [global]),
?assertMatch([Tag, _], ResourceParts).

stream_resumption_overrides_bind_request(Config) ->
Steps = [create_user, buffer_messages_and_die, connect_tls, start_stream_get_features,
{?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)).


%% Step helpers
auth_and_bind(Config, Client, Data) ->
plain_auth(Config, Client, Data, [], []).

auth_and_bind_wrong_resource(Config, Client, Data) ->
Tag = ?BAD_TAG,
plain_auth(Config, Client, Data#{tag => Tag}, [bind_tag(Tag)], []).

auth_and_bind_with_user_agent_uuid(Config, Client, Data) ->
Uuid = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
plain_auth(Config, Client, Data#{uuid => Uuid}, [], [good_user_agent_elem(Uuid)]).

auth_and_bind_with_tag(Config, Client, Data) ->
Tag = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
plain_auth(Config, Client, Data#{tag => Tag}, [bind_tag(Tag)], []).

auth_and_bind_with_resumption_unknown_smid(Config, Client, Data) ->
Tag = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
Resume = escalus_stanza:resume(<<"123456">>, 1),
plain_auth(Config, Client, Data#{tag => Tag}, [bind_tag(Tag)], [Resume]).

auth_and_bind_with_resumption(Config, Client, #{smid := SMID, texts := Texts} = Data) ->
Tag = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
Resume = escalus_stanza:resume(SMID, 1),
{Client1, Data1} = plain_auth(Config, Client, Data#{tag => Tag}, [bind_tag(Tag)], [Resume]),
Msgs = sm_helper:wait_for_messages(Client, Texts),
{Client1, Data1#{sm_storage => Msgs}}.

plain_auth(_Config, Client, Data, BindElems, Extra) ->
InitEl = sasl2_helper:plain_auth_initial_response(Client),
BindEl = #xmlel{name = <<"bind">>,
attrs = [{<<"xmlns">>, ?NS_BIND_2}],
children = BindElems},
Authenticate = auth_elem(<<"PLAIN">>, [InitEl, BindEl | Extra]),
escalus:send(Client, Authenticate),
Answer = escalus_client:wait_for_stanza(Client),
{Client, Data#{answer => Answer}}.

%% XML helpers
auth_elem(Mech, Children) ->
#xmlel{name = <<"authenticate">>,
attrs = [{<<"xmlns">>, ?NS_SASL_2}, {<<"mechanism">>, Mech}],
children = Children}.

bind_tag(Tag) ->
#xmlel{name = <<"tag">>, children = [#xmlcdata{content = Tag}]}.

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

user_agent_elem(Id, Software, Device) ->
SoftEl = [#xmlel{name = <<"software">>, children = [#xmlcdata{content = Value}]}
|| Value <- [Software], Value =/= undefined ],
DeviEl = [#xmlel{name = <<"device">>, children = [#xmlcdata{content = Value}]}
|| Value <- [Device], Value =/= undefined ],
Attrs = [{<<"id">>, Value} || Value <- [Id], Value =/= undefined ],
#xmlel{name = <<"user-agent">>, attrs = Attrs, children = SoftEl ++ DeviEl}.
24 changes: 11 additions & 13 deletions big_tests/tests/sasl2_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,7 @@ end_per_testcase(Name, Config) ->
load_sasl_extensible(Config) ->
HostType = domain_helper:host_type(),
Config1 = dynamic_modules:save_modules(HostType, Config),
Modules = [{mod_sasl2, config_parser_helper:default_mod_config(mod_sasl2)},
{mod_stream_management, config_parser_helper:mod_config(mod_stream_management, #{ack_freq => never})}],
dynamic_modules:ensure_modules(HostType, Modules),
sasl2_helper:load_all_sasl2_modules(HostType),
Config1.

%%--------------------------------------------------------------------
Expand All @@ -103,29 +101,29 @@ server_does_not_announce_if_not_tls(Config) ->
?assertEqual(undefined, Sasl2).

server_announces_sasl2_with_some_mechanism(Config) ->
Steps = [create_user, connect_tls, start_stream_get_features],
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).

authenticate_stanza_has_invalid_mechanism(Config) ->
Steps = [create_user, connect_tls, start_stream_get_features, send_invalid_mech_auth_stanza],
Steps = [create_connect_tls, start_stream_get_features, 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_user, connect_tls, start_stream_get_features, send_bad_user_agent],
Steps = [create_connect_tls, start_stream_get_features, send_bad_user_agent],
#{answer := Response} = sasl2_helper:apply_steps(Steps, Config),
escalus:assert(is_stream_error, [<<"policy-violation">>, <<>>], Response).

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

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

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

authenticate_with_scram_abort(Config) ->
Steps = [create_user, connect_tls, start_stream_get_features, scram_step_1, scram_abort],
Steps = [create_connect_tls, start_stream_get_features, 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_user, connect_tls, start_stream_get_features, scram_step_1, scram_bad_abort],
Steps = [create_connect_tls, start_stream_get_features, 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_user, connect_tls, start_stream_get_features, scram_step_1, scram_bad_ns_response],
Steps = [create_connect_tls, start_stream_get_features, 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_user, connect_tls, start_stream_get_features,
Steps = [create_connect_tls, start_stream_get_features,
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),
Expand All @@ -166,7 +164,7 @@ authenticate_with_scram(Config) ->
?assertMatch(#xmlel{name = <<"stream:features">>}, Features).

authenticate_again_results_in_stream_error(Config) ->
Steps = [create_user, connect_tls, start_stream_get_features,
Steps = [create_connect_tls, start_stream_get_features,
plain_authentication, receive_features, plain_authentication],
#{answer := Response} = sasl2_helper:apply_steps(Steps, Config),
escalus:assert(is_stream_error, [<<"policy-violation">>, <<>>], Response).
Expand Down
10 changes: 10 additions & 0 deletions big_tests/tests/sasl2_helper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
ns() ->
?NS_SASL_2.

load_all_sasl2_modules(HostType) ->
Modules = [{mod_bind2, config_parser_helper:default_mod_config(mod_bind2)},
{mod_sasl2, config_parser_helper:default_mod_config(mod_sasl2)},
{mod_stream_management, config_parser_helper:mod_config(mod_stream_management, #{ack_freq => never})}],
dynamic_modules:ensure_modules(HostType, Modules).

apply_steps(Steps, Config) ->
apply_steps(Steps, Config, undefined, #{}).

Expand All @@ -31,6 +37,10 @@ connect_non_tls_user(Config, _, Data) ->
Client1 = escalus_connection:connect(Spec),
{Client1, Data#{spec => Spec}}.

create_connect_tls(Config, Client, Data) ->
{Client1, Data1} = create_user(Config, Client, Data),
connect_tls(Config, Client1, Data1).

create_user(Config, Client, Data) ->
Spec = escalus_fresh:create_fresh_user(Config, alice),
{Client, Data#{spec => Spec}}.
Expand Down
5 changes: 5 additions & 0 deletions doc/modules/mod_bind2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Module Description

Implements [XEP-0386: Bind 2](http://xmpp.org/extensions/xep-0386.html).


1 change: 1 addition & 0 deletions include/mongoose_ns.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
-define(NS_SASL_2, <<"urn:xmpp:sasl:2">>).
-define(NS_SESSION, <<"urn:ietf:params:xml:ns:xmpp-session">>).
-define(NS_BIND, <<"urn:ietf:params:xml:ns:xmpp-bind">>).
-define(NS_BIND_2, <<"urn:xmpp:bind:0">>).

-define(NS_FEATURE_IQAUTH, <<"http://jabber.org/features/iq-auth">>).
-define(NS_FEATURE_IQREGISTER, <<"http://jabber.org/features/iq-register">>).
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ nav:
- 'mod_adhoc': 'modules/mod_adhoc.md'
- 'mod_amp': 'modules/mod_amp.md'
- 'mod_auth_token': 'modules/mod_auth_token.md'
- 'mod_bind2': 'modules/mod_bind2.md'
- 'mod_blocking': 'modules/mod_blocking.md'
- 'mod_bosh': 'modules/mod_bosh.md'
- 'mod_caps': 'modules/mod_caps.md'
Expand Down
19 changes: 19 additions & 0 deletions src/hooks/mongoose_hooks.erl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@

%% sasl2 handlers
-export([sasl2_stream_features/2,
bind2_stream_features/2,
bind2_enable_features/3,
sasl2_start/3,
sasl2_success/3]).

Expand Down Expand Up @@ -497,6 +499,23 @@ sasl2_stream_features(C2SData, InitialFeatures) ->
HostType = mongoose_c2s:get_host_type(C2SData),
run_hook_for_host_type(sasl2_stream_features, HostType, InitialFeatures, Params).

-spec bind2_stream_features(C2SData, InitialFeatures) -> Result when
C2SData :: mongoose_c2s:data(),
InitialFeatures :: [exml:element()],
Result :: [exml:element()].
bind2_stream_features(C2SData, InitialFeatures) ->
Params = #{c2s_data => C2SData},
HostType = mongoose_c2s:get_host_type(C2SData),
run_hook_for_host_type(bind2_stream_features, HostType, InitialFeatures, Params).

-spec bind2_enable_features(HostType, Acc, Params) -> Result when
HostType :: mongooseim:host_type(),
Acc :: mongoose_acc:t(),
Params :: mod_sasl2:c2s_state_data(),
Result :: mongoose_acc:t().
bind2_enable_features(HostType, Acc, Params) ->
run_hook_for_host_type(bind2_enable_features, HostType, Acc, Params).

%% This hook will cache in the accumulator all the requests from sasl2 inlined features
-spec sasl2_start(HostType, Acc, Element) -> Result when
HostType :: mongooseim:host_type(),
Expand Down
Loading

0 comments on commit 9ee0ac3

Please sign in to comment.