Skip to content

Commit 0c21c93

Browse files
Accept float also for token expiry
1 parent b11526f commit 0c21c93

3 files changed

Lines changed: 43 additions & 7 deletions

File tree

deps/oauth2_client/src/jwt_helper.erl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ decode(Token) ->
1818
{error, {invalid_token, Type, Err, Stacktrace}}
1919
end.
2020

21-
get_expiration_time(#{<<"exp">> := Exp}) when is_integer(Exp) -> {ok, Exp};
21+
get_expiration_time(#{<<"exp">> := Exp}) when is_number(Exp) -> {ok, trunc(Exp)};
22+
get_expiration_time(#{<<"exp">> := _}) -> {error, invalid_exp_field};
2223
get_expiration_time(#{}) -> {error, missing_exp_field}.

deps/rabbitmq_auth_backend_oauth2/src/rabbit_auth_backend_oauth2.erl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ 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" may be a fractional NumericDate (RFC 7519); accept floats too.
164163
#{<<"exp">> := Exp} when is_number(Exp) ->
165164
trunc(Exp);
166165
_ ->
@@ -236,14 +235,15 @@ ensure_same_username(PreferredUsernameClaims, CurrentDecodedToken, NewDecodedTok
236235
_ -> {error, mismatch_username_after_token_refresh}
237236
end.
238237

239-
%% "exp" may be a fractional NumericDate (RFC 7519); accept floats too.
240238
validate_token_expiry(#{<<"exp">> := Exp}) when is_number(Exp) ->
241-
ExpSeconds = trunc(Exp),
242239
Now = os:system_time(seconds),
243-
case ExpSeconds =< Now of
244-
true -> {error, rabbit_misc:format("Provided JWT token has expired at timestamp ~tp (validated at ~tp)", [ExpSeconds, Now])};
240+
ExpTs = trunc(Exp),
241+
case ExpTs =< Now of
242+
true -> {error, rabbit_misc:format("Provided JWT token has expired at timestamp ~tp (validated at ~tp)", [ExpTs, Now])};
245243
false -> ok
246244
end;
245+
validate_token_expiry(#{<<"exp">> := _}) ->
246+
{error, "Provided JWT token has an invalid exp claim: value is not a number"};
247247
validate_token_expiry(#{}) -> ok.
248248

249249
-spec check_token(raw_jwt_token(), {resource_server(), internal_oauth_provider()}) ->

deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ 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,
43+
test_token_expiry_with_float_exp,
44+
test_token_expiry_with_non_numeric_exp,
4445
test_invalid_signature,
4546
test_incorrect_kid,
4647
normalize_token_scope_using_multiple_scopes_key,
@@ -1242,6 +1243,40 @@ test_token_expiration(_) ->
12421243
?assertMatch({refused, _, _},
12431244
user_login_authentication(Username, [{password, Token}])).
12441245

1246+
test_token_expiry_with_float_exp(_) ->
1247+
Username = <<"username">>,
1248+
set_env(resource_server_id, <<"rabbitmq">>),
1249+
1250+
%% A valid, future float exp must be accepted and expiry_timestamp must
1251+
%% return the truncated integer — not the atom 'never'.
1252+
FutureFloatExp = float(os:system_time(seconds) + 600),
1253+
FutureToken = (?UTIL_MOD:token_with_sub(?UTIL_MOD:expirable_token(), Username))
1254+
#{<<"exp">> := FutureFloatExp},
1255+
{ok, #auth_user{username = Username} = User} =
1256+
user_login_authentication(Username, [{password, FutureToken}]),
1257+
?assertEqual(trunc(FutureFloatExp), rabbit_auth_backend_oauth2:expiry_timestamp(User)),
1258+
1259+
%% An already-expired float exp must be refused, not silently accepted.
1260+
PastFloatExp = float(os:system_time(seconds) - 10),
1261+
ExpiredToken = (?UTIL_MOD:token_with_sub(?UTIL_MOD:expirable_token(), Username))
1262+
#{<<"exp">> := PastFloatExp},
1263+
?assertMatch({refused, _, _},
1264+
user_login_authentication(Username, [{password, ExpiredToken}])).
1265+
1266+
test_token_expiry_with_non_numeric_exp(_) ->
1267+
Username = <<"username">>,
1268+
set_env(resource_server_id, <<"rabbitmq">>),
1269+
1270+
%% A token whose exp claim is not a number (e.g. a string) must be refused,
1271+
%% not silently accepted because the is_number guard falls through to the
1272+
%% no-exp-field catch-all clause.
1273+
lists:foreach(fun(NonNumericExp) ->
1274+
InvalidToken = (?UTIL_MOD:token_with_sub(?UTIL_MOD:expirable_token(), Username))
1275+
#{<<"exp">> := NonNumericExp},
1276+
?assertMatch({refused, _, _},
1277+
user_login_authentication(Username, [{password, InvalidToken}]))
1278+
end, [<<"1700000300">>, true, false, null]).
1279+
12451280
test_incorrect_kid(_) ->
12461281
AltKid = <<"other-token-key">>,
12471282
Username = <<"username">>,

0 commit comments

Comments
 (0)