Skip to content

Commit 4a99b4d

Browse files
authored
fix: application:get_env/2 returns {ok, Value} (#288)
1 parent 71896a0 commit 4a99b4d

4 files changed

+285
-4
lines changed

src/oidcc_authorization.erl

+1-1
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ attempt_request_object(QueryParams, #oidcc_client_context{
222222
MaxClockSkew =
223223
case application:get_env(oidcc, max_clock_skew) of
224224
undefined -> 0;
225-
ClockSkew -> ClockSkew
225+
{ok, ClockSkew} -> ClockSkew
226226
end,
227227

228228
Claims = maps:merge(

src/oidcc_token.erl

+3-3
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ verify_exp_claim(#{<<"exp">> := Expiry}) ->
775775
MaxClockSkew =
776776
case application:get_env(oidcc, max_clock_skew) of
777777
undefined -> 0;
778-
ClockSkew -> ClockSkew
778+
{ok, ClockSkew} -> ClockSkew
779779
end,
780780
case erlang:system_time(second) > Expiry + MaxClockSkew of
781781
true -> {error, token_expired};
@@ -787,7 +787,7 @@ verify_nbf_claim(#{<<"nbf">> := Expiry}) ->
787787
MaxClockSkew =
788788
case application:get_env(oidcc, max_clock_skew) of
789789
undefined -> 0;
790-
ClockSkew -> ClockSkew
790+
{ok, ClockSkew} -> ClockSkew
791791
end,
792792
case erlang:system_time(second) < Expiry - MaxClockSkew of
793793
true -> {error, token_not_yet_valid};
@@ -1071,7 +1071,7 @@ token_request_claims(#oidcc_client_context{
10711071
MaxClockSkew =
10721072
case application:get_env(oidcc, max_clock_skew) of
10731073
undefined -> 0;
1074-
ClockSkew -> ClockSkew
1074+
{ok, ClockSkew} -> ClockSkew
10751075
end,
10761076

10771077
#{

test/oidcc_authorization_test.erl

+87
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,93 @@ create_redirect_url_with_request_object_test() ->
252252

253253
ok.
254254

255+
create_redirect_url_with_request_object_and_max_clock_skew_test() ->
256+
PrivDir = code:priv_dir(oidcc),
257+
258+
%% Enable none algorithm for test
259+
jose:unsecured_signing(true),
260+
261+
{ok, ValidConfigString} = file:read_file(PrivDir ++ "/test/fixtures/example-metadata.json"),
262+
{ok, #oidcc_provider_configuration{} = Configuration0} = oidcc_provider_configuration:decode_configuration(
263+
jose:decode(ValidConfigString)
264+
),
265+
266+
Configuration = Configuration0#oidcc_provider_configuration{
267+
request_parameter_supported = true,
268+
request_object_signing_alg_values_supported = [
269+
<<"none">>,
270+
<<"HS256">>,
271+
<<"RS256">>,
272+
<<"PS256">>,
273+
<<"ES256">>,
274+
<<"EdDSA">>
275+
],
276+
request_object_encryption_alg_values_supported = [
277+
<<"RSA1_5">>,
278+
<<"RSA-OAEP">>,
279+
<<"RSA-OAEP-256">>,
280+
<<"RSA-OAEP-384">>,
281+
<<"RSA-OAEP-512">>,
282+
<<"ECDH-ES">>,
283+
<<"ECDH-ES+A128KW">>,
284+
<<"ECDH-ES+A192KW">>,
285+
<<"ECDH-ES+A256KW">>,
286+
<<"A128KW">>,
287+
<<"A192KW">>,
288+
<<"A256KW">>,
289+
<<"A128GCMKW">>,
290+
<<"A192GCMKW">>,
291+
<<"A256GCMKW">>,
292+
<<"dir">>
293+
],
294+
request_object_encryption_enc_values_supported = [
295+
<<"A128CBC-HS256">>,
296+
<<"A192CBC-HS384">>,
297+
<<"A256CBC-HS512">>,
298+
<<"A128GCM">>,
299+
<<"A192GCM">>,
300+
<<"A256GCM">>
301+
]
302+
},
303+
304+
ClientId = <<"client_id">>,
305+
ClientSecret = <<"at_least_32_character_client_secret">>,
306+
307+
Jwks0 = jose_jwk:from_pem_file(PrivDir ++ "/test/fixtures/jwk.pem"),
308+
Jwks = Jwks0#jose_jwk{fields = #{<<"use">> => <<"enc">>}},
309+
310+
RedirectUri = <<"https://my.server/return">>,
311+
312+
ClientContext =
313+
oidcc_client_context:from_manual(Configuration, Jwks, ClientId, ClientSecret),
314+
315+
application:set_env(oidcc, max_clock_skew, 10),
316+
{ok, Url} = oidcc_authorization:create_redirect_url(ClientContext, #{
317+
redirect_uri => RedirectUri
318+
}),
319+
application:unset_env(oidcc, max_clock_skew),
320+
321+
#{query := QueryString} = uri_string:parse(Url),
322+
QueryParams0 = uri_string:dissect_query(QueryString),
323+
QueryParams1 = lists:map(
324+
fun({Key, Value}) -> {list_to_binary(Key), list_to_binary(Value)} end, QueryParams0
325+
),
326+
QueryParams = maps:from_list(QueryParams1),
327+
328+
{SignedToken, _} = jose_jwe:block_decrypt(Jwks, maps:get(<<"request">>, QueryParams)),
329+
330+
{true, Jwt, _} = jose_jwt:verify(jose_jwk:from_oct(ClientSecret), SignedToken),
331+
332+
#jose_jwt{
333+
fields = #{
334+
<<"nbf">> := ClientNbf
335+
}
336+
} = Jwt,
337+
338+
?assert(ClientNbf < os:system_time(seconds) - 5),
339+
340+
ok.
341+
255342
create_redirect_url_with_invalid_request_object_test() ->
256343
PrivDir = code:priv_dir(oidcc),
257344

test/oidcc_token_test.erl

+194
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,101 @@ retrieve_hs256_test() ->
326326

327327
ok.
328328

329+
retrieve_hs256_with_max_clock_skew_test() ->
330+
PrivDir = code:priv_dir(oidcc),
331+
332+
{ok, _} = application:ensure_all_started(oidcc),
333+
334+
{ok, ConfigurationBinary} = file:read_file(PrivDir ++ "/test/fixtures/example-metadata.json"),
335+
{ok,
336+
#oidcc_provider_configuration{token_endpoint = TokenEndpoint, issuer = Issuer} =
337+
Configuration} =
338+
oidcc_provider_configuration:decode_configuration(jose:decode(ConfigurationBinary)),
339+
340+
ClientId = <<"client_id">>,
341+
ClientSecret = <<"at_least_32_character_client_secret">>,
342+
LocalEndpoint = <<"https://my.server/auth">>,
343+
AuthCode = <<"1234567890">>,
344+
AccessToken = <<"access_token">>,
345+
RefreshToken = <<"refresh_token">>,
346+
Claims =
347+
#{
348+
<<"iss">> => Issuer,
349+
<<"sub">> => <<"sub">>,
350+
<<"aud">> => ClientId,
351+
<<"nbf">> => erlang:system_time(second) + 5,
352+
<<"iat">> => erlang:system_time(second) + 5,
353+
<<"exp">> => erlang:system_time(second) + 15,
354+
<<"at_hash">> => <<"hrOQHuo3oE6FR82RIiX1SA">>
355+
},
356+
357+
Jwk = jose_jwk:from_oct(<<"at_least_32_character_client_secret">>),
358+
359+
Jwt = jose_jwt:from(Claims),
360+
Jws = #{<<"alg">> => <<"HS256">>},
361+
{_Jws, Token} = jose_jws:compact(jose_jwt:sign(Jwk, Jws, Jwt)),
362+
363+
OtherJwk = jose_jwk:from_file(PrivDir ++ "/test/fixtures/openid-certification-jwks.json"),
364+
365+
TokenData =
366+
jsx:encode(#{
367+
<<"access_token">> => AccessToken,
368+
<<"token_type">> => <<"Bearer">>,
369+
<<"id_token">> => Token,
370+
<<"scope">> => <<"profile openid">>,
371+
<<"refresh_token">> => RefreshToken
372+
}),
373+
374+
ClientContext = oidcc_client_context:from_manual(
375+
Configuration, OtherJwk, ClientId, ClientSecret
376+
),
377+
378+
ok = meck:new(httpc, [no_link]),
379+
HttpFun =
380+
fun(
381+
post,
382+
{ReqTokenEndpoint, _Header, "application/x-www-form-urlencoded", _Body},
383+
_HttpOpts,
384+
_Opts
385+
) ->
386+
TokenEndpoint = ReqTokenEndpoint,
387+
{ok, {{"HTTP/1.1", 200, "OK"}, [{"content-type", "application/json"}], TokenData}}
388+
end,
389+
ok = meck:expect(httpc, request, HttpFun),
390+
391+
?assertMatch(
392+
{error, token_not_yet_valid},
393+
oidcc_token:retrieve(
394+
AuthCode,
395+
ClientContext,
396+
#{redirect_uri => LocalEndpoint}
397+
)
398+
),
399+
400+
application:set_env(oidcc, max_clock_skew, 10),
401+
402+
?assertMatch(
403+
{ok, #oidcc_token{
404+
id = #oidcc_token_id{token = Token, claims = Claims},
405+
access = #oidcc_token_access{token = AccessToken},
406+
refresh = #oidcc_token_refresh{token = RefreshToken},
407+
scope = [<<"profile">>, <<"openid">>]
408+
}},
409+
oidcc_token:retrieve(
410+
AuthCode,
411+
ClientContext,
412+
#{redirect_uri => LocalEndpoint}
413+
)
414+
),
415+
416+
true = meck:validate(httpc),
417+
418+
meck:unload(httpc),
419+
420+
application:unset_env(oidcc, max_clock_skew),
421+
422+
ok.
423+
329424
auth_method_client_secret_jwt_test() ->
330425
PrivDir = code:priv_dir(oidcc),
331426

