Skip to content

Commit 53b69ce

Browse files
OAuth 2: honour fractional exp token expiry timestamps
RFC 7519 allows exp to be a fractional value but `validate_token_expiry/1` and `expiry_timestamp/1` both guarded on `is_integer/1`.
1 parent 446f76e commit 53b69ce

2 files changed

Lines changed: 34 additions & 5 deletions

File tree

deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,9 @@ update_state(AuthUser, NewToken) ->
160160
-spec expiry_timestamp(rabbit_types:auth_user()) -> integer() | never.
161161
expiry_timestamp(#auth_user{impl = DecodedTokenFun}) ->
162162
case DecodedTokenFun() of
163-
#{<<"exp">> := Exp} when is_integer(Exp) ->
164-
Exp;
163+
%% "exp" may be a fractional NumericDate (RFC 7519); accept floats too.
164+
#{<<"exp">> := Exp} when is_number(Exp) ->
165+
trunc(Exp);
165166
_ ->
166167
never
167168
end.
@@ -235,10 +236,12 @@ ensure_same_username(PreferredUsernameClaims, CurrentDecodedToken, NewDecodedTok
235236
_ -> {error, mismatch_username_after_token_refresh}
236237
end.
237238

238-
validate_token_expiry(#{<<"exp">> := Exp}) when is_integer(Exp) ->
239+
%% "exp" may be a fractional NumericDate (RFC 7519); accept floats too.
240+
validate_token_expiry(#{<<"exp">> := Exp}) when is_number(Exp) ->
241+
ExpSeconds = trunc(Exp),
239242
Now = os:system_time(seconds),
240-
case Exp =< Now of
241-
true -> {error, rabbit_misc:format("Provided JWT token has expired at timestamp ~tp (validated at ~tp)", [Exp, Now])};
243+
case ExpSeconds =< Now of
244+
true -> {error, rabbit_misc:format("Provided JWT token has expired at timestamp ~tp (validated at ~tp)", [ExpSeconds, Now])};
242245
false -> ok
243246
end;
244247
validate_token_expiry(#{}) -> ok.

deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ all() ->
4040
test_restricted_vhost_access_with_a_valid_token,
4141
test_insufficient_permissions_in_a_valid_token,
4242
test_token_expiration,
43+
test_token_expiration_with_float_exp,
4344
test_invalid_signature,
4445
test_incorrect_kid,
4546
normalize_token_scope_using_multiple_scopes_key,
@@ -1176,6 +1177,31 @@ test_insufficient_permissions_in_a_valid_token(_) ->
11761177
assert_topic_access_refused(User, VHost, <<"bar">>, read,
11771178
#{routing_key => <<"foo/#">>}).
11781179

1180+
%% A fractional "exp" (RFC 7519) must still be honoured: an expired float
1181+
%% "exp" is rejected at login, a future one yields a numeric expiry_timestamp.
1182+
test_token_expiration_with_float_exp(_) ->
1183+
Username = <<"username">>,
1184+
Jwk = ?UTIL_MOD:fixture_jwk(),
1185+
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
1186+
set_env(key_config, UaaEnv),
1187+
set_env(resource_server_id, <<"rabbitmq">>),
1188+
1189+
Now = os:system_time(seconds),
1190+
1191+
ExpiredToken = (?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username))#{
1192+
<<"exp">> => (Now - 10) + 0.5},
1193+
ExpiredSigned = ?UTIL_MOD:sign_token_hs(ExpiredToken, Jwk),
1194+
?assertMatch({refused, _, _},
1195+
user_login_authentication(Username, [{password, ExpiredSigned}])),
1196+
1197+
FutureExp = Now + 60,
1198+
ValidToken = (?UTIL_MOD:token_with_sub(?UTIL_MOD:fixture_token(), Username))#{
1199+
<<"exp">> => FutureExp + 0.5},
1200+
ValidSigned = ?UTIL_MOD:sign_token_hs(ValidToken, Jwk),
1201+
{ok, #auth_user{username = Username} = User} =
1202+
user_login_authentication(Username, [{password, ValidSigned}]),
1203+
?assertEqual(FutureExp, rabbit_auth_backend_oauth2:expiry_timestamp(User)).
1204+
11791205
test_invalid_signature(_) ->
11801206
Username = <<"username">>,
11811207
Jwk = ?UTIL_MOD:fixture_jwk(),

0 commit comments

Comments
 (0)