Skip to content

Commit

Permalink
Merge pull request #4379 from esl/XEP-0484
Browse files Browse the repository at this point in the history
XEP-0484 Fast Token Auth
  • Loading branch information
NelsonVides authored Jan 21, 2025
2 parents 83eb36a + a48bb51 commit 6acae7b
Show file tree
Hide file tree
Showing 34 changed files with 1,740 additions and 46 deletions.
1 change: 1 addition & 0 deletions big_tests/default.spec
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
{suites, "tests", amp_big_SUITE}.
{suites, "tests", anonymous_SUITE}.
{suites, "tests", bind2_SUITE}.
{suites, "tests", fast_auth_token_SUITE}.
{suites, "tests", bosh_SUITE}.
{suites, "tests", carboncopy_SUITE}.
{suites, "tests", connect_SUITE}.
Expand Down
1 change: 1 addition & 0 deletions big_tests/dynamic_domains.spec
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
{suites, "tests", amp_big_SUITE}.
{suites, "tests", anonymous_SUITE}.
{suites, "tests", bind2_SUITE}.
{suites, "tests", fast_auth_token_SUITE}.
{suites, "tests", bosh_SUITE}.
{suites, "tests", carboncopy_SUITE}.
{suites, "tests", connect_SUITE}.
Expand Down
24 changes: 24 additions & 0 deletions big_tests/src/time_helper.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-module(time_helper).
-export([validate_datetime/1]).

%% @doc Validates that string is in ISO 8601 format
-spec validate_datetime(string()) -> boolean().
validate_datetime(TimeStr) ->
[Date, Time] = string:tokens(TimeStr, "T"),
validate_date(Date) and validate_time(Time).

validate_date(Date) ->
[Y, M, D] = string:tokens(Date, "-"),
Date1 = {list_to_integer(Y), list_to_integer(M), list_to_integer(D)},
calendar:valid_date(Date1).

validate_time(Time) ->
[T | _] = string:tokens(Time, "Z"),
validate_time1(T).

validate_time1(Time) ->
[H, M, S] = string:tokens(Time, ":"),
check_list([{H, 24}, {M, 60}, {S, 60}]).

check_list(List) ->
lists:all(fun({V, L}) -> I = list_to_integer(V), I >= 0 andalso I < L end, List).
512 changes: 512 additions & 0 deletions big_tests/tests/fast_auth_token_SUITE.erl

Large diffs are not rendered by default.

24 changes: 1 addition & 23 deletions big_tests/tests/gdpr_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,7 @@ retrieve_offline(Config) ->
#{ "packet" => [{contains, Body}],
"from" => binary_to_list(From),
"to" => binary_to_list(To),
"timestamp" => [{validate, fun validate_datetime/1}]}
"timestamp" => [{validate, fun time_helper:validate_datetime/1}]}
end, Expected),

retrieve_and_validate_personal_data(
Expand Down Expand Up @@ -1777,28 +1777,6 @@ send_and_assert_is_chat_message(UserFrom, UserTo, Body) ->
Msg = escalus:wait_for_stanza(UserTo),
escalus:assert(is_chat_message, [Body], Msg).

validate_datetime(TimeStr) ->
[Date, Time] = string:tokens(TimeStr, "T"),
validate_date(Date),
validate_time(Time).

validate_date(Date) ->
[Y, M, D] = string:tokens(Date, "-"),
Date1 = {list_to_integer(Y), list_to_integer(M), list_to_integer(D)},
calendar:valid_date(Date1).

validate_time(Time) ->
[T | _] = string:tokens(Time, "Z"),
validate_time1(T).


validate_time1(Time) ->
[H, M, S] = string:tokens(Time, ":"),
check_list([{H, 24}, {M, 60}, {S, 60}]).

check_list(List) ->
lists:all(fun({V, L}) -> I = list_to_integer(V), I >= 0 andalso I < L end, List).

expected_header(mod_roster) -> ["jid", "name", "subscription",
"ask", "groups", "askmessage", "xs"].

Expand Down
11 changes: 10 additions & 1 deletion big_tests/tests/sasl2_helper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,18 @@ load_all_sasl2_modules(HostType) ->
{mod_sasl2, default_mod_config(mod_sasl2)},
{mod_csi, default_mod_config(mod_csi)},
{mod_carboncopy, default_mod_config(mod_carboncopy)},
{mod_stream_management, mod_config(mod_stream_management, SMOpts)}],
{mod_stream_management, mod_config(mod_stream_management, SMOpts)}]
++ rdbms_mods(),
dynamic_modules:ensure_modules(HostType, Modules).

