Skip to content

Commit

Permalink
PAYARA-3885 Implement Parameterized dynamically resolved role names
Browse files Browse the repository at this point in the history
  • Loading branch information
jGauravGupta committed Aug 13, 2019
1 parent 06120f6 commit 7cc91cf
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 21 deletions.
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) [2017-2018] Payara Foundation and/or its affiliates.
* Copyright (c) [2017-2019] Payara Foundation and/or its affiliates.
* All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
Expand Down Expand Up @@ -66,47 +66,69 @@

import fish.payara.cdi.auth.roles.CallerAccessException;
import fish.payara.cdi.auth.roles.RolesPermitted;
import java.lang.reflect.Parameter;
import javax.el.ELProcessor;
import javax.inject.Named;
import javax.security.enterprise.AuthenticationStatus;
import static javax.security.enterprise.AuthenticationStatus.NOT_DONE;
import static javax.security.enterprise.AuthenticationStatus.SEND_FAILURE;
import static javax.security.enterprise.AuthenticationStatus.SUCCESS;
import static javax.security.enterprise.authentication.mechanism.http.AuthenticationParameters.withParams;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.core.Context;
import static org.glassfish.soteria.cdi.AnnotationELPProcessor.evalELExpression;
import static org.glassfish.soteria.cdi.AnnotationELPProcessor.hasAnyELExpression;