@@ -454,6 +549,105 @@ auth_method_client_secret_jwt_test() ->
454549

455550
ok.
456551

552+
auth_method_client_secret_jwt_with_max_clock_skew_test() ->
553+
PrivDir = code:priv_dir(oidcc),
554+
555+
{ok, _} = application:ensure_all_started(oidcc),
556+
557+
{ok, ConfigurationBinary} = file:read_file(PrivDir ++ "/test/fixtures/example-metadata.json"),
558+
{ok, Configuration0} = oidcc_provider_configuration:decode_configuration(
559+
jose:decode(ConfigurationBinary)
560+
),
561+
562+
#oidcc_provider_configuration{token_endpoint = TokenEndpoint, issuer = Issuer} =
563+
Configuration = Configuration0#oidcc_provider_configuration{
564+
token_endpoint_auth_methods_supported = [
565+
<<"client_secret_jwt">>, <<"client_secret_basic">>
566+
],
567+
token_endpoint_auth_signing_alg_values_supported = [<<"HS256">>]
568+
},
569+
570+
ClientId = <<"client_id">>,
571+
ClientSecret = <<"client_secret">>,
572+
LocalEndpoint = <<"https://my.server/auth">>,
573+
AuthCode = <<"1234567890">>,
574+
AccessToken = <<"access_token">>,
575+
RefreshToken = <<"refresh_token">>,
576+
Claims =
577+
#{
578+
<<"iss">> => Issuer,
579+
<<"sub">> => <<"sub">>,
580+
<<"aud">> => ClientId,
581+
<<"iat">> => erlang:system_time(second),
582+
<<"exp">> => erlang:system_time(second) + 10,
583+
<<"at_hash">> => <<"hrOQHuo3oE6FR82RIiX1SA">>
584+
},
585+
586+
Jwk = jose_jwk:from_pem_file(PrivDir ++ "/test/fixtures/jwk.pem"),
587+
588+
Jwt = jose_jwt:from(Claims),
589+
Jws = #{<<"alg">> => <<"RS256">>},
590+
{_Jws, Token} =
591+
jose_jws:compact(
592+
jose_jwt:sign(Jwk, Jws, Jwt)
593+
),
594+
595+
TokenData =
596+
jsx:encode(#{
597+
<<"access_token">> => AccessToken,
598+
<<"token_type">> => <<"Bearer">>,
599+
<<"id_token">> => Token,
600+
<<"scope">> => <<"profile openid">>,
601+
<<"refresh_token">> => RefreshToken
602+
}),
603+
604+
ClientContext = oidcc_client_context:from_manual(Configuration, Jwk, ClientId, ClientSecret),
605+
606+
ok = meck:new(httpc, [no_link]),
607+
HttpFun =
608+
fun(
609+
post,
610+
{ReqTokenEndpoint, _, "application/x-www-form-urlencoded", Body},
611+
_HttpOpts,
612+
_Opts
613+
) ->
614+
TokenEndpoint = ReqTokenEndpoint,
615+
BodyMap = maps:from_list(uri_string:dissect_query(Body)),
616+
617+
ClientAssertion = maps:get("client_assertion", BodyMap),
618+
619+
{true, ClientAssertionJwt, _} = jose_jwt:verify(
620+
jose_jwk:from_oct(ClientSecret), binary:list_to_bin(ClientAssertion)
621+
),
622+
623+
#jose_jwt{
624+
fields = #{
625+
<<"nbf">> := ClientTokenNbf
626+
}
627+
} = ClientAssertionJwt,
628+
629+
?assert(ClientTokenNbf < os:system_time(seconds) - 5),
630+
631+
{ok, {{"HTTP/1.1", 200, "OK"}, [{"content-type", "application/json"}], TokenData}}
632+
end,
633+
ok = meck:expect(httpc, request, HttpFun),
634+
635+
application:set_env(oidcc, max_clock_skew, 10),
636+
637+
oidcc_token:retrieve(
638+
AuthCode,
639+
ClientContext,
640+
#{redirect_uri => LocalEndpoint}
641+
),
642+
643+
true = meck:validate(httpc),
644+
645+
meck:unload(httpc),
646+
647+
application:unset_env(oidcc, max_clock_skew),
648+
649+
ok.
650+
457651
auth_method_private_key_jwt_no_supported_alg_test() ->
458652
PrivDir = code:priv_dir(oidcc),
459653

0 commit comments

Comments
 (0)