rdbms_mods() ->
case mongoose_helper:is_rdbms_enabled(domain_helper:host_type()) of
true ->
[{mod_fast_auth_token, mod_config(mod_fast_auth_token, #{backend => rdbms})}];
false ->
[]
end.

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

Expand Down
3 changes: 3 additions & 0 deletions doc/configuration/Modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ This applies to situations such as sending messages or presences to mobile/SMS/e
Implements [XEP-0215: External Service Discovery](http://xmpp.org/extensions/xep-0215.html) for discovering information about services external to the XMPP network.
The main use-case is to help discover STUN/TURN servers to allow for negotiating media exchanges.

### [mod_fast_auth_token](../modules/mod_fast_auth_token.md)
A module that implements [XEP-0484: Fast Authentication Streamlining Tokens](https://xmpp.org/extensions/xep-0484.html)..

### [mod_http_upload](../modules/mod_http_upload.md)
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.

Expand Down
39 changes: 39 additions & 0 deletions doc/modules/mod_fast_auth_token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## Module Description

This module implements [XEP-0484: Fast Authentication Streamlining Tokens](https://xmpp.org/extensions/xep-0484.html).
It provides services necessary to:

* issue auth tokens for authenticated users;
* reconnect to the server using the tokens instead of the original auth method.

Tokens are stored in RDBMS.

It is not related to another similar module `mod_auth_token`.

## Options

### `modules.mod_fast_auth_token.backend`
* **Syntax:** non-empty string
* **Default:** `"rdbms"`
* **Example:** `backend = "rdbms"`

Token storage backend. Currently only `"rdbms"` is supported.

### `modules.mod_fast_auth_token.validity_period`
* **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"`).
* **Default:** `{access = {value = 3, unit = "days"}, rotate_before_expire = {value = 6, unit = "hours"}}`
* **Example:** `validity_period.access = {value = 30, unit = "minutes"}`

The user can use each token for `access` period of time before it expired.

The server would [send](https://xmpp.org/extensions/xep-0484.html#token-rotation)
a new token at the login time `rotate_before_expire` time before it expires.
Set it to 0 to disable automatic rotation.

## Example configuration

```toml
[modules.mod_fast_auth_token]
validity_period.access = {value = 1, unit = "days"}
validity_period.rotate_before_expire = {value = 0, unit = "days"}
```
1 change: 1 addition & 0 deletions include/mongoose_ns.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
-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_FAST, <<"urn:xmpp:fast: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 @@ -133,6 +133,7 @@ nav:
- 'RabbitMQ backend': 'modules/mod_event_pusher_rabbit.md'
- 'SNS backend': 'modules/mod_event_pusher_sns.md'
- 'mod_extdisco': 'modules/mod_extdisco.md'
- 'mod_fast_auth_token': 'modules/mod_fast_auth_token.md'
- 'mod_global_distrib': 'modules/mod_global_distrib.md'
- 'mod_http_upload': 'modules/mod_http_upload.md'
- 'mod_inbox': 'modules/mod_inbox.md'
Expand Down
20 changes: 20 additions & 0 deletions priv/cockroachdb.sql
Original file line number Diff line number Diff line change
Expand Up @@ -524,3 +524,23 @@ CREATE TABLE caps (
features text NOT NULL,
PRIMARY KEY (node, sub_node)
);

-- XEP-0484: Fast Authentication Streamlining Tokens
-- Module: mod_fast_auth_token
CREATE TABLE fast_auth_token(
server VARCHAR(250) NOT NULL,
username VARCHAR(250) NOT NULL,
-- Device installation ID (User-Agent ID)
-- Unique for each device
-- https://xmpp.org/extensions/xep-0388.html#initiation
user_agent_id VARCHAR(250) NOT NULL,
current_token VARCHAR(250),
current_expire BIGINT, -- seconds unix timestamp
current_count INT, -- replay counter
current_mech_id smallint,
new_token VARCHAR(250),
new_expire BIGINT, -- seconds unix timestamp
new_count INT,
new_mech_id smallint,
PRIMARY KEY(server, username, user_agent_id)
);
20 changes: 20 additions & 0 deletions priv/mssql2012.sql
Original file line number Diff line number Diff line change
Expand Up @@ -768,3 +768,23 @@ CREATE TABLE caps (
features text NOT NULL,
PRIMARY KEY (node, sub_node)
);

-- XEP-0484: Fast Authentication Streamlining Tokens
-- Module: mod_fast_auth_token
CREATE TABLE fast_auth_token(
server VARCHAR(250) NOT NULL,
username VARCHAR(250) NOT NULL,
-- Device installation ID (User-Agent ID)
-- Unique for each device
-- https://xmpp.org/extensions/xep-0388.html#initiation
user_agent_id VARCHAR(250) NOT NULL,
current_token VARCHAR(250),
current_expire BIGINT, -- seconds unix timestamp
current_count INT, -- replay counter
current_mech_id TINYINT,
new_token VARCHAR(250),
new_expire BIGINT, -- seconds unix timestamp
new_count INT,
new_mech_id TINYINT,
PRIMARY KEY(server, username, user_agent_id)
);
20 changes: 20 additions & 0 deletions priv/mysql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -557,3 +557,23 @@ CREATE TABLE caps (
features text NOT NULL,
PRIMARY KEY (node, sub_node)
);

-- XEP-0484: Fast Authentication Streamlining Tokens
-- Module: mod_fast_auth_token
CREATE TABLE fast_auth_token(
server VARCHAR(250) NOT NULL,
username VARCHAR(250) NOT NULL,
-- Device installation ID (User-Agent ID)
-- Unique for each device
-- https://xmpp.org/extensions/xep-0388.html#initiation
user_agent_id VARCHAR(250) NOT NULL,
current_token VARCHAR(250),
current_expire BIGINT, -- seconds unix timestamp
current_count INT, -- replay counter
current_mech_id TINYINT UNSIGNED,
new_token VARCHAR(250),
new_expire BIGINT, -- seconds unix timestamp
new_count INT,
new_mech_id TINYINT UNSIGNED,
PRIMARY KEY(server, username, user_agent_id)
);
20 changes: 20 additions & 0 deletions priv/pg.sql
Original file line number Diff line number Diff line change
Expand Up @@ -499,3 +499,23 @@ CREATE TABLE caps (
features text NOT NULL,
PRIMARY KEY (node, sub_node)
);

-- XEP-0484: Fast Authentication Streamlining Tokens
-- Module: mod_fast_auth_token
CREATE TABLE fast_auth_token(
server VARCHAR(250) NOT NULL,
username VARCHAR(250) NOT NULL,
-- Device installation ID (User-Agent ID)
-- Unique for each device
-- https://xmpp.org/extensions/xep-0388.html#initiation
user_agent_id VARCHAR(250) NOT NULL,
current_token VARCHAR(250),
current_expire BIGINT, -- seconds unix timestamp
current_count INT, -- replay counter
current_mech_id smallint,
new_token VARCHAR(250),
new_expire BIGINT, -- seconds unix timestamp
new_count INT,
new_mech_id smallint,
PRIMARY KEY(server, username, user_agent_id)
);
13 changes: 12 additions & 1 deletion src/c2s/mongoose_c2s.erl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
get_info/1, set_info/2,
get_mod_state/2, get_listener_opts/1, merge_mod_state/2, remove_mod_state/2,
get_ip/1, get_socket/1, get_lang/1, get_stream_id/1, hook_arg/5]).
-export([get_auth_mechs/1, c2s_stream_error/2, maybe_retry_state/1, merge_states/2]).
-export([get_auth_mechs/1, get_auth_mechs_to_announce/1,
c2s_stream_error/2, maybe_retry_state/1, merge_states/2]).
-export([route/2, reroute_buffer/2, reroute_buffer_to_pid/3, open_session/1]).
-export([set_jid/2, set_auth_module/2, state_timeout/1, handle_state_after_packet/3]).
-export([replace_resource/2, generate_random_resource/0]).
Expand Down Expand Up @@ -1158,6 +1159,16 @@ create_data(#{host_type := HostType, jid := Jid}) ->
get_auth_mechs(#c2s_data{host_type = HostType} = StateData) ->
[M || M <- cyrsasl:listmech(HostType), filter_mechanism(StateData, M)].

%% Mechanisms without XEP-0484 token mechanisms
%% (HT mechanisms are announced as inlined instead)
-spec get_auth_mechs_to_announce(data()) -> [mongoose_c2s_sasl:mechanism()].
get_auth_mechs_to_announce(StateData) ->
[M || M <- get_auth_mechs(StateData), not skip_announce_mechanism(M)].

-spec skip_announce_mechanism(binary()) -> boolean().
skip_announce_mechanism(Mech) ->
mod_fast_auth_token_generic_mech:skip_announce_mechanism(Mech).

-spec filter_mechanism(data(), binary()) -> boolean().
filter_mechanism(#c2s_data{socket = Socket}, <<"SCRAM-SHA-1-PLUS">>) ->
mongoose_c2s_socket:is_channel_binding_supported(Socket);
Expand Down
10 changes: 7 additions & 3 deletions src/c2s/mongoose_c2s_sasl.erl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
-type maybe_username() :: undefined | jid:luser().
-type success() :: #{server_out := undefined | binary(),
jid := jid:jid(),
auth_module := cyrsasl:sasl_module()}.
auth_module := cyrsasl:sasl_module(),
creds := mongoose_credentials:t()}.
-type continue() :: #{server_out := binary()}.
-type failure() :: #{server_out := binary() | {binary(), undefined | iodata()},
maybe_username := maybe_username()}.
Expand Down Expand Up @@ -48,7 +49,10 @@ start(C2SData, SaslAcc, Mech, ClientIn) ->
{error, SaslAcc, #{type => policy_violation, text => <<"Use of STARTTLS required">>}};
_ ->
AuthMech = mongoose_c2s:get_auth_mechs(C2SData),
SocketData = #{socket => Socket, auth_mech => AuthMech, listener_opts => LOpts},
%% Provide SaslAcc for readonly access, so the cyrsasl mechanism
%% has more visibility to initialize the mechanism state.
SocketData = #{socket => Socket, auth_mech => AuthMech, listener_opts => LOpts,
sasl_state => SaslAcc},
CyrSaslState = get_cyrsasl_state_from_acc(SaslAcc),
CyrSaslResult = cyrsasl:server_start(CyrSaslState, Mech, ClientIn, SocketData),
handle_sasl_step(C2SData, CyrSaslResult, SaslAcc)
Expand Down Expand Up @@ -78,7 +82,7 @@ handle_sasl_success(C2SData, Creds, SaslAcc) ->
User = mongoose_credentials:get(Creds, username),
LServer = mongoose_c2s:get_lserver(C2SData),
Jid = jid:make_bare(User, LServer),
Ret = #{server_out => ServerOut, jid => Jid, auth_module => AuthModule},
Ret = #{server_out => ServerOut, jid => Jid, auth_module => AuthModule, creds => Creds},
{success, SaslAcc, Ret}.

-spec handle_sasl_continue(
Expand Down
2 changes: 1 addition & 1 deletion src/c2s/mongoose_c2s_stanzas.erl
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ determine_features(StateData, HostType, LServer, _, _) ->

-spec maybe_sasl_mechanisms(mongoose_c2s:data()) -> [exml:element()].
maybe_sasl_mechanisms(StateData) ->
case mongoose_c2s:get_auth_mechs(StateData) of
case mongoose_c2s:get_auth_mechs_to_announce(StateData) of
[] -> [];
Mechanisms ->
[#xmlel{name = <<"mechanisms">>,
Expand Down
5 changes: 5 additions & 0 deletions src/config/mongoose_config_spec.erl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
process_domain_cert/1,
process_infinity_as_zero/1]).

%% For tests
-export([configurable_modules/0]).
-ignore_xref([configurable_modules/0]).

-include("mongoose_config_spec.hrl").

-type config_node() :: config_section() | config_list() | config_option().
Expand Down Expand Up @@ -753,6 +757,7 @@ configurable_modules() ->
mod_disco,
mod_event_pusher,
mod_extdisco,
mod_fast_auth_token,
mod_global_distrib,
mod_http_upload,
mod_inbox,
Expand Down
Loading

0 comments on commit 6acae7b

Please sign in to comment.