Skip to content

Commit

Permalink
This closes #173
Browse files Browse the repository at this point in the history
  • Loading branch information
jeanouii committed Dec 10, 2018
2 parents 9098aaf + c3ad279 commit 7aa3e84
Show file tree
Hide file tree
Showing 33 changed files with 1,262 additions and 218 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ protected TomEEContainer() {
this.options = new Options(System.getProperties());
}

public Configuration getConfiguration() {
return configuration;
}

protected void resetSerialization() {
if (this.configuration.isUnsafeEjbd() && "-".equals(System.getProperty("tomee.serialization.class.blacklist"))) {
System.clearProperty("tomee.serialization.class.blacklist");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,72 @@

import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.spi.SecurityService;
import org.apache.webbeans.config.WebBeansContext;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class ManagedSecurityService implements org.apache.webbeans.spi.SecurityService {
private final org.apache.webbeans.corespi.security.ManagedSecurityService delegate = new org.apache.webbeans.corespi.security.ManagedSecurityService();

private final boolean useWrapper;
private Principal proxy = null;


public ManagedSecurityService(final WebBeansContext context) {
useWrapper = (!Boolean.parseBoolean(context.getOpenWebBeansConfiguration()
.getProperty("org.apache.webbeans.component.PrincipalBean.proxy", "true").trim()));

if (useWrapper) {
final ClassLoader loader = ManagedSecurityService.class.getClassLoader();

final String[] apiInterfaces = context.getOpenWebBeansConfiguration()
.getProperty("org.apache.webbeans.component.PrincipalBean.proxyApis", "org.eclipse.microprofile.jwt.JsonWebToken").split(",");

List<Class> interfaceList = new ArrayList<>();

for (final String apiInterface : apiInterfaces) {
try {
final Class<?> clazz = loader.loadClass(apiInterface.trim());
interfaceList.add(clazz);
} catch (NoClassDefFoundError | ClassNotFoundException e) {
// TODO: log severe error here with guidance
}
}

proxy = Principal.class.cast(Proxy.newProxyInstance(loader, interfaceList.toArray(new Class[0]), new InvocationHandler() {
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
return method.invoke(doGetPrincipal(), args);
}
}));
}
}

@Override
public Principal getCurrentPrincipal() {
if (useWrapper) {
return proxy;
}

return doGetPrincipal();
}

private Principal doGetPrincipal() {
final SecurityService<?> service = SystemInstance.get().getComponent(SecurityService.class);
if (service != null) {
return service.getCallerPrincipal();
}

return null;
}

Expand Down Expand Up @@ -104,4 +152,5 @@ public String doPrivilegedGetSystemProperty(final String propertyName, final Str
public Properties doPrivilegedGetSystemProperties() {
return delegate.doPrivilegedGetSystemProperties();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ public void initialize(final StartupObject startupObject) {
properties.put(ContextsService.class.getName(), CdiAppContextsService.class.getName());
properties.put(ResourceInjectionService.class.getName(), CdiResourceInjectionService.class.getName());
properties.put(TransactionService.class.getName(), OpenEJBTransactionService.class.getName());
properties.put("org.apache.webbeans.component.PrincipalBean.proxy", "false");


// NOTE: ensure user can extend/override all the services = set it only if not present in properties, see WebBeansContext#getService()
final Map<Class<?>, Object> services = new HashMap<>();
Expand Down
2 changes: 1 addition & 1 deletion examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ BROKEN, see TOMEE-2140
<module>rest-applicationcomposer</module>
<module>rest-cdi</module>
<module>rest-jaas</module>
<module>rest-mp-jwt</module>
<!--<module>rest-mp-jwt</module>-->
<module>rest-on-ejb</module>
<module>rest-example</module>
<module>rest-example-with-application</module>
Expand Down
12 changes: 12 additions & 0 deletions mp-jwt/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@
<version>${microprofile.jwt.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.config</groupId>
<artifactId>microprofile-config-api</artifactId>
<version>${microprofile.config.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomee</groupId>
<artifactId>javaee-api</artifactId>
Expand Down Expand Up @@ -68,6 +74,12 @@
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomee</groupId>
<artifactId>tomee-catalina</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
*/
package org.apache.tomee.microprofile.jwt;

import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.spi.SecurityService;
import org.apache.tomee.catalina.OpenEJBSecurityListener;
import org.apache.tomee.catalina.TomcatSecurityService;
import org.apache.tomee.microprofile.jwt.config.ConfigurableJWTAuthContextInfo;
import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo;
import org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory;
import org.eclipse.microprofile.jwt.JsonWebToken;
Expand All @@ -41,26 +46,24 @@
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Function;
import java.util.stream.Collectors;

// async is supported because we only need to do work on the way in
@WebFilter(asyncSupported = true, urlPatterns = "/*")
//@WebFilter(asyncSupported = true, urlPatterns = "/*")
public class MPJWTFilter implements Filter {

@Inject
private Instance<JWTAuthContextInfo> authContextInfo;

@Override
public void init(final FilterConfig filterConfig) throws ServletException {
// nothing so far
}

@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
if (authContextInfo.isUnsatisfied()) {
final Optional<JWTAuthContextInfo> authContextInfo = getAuthContextInfo();
if (!authContextInfo.isPresent()) {
chain.doFilter(request,response);
return;
}
Expand All @@ -69,8 +72,15 @@ public void doFilter(final ServletRequest request, final ServletResponse respons

// now wrap the httpServletRequest and override the principal so CXF can propagate into the SecurityContext
try {
chain.doFilter(new MPJWTServletRequestWrapper(httpServletRequest, authContextInfo.get()), response);

final MPJWTServletRequestWrapper wrappedRequest = new MPJWTServletRequestWrapper(httpServletRequest, authContextInfo.get());
chain.doFilter(wrappedRequest, response);

Object state = request.getAttribute("MP_JWT_PRE_LOGIN_STATE");
final SecurityService securityService = SystemInstance.get().getComponent(SecurityService.class);
if (TomcatSecurityService.class.isInstance(securityService) && state != null) {
final TomcatSecurityService tomcatSecurityService = TomcatSecurityService.class.cast(securityService);
tomcatSecurityService.exitWebApp(state);
}
} catch (final Exception e) {
// this is an alternative to the @Provider bellow which requires registration on the fly
// or users to add it into their webapp for scanning or into the Application itself
Expand All @@ -91,6 +101,19 @@ public void destroy() {
// nothing to do
}

@Inject
private Instance<JWTAuthContextInfo> authContextInfo;
@Inject
private ConfigurableJWTAuthContextInfo configurableJWTAuthContextInfo;

private Optional<JWTAuthContextInfo> getAuthContextInfo() {
if (!authContextInfo.isUnsatisfied()) {
return Optional.of(authContextInfo.get());
}

return configurableJWTAuthContextInfo.getJWTAuthContextInfo();
}

private static Function<HttpServletRequest, JsonWebToken> token(final HttpServletRequest httpServletRequest, final JWTAuthContextInfo authContextInfo) {

return new Function<HttpServletRequest, JsonWebToken>() {
Expand Down Expand Up @@ -123,6 +146,19 @@ public JsonWebToken apply(final HttpServletRequest request) {
throw new InvalidTokenException(token, e);
}

// TODO - do the login here, save the state to the request so we can recover it later.

final SecurityService securityService = SystemInstance.get().getComponent(SecurityService.class);
if (TomcatSecurityService.class.isInstance(securityService)) {
TomcatSecurityService tomcatSecurityService = TomcatSecurityService.class.cast(securityService);
final org.apache.catalina.connector.Request req = OpenEJBSecurityListener.requests.get();
Object state = tomcatSecurityService.enterWebApp(req.getWrapper().getRealm(), jsonWebToken, req.getWrapper().getRunAs());

request.setAttribute("MP_JWT_PRE_LOGIN_STATE", state);
}

// TODO Also check if it is an async request and add a listener to close off the state

return jsonWebToken;

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.security.Principal;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
Expand Down Expand Up @@ -277,16 +278,22 @@ public static String getClaimKey(final Claim claim) {
}

private T getClaimValue(final String name) {
final Bean<?> bean = bm.resolve(bm.getBeans(JsonWebToken.class));
JsonWebToken jsonWebToken = null;
if (RequestScoped.class.equals(bean.getScope())) {
jsonWebToken = JsonWebToken.class.cast(bm.getReference(bean, JsonWebToken.class, null));
}
if (jsonWebToken == null || !bean.getScope().equals(RequestScoped.class)) {
final Bean<?> bean = bm.resolve(bm.getBeans(Principal.class));
final Principal principal = Principal.class.cast(bm.getReference(bean, Principal.class, null));

if (principal == null) {
logger.warning(String.format("Can't retrieve claim %s. No active principal.", name));
return null;
}

JsonWebToken jsonWebToken = null;
if (! JsonWebToken.class.isInstance(principal)) {
logger.warning(String.format("Can't retrieve claim %s. Active principal is not a JWT.", name));
return null;
}

jsonWebToken = JsonWebToken.class.cast(principal);

final Optional<T> claimValue = jsonWebToken.claim(name);
logger.finest(String.format("Found ClaimValue=%s for name=%s", claimValue, name));
return claimValue.orElse(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,21 @@
import org.apache.openejb.loader.SystemInstance;
import org.apache.tomee.microprofile.jwt.MPJWTFilter;
import org.apache.tomee.microprofile.jwt.MPJWTInitializer;
import org.apache.tomee.microprofile.jwt.config.ConfigurableJWTAuthContextInfo;
import org.apache.tomee.microprofile.jwt.jaxrs.MPJWPProviderRegistration;
import org.eclipse.microprofile.jwt.Claim;
import org.eclipse.microprofile.jwt.JsonWebToken;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.Extension;
Expand All @@ -33,6 +42,7 @@
import javax.inject.Provider;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.security.Principal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
Expand All @@ -57,7 +67,7 @@ public class MPJWTCDIExtension implements Extension {

private Set<InjectionPoint> injectionPoints = new HashSet<>();

public void collectConfigProducer(@Observes final ProcessInjectionPoint<?, ?> pip) {
public void collectConfigProducer(@Observes final ProcessInjectionPoint<?, ?> pip, final BeanManager bm) {
final Claim claim = pip.getInjectionPoint().getAnnotated().getAnnotation(Claim.class);
if (claim != null) {
injectionPoints.add(pip.getInjectionPoint());
Expand Down Expand Up @@ -93,13 +103,40 @@ public void accept(final ClaimBean claimBean) {
abd.addBean(claimBean);
}
});

abd.addBean()
.id(MPJWTCDIExtension.class.getName() + "#" + JsonWebToken.class.getName())
.beanClass(JsonWebToken.class)
.types(JsonWebToken.class, Object.class)
.qualifiers(Default.Literal.INSTANCE, Any.Literal.INSTANCE)
.scope(Dependent.class)
.createWith(ctx -> {
final Principal principal = getContextualReference(Principal.class, bm);
if (JsonWebToken.class.isInstance(principal)) {
return JsonWebToken.class.cast(principal);
}

return null;
});
}

public void observeBeforeBeanDiscovery(@Observes final BeforeBeanDiscovery bbd, final BeanManager beanManager) {
bbd.addAnnotatedType(beanManager.createAnnotatedType(ConfigurableJWTAuthContextInfo.class));
bbd.addAnnotatedType(beanManager.createAnnotatedType(JsonbProducer.class));
bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTFilter.class));
bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTInitializer.class));
bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTProducer.class));
}

public static <T> T getContextualReference(Class<T> type, final BeanManager beanManager) {
final Set<Bean<?>> beans = beanManager.getBeans(type);

if (beans == null || beans.isEmpty()) {
throw new IllegalStateException("Could not find beans for Type=" + type);
}

final Bean<?> bean = beanManager.resolve(beans);
final CreationalContext<?> creationalContext = beanManager.createCreationalContext(bean);
return (T) beanManager.getReference(bean, type, creationalContext);
}

static {
Expand Down
Loading

0 comments on commit 7aa3e84

Please sign in to comment.