@Override public Jwk get(final String keyId) throws JwkException { if (!bucket.consume()) { throw new RateLimitReachedException(bucket.willLeakIn()); } return provider.get(keyId); }
@Override public Jwk call() throws Exception { return provider.get(keyId); } });
/** * Creates a {@link JwkProvider} * * @return a newly created {@link JwkProvider} */ public JwkProvider build() { JwkProvider urlProvider = new UrlJwkProvider(url); if (this.rateLimited) { urlProvider = new RateLimitedJwkProvider(urlProvider, bucket); } if (this.cached) { urlProvider = new GuavaCachedJwkProvider(urlProvider, cacheSize, expiresIn, expiresUnit); } return urlProvider; } }
@Override public RSAPublicKey getPublicKeyById(String keyId) { try { final PublicKey publicKey = jwkProvider.get(keyId).getPublicKey(); if (!(publicKey instanceof RSAPublicKey)) { throw new IllegalArgumentException(String.format("Key with ID '%s' was found in JWKS but is not a RSA-key.", keyId)); } return (RSAPublicKey) publicKey; } catch (JwkException e) { throw new IllegalArgumentException(String.format("Key with ID '%s' couldn't be fetched from JWKS.", keyId), e); } }
@Test public void shouldNotAddMoreTokensThatTheBucketSize() throws Exception { Bucket bucket = new BucketImpl(SIZE, RATE, TimeUnit.MILLISECONDS); assertThat(bucket, notNullValue()); assertThat(bucket.willLeakIn(SIZE), equalTo(0L)); //Give some time to fill the already full bucket pause(SIZE * RATE * 2); assertThat(bucket.consume(SIZE), equalTo(true)); assertThat(bucket.consume(), equalTo(false)); }
@Test public void shouldCreateCachedAndRateLimitedProviderWithCustomValues() { JwkProvider provider = new JwkProviderBuilder(domain) .cached(10, 24, TimeUnit.HOURS) .rateLimited(10, 24, TimeUnit.HOURS) .build(); assertThat(provider, notNullValue()); assertThat(provider, instanceOf(GuavaCachedJwkProvider.class)); JwkProvider baseProvider = ((GuavaCachedJwkProvider) provider).getBaseProvider(); assertThat(baseProvider, instanceOf(RateLimitedJwkProvider.class)); assertThat(((RateLimitedJwkProvider) baseProvider).getBaseProvider(), instanceOf(UrlJwkProvider.class)); }
@Test public void shouldThrowForNonRSAKey() throws Exception { final String kid = randomKeyId(); Map<String, Object> values = nonRSAValues(kid); Jwk jwk = Jwk.fromValues(values); expectedException.expect(InvalidPublicKeyException.class); expectedException.expectMessage("The key is not of type RSA"); jwk.getPublicKey(); }
@Test public void shouldCreateCachedAndRateLimitedProviderByDefault() { JwkProvider provider = new JwkProviderBuilder(domain).build(); assertThat(provider, notNullValue()); assertThat(provider, instanceOf(GuavaCachedJwkProvider.class)); JwkProvider baseProvider = ((GuavaCachedJwkProvider) provider).getBaseProvider(); assertThat(baseProvider, instanceOf(RateLimitedJwkProvider.class)); assertThat(((RateLimitedJwkProvider) baseProvider).getBaseProvider(), instanceOf(UrlJwkProvider.class)); }
public JwksRSAKeyProvider(URL jwksUri) { this.jwkProvider = new JwkProviderBuilder(jwksUri).build(); }
@Test public void shouldNotThrowInvalidArgumentExceptionOnMissingKidParam() throws Exception { //kid is optional - https://tools.ietf.org/html/rfc7517#section-4.5 final String kid = randomKeyId(); Map<String, Object> values = publicKeyValues(kid, KEY_OPS_LIST); values.remove("kid"); Jwk.fromValues(values); }
private void updateAvailableTokens() { final long ratePerToken = getRatePerToken(); final long elapsed = getTimeSinceLastTokenAddition(); if (elapsed < ratePerToken) { return; } accumDelta = elapsed % ratePerToken; long count = elapsed / ratePerToken; if (count > size - available) { count = size - available; } if (count > 0) { available += count; } restartStopWatch(); }
/** * Enable the cache specifying size and expire time. * * @param bucketSize max number of jwks to deliver in the given rate. * @param refillRate amount of time to wait before a jwk can the jwk will be cached * @param unit unit of time for the expire of jwk * @return the builder */ public JwkProviderBuilder rateLimited(long bucketSize, long refillRate, TimeUnit unit) { bucket = new BucketImpl(bucketSize, refillRate, unit); return this; }
@Override public synchronized boolean consume() { return consume(1); }
@Override public synchronized long willLeakIn() { return willLeakIn(1); }
/** * Creates a provider that loads from the given domain's well-known directory. * <br><br> It can be a url link 'https://samples.auth0.com' or just a domain 'samples.auth0.com'. * If the protocol (http or https) is not provided then https is used by default. * The default jwks path "/.well-known/jwks.json" is appended to the given string domain. * <br><br> For example, when the domain is "samples.auth0.com" * the jwks url that will be used is "https://samples.auth0.com/.well-known/jwks.json" * <br><br> Use {@link #UrlJwkProvider(URL)} if you need to pass a full URL. * * @param domain where jwks is published */ public UrlJwkProvider(String domain) { this(urlForDomain(domain)); }
/** * Creates a new Builder with a domain where to look for the jwks. * <br><br> It can be a url link 'https://samples.auth0.com' or just a domain 'samples.auth0.com'. * If the protocol (http or https) is not provided then https is used by default. * The default jwks path "/.well-known/jwks.json" is appended to the given string domain. * <br><br> For example, when the domain is "samples.auth0.com" * the jwks url that will be used is "https://samples.auth0.com/.well-known/jwks.json" * <br><br> Use {@link #JwkProviderBuilder(URL)} if you need to pass a full URL. * @param domain where jwks is published * @throws IllegalStateException if domain is null * @see UrlJwkProvider#UrlJwkProvider(String) */ public JwkProviderBuilder(String domain) { this(buildJwkUrl(domain)); }
@Test public void shouldCalculateRemainingLeakTimeForOneToken() throws Exception { Bucket bucket = new BucketImpl(SIZE, RATE, TimeUnit.MILLISECONDS); assertThat(bucket, notNullValue()); //Consume 5 tokens assertThat(bucket.consume(5), equalTo(true)); assertThat(bucket.willLeakIn(), allOf(greaterThan(0L), lessThanOrEqualTo(RATE))); // wait half rate time and check if the wait time is correct pause(RATE / 2); assertThat(bucket.willLeakIn(), allOf(greaterThan(0L), lessThanOrEqualTo(RATE / 2))); }
@Test public void shouldCreateCachedAndRateLimitedProvider() { JwkProvider provider = new JwkProviderBuilder(domain) .cached(true) .rateLimited(true) .build(); assertThat(provider, notNullValue()); assertThat(provider, instanceOf(GuavaCachedJwkProvider.class)); JwkProvider baseProvider = ((GuavaCachedJwkProvider) provider).getBaseProvider(); assertThat(baseProvider, instanceOf(RateLimitedJwkProvider.class)); assertThat(((RateLimitedJwkProvider) baseProvider).getBaseProvider(), instanceOf(UrlJwkProvider.class)); }
@Test public void shouldCarryDeltaWhenManyTokensAreRequested() throws Exception { Bucket bucket = new BucketImpl(5, 1000, TimeUnit.MILLISECONDS); assertThat(bucket, notNullValue()); //Consume all tokens. Expect to wait 5 seconds for refill assertThat(bucket.consume(5), equalTo(true)); assertThat(bucket.willLeakIn(5), allOf(greaterThanOrEqualTo(4900L), lessThanOrEqualTo(5000L))); //wait 1500ms to have 1 token. pause(1500); //Consume 1 and expect to wait 500 + 4000 ms if we want to consume 5 again. assertThat(bucket.consume(), equalTo(true)); assertThat(bucket.willLeakIn(5), allOf(greaterThanOrEqualTo(4400L), lessThanOrEqualTo(4500L))); }
@Test public void shouldCalculateRemainingLeakTimeForManyTokens() throws Exception { Bucket bucket = new BucketImpl(SIZE, RATE, TimeUnit.MILLISECONDS); assertThat(bucket, notNullValue()); //Consume 3 tokens assertThat(bucket.consume(3), equalTo(true)); //Expected to wait 3 * RATE time at most to be able to consume 5 tokens assertThat(bucket.willLeakIn(5), allOf(greaterThanOrEqualTo(RATE * 2), lessThanOrEqualTo(RATE * 3))); pause(RATE * 3); assertThat(bucket.willLeakIn(5), allOf(greaterThanOrEqualTo(0L), lessThanOrEqualTo(RATE))); }