10
10
11
11
groups () -> [].
12
12
13
- all () -> [validate_jwt , keys_are_cached ].
13
+ all () -> [ validate_jwt ,
14
+ keys_are_only_refreshed_once_per_kid ].
14
15
15
16
init_per_suite (Config ) ->
16
17
application :ensure_all_started (jose ),
@@ -22,9 +23,9 @@ init_per_testcase(_TestCase, Config) ->
22
23
id_token_jws :generate_key_for (<<" RS256" >>, #{key_size => 1024 }),
23
24
Claims = #{ <<" exp" >> => erlang :system_time (second ) + 10 },
24
25
Jwt = id_token_jws :sign (Claims , Jwk ),
25
- mock_id_provider (PublicKeyMap ),
26
+ mock_id_provider (PublicKeyMap , 0 ),
26
27
application :ensure_all_started (id_token ),
27
- [{jwt , Jwt } | Config ].
28
+ [{jwt , Jwt }, { pubkeys , [ PublicKeyMap ]} | Config ].
28
29
end_per_testcase (_TestCase , Config ) ->
29
30
application :stop (id_token ),
30
31
meck :unload ([id_token_jwks , hackney ]),
@@ -36,19 +37,47 @@ validate_jwt(Config) ->
36
37
37
38
keys_are_cached (Config ) ->
38
39
Jwt = ? config (jwt , Config ),
39
- id_token :validate (? ID_PROVIDER , Jwt ),
40
- id_token :validate (? ID_PROVIDER , Jwt ),
40
+ ? assertMatch ({ ok , _ }, id_token :validate (? ID_PROVIDER , Jwt ) ),
41
+ ? assertMatch ({ ok , _ }, id_token :validate (? ID_PROVIDER , Jwt ) ),
41
42
1 = meck :num_calls (id_token_jwks , get_pub_keys , 1 ),
42
43
ok .
43
44
44
- mock_id_provider (PublicKeyMap ) ->
45
- meck :expect (id_token_jwks , get_providers , 0 ,
46
- [{? ID_PROVIDER , ? WELL_KNOWN_URI }]),
45
+ keys_are_only_refreshed_once_per_kid (Config ) ->
46
+ % % init pubkey cache with config from init_per_testcase
47
+ CurrentKeyCache = #{ exp_at => id_token_util :now_gregorian_seconds () + 10 ,
48
+ keys => ? config (pubkeys , Config )},
49
+ ok = meck :expect (id_token_provider , get_cached_keys , 1 , CurrentKeyCache ),
50
+
51
+ % % create JWT with kid that's not yet in the pubkey cache
52
+ {Jwk , NewPubkey } = id_token_jws :generate_key_for (<<" RS256" >>, #{key_size => 1024 }),
53
+ Claims = #{ <<" exp" >> => erlang :system_time (second ) + 10 },
54
+ Jwt = id_token_jws :sign (Claims , Jwk ),
55
+
56
+ % % set up provider to return new pubkeys with a 50 ms delay
57
+ HttpReponseDelay = 50 ,
58
+ mock_id_provider (NewPubkey , HttpReponseDelay ),
59
+ ? assertEqual (0 , meck :num_calls (id_token_jwks , get_pub_keys , 1 )),
60
+
61
+ % % try to validate multiple JWTs based on kid that's not yet in the key cache
62
+ spawn (fun () -> id_token :validate (? ID_PROVIDER , Jwt ) end ),
63
+ spawn (fun () -> id_token :validate (? ID_PROVIDER , Jwt ) end ),
64
+ spawn (fun () -> id_token :validate (? ID_PROVIDER , Jwt ) end ),
65
+ timer :sleep (10 * HttpReponseDelay ),
66
+
67
+ % % ensure that the pubkey cache was only refreshed once
68
+ ? assertEqual (1 , meck :num_calls (id_token_jwks , get_pub_keys , 1 )),
69
+ ? assertMatch ({ok , _ }, id_token :validate (? ID_PROVIDER , Jwt )),
70
+
71
+ meck :unload ([id_token_provider ]).
72
+
73
+ mock_id_provider (PublicKeyMap , HttpReponseDelay ) ->
74
+ meck :expect (id_token_jwks , get_providers , 0 , [{? ID_PROVIDER , ? WELL_KNOWN_URI }]),
47
75
meck :expect (hackney , request ,
48
76
fun (get , ? WELL_KNOWN_URI , _ , _ , _ ) ->
49
77
Body = jsx :encode (#{<<" jwks_uri" >> => ? JWKS_URI }),
50
78
{ok , 200 , [], Body };
51
79
(get , ? JWKS_URI , _ , _ , _ ) ->
80
+ timer :sleep (HttpReponseDelay ),
52
81
Body = jsx :encode (#{<<" keys" >> => [PublicKeyMap ]}),
53
82
MaxAge = <<" max-age=3600" >>,
54
83
Headers = [{<<" Cache-Control" >>, MaxAge }],
0 commit comments