Skip to content

Commit

Permalink
fix(gateway-keys): when using gateway keys resolverParameter should b…
Browse files Browse the repository at this point in the history
…e ignored
  • Loading branch information
wbabyte committed Jun 26, 2024
1 parent 2695d79 commit ce04d1b
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import java.util.concurrent.ConcurrentHashMap;

/**
* Default processor provider that can be used by the policy to select the right {@link JWTProcessor}
* Default processor provider that can be used by the policy to select the right {@link com.nimbusds.jwt.proc.JWTProcessor}
* Internally, relies on different {@link JWTProcessorProvider} and act as a cache to avoid useless recreation and allow subsequent reuse.
*
* @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
Expand All @@ -36,13 +36,15 @@ public class DefaultJWTProcessorProvider implements JWTProcessorProvider {
static final String ATTR_INTERNAL_RESOLVED_PARAMETER = "resolvedParameter";

private final String resolverParameter;
private final KeyResolver publicKeyResolver;
private final Map<String, JWTProcessor<SecurityContext>> jwtProcessors;
private final JWTProcessorProvider jwtProcessorProvider;

public DefaultJWTProcessorProvider(final JWTPolicyConfiguration configuration) {
this.resolverParameter = configuration.getResolverParameter();
this.jwtProcessors = new ConcurrentHashMap<>();
this.publicKeyResolver = configuration.getPublicKeyResolver();
this.jwtProcessorProvider = initJWTProcessorResolver(configuration);
this.resolverParameter = initResolverParameter(configuration);
}

@Override
Expand All @@ -59,20 +61,26 @@ public Maybe<JWTProcessor<SecurityContext>> provide(HttpExecutionContext ctx) {
return Maybe.just(jwtProcessor);
}

// Resolver parameter is probably an EL expression, evaluate and try to hit the cache again.
final String resolvedParameter = ctx.getTemplateEngine().getValue(resolverParameter, String.class);
// EL evaluation is not allowed for GATEWAY_KEYS.
if (publicKeyResolver != KeyResolver.GATEWAY_KEYS) {
// Resolver parameter is probably an EL expression, evaluate and try to hit the cache again.
final String resolvedParameter = ctx.getTemplateEngine().getValue(resolverParameter, String.class);

// Put resolved parameter to be eventually reused by other processor providers and avoid multiple EL evaluations.
ctx.putInternalAttribute(ATTR_INTERNAL_RESOLVED_PARAMETER, resolvedParameter);
// Put resolved parameter to be eventually reused by other processor providers and avoid multiple EL evaluations.
ctx.putInternalAttribute(ATTR_INTERNAL_RESOLVED_PARAMETER, resolvedParameter);

jwtProcessor = jwtProcessors.get(resolvedParameter);
jwtProcessor = jwtProcessors.get(resolvedParameter);

if (jwtProcessor != null) {
return Maybe.just(jwtProcessor);
}
if (jwtProcessor != null) {
return Maybe.just(jwtProcessor);
}

// JWTProcessor is not cached yet, create it.
return jwtProcessorProvider.provide(ctx).doOnSuccess(p -> jwtProcessors.put(resolvedParameter, p));
// JWTProcessor is not cached yet, create it.
return jwtProcessorProvider.provide(ctx).doOnSuccess(p -> jwtProcessors.put(resolvedParameter, p));
} else {
// JWTProcessor is not cached yet, create it.
return jwtProcessorProvider.provide(ctx).doOnSuccess(p -> jwtProcessors.put(resolverParameter, p));
}
});
}

Expand All @@ -94,4 +102,22 @@ private JWTProcessorProvider initJWTProcessorResolver(JWTPolicyConfiguration con
throw new IllegalArgumentException("Unsupported key resolver " + publicKeyResolver);
}
}

private String initResolverParameter(JWTPolicyConfiguration configuration) {
final KeyResolver publicKeyResolver = configuration.getPublicKeyResolver();

if (publicKeyResolver == null) {
return null;
}

switch (publicKeyResolver) {
case GIVEN_KEY:
case JWKS_URL:
return configuration.getResolverParameter();
case GATEWAY_KEYS:
return KeyResolver.GATEWAY_KEYS.name();
default:
throw new IllegalArgumentException("Unsupported key resolver " + publicKeyResolver);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,12 @@ public static Stream<Arguments> provideParameters() {
void shouldProvideDependingOnConfiguredKeyResolver(KeyResolver keyResolver, Class<JWTProcessorProvider> providerClass) {
final JWTProcessor<SecurityContext> jwtProcessor = mock(JWTProcessor.class);

if (keyResolver != KeyResolver.GATEWAY_KEYS) {
when(configuration.getResolverParameter()).thenReturn(KEY);
when(templateEngine.getValue(KEY, String.class)).thenReturn(KEY);
when(ctx.getTemplateEngine()).thenReturn(templateEngine);
}
when(configuration.getPublicKeyResolver()).thenReturn(keyResolver);
when(configuration.getResolverParameter()).thenReturn(KEY);
when(ctx.getTemplateEngine()).thenReturn(templateEngine);
when(templateEngine.getValue(KEY, String.class)).thenReturn(KEY);

final DefaultJWTProcessorProvider cut = new DefaultJWTProcessorProvider(configuration);

Expand All @@ -89,27 +91,38 @@ void shouldProvideDependingOnConfiguredKeyResolver(KeyResolver keyResolver, Clas
final TestObserver<JWTProcessor<SecurityContext>> obs = cut.provide(ctx).test();
obs.assertResult(jwtProcessor);

// Check the evaluated EL has been pushed to internal context for eventually reuse.
verify(ctx).putInternalAttribute(ATTR_INTERNAL_RESOLVED_PARAMETER, KEY);
if (keyResolver != KeyResolver.GATEWAY_KEYS) {
// Check the evaluated EL has been pushed to internal context for eventually reuse.
verify(ctx).putInternalAttribute(ATTR_INTERNAL_RESOLVED_PARAMETER, KEY);

// Check the JWTProcessor has been put in cache.
assertEquals(jwtProcessor, cachedProcessors.get(KEY));
// Check the JWTProcessor has been put in cache.
assertEquals(jwtProcessor, cachedProcessors.get(KEY));
} else {
// Check the JWTProcessor has been put in cache.
assertEquals(jwtProcessor, cachedProcessors.get(KeyResolver.GATEWAY_KEYS.name()));
}
}

@ParameterizedTest
@MethodSource("provideParameters")
void shouldHitTheCacheWhenAlreadyProvided(KeyResolver keyResolver) {
final JWTProcessor<SecurityContext> jwtProcessor = mock(JWTProcessor.class);

if (keyResolver != KeyResolver.GATEWAY_KEYS) {
when(configuration.getResolverParameter()).thenReturn(KEY);
}
when(configuration.getPublicKeyResolver()).thenReturn(keyResolver);
when(configuration.getResolverParameter()).thenReturn(KEY);

final DefaultJWTProcessorProvider cut = new DefaultJWTProcessorProvider(configuration);

final JWTProcessorProvider jwtProcessorProvider = spyJWTProcessorProvider(cut);
final Map<String, JWTProcessor<SecurityContext>> cachedProcessors = spyJWTProcessors(cut);

cachedProcessors.put(KEY, jwtProcessor);
if (keyResolver == KeyResolver.GATEWAY_KEYS) {
cachedProcessors.put(KeyResolver.GATEWAY_KEYS.name(), jwtProcessor);
} else {
cachedProcessors.put(KEY, jwtProcessor);
}

final TestObserver<JWTProcessor<SecurityContext>> obs = cut.provide(ctx).test();
obs.assertResult(jwtProcessor);
Expand All @@ -123,10 +136,12 @@ void shouldHitTheCacheWhenAlreadyProvided(KeyResolver keyResolver) {
void shouldPutInCacheWithResolvedParameterWhenElExpression(KeyResolver keyResolver, Class<JWTProcessorProvider> providerClass) {
final JWTProcessor<SecurityContext> jwtProcessor = mock(JWTProcessor.class);

if (keyResolver != KeyResolver.GATEWAY_KEYS) {
when(configuration.getResolverParameter()).thenReturn(EL_EXPRESSION);
when(templateEngine.getValue(EL_EXPRESSION, String.class)).thenReturn(KEY);
when(ctx.getTemplateEngine()).thenReturn(templateEngine);
}
when(configuration.getPublicKeyResolver()).thenReturn(keyResolver);
when(configuration.getResolverParameter()).thenReturn(EL_EXPRESSION);
when(ctx.getTemplateEngine()).thenReturn(templateEngine);
when(templateEngine.getValue(EL_EXPRESSION, String.class)).thenReturn(KEY);

final DefaultJWTProcessorProvider cut = new DefaultJWTProcessorProvider(configuration);

Expand All @@ -140,36 +155,51 @@ void shouldPutInCacheWithResolvedParameterWhenElExpression(KeyResolver keyResolv
final TestObserver<JWTProcessor<SecurityContext>> obs = cut.provide(ctx).test();
obs.assertResult(jwtProcessor);

// Check the evaluated EL has been pushed to internal context for eventually reuse.
verify(ctx).putInternalAttribute(ATTR_INTERNAL_RESOLVED_PARAMETER, KEY);
if (keyResolver != KeyResolver.GATEWAY_KEYS) {
// Check the evaluated EL has been pushed to internal context for eventually reuse.
verify(ctx).putInternalAttribute(ATTR_INTERNAL_RESOLVED_PARAMETER, KEY);

// Check the JWTProcessor has been put in cache with the resolved EL expression.
assertEquals(jwtProcessor, cachedProcessors.get(KEY));
// Check the JWTProcessor has been put in cache with the resolved EL expression.
assertEquals(jwtProcessor, cachedProcessors.get(KEY));
} else {
// Check the JWTProcessor has been put in cache.
assertEquals(jwtProcessor, cachedProcessors.get(KeyResolver.GATEWAY_KEYS.name()));
}
}

@ParameterizedTest
@MethodSource("provideParameters")
void shouldHitTheCacheWithResolvedParameterWhenElExpression(KeyResolver keyResolver, Class<JWTProcessorProvider> providerClass) {
final JWTProcessor<SecurityContext> jwtProcessor = mock(JWTProcessor.class);

if (keyResolver != KeyResolver.GATEWAY_KEYS) {
when(configuration.getResolverParameter()).thenReturn(EL_EXPRESSION);
when(templateEngine.getValue(EL_EXPRESSION, String.class)).thenReturn(KEY);
when(ctx.getTemplateEngine()).thenReturn(templateEngine);
}
when(configuration.getPublicKeyResolver()).thenReturn(keyResolver);
when(configuration.getResolverParameter()).thenReturn(EL_EXPRESSION);
when(ctx.getTemplateEngine()).thenReturn(templateEngine);
when(templateEngine.getValue(EL_EXPRESSION, String.class)).thenReturn(KEY);

final DefaultJWTProcessorProvider cut = new DefaultJWTProcessorProvider(configuration);

final JWTProcessorProvider jwtProcessorProvider = spyJWTProcessorProvider(cut);
final Map<String, JWTProcessor<SecurityContext>> cachedProcessors = spyJWTProcessors(cut);

cachedProcessors.put(KEY, jwtProcessor);
if (keyResolver == KeyResolver.GATEWAY_KEYS) {
cachedProcessors.put(KeyResolver.GATEWAY_KEYS.name(), jwtProcessor);
} else {
cachedProcessors.put(KEY, jwtProcessor);
}

final TestObserver<JWTProcessor<SecurityContext>> obs = cut.provide(ctx).test();
obs.assertResult(jwtProcessor);

// Check cache tried with non resolved expression then resolved.
verify(cachedProcessors).get(EL_EXPRESSION);
verify(cachedProcessors).get(KEY);
if (keyResolver != KeyResolver.GATEWAY_KEYS) {
// Check cache tried with non resolved expression then resolved.
verify(cachedProcessors).get(EL_EXPRESSION);
verify(cachedProcessors).get(KEY);
} else {
verify(cachedProcessors).get(KeyResolver.GATEWAY_KEYS.name());
}
verifyNoInteractions(jwtProcessorProvider);
}

Expand Down

0 comments on commit ce04d1b

Please sign in to comment.