Skip to content

Commit

Permalink
feature: support meta-annotations (#191)
Browse files Browse the repository at this point in the history
* Look for annotations more robustly by using Spring `AnnotationsUtils`
  • Loading branch information
janolaveide committed Aug 5, 2020
1 parent 96788a2 commit bc8c32f
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private boolean assertValidAnnotation(Annotation annotation) {
}
}

private Annotation getAnnotation(Method method, List<Class<? extends Annotation>> types) {
protected Annotation getAnnotation(Method method, List<Class<? extends Annotation>> types) {
Annotation annotation = findAnnotation(method.getAnnotations(), types);
if (annotation != null) {
log.debug("method " + method + " marked @{}", annotation.annotationType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import no.nav.security.token.support.filter.JwtTokenValidationFilter;
import no.nav.security.token.support.spring.validation.interceptor.BearerTokenClientHttpRequestInterceptor;
import no.nav.security.token.support.spring.validation.interceptor.JwtTokenHandlerInterceptor;
import no.nav.security.token.support.spring.validation.interceptor.SpringJwtTokenAnnotationHandler;

@Configuration
@EnableConfigurationProperties(MultiIssuerProperties.class)
Expand Down Expand Up @@ -90,7 +91,7 @@ public RequestContextListener requestContextListener() {

@Bean
public JwtTokenValidationFilter tokenValidationFilter(MultiIssuerConfiguration config, TokenValidationContextHolder tokenValidationContextHolder) {
return new JwtTokenValidationFilter(new JwtTokenValidationHandler(config), tokenValidationContextHolder);
return new JwtTokenValidationFilter(new JwtTokenValidationHandler(config), tokenValidationContextHolder);

}

Expand All @@ -104,7 +105,7 @@ public BearerTokenClientHttpRequestInterceptor bearerTokenClientHttpRequestInter
public JwtTokenHandlerInterceptor getControllerInterceptor() {
logger.debug("registering OIDC token controller handler interceptor");
return new JwtTokenHandlerInterceptor(enableOIDCTokenValidation,
new JwtTokenAnnotationHandler(new SpringTokenValidationContextHolder()));
new SpringJwtTokenAnnotationHandler(new SpringTokenValidationContextHolder()));
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package no.nav.security.token.support.spring.validation.interceptor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import org.springframework.core.annotation.AnnotationUtils;

import no.nav.security.token.support.core.context.TokenValidationContextHolder;
import no.nav.security.token.support.core.validation.JwtTokenAnnotationHandler;

public class SpringJwtTokenAnnotationHandler extends JwtTokenAnnotationHandler {


public SpringJwtTokenAnnotationHandler(TokenValidationContextHolder tokenValidationContextHolder) {
super(tokenValidationContextHolder);
}

@Override
protected Annotation getAnnotation(Method method, List<Class<? extends Annotation>> types) {
return Optional.ofNullable(scanAnnotation(method, types))
.orElseGet(() -> scanAnnotation(method.getDeclaringClass(), types));
}

private static Annotation scanAnnotation(Method m, List<Class<? extends Annotation>> types) {
return types.stream()
.map(t -> AnnotationUtils.findAnnotation(m, t))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}

private static Annotation scanAnnotation(Class<?> clazz, List<Class<? extends Annotation>> types) {
return types.stream()
.map(t -> AnnotationUtils.findAnnotation(clazz, t))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class JwtTokenHandlerInterceptorTest {
void setup() {
contextHolder = createContextHolder();
contextHolder.setTokenValidationContext(new TokenValidationContext(Collections.emptyMap()));
JwtTokenAnnotationHandler jwtTokenAnnotationHandler = new JwtTokenAnnotationHandler(contextHolder);
JwtTokenAnnotationHandler jwtTokenAnnotationHandler = new SpringJwtTokenAnnotationHandler(contextHolder);
Map<String, Object> annotationAttributesMap = new HashMap<>();
annotationAttributesMap.put("ignore", new String[]{"org.springframework", IgnoreClass.class.getName()});
AnnotationAttributes annotationAttrs = AnnotationAttributes.fromMap(annotationAttributesMap);
Expand Down Expand Up @@ -113,6 +113,55 @@ void methodShouldBeProtectedOnClassProtectedWithClaims() {
assertTrue(interceptor.preHandle(request, response, handlerMethod));
}

@Test
void methodIsUnprotectedAccessShouldBeAllowedMeta() {
HandlerMethod handlerMethod = handlerMethod(new UnprotectedClassMeta(), "test");
assertTrue(interceptor.preHandle(request, response, handlerMethod));
}

@Test
void methodShouldBeProtectedOnUnprotectedClassMeta() {
HandlerMethod handlerMethod = handlerMethod(new UnprotectedClassProtectedMethodMeta(), "protectedMethod");
assertThrows(JwtTokenUnauthorizedException.class,
() -> interceptor.preHandle(request, response, handlerMethod));
setupValidOidcContext();
assertTrue(interceptor.preHandle(request, response, handlerMethod));
}

@Test
void methodShouldBeUnprotectedOnProtectedClassMeta() {
HandlerMethod handlerMethod = handlerMethod(new ProtectedClassUnprotectedMethodMeta(), "unprotectedMethod");
assertTrue(interceptor.preHandle(request, response, handlerMethod));
}

@Test
void methodShouldBeProtectedOnProtectedSuperClassMeta() {
HandlerMethod handlerMethod = handlerMethod(new ProtectedSubClassMeta(), "test");
assertThrows(JwtTokenUnauthorizedException.class,
() -> interceptor.preHandle(request, response, handlerMethod));
setupValidOidcContext();
assertTrue(interceptor.preHandle(request, response, handlerMethod));
}

@Test
void unprotectedMetaClassProtectedMethodMeta() {
HandlerMethod handlerMethod = handlerMethod(new UnprotectedClassProtectedMethodMeta(), "protectedMethod");
assertThrows(JwtTokenUnauthorizedException.class,
() -> interceptor.preHandle(request, response, handlerMethod));
setupValidOidcContext();
assertTrue(interceptor.preHandle(request, response, handlerMethod));
}

@Test
void methodShouldBeProtectedOnClassProtectedWithClaimsMeta() {
HandlerMethod handlerMethod = handlerMethod(new ProtectedWithClaimsClassProtectedMethodMeta(),
"protectedMethod");
assertThrows(JwtTokenUnauthorizedException.class,
() -> interceptor.preHandle(request, response, handlerMethod));
setupValidOidcContext();
assertTrue(interceptor.preHandle(request, response, handlerMethod));
}

private static TokenValidationContext createOidcValidationContext(String issuerShortName, JwtToken jwtToken) {
Map<String, JwtToken> map = new ConcurrentHashMap<>();
map.put(issuerShortName, jwtToken);
Expand Down Expand Up @@ -233,4 +282,63 @@ public void protectedWithClaimsMethod() {
}
}

@UnprotectedMeta
private class UnprotectedClassMeta {
public void test() {
}
}

@UnprotectedMeta
private class UnprotectedClassProtectedMethodMeta {
@ProtectedMeta
public void protectedMethod() {
}
}



@ProtectedMeta
private class ProtectedClassMeta {
public void test() {
}
}

@ProtectedMeta
private class ProtectedSuperClassMeta {

}

private class ProtectedSubClassMeta extends ProtectedSuperClassMeta {
public void test() {
}
}



@ProtectedMeta
private class ProtectedClassUnprotectedMethodMeta {
public void protectedMethod() {
}

@UnprotectedMeta
public void unprotectedMethod() {
}
}



@ProtectedWithClaimsMeta
private class ProtectedWithClaimsClassProtectedMethodMeta {
@ProtectedMeta
public void protectedMethod() {
}

@UnprotectedMeta
public void unprotectedMethod() {
}

public void protectedWithClaimsMethod() {
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package no.nav.security.token.support.spring.validation.interceptor;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import no.nav.security.token.support.core.api.Protected;
import no.nav.security.token.support.core.api.ProtectedWithClaims;
import no.nav.security.token.support.core.api.Unprotected;

@Protected
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)

@interface ProtectedMeta {

}

@ProtectedWithClaims(issuer = "issuer1", claimMap = { "acr=Level4" })
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)

@interface ProtectedWithClaimsMeta {

}
@Unprotected
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)

@interface UnprotectedMeta {

}

0 comments on commit bc8c32f

Please sign in to comment.