/**
* The Roles CDI Interceptor authenticates requests to methods and classes annotated with the @Roles annotation. If the
* security context cannot find a role within the requestor which matches either all (if using the AND semantic within
* the Roles annotation) or one of (if using the OR semantic within the Roles annotation), then a NotAuthorizedException
* The RolesPermitted Interceptor authenticates requests to methods and classes
* annotated with the @RolesPermitted annotation. If the security context cannot
* find a role within the requestor which matches either all (if using the AND
* semantic within the RolesPermitted annotation) or one of (if using the OR
* semantic within the RolesPermitted annotation), then a CallerAccessException
* is thrown.
*
* @author Michael Ranaldo <michael@ranaldo.co.uk>
* @author Arjan Tijms
*/
@Interceptor
@RolesPermitted
@Priority(Interceptor.Priority.PLATFORM_AFTER)
public class RolesCDIInterceptor {
@Priority(Interceptor.Priority.PLATFORM_AFTER + 1000)
public class RolesPermittedInterceptor {

private final SecurityContext securityContext;

private BeanManager beanManager;

private Bean<?> interceptedBean;

@Context
private HttpServletRequest request;

@Context
private HttpServletResponse response;

@Inject
public RolesCDIInterceptor(@Intercepted Bean<?> interceptedBean, BeanManager beanManager) {
public RolesPermittedInterceptor(@Intercepted Bean<?> interceptedBean, BeanManager beanManager) {
this.securityContext = CDI.current().select(SecurityContext.class).get();
this.interceptedBean = interceptedBean;
this.beanManager = beanManager;
}

/**
* Method invoked whenever a method annotated with @Roles, or a method within a class annotated with @Roles is
* called.
* Method invoked whenever a method annotated with @Roles, or a method
* within a class annotated with @Roles is called.
*
* @param invocationContext Context provided by Weld.
* @return Proceed to next interceptor in chain.
* @throws java.lang.Exception
*/
@AroundInvoke
public Object method(InvocationContext invocationContext) throws Exception {
RolesPermitted roles = getRolesPermitted(invocationContext);

boolean isAccessPermitted = checkAccessPermitted(roles);

boolean isAccessPermitted = checkAccessPermitted(roles, invocationContext);
if (!isAccessPermitted) {
throw new CallerAccessException("Caller was not permitted access to a protected resource");
}
Expand All @@ -115,22 +137,37 @@ public Object method(InvocationContext invocationContext) throws Exception {
}

/**
* Check that the roles allowed by the class or method match the roles currently granted to the caller.
* Check that the roles allowed by the class or method match the roles
* currently granted to the caller.
*
* @param roles The roles declared within the @Roles annotation.
* @param invocationContext
* @return True if access is allowed, false otherwise
*/
public boolean checkAccessPermitted(RolesPermitted roles) {
public boolean checkAccessPermitted(RolesPermitted roles, InvocationContext invocationContext) {
List<String> permittedRoles = asList(roles.value());

authenticate(roles.value());

ELProcessor eLProcessor = null;
if (hasAnyELExpression(roles.value())) {
eLProcessor = getElProcessor(invocationContext);
}

if (roles.semantics().equals(OR)) {
for (String role : permittedRoles) {
if (eLProcessor != null && hasAnyELExpression(role)) {
role = evalELExpression(eLProcessor, role);
}
if (securityContext.isCallerInRole(role)) {
return true;
}
}
} else if (roles.semantics().equals(AND)) {
for (String role : permittedRoles) {
if (eLProcessor != null && hasAnyELExpression(role)) {
role = evalELExpression(eLProcessor, role);
}
if (!securityContext.isCallerInRole(role)) {
return false;
}
Expand Down Expand Up @@ -193,13 +230,58 @@ public static <A extends Annotation> Optional<A> getAnnotationFromMethod(BeanMan

if (beanManager.isStereotype(annotation.annotationType())) {
annotations.addAll(
beanManager.getStereotypeDefinition(
annotation.annotationType()
)
beanManager.getStereotypeDefinition(
annotation.annotationType()
)
);
}
}

return Optional.empty();
}

private ELProcessor getElProcessor(InvocationContext invocationContext) {
ELProcessor elProcessor = new ELProcessor();
elProcessor.getELManager().addELResolver(beanManager.getELResolver());
elProcessor.defineBean("self", invocationContext.getTarget());

Parameter[] parameters = invocationContext.getMethod().getParameters();
Object[] values = invocationContext.getParameters();
boolean paramAdded = false;
for (int i = 0; i < parameters.length; i++) {
Parameter param = parameters[i];
Named named = param.getAnnotation(Named.class);
String key = null;
if (named != null && !(key = named.value().trim()).isEmpty()) {
elProcessor.defineBean(key, values[i]);
paramAdded = true;
}
}
if (!paramAdded && parameters.length == 1) {
elProcessor.defineBean("param", values[0]);
}

return elProcessor;
}

private void authenticate(String[] roles) {
if (request != null && response != null
&& roles.length > 0 && !isAuthenticated()) {
AuthenticationStatus status = securityContext.authenticate(request, response, withParams());

// Authentication was not done at all (i.e. no credentials present) or
// authentication failed (i.e. wrong credentials, credentials expired, etc)
if (status == NOT_DONE || status == SEND_FAILURE) {
throw new NotAuthorizedException("Authentication resulted in " + status);
}

if (status == SUCCESS && !isAuthenticated()) { // compensate for possible Soteria bug, need to investigate
throw new NotAuthorizedException("Authentication not done (i.e. no credential found)");
}
}
}

private boolean isAuthenticated() {
return securityContext.getCallerPrincipal() != null;
}
}
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) [2017-2018] Payara Foundation and/or its affiliates.
* Copyright (c) [2017-2019] Payara Foundation and/or its affiliates.
* All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
Expand Down Expand Up @@ -45,7 +45,7 @@
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.Extension;

import fish.payara.appserver.cdi.auth.roles.RolesCDIInterceptor;
import fish.payara.appserver.cdi.auth.roles.RolesPermittedInterceptor;
import fish.payara.cdi.auth.roles.RolesPermitted;

/**
Expand All @@ -58,8 +58,7 @@ public class RolesCDIExtension implements Extension {
void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscovery, BeanManager beanManager) {
beforeBeanDiscovery.addInterceptorBinding(RolesPermitted.class);

beforeBeanDiscovery.addAnnotatedType(
beanManager.createAnnotatedType(RolesCDIInterceptor.class),
"RolesCDIExtension " + RolesCDIInterceptor.class.getName());
beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(RolesPermittedInterceptor.class),
"RolesCDIExtension " + RolesPermittedInterceptor.class.getName());
}
}

0 comments on commit 7cc91cf

Please sign in to comment.