Skip to content

Commit 6acae7b

Browse files
authored
Merge pull request #4379 from esl/XEP-0484
XEP-0484 Fast Token Auth
2 parents 83eb36a + a48bb51 commit 6acae7b

34 files changed

+1740
-46
lines changed

big_tests/default.spec

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
{suites, "tests", amp_big_SUITE}.
1818
{suites, "tests", anonymous_SUITE}.
1919
{suites, "tests", bind2_SUITE}.
20+
{suites, "tests", fast_auth_token_SUITE}.
2021
{suites, "tests", bosh_SUITE}.
2122
{suites, "tests", carboncopy_SUITE}.
2223
{suites, "tests", connect_SUITE}.

big_tests/dynamic_domains.spec

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
{suites, "tests", amp_big_SUITE}.
1717
{suites, "tests", anonymous_SUITE}.
1818
{suites, "tests", bind2_SUITE}.
19+
{suites, "tests", fast_auth_token_SUITE}.
1920
{suites, "tests", bosh_SUITE}.
2021
{suites, "tests", carboncopy_SUITE}.
2122
{suites, "tests", connect_SUITE}.

big_tests/src/time_helper.erl

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-module(time_helper).
2+
-export([validate_datetime/1]).
3+
4+
%% @doc Validates that string is in ISO 8601 format
5+
-spec validate_datetime(string()) -> boolean().
6+
validate_datetime(TimeStr) ->
7+
[Date, Time] = string:tokens(TimeStr, "T"),
8+
validate_date(Date) and validate_time(Time).
9+
10+
validate_date(Date) ->
11+
[Y, M, D] = string:tokens(Date, "-"),
12+
Date1 = {list_to_integer(Y), list_to_integer(M), list_to_integer(D)},
13+
calendar:valid_date(Date1).
14+
15+
validate_time(Time) ->
16+
[T | _] = string:tokens(Time, "Z"),
17+
validate_time1(T).
18+
19+
validate_time1(Time) ->
20+
[H, M, S] = string:tokens(Time, ":"),
21+
check_list([{H, 24}, {M, 60}, {S, 60}]).
22+
23+
check_list(List) ->
24+
lists:all(fun({V, L}) -> I = list_to_integer(V), I >= 0 andalso I < L end, List).

big_tests/tests/fast_auth_token_SUITE.erl

+512
Large diffs are not rendered by default.

big_tests/tests/gdpr_SUITE.erl

+1-23
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,7 @@ retrieve_offline(Config) ->
963963
#{ "packet" => [{contains, Body}],
964964
"from" => binary_to_list(From),
965965
"to" => binary_to_list(To),
966-
"timestamp" => [{validate, fun validate_datetime/1}]}
966+
"timestamp" => [{validate, fun time_helper:validate_datetime/1}]}
967967
end, Expected),
968968

969969
retrieve_and_validate_personal_data(
@@ -1777,28 +1777,6 @@ send_and_assert_is_chat_message(UserFrom, UserTo, Body) ->
17771777
Msg = escalus:wait_for_stanza(UserTo),
17781778
escalus:assert(is_chat_message, [Body], Msg).
17791779

1780-
validate_datetime(TimeStr) ->
1781-
[Date, Time] = string:tokens(TimeStr, "T"),
1782-
validate_date(Date),
1783-
validate_time(Time).
1784-
1785-
validate_date(Date) ->
1786-
[Y, M, D] = string:tokens(Date, "-"),
1787-
Date1 = {list_to_integer(Y), list_to_integer(M), list_to_integer(D)},
1788-
calendar:valid_date(Date1).
1789-
1790-
validate_time(Time) ->
1791-
[T | _] = string:tokens(Time, "Z"),
1792-
validate_time1(T).
1793-
1794-
1795-
validate_time1(Time) ->
1796-
[H, M, S] = string:tokens(Time, ":"),
1797-
check_list([{H, 24}, {M, 60}, {S, 60}]).
1798-
1799-
check_list(List) ->
1800-
lists:all(fun({V, L}) -> I = list_to_integer(V), I >= 0 andalso I < L end, List).
1801-
18021780
expected_header(mod_roster) -> ["jid", "name", "subscription",
18031781
"ask", "groups", "askmessage", "xs"].
18041782

big_tests/tests/sasl2_helper.erl

+10-1
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,18 @@ load_all_sasl2_modules(HostType) ->
2121
{mod_sasl2, default_mod_config(mod_sasl2)},
2222
{mod_csi, default_mod_config(mod_csi)},
2323
{mod_carboncopy, default_mod_config(mod_carboncopy)},
24-
{mod_stream_management, mod_config(mod_stream_management, SMOpts)}],
24+
{mod_stream_management, mod_config(mod_stream_management, SMOpts)}]
25+
++ rdbms_mods(),
2526
dynamic_modules:ensure_modules(HostType, Modules).
2627

28+
rdbms_mods() ->
29+
case mongoose_helper:is_rdbms_enabled(domain_helper:host_type()) of
30+
true ->
31+
[{mod_fast_auth_token, mod_config(mod_fast_auth_token, #{backend => rdbms})}];
32+
false ->
33+
[]
34+
end.
35+
2736
apply_steps(Steps, Config) ->
2837
apply_steps(Steps, Config, undefined, #{}).
2938

doc/configuration/Modules.md

+3
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ This applies to situations such as sending messages or presences to mobile/SMS/e
9999
Implements [XEP-0215: External Service Discovery](http://xmpp.org/extensions/xep-0215.html) for discovering information about services external to the XMPP network.
100100
The main use-case is to help discover STUN/TURN servers to allow for negotiating media exchanges.
101101

102+
### [mod_fast_auth_token](../modules/mod_fast_auth_token.md)
103+
A module that implements [XEP-0484: Fast Authentication Streamlining Tokens](https://xmpp.org/extensions/xep-0484.html)..
104+
102105
### [mod_http_upload](../modules/mod_http_upload.md)
103106
Implements [XEP-0363: HTTP File Upload](https://xmpp.org/extensions/xep-0363.html) for coordinating with an XMPP server to upload files via HTTP and receive URLs that can be shared in messages.
104107

doc/modules/mod_fast_auth_token.md

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
## Module Description
2+
3+
This module implements [XEP-0484: Fast Authentication Streamlining Tokens](https://xmpp.org/extensions/xep-0484.html).
4+
It provides services necessary to:
5+
6+
* issue auth tokens for authenticated users;
7+
* reconnect to the server using the tokens instead of the original auth method.
8+
9+
Tokens are stored in RDBMS.
10+
11+
It is not related to another similar module `mod_auth_token`.
12+
13+
## Options
14+
15+
### `modules.mod_fast_auth_token.backend`
16+
* **Syntax:** non-empty string
17+
* **Default:** `"rdbms"`
18+
* **Example:** `backend = "rdbms"`
19+
20+
Token storage backend. Currently only `"rdbms"` is supported.
21+
22+
### `modules.mod_fast_auth_token.validity_period`
23+
* **Syntax:** TOML table. Each key is either `access` or `rotate_before_expire`.Each value is a nested TOML table with the following mandatory keys: `value` (non-negative integer) and `unit` (`"days"`, `"hours"`, `"minutes"` or `"seconds"`).
24+
* **Default:** `{access = {value = 3, unit = "days"}, rotate_before_expire = {value = 6, unit = "hours"}}`
25+
* **Example:** `validity_period.access = {value = 30, unit = "minutes"}`
26+
27+
The user can use each token for `access` period of time before it expired.
28+
29+
The server would [send](https://xmpp.org/extensions/xep-0484.html#token-rotation)
30+
a new token at the login time `rotate_before_expire` time before it expires.
31+
Set it to 0 to disable automatic rotation.
32+
33+
## Example configuration
34+
35+
```toml
36+
[modules.mod_fast_auth_token]
37+
validity_period.access = {value = 1, unit = "days"}
38+
validity_period.rotate_before_expire = {value = 0, unit = "days"}
39+
```

include/mongoose_ns.hrl

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
-define(NS_SESSION, <<"urn:ietf:params:xml:ns:xmpp-session">>).
8686
-define(NS_BIND, <<"urn:ietf:params:xml:ns:xmpp-bind">>).
8787
-define(NS_BIND_2, <<"urn:xmpp:bind:0">>).
88+
-define(NS_FAST, <<"urn:xmpp:fast:0">>).
8889

8990
-define(NS_FEATURE_IQAUTH, <<"http://jabber.org/features/iq-auth">>).
9091
-define(NS_FEATURE_IQREGISTER, <<"http://jabber.org/features/iq-register">>).

mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ nav:
133133
- 'RabbitMQ backend': 'modules/mod_event_pusher_rabbit.md'
134134
- 'SNS backend': 'modules/mod_event_pusher_sns.md'
135135
- 'mod_extdisco': 'modules/mod_extdisco.md'
136+
- 'mod_fast_auth_token': 'modules/mod_fast_auth_token.md'
136137
- 'mod_global_distrib': 'modules/mod_global_distrib.md'
137138
- 'mod_http_upload': 'modules/mod_http_upload.md'
138139
- 'mod_inbox': 'modules/mod_inbox.md'

priv/cockroachdb.sql

+20
Original file line numberDiff line numberDiff line change
@@ -524,3 +524,23 @@ CREATE TABLE caps (
524524
features text NOT NULL,
525525
PRIMARY KEY (node, sub_node)
526526
);
527+
528+
-- XEP-0484: Fast Authentication Streamlining Tokens
529+
-- Module: mod_fast_auth_token
530+
CREATE TABLE fast_auth_token(
531+
server VARCHAR(250) NOT NULL,
532+
username VARCHAR(250) NOT NULL,
533+
-- Device installation ID (User-Agent ID)
534+
-- Unique for each device
535+
-- https://xmpp.org/extensions/xep-0388.html#initiation
536+
user_agent_id VARCHAR(250) NOT NULL,
537+
current_token VARCHAR(250),
538+
current_expire BIGINT, -- seconds unix timestamp
539+
current_count INT, -- replay counter
540+
current_mech_id smallint,
541+
new_token VARCHAR(250),
542+
new_expire BIGINT, -- seconds unix timestamp
543+
new_count INT,
544+
new_mech_id smallint,
545+
PRIMARY KEY(server, username, user_agent_id)
546+
);

priv/mssql2012.sql

+20
Original file line numberDiff line numberDiff line change
@@ -768,3 +768,23 @@ CREATE TABLE caps (
768768
features text NOT NULL,
769769
PRIMARY KEY (node, sub_node)
770770
);
771+
772+
-- XEP-0484: Fast Authentication Streamlining Tokens
773+
-- Module: mod_fast_auth_token
774+
CREATE TABLE fast_auth_token(
775+
server VARCHAR(250) NOT NULL,
776+
username VARCHAR(250) NOT NULL,
777+
-- Device installation ID (User-Agent ID)
778+
-- Unique for each device
779+
-- https://xmpp.org/extensions/xep-0388.html#initiation
780+
user_agent_id VARCHAR(250) NOT NULL,
781+
current_token VARCHAR(250),
782+
current_expire BIGINT, -- seconds unix timestamp
783+
current_count INT, -- replay counter
784+
current_mech_id TINYINT,
785+
new_token VARCHAR(250),
786+
new_expire BIGINT, -- seconds unix timestamp
787+
new_count INT,
788+
new_mech_id TINYINT,
789+
PRIMARY KEY(server, username, user_agent_id)
790+
);

priv/mysql.sql

+20
Original file line numberDiff line numberDiff line change
@@ -557,3 +557,23 @@ CREATE TABLE caps (
557557
features text NOT NULL,
558558
PRIMARY KEY (node, sub_node)
559559
);
560+
561+
-- XEP-0484: Fast Authentication Streamlining Tokens
562+
-- Module: mod_fast_auth_token
563+
CREATE TABLE fast_auth_token(
564+
server VARCHAR(250) NOT NULL,
565+
username VARCHAR(250) NOT NULL,
566+
-- Device installation ID (User-Agent ID)
567+
-- Unique for each device
568+
-- https://xmpp.org/extensions/xep-0388.html#initiation
569+
user_agent_id VARCHAR(250) NOT NULL,
570+
current_token VARCHAR(250),
571+
current_expire BIGINT, -- seconds unix timestamp
572+
current_count INT, -- replay counter
573+
current_mech_id TINYINT UNSIGNED,
574+
new_token VARCHAR(250),
575+
new_expire BIGINT, -- seconds unix timestamp
576+
new_count INT,
577+
new_mech_id TINYINT UNSIGNED,
578+
PRIMARY KEY(server, username, user_agent_id)
579+
);

priv/pg.sql

+20
Original file line numberDiff line numberDiff line change
@@ -499,3 +499,23 @@ CREATE TABLE caps (
499499
features text NOT NULL,
500500
PRIMARY KEY (node, sub_node)
501501
);
502+
503+
-- XEP-0484: Fast Authentication Streamlining Tokens
504+
-- Module: mod_fast_auth_token
505+
CREATE TABLE fast_auth_token(
506+
server VARCHAR(250) NOT NULL,
507+
username VARCHAR(250) NOT NULL,
508+
-- Device installation ID (User-Agent ID)
509+
-- Unique for each device
510+
-- https://xmpp.org/extensions/xep-0388.html#initiation
511+
user_agent_id VARCHAR(250) NOT NULL,
512+
current_token VARCHAR(250),
513+
current_expire BIGINT, -- seconds unix timestamp
514+
current_count INT, -- replay counter
515+
current_mech_id smallint,
516+
new_token VARCHAR(250),
517+
new_expire BIGINT, -- seconds unix timestamp
518+
new_count INT,
519+
new_mech_id smallint,
520+
PRIMARY KEY(server, username, user_agent_id)
521+
);

src/c2s/mongoose_c2s.erl

+12-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
get_info/1, set_info/2,
2121
get_mod_state/2, get_listener_opts/1, merge_mod_state/2, remove_mod_state/2,
2222
get_ip/1, get_socket/1, get_lang/1, get_stream_id/1, hook_arg/5]).
23-
-export([get_auth_mechs/1, c2s_stream_error/2, maybe_retry_state/1, merge_states/2]).
23+
-export([get_auth_mechs/1, get_auth_mechs_to_announce/1,
24+
c2s_stream_error/2, maybe_retry_state/1, merge_states/2]).
2425
-export([route/2, reroute_buffer/2, reroute_buffer_to_pid/3, open_session/1]).
2526
-export([set_jid/2, set_auth_module/2, state_timeout/1, handle_state_after_packet/3]).
2627
-export([replace_resource/2, generate_random_resource/0]).
@@ -1158,6 +1159,16 @@ create_data(#{host_type := HostType, jid := Jid}) ->
11581159
get_auth_mechs(#c2s_data{host_type = HostType} = StateData) ->
11591160
[M || M <- cyrsasl:listmech(HostType), filter_mechanism(StateData, M)].
11601161

1162+
%% Mechanisms without XEP-0484 token mechanisms
1163+
%% (HT mechanisms are announced as inlined instead)
1164+
-spec get_auth_mechs_to_announce(data()) -> [mongoose_c2s_sasl:mechanism()].
1165+
get_auth_mechs_to_announce(StateData) ->
1166+
[M || M <- get_auth_mechs(StateData), not skip_announce_mechanism(M)].
1167+
1168+
-spec skip_announce_mechanism(binary()) -> boolean().
1169+
skip_announce_mechanism(Mech) ->
1170+
mod_fast_auth_token_generic_mech:skip_announce_mechanism(Mech).
1171+
11611172
-spec filter_mechanism(data(), binary()) -> boolean().
11621173
filter_mechanism(#c2s_data{socket = Socket}, <<"SCRAM-SHA-1-PLUS">>) ->
11631174
mongoose_c2s_socket:is_channel_binding_supported(Socket);

src/c2s/mongoose_c2s_sasl.erl

+7-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
-type maybe_username() :: undefined | jid:luser().
1919
-type success() :: #{server_out := undefined | binary(),
2020
jid := jid:jid(),
21-
auth_module := cyrsasl:sasl_module()}.
21+
auth_module := cyrsasl:sasl_module(),
22+
creds := mongoose_credentials:t()}.
2223
-type continue() :: #{server_out := binary()}.
2324
-type failure() :: #{server_out := binary() | {binary(), undefined | iodata()},
2425
maybe_username := maybe_username()}.
@@ -48,7 +49,10 @@ start(C2SData, SaslAcc, Mech, ClientIn) ->
4849
{error, SaslAcc, #{type => policy_violation, text => <<"Use of STARTTLS required">>}};
4950
_ ->
5051
AuthMech = mongoose_c2s:get_auth_mechs(C2SData),
51-
SocketData = #{socket => Socket, auth_mech => AuthMech, listener_opts => LOpts},
52+
%% Provide SaslAcc for readonly access, so the cyrsasl mechanism
53+
%% has more visibility to initialize the mechanism state.
54+
SocketData = #{socket => Socket, auth_mech => AuthMech, listener_opts => LOpts,
55+
sasl_state => SaslAcc},
5256
CyrSaslState = get_cyrsasl_state_from_acc(SaslAcc),
5357
CyrSaslResult = cyrsasl:server_start(CyrSaslState, Mech, ClientIn, SocketData),
5458
handle_sasl_step(C2SData, CyrSaslResult, SaslAcc)
@@ -78,7 +82,7 @@ handle_sasl_success(C2SData, Creds, SaslAcc) ->
7882
User = mongoose_credentials:get(Creds, username),
7983
LServer = mongoose_c2s:get_lserver(C2SData),
8084
Jid = jid:make_bare(User, LServer),
81-
Ret = #{server_out => ServerOut, jid => Jid, auth_module => AuthModule},
85+
Ret = #{server_out => ServerOut, jid => Jid, auth_module => AuthModule, creds => Creds},
8286
{success, SaslAcc, Ret}.
8387

8488
-spec handle_sasl_continue(

src/c2s/mongoose_c2s_stanzas.erl

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ determine_features(StateData, HostType, LServer, _, _) ->
7171

7272
-spec maybe_sasl_mechanisms(mongoose_c2s:data()) -> [exml:element()].
7373
maybe_sasl_mechanisms(StateData) ->
74-
case mongoose_c2s:get_auth_mechs(StateData) of
74+
case mongoose_c2s:get_auth_mechs_to_announce(StateData) of
7575
[] -> [];
7676
Mechanisms ->
7777
[#xmlel{name = <<"mechanisms">>,

src/config/mongoose_config_spec.erl

+5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
process_domain_cert/1,
3333
process_infinity_as_zero/1]).
3434

35+
%% For tests
36+
-export([configurable_modules/0]).
37+
-ignore_xref([configurable_modules/0]).
38+
3539
-include("mongoose_config_spec.hrl").
3640

3741
-type config_node() :: config_section() | config_list() | config_option().
@@ -753,6 +757,7 @@ configurable_modules() ->
753757
mod_disco,
754758
mod_event_pusher,
755759
mod_extdisco,
760+
mod_fast_auth_token,
756761
mod_global_distrib,
757762
mod_http_upload,
758763
mod_inbox,

0 commit comments

Comments
 (0)