You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by jl...@apache.org on 2018/04/30 14:34:04 UTC
[3/3] tomee git commit: Backport MicroProfile from fb_tomee8 branch
Backport MicroProfile from fb_tomee8 branch
Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/c456f4be
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/c456f4be
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/c456f4be
Branch: refs/heads/tomee-7.1.x
Commit: c456f4bed23674bf9d0f1db59071e2a981b47559
Parents: 6bcf91d
Author: Jean-Louis Monteiro <je...@gmail.com>
Authored: Mon Apr 30 16:29:19 2018 +0200
Committer: Jean-Louis Monteiro <je...@gmail.com>
Committed: Mon Apr 30 16:29:19 2018 +0200
----------------------------------------------------------------------
.../java/org/apache/openejb/util/Index.java | 3 -
mp-jwt/pom.xml | 75 ++
.../tomee/microprofile/jwt/MPJWTFilter.java | 271 ++++
.../microprofile/jwt/MPJWTInitializer.java | 64 +
.../tomee/microprofile/jwt/ParseException.java | 32 +
.../tomee/microprofile/jwt/cdi/ClaimBean.java | 373 ++++++
.../jwt/cdi/ClaimInjectionPoint.java | 70 +
.../microprofile/jwt/cdi/ClaimValueWrapper.java | 53 +
.../microprofile/jwt/cdi/DefaultLiteral.java | 24 +
.../microprofile/jwt/cdi/JsonbProducer.java | 46 +
.../microprofile/jwt/cdi/MPJWTCDIExtension.java | 103 ++
.../microprofile/jwt/cdi/MPJWTProducer.java | 49 +
.../jwt/config/JWTAuthContextInfo.java | 67 +
.../jwt/jaxrs/MPJWPProviderRegistration.java | 36 +
.../MPJWTSecurityAnnotationsInterceptor.java | 57 +
...TSecurityAnnotationsInterceptorsFeature.java | 144 +++
.../principal/DefaultJWTCallerPrincipal.java | 360 ++++++
.../DefaultJWTCallerPrincipalFactory.java | 92 ++
.../jwt/principal/JWTCallerPrincipal.java | 59 +
.../principal/JWTCallerPrincipalFactory.java | 129 ++
.../META-INF/org.apache.openejb.extension | 1 +
.../javax.enterprise.inject.spi.Extension | 1 +
.../javax.servlet.ServletContainerInitializer | 1 +
...file.jwt.principal.JWTCallerPrincipalFactory | 1 +
pom.xml | 14 +-
tck/microprofile-tck/config/pom.xml | 136 ++
.../microprofile/config/ConfigurationBean.java | 25 +
.../MicroProfileConfigTCKArchiveProcessor.java | 38 +
.../config/MicroProfileConfigTCKExtension.java | 11 +
....jboss.arquillian.core.spi.LoadableExtension | 1 +
.../config/src/test/resources/arquillian.xml | 40 +
tck/microprofile-tck/jwt/pom.xml | 192 +++
.../jwt/JWTAuthContextInfoProvider.java | 63 +
.../tomee/microprofile/jwt/TCKTokenParser.java | 40 +
.../jwt/AppDeploymentExtension.java | 77 ++
...lipse.microprofile.jwt.tck.util.ITokenParser | 1 +
....jboss.arquillian.core.spi.LoadableExtension | 1 +
.../jwt/src/test/resources/arquillian.xml | 39 +
.../jwt/src/test/resources/dev.xml | 93 ++
tck/microprofile-tck/pom.xml | 38 +
tck/pom.xml | 1 +
tomee/apache-tomee/pom.xml | 70 +-
.../src/main/assembly/tomee-microprofile.xml | 83 ++
tomee/pom.xml | 1 +
tomee/tomee-microprofile-webapp/pom.xml | 105 ++
.../src/main/assembly/war.xml | 92 ++
.../src/main/resources/META-INF/LICENSE | 1197 ++++++++++++++++++
.../src/main/resources/META-INF/NOTICE | 140 ++
48 files changed, 4600 insertions(+), 9 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/container/openejb-core/src/main/java/org/apache/openejb/util/Index.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/util/Index.java b/container/openejb-core/src/main/java/org/apache/openejb/util/Index.java
index 26eb705..0ad6bbd 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/util/Index.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/util/Index.java
@@ -165,9 +165,6 @@ public class Index<K, V> extends AbstractMap<K, V> {
return toArray(new Object[entries.length]);
}
- public interface ListSet extends List, Set {
- }
-
public Object[] toArray(Object[] values) {
if (values.length < entries.length) {
values = (Object[]) Array.newInstance(values.getClass().getComponentType(), entries.length);
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/pom.xml
----------------------------------------------------------------------
diff --git a/mp-jwt/pom.xml b/mp-jwt/pom.xml
new file mode 100644
index 0000000..b1b86f6
--- /dev/null
+++ b/mp-jwt/pom.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>tomee-project</artifactId>
+ <groupId>org.apache.tomee</groupId>
+ <version>8.0.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>mp-jwt</artifactId>
+ <packaging>jar</packaging>
+ <name>OpenEJB :: Microprofile JWT</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.tomee</groupId>
+ <artifactId>javaee-api</artifactId>
+ <version>${version.javaee-api}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>openejb-core</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>openejb-cxf-rs</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.bitbucket.b_c</groupId>
+ <artifactId>jose4j</artifactId>
+ <version>0.6.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.johnzon</groupId>
+ <artifactId>johnzon-jsonb</artifactId>
+ <version>${johnzon.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.microprofile.jwt</groupId>
+ <artifactId>microprofile-jwt-auth-api</artifactId>
+ <version>${mp-jwt.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+
+</project>
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
new file mode 100644
index 0000000..a7b47d3
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt;
+
+import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo;
+import org.apache.tomee.microprofile.jwt.principal.JWTCallerPrincipalFactory;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+import javax.inject.Inject;
+import javax.security.auth.Subject;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Locale;
+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 = "/*")
+public class MPJWTFilter implements Filter {
+
+ @Inject
+ private 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 {
+
+ final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+
+ // now wrap the httpServletRequest and override the principal so CXF can propagate into the SecurityContext
+ try {
+ chain.doFilter(new MPJWTServletRequestWrapper(httpServletRequest, authContextInfo), response);
+
+ } 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
+ if (MPJWTException.class.isInstance(e)) {
+ final MPJWTException jwtException = MPJWTException.class.cast(e);
+ HttpServletResponse.class.cast(response).sendError(jwtException.getStatus(), jwtException.getMessage());
+ }
+
+ if (MPJWTException.class.isInstance(e.getCause())) {
+ final MPJWTException jwtException = MPJWTException.class.cast(e.getCause());
+ HttpServletResponse.class.cast(response).sendError(jwtException.getStatus(), jwtException.getMessage());
+ }
+
+ }
+
+ }
+
+ @Override
+ public void destroy() {
+ // nothing to do
+ }
+
+ private static Function<HttpServletRequest, JsonWebToken> token(final HttpServletRequest httpServletRequest, final JWTAuthContextInfo authContextInfo) {
+
+ return new Function<HttpServletRequest, JsonWebToken>() {
+
+ private JsonWebToken jsonWebToken;
+
+ @Override
+ public JsonWebToken apply(final HttpServletRequest request) {
+
+ // not sure it's worth having synchronization inside a single request
+ // worth case, we would parse and validate the token twice
+ if (jsonWebToken != null) {
+ return jsonWebToken;
+ }
+
+ final String authorizationHeader = httpServletRequest.getHeader("Authorization");
+ if (authorizationHeader == null || authorizationHeader.isEmpty()) {
+ throw new MissingAuthorizationHeaderException();
+ }
+
+ if (!authorizationHeader.toLowerCase(Locale.ENGLISH).startsWith("bearer ")) {
+ throw new BadAuthorizationPrefixException(authorizationHeader);
+ }
+
+ final String token = authorizationHeader.substring("bearer ".length());
+ try {
+ jsonWebToken = validate(token, authContextInfo);
+
+ } catch (final ParseException e) {
+ throw new InvalidTokenException(token, e);
+ }
+
+ return jsonWebToken;
+
+ }
+ };
+
+ }
+
+ private static JsonWebToken validate(final String bearerToken, final JWTAuthContextInfo authContextInfo) throws ParseException {
+ JWTCallerPrincipalFactory factory = JWTCallerPrincipalFactory.instance();
+ return factory.parse(bearerToken, authContextInfo);
+ }
+
+ public static class MPJWTServletRequestWrapper extends HttpServletRequestWrapper {
+
+ private final Function<HttpServletRequest, JsonWebToken> tokenFunction;
+ private final HttpServletRequest request;
+
+ /**
+ * Constructs a request object wrapping the given request.
+ *
+ * @param request The request to wrap
+ * @param authContextInfo the context configuration to validate the token
+ * @throws IllegalArgumentException if the request is null
+ */
+ public MPJWTServletRequestWrapper(final HttpServletRequest request, final JWTAuthContextInfo authContextInfo) {
+ super(request);
+ this.request = request;
+ tokenFunction = token(request, authContextInfo);
+
+ // this is so that the MPJWTProducer can find the function and apply it if necessary
+ request.setAttribute(JsonWebToken.class.getName(), tokenFunction);
+ request.setAttribute("javax.security.auth.subject.callable", (Callable<Subject>) new Callable<Subject>() {
+ @Override
+ public Subject call() throws Exception {
+ final Set<Principal> principals = new LinkedHashSet<>();
+ final JsonWebToken namePrincipal = tokenFunction.apply(request);
+ principals.add(namePrincipal);
+ principals.addAll(namePrincipal.getGroups().stream().map(new Function<String, Principal>() {
+ @Override
+ public Principal apply(final String role) {
+ return (Principal) new Principal() {
+ @Override
+ public String getName() {
+ return role;
+ }
+ };
+ }
+ }).collect(Collectors.<Principal>toList()));
+ return new Subject(true, principals, Collections.emptySet(), Collections.emptySet());
+ }
+ });
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return tokenFunction.apply(request);
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ final JsonWebToken jsonWebToken = tokenFunction.apply(request);
+ return jsonWebToken.getGroups().contains(role);
+ }
+
+ @Override
+ public String getAuthType() {
+ return "MP-JWT";
+ }
+
+ }
+
+ private static abstract class MPJWTException extends RuntimeException {
+
+ public MPJWTException() {
+ super();
+ }
+
+ public MPJWTException(final Throwable cause) {
+ super(cause);
+ }
+
+ public abstract int getStatus();
+
+ public abstract String getMessage();
+ }
+
+ private static class MissingAuthorizationHeaderException extends MPJWTException {
+
+ @Override
+ public int getStatus() {
+ return HttpServletResponse.SC_UNAUTHORIZED;
+ }
+
+ @Override
+ public String getMessage() {
+ return "No authorization header provided. Can't validate the JWT.";
+ }
+ }
+
+ private static class BadAuthorizationPrefixException extends MPJWTException {
+
+ private String authorizationHeader;
+
+ public BadAuthorizationPrefixException(final String authorizationHeader) {
+ this.authorizationHeader = authorizationHeader;
+ }
+
+ @Override
+ public int getStatus() {
+ return HttpServletResponse.SC_UNAUTHORIZED;
+ }
+
+ @Override
+ public String getMessage() {
+ return "Authorization header does not use the Bearer prefix. Can't validate header " + authorizationHeader;
+ }
+ }
+
+ private static class InvalidTokenException extends MPJWTException {
+
+ private final String token;
+
+ public InvalidTokenException(final String token, final Throwable cause) {
+ super(cause);
+ this.token = token;
+ }
+
+ @Override
+ public int getStatus() {
+ return HttpServletResponse.SC_UNAUTHORIZED;
+ }
+
+ @Override
+ public String getMessage() {
+ return "Invalid or not parsable JWT " + token; // we might want to break down the exceptions so we can have more messages.
+ }
+ }
+
+ @Provider // would be the ideal but not automatically registered
+ public static class MPJWTExceptionMapper implements ExceptionMapper<MPJWTException> {
+
+ @Override
+ public Response toResponse(final MPJWTException exception) {
+ return Response.status(exception.getStatus()).entity(exception.getMessage()).build();
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java
new file mode 100644
index 0000000..cede7dc
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt;
+
+import org.eclipse.microprofile.auth.LoginConfig;
+
+import javax.servlet.FilterRegistration;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.HandlesTypes;
+import javax.ws.rs.core.Application;
+import java.util.Set;
+
+/**
+ * Responsible for adding the filter into the chain and doing all other initialization
+ */
+@HandlesTypes(LoginConfig.class)
+public class MPJWTInitializer implements ServletContainerInitializer {
+
+ @Override
+ public void onStartup(final Set<Class<?>> classes, final ServletContext ctx) throws ServletException {
+
+ if (classes == null || classes.isEmpty()) {
+ return; // no classe having @LoginConfig on it
+ }
+
+ for (Class<?> clazz : classes) {
+ final LoginConfig loginConfig = clazz.getAnnotation(LoginConfig.class);
+
+ if (loginConfig.authMethod() == null && !"MP-JWT".equals(loginConfig.authMethod())) {
+ continue;
+ }
+
+ if (!Application.class.isAssignableFrom(clazz)) {
+ continue;
+ // do we really want Application?
+ // See https://github.com/eclipse/microprofile-jwt-auth/issues/70 to clarify this point
+ }
+
+ final FilterRegistration.Dynamic mpJwtFilter = ctx.addFilter("mp-jwt-filter", MPJWTFilter.class);
+ mpJwtFilter.setAsyncSupported(true);
+ mpJwtFilter.addMappingForUrlPatterns(null, false, "/*");
+
+ break; // no need to add it more than once
+ }
+
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/ParseException.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/ParseException.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/ParseException.java
new file mode 100644
index 0000000..d9572d5
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/ParseException.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt;
+
+/**
+ * The exception thrown when
+ */
+public class ParseException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ParseException(final String message) {
+ super(message);
+ }
+
+ public ParseException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimBean.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimBean.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimBean.java
new file mode 100644
index 0000000..be83a9b
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimBean.java
@@ -0,0 +1,373 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt.cdi;
+
+import org.eclipse.microprofile.jwt.Claim;
+import org.eclipse.microprofile.jwt.ClaimValue;
+import org.eclipse.microprofile.jwt.Claims;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.Instance;
+import javax.enterprise.inject.Vetoed;
+import javax.enterprise.inject.spi.Annotated;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.PassivationCapable;
+import javax.enterprise.util.AnnotationLiteral;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonValue;
+import javax.json.bind.Jsonb;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.logging.Logger;
+
+@Vetoed
+public class ClaimBean<T> implements Bean<T>, PassivationCapable {
+
+ private static final Logger logger = Logger.getLogger(MPJWTCDIExtension.class.getName());
+
+ private static final Set<Annotation> QUALIFIERS = new HashSet<>();
+
+ static {
+ QUALIFIERS.add(new ClaimLiteral());
+ }
+
+ @Inject
+ private Jsonb jsonb;
+
+ private final BeanManager bm;
+ private final Class rawType;
+ private final Set<Type> types;
+ private final String id;
+ private final Class<? extends Annotation> scope;
+
+ public ClaimBean(final BeanManager bm, final Type type) {
+ this.bm = bm;
+ types = new HashSet<>();
+ types.add(type);
+ rawType = getRawType(type);
+ this.id = "ClaimBean_" + types;
+ scope = Dependent.class;
+ }
+
+ private Class getRawType(final Type type) {
+ if (Class.class.isInstance(type)) {
+ return Class.class.cast(type);
+
+ } else if (ParameterizedType.class.isInstance(type)) {
+ final ParameterizedType paramType = ParameterizedType.class.cast(type);
+ return Class.class.cast(paramType.getRawType());
+ }
+
+ throw new UnsupportedOperationException("Unsupported type " + type);
+ }
+
+
+ @Override
+ public Set<InjectionPoint> getInjectionPoints() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Class<?> getBeanClass() {
+ return rawType;
+ }
+
+ @Override
+ public boolean isNullable() {
+ return false;
+ }
+
+ @Override
+ public void destroy(final T instance, final CreationalContext<T> context) {
+ logger.finest("Destroying CDI Bean for type " + types.iterator().next());
+ }
+
+ @Override
+ public Set<Type> getTypes() {
+ return types;
+ }
+
+ @Override
+ public Set<Annotation> getQualifiers() {
+ return QUALIFIERS;
+ }
+
+ @Override
+ public Class<? extends Annotation> getScope() {
+ return scope;
+ }
+
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public Set<Class<? extends Annotation>> getStereotypes() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean isAlternative() {
+ return false;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public T create(final CreationalContext<T> context) {
+ logger.finest("Creating CDI Bean for type " + types.iterator().next());
+ final InjectionPoint ip = (InjectionPoint) bm.getInjectableReference(new ClaimInjectionPoint(this), context);
+ if (ip == null) {
+ throw new IllegalStateException("Could not retrieve InjectionPoint for type " + types.iterator().next());
+ }
+
+ final Annotated annotated = ip.getAnnotated();
+ final Claim claim = annotated.getAnnotation(Claim.class);
+ final String key = getClaimKey(claim);
+
+ logger.finest(String.format("Found Claim injection with name=%s and for %s", key, ip.toString()));
+
+ if (ParameterizedType.class.isInstance(annotated.getBaseType())) {
+ final ParameterizedType paramType = ParameterizedType.class.cast(annotated.getBaseType());
+ final Type rawType = paramType.getRawType();
+ if (Class.class.isInstance(rawType) && paramType.getActualTypeArguments().length == 1) {
+
+ final Class<?> rawTypeClass = ((Class<?>) rawType);
+
+ // handle Provider<T>
+ if (rawTypeClass.isAssignableFrom(Provider.class)) {
+ final Type providerType = paramType.getActualTypeArguments()[0];
+ if (ParameterizedType.class.isInstance(providerType) && isOptional(ParameterizedType.class.cast(providerType))) {
+ return (T) Optional.ofNullable(getClaimValue(key));
+ }
+ return getClaimValue(key);
+ }
+
+ // handle Instance<T>
+ if (rawTypeClass.isAssignableFrom(Instance.class)) {
+ final Type instanceType = paramType.getActualTypeArguments()[0];
+ if (ParameterizedType.class.isInstance(instanceType) && isOptional(ParameterizedType.class.cast(instanceType))) {
+ return (T) Optional.ofNullable(getClaimValue(key));
+ }
+ return getClaimValue(key);
+ }
+
+ // handle ClaimValue<T>
+ if (rawTypeClass.isAssignableFrom(ClaimValue.class)) {
+ final Type claimValueType = paramType.getActualTypeArguments()[0];
+
+ final ClaimValueWrapper claimValueWrapper = new ClaimValueWrapper(key);
+ if (ParameterizedType.class.isInstance(claimValueType) && isOptional(ParameterizedType.class.cast(claimValueType))) {
+ claimValueWrapper.setValue(new Supplier() {
+ @Override
+ public Object get() {
+ final T claimValue = ClaimBean.this.getClaimValue(key);
+ return Optional.ofNullable(claimValue);
+ }
+ });
+
+ } else if (ParameterizedType.class.isInstance(claimValueType) && isSet(ParameterizedType.class.cast(claimValueType))) {
+ claimValueWrapper.setValue(new Supplier() {
+ @Override
+ public Object get() {
+ final T claimValue = ClaimBean.this.getClaimValue(key);
+ return claimValue;
+ }
+ });
+
+ } else if (ParameterizedType.class.isInstance(claimValueType) && isList(ParameterizedType.class.cast(claimValueType))) {
+ claimValueWrapper.setValue(new Supplier() {
+ @Override
+ public Object get() {
+ final T claimValue = ClaimBean.this.getClaimValue(key);
+ return claimValue;
+ }
+ });
+
+ } else if (Class.class.isInstance(claimValueType)) {
+ claimValueWrapper.setValue(new Supplier() {
+ @Override
+ public Object get() {
+ final T claimValue = ClaimBean.this.getClaimValue(key);
+ return claimValue;
+ }
+ });
+
+ } else {
+ throw new IllegalArgumentException("Unsupported ClaimValue type " + claimValueType.toString());
+ }
+
+ return (T) claimValueWrapper;
+ }
+
+ // handle Optional<T>
+ if (rawTypeClass.isAssignableFrom(Optional.class)) {
+ return getClaimValue(key);
+ }
+
+ // handle Set<T>
+ if (rawTypeClass.isAssignableFrom(Set.class)) {
+ return getClaimValue(key);
+ }
+
+ // handle List<T>
+ if (rawTypeClass.isAssignableFrom(List.class)) {
+ return getClaimValue(key);
+ }
+ }
+
+ } else if (annotated.getBaseType().getTypeName().startsWith("javax.json.Json")) {
+ // handle JsonValue<T> (number, string, etc)
+ return (T) toJson(key);
+
+ } else {
+ // handle Raw types
+ return getClaimValue(key);
+ }
+
+ throw new IllegalStateException("Unhandled Claim type " + annotated.getBaseType());
+ }
+
+ public static String getClaimKey(final Claim claim) {
+ return claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name();
+ }
+
+ 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)) {
+ logger.warning(String.format("Can't retrieve claim %s. No active principal.", name));
+ return null;
+ }
+
+ final Optional<T> claimValue = jsonWebToken.claim(name);
+ logger.finest(String.format("Found ClaimValue=%s for name=%s", claimValue, name));
+ return claimValue.orElse(null);
+ }
+
+ private JsonValue toJson(final String name) {
+ final T claimValue = getClaimValue(name);
+ return wrapValue(claimValue);
+ }
+
+ private static final String TMP = "tmp";
+
+ private JsonValue wrapValue(final Object value) {
+ JsonValue jsonValue = null;
+
+ if (JsonValue.class.isInstance(value)) {
+ // This may already be a JsonValue
+ jsonValue = JsonValue.class.cast(value);
+
+ } else if (String.class.isInstance(value)) {
+ jsonValue = Json.createObjectBuilder()
+ .add(TMP, value.toString())
+ .build()
+ .getJsonString(TMP);
+
+ } else if (Number.class.isInstance(value)) {
+ final Number number = Number.class.cast(value);
+ if ((Long.class.isInstance(number)) || (Integer.class.isInstance(number))) {
+ jsonValue = Json.createObjectBuilder()
+ .add(TMP, number.longValue())
+ .build()
+ .getJsonNumber(TMP);
+
+ } else {
+ jsonValue = Json.createObjectBuilder()
+ .add(TMP, number.doubleValue())
+ .build()
+ .getJsonNumber(TMP);
+ }
+
+ } else if (Boolean.class.isInstance(value)) {
+ final Boolean flag = Boolean.class.cast(value);
+ jsonValue = flag ? JsonValue.TRUE : JsonValue.FALSE;
+
+ } else if (Collection.class.isInstance(value)) {
+ final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+ final Collection list = Collection.class.cast(value);
+
+ for (Object element : list) {
+ if (String.class.isInstance(element)) {
+ arrayBuilder.add(element.toString());
+
+ } else {
+ final JsonValue jvalue = wrapValue(element);
+ arrayBuilder.add(jvalue);
+ }
+ }
+ jsonValue = arrayBuilder.build();
+
+ } else if (Map.class.isInstance(value)) {
+ jsonValue = jsonb.fromJson(jsonb.toJson(value), JsonObject.class);
+
+ }
+ return jsonValue;
+ }
+
+ private boolean isOptional(final ParameterizedType type) {
+ return ((Class) type.getRawType()).isAssignableFrom(Optional.class);
+ }
+
+ private boolean isSet(final ParameterizedType type) {
+ return ((Class) type.getRawType()).isAssignableFrom(Set.class);
+ }
+
+ private boolean isList(final ParameterizedType type) {
+ return ((Class) type.getRawType()).isAssignableFrom(List.class);
+ }
+
+ private static class ClaimLiteral extends AnnotationLiteral<Claim> implements Claim {
+
+ @Override
+ public String value() {
+ return "";
+ }
+
+ @Override
+ public Claims standard() {
+ return Claims.UNKNOWN;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimInjectionPoint.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimInjectionPoint.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimInjectionPoint.java
new file mode 100644
index 0000000..2f1fd3d
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimInjectionPoint.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt.cdi;
+
+import javax.enterprise.inject.spi.Annotated;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.InjectionPoint;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Member;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.Set;
+
+public class ClaimInjectionPoint implements InjectionPoint {
+
+ private final Bean bean;
+
+ public ClaimInjectionPoint(final Bean bean) {
+ this.bean = bean;
+ }
+
+ @Override
+ public boolean isTransient() {
+ return false;
+ }
+
+ @Override
+ public boolean isDelegate() {
+ return false;
+ }
+
+ @Override
+ public Type getType() {
+ return InjectionPoint.class;
+ }
+
+ @Override
+ public Set<Annotation> getQualifiers() {
+ return Collections.<Annotation>singleton(DefaultLiteral.INSTANCE);
+ }
+
+ @Override
+ public Member getMember() {
+ return null;
+ }
+
+ @Override
+ public Bean<?> getBean() {
+ return bean;
+ }
+
+ @Override
+ public Annotated getAnnotated() {
+ return null;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueWrapper.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueWrapper.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueWrapper.java
new file mode 100644
index 0000000..2836abd
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/ClaimValueWrapper.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt.cdi;
+
+import org.eclipse.microprofile.jwt.ClaimValue;
+
+import java.util.function.Supplier;
+
+public class ClaimValueWrapper<T> implements ClaimValue<T> {
+
+ private final String name;
+ private Supplier<T> value;
+
+ public ClaimValueWrapper(final String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public T getValue() {
+ return value.get();
+ }
+
+ void setValue(final Supplier<T> value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return "ClaimValueWrapper{" +
+ "name='" + name + '\'' +
+ ", value=" + value.get() +
+ '}';
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/DefaultLiteral.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/DefaultLiteral.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/DefaultLiteral.java
new file mode 100644
index 0000000..273ff96
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/DefaultLiteral.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt.cdi;
+
+import javax.enterprise.inject.Default;
+import javax.enterprise.util.AnnotationLiteral;
+
+public class DefaultLiteral extends AnnotationLiteral<Default> implements Default {
+ public static final Default INSTANCE = new DefaultLiteral();
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonbProducer.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonbProducer.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonbProducer.java
new file mode 100644
index 0000000..53a9088
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/JsonbProducer.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt.cdi;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Disposes;
+import javax.enterprise.inject.Produces;
+import javax.json.bind.Jsonb;
+import javax.json.bind.spi.JsonbProvider;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+@ApplicationScoped
+// todo add a qualifier here so we isolate our instance from what applications would do
+public class JsonbProducer {
+
+ private static final Logger log = Logger.getLogger(MPJWTCDIExtension.class.getName());
+
+ @Produces
+ public Jsonb create() {
+ return JsonbProvider.provider().create().build();
+ }
+
+ public void close(@Disposes final Jsonb jsonb) {
+ try {
+ jsonb.close();
+
+ } catch (final Exception e) {
+ log.log(Level.WARNING, e.getMessage(), e);
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java
new file mode 100644
index 0000000..ca69b0a
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt.cdi;
+
+import org.apache.tomee.microprofile.jwt.MPJWTFilter;
+import org.apache.tomee.microprofile.jwt.MPJWTInitializer;
+import org.eclipse.microprofile.jwt.Claim;
+
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Instance;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.ProcessInjectionPoint;
+import javax.inject.Provider;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public class MPJWTCDIExtension implements Extension {
+
+ private static final Predicate<InjectionPoint> NOT_PROVIDERS = ip -> (Class.class.isInstance(ip.getType())) || (ParameterizedType.class.isInstance(ip.getType()) && ((ParameterizedType) ip.getType()).getRawType() != Provider.class);
+ private static final Predicate<InjectionPoint> NOT_INSTANCES = ip -> (Class.class.isInstance(ip.getType())) || (ParameterizedType.class.isInstance(ip.getType()) && ((ParameterizedType) ip.getType()).getRawType() != Instance.class);
+ private static final Map<Type, Type> REPLACED_TYPES = new HashMap<>();
+
+ static {
+ REPLACED_TYPES.put(double.class, Double.class);
+ REPLACED_TYPES.put(int.class, Integer.class);
+ REPLACED_TYPES.put(float.class, Float.class);
+ REPLACED_TYPES.put(long.class, Long.class);
+ REPLACED_TYPES.put(boolean.class, Boolean.class);
+ }
+
+ private Set<InjectionPoint> injectionPoints = new HashSet<>();
+
+ public void collectConfigProducer(@Observes final ProcessInjectionPoint<?, ?> pip) {
+ final Claim claim = pip.getInjectionPoint().getAnnotated().getAnnotation(Claim.class);
+ if (claim != null) {
+ injectionPoints.add(pip.getInjectionPoint());
+ }
+ }
+
+ public void registerClaimProducer(@Observes final AfterBeanDiscovery abd, final BeanManager bm) {
+
+ final Set<Type> types = injectionPoints.stream()
+ .filter(NOT_PROVIDERS)
+ .filter(NOT_INSTANCES)
+ .map(ip -> REPLACED_TYPES.getOrDefault(ip.getType(), ip.getType()))
+ .collect(Collectors.<Type>toSet());
+
+ final Set<Type> providerTypes = injectionPoints.stream()
+ .filter(NOT_PROVIDERS.negate())
+ .map(ip -> ((ParameterizedType) ip.getType()).getActualTypeArguments()[0])
+ .collect(Collectors.<Type>toSet());
+
+ final Set<Type> instanceTypes = injectionPoints.stream()
+ .filter(NOT_INSTANCES.negate())
+ .map(ip -> ((ParameterizedType) ip.getType()).getActualTypeArguments()[0])
+ .collect(Collectors.<Type>toSet());
+
+ types.addAll(providerTypes);
+ types.addAll(instanceTypes);
+
+ types.stream()
+ .map(type -> new ClaimBean<>(bm, type))
+ .forEach(new Consumer<ClaimBean>() {
+ @Override
+ public void accept(final ClaimBean claimBean) {
+ abd.addBean(claimBean);
+ }
+ });
+ }
+
+ public void observeBeforeBeanDiscovery(@Observes final BeforeBeanDiscovery bbd, final BeanManager beanManager) {
+ bbd.addAnnotatedType(beanManager.createAnnotatedType(JsonbProducer.class));
+ bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTFilter.class));
+ bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTInitializer.class));
+ bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTProducer.class));
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTProducer.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTProducer.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTProducer.java
new file mode 100644
index 0000000..42034b9
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTProducer.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt.cdi;
+
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.inject.Produces;
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Objects;
+import java.util.function.Function;
+
+@ApplicationScoped
+public class MPJWTProducer {
+
+ @Inject
+ private HttpServletRequest httpServletRequest;
+
+ @Produces
+ @RequestScoped
+ public JsonWebToken currentPrincipal() {
+ Objects.requireNonNull(httpServletRequest, "HTTP Servlet Request is required to produce a JSonWebToken principal.");
+
+ // not very beautiful, but avoids having the MPJWTFilter setting the request or the principal in a thread local
+ // CDI integration already has one - dunno which approach is the best for now
+ final Object tokenAttribute = httpServletRequest.getAttribute(JsonWebToken.class.getName());
+ if (Function.class.isInstance(tokenAttribute)) {
+ return (JsonWebToken) Function.class.cast(tokenAttribute).apply(httpServletRequest);
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java
new file mode 100644
index 0000000..a969515
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfo.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt.config;
+
+import java.security.interfaces.RSAPublicKey;
+
+/**
+ * The public key and expected issuer needed to validate a token.
+ */
+public class JWTAuthContextInfo {
+
+ private RSAPublicKey signerKey;
+ private String issuedBy;
+ private int expGracePeriodSecs = 60;
+
+ public JWTAuthContextInfo() {
+ }
+
+ public JWTAuthContextInfo(final RSAPublicKey signerKey, final String issuedBy) {
+ this.signerKey = signerKey;
+ this.issuedBy = issuedBy;
+ }
+
+ public JWTAuthContextInfo(final JWTAuthContextInfo orig) {
+ this.signerKey = orig.signerKey;
+ this.issuedBy = orig.issuedBy;
+ this.expGracePeriodSecs = orig.expGracePeriodSecs;
+ }
+
+ public RSAPublicKey getSignerKey() {
+ return signerKey;
+ }
+
+ public void setSignerKey(final RSAPublicKey signerKey) {
+ this.signerKey = signerKey;
+ }
+
+ public String getIssuedBy() {
+ return issuedBy;
+ }
+
+ public void setIssuedBy(final String issuedBy) {
+ this.issuedBy = issuedBy;
+ }
+
+ public int getExpGracePeriodSecs() {
+ return expGracePeriodSecs;
+ }
+
+ public void setExpGracePeriodSecs(final int expGracePeriodSecs) {
+ this.expGracePeriodSecs = expGracePeriodSecs;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWPProviderRegistration.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWPProviderRegistration.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWPProviderRegistration.java
new file mode 100644
index 0000000..34f152f
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWPProviderRegistration.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt.jaxrs;
+
+import org.apache.openejb.observer.Observes;
+import org.apache.openejb.server.cxf.rs.event.ExtensionProviderRegistration;
+import org.apache.tomee.microprofile.jwt.MPJWTFilter;
+
+/**
+ * OpenEJB/TomEE hack to register a new provider on the fly
+ * Could be package in tomee only or done in another way
+ *
+ * As soon as Roberto is done with the packaging, we can remove all this and providers are going to be scanned automatically
+ */
+public class MPJWPProviderRegistration {
+
+ public void registerProvider(@Observes final ExtensionProviderRegistration event) {
+ event.getProviders().add(new MPJWTFilter.MPJWTExceptionMapper());
+ event.getProviders().add(new MPJWTSecurityAnnotationsInterceptorsFeature());
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptor.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptor.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptor.java
new file mode 100644
index 0000000..f604e6b
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptor.java
@@ -0,0 +1,57 @@
+package org.apache.tomee.microprofile.jwt.jaxrs;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.HttpURLConnection;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+public class MPJWTSecurityAnnotationsInterceptor implements ContainerRequestFilter {
+
+ private final javax.ws.rs.container.ResourceInfo resourceInfo;
+ private final ConcurrentMap<Method, Set<String>> rolesAllowed;
+ private final Set<Method> denyAll;
+ private final Set<Method> permitAll;
+
+ public MPJWTSecurityAnnotationsInterceptor(final javax.ws.rs.container.ResourceInfo resourceInfo,
+ final ConcurrentMap<Method, Set<String>> rolesAllowed,
+ final Set<Method> denyAll,
+ final Set<Method> permitAll) {
+ this.resourceInfo = resourceInfo;
+ this.rolesAllowed = rolesAllowed;
+ this.denyAll = denyAll;
+ this.permitAll = permitAll;
+ }
+
+ @Override
+ public void filter(final ContainerRequestContext requestContext) throws IOException {
+ if (permitAll.contains(resourceInfo.getResourceMethod())) {
+ return;
+ }
+
+ if (denyAll.contains(resourceInfo.getResourceMethod())) {
+ forbidden(requestContext);
+ return;
+ }
+
+ final Set<String> roles = rolesAllowed.get(resourceInfo.getResourceMethod());
+ if (roles != null && !roles.isEmpty()) {
+ final SecurityContext securityContext = requestContext.getSecurityContext();
+ for (String role : roles) {
+ if (!securityContext.isUserInRole(role)) {
+ forbidden(requestContext);
+ break;
+ }
+ }
+ }
+
+ }
+
+ private void forbidden(final ContainerRequestContext requestContext) {
+ requestContext.abortWith(Response.status(HttpURLConnection.HTTP_FORBIDDEN).build());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeature.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeature.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeature.java
new file mode 100644
index 0000000..58b3203
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeature.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt.jaxrs;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.container.DynamicFeature;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.FeatureContext;
+import javax.ws.rs.ext.Provider;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+@Provider
+public class MPJWTSecurityAnnotationsInterceptorsFeature implements DynamicFeature {
+
+ private final ConcurrentMap<Method, Set<String>> rolesAllowed = new ConcurrentHashMap<>();
+ private final Set<Method> denyAll = new HashSet<>();
+ private final Set<Method> permitAll = new HashSet<>();
+
+ @Override
+ public void configure(final ResourceInfo resourceInfo, final FeatureContext context) {
+
+ final boolean hasSecurity = processSecurityAnnotations(resourceInfo.getResourceClass(), resourceInfo.getResourceMethod());
+
+ if (hasSecurity) { // no need to add interceptor on the resources that don(t have any security requirements to enforce
+ context.register(new MPJWTSecurityAnnotationsInterceptor(resourceInfo, rolesAllowed, denyAll, permitAll));
+ }
+
+ }
+
+ private boolean processSecurityAnnotations(final Class clazz, final Method method) {
+
+ final List<Class<? extends Annotation>[]> classSecurityAnnotations = hasClassLevelAnnotations(clazz,
+ RolesAllowed.class, PermitAll.class, DenyAll.class);
+
+ final List<Class<? extends Annotation>[]> methodSecurityAnnotations = hasMethodLevelAnnotations(method,
+ RolesAllowed.class, PermitAll.class, DenyAll.class);
+
+ if (classSecurityAnnotations.size() == 0 && methodSecurityAnnotations.size() == 0) {
+ return false; // nothing to do
+ }
+
+ /*
+ * Process annotations at the class level
+ */
+ if (classSecurityAnnotations.size() > 1) {
+ throw new IllegalStateException(clazz.getName() + " has more than one security annotation (RolesAllowed, PermitAll, DenyAll).");
+ }
+
+ if (methodSecurityAnnotations.size() > 1) {
+ throw new IllegalStateException(method.toString() + " has more than one security annotation (RolesAllowed, PermitAll, DenyAll).");
+ }
+
+ if (methodSecurityAnnotations.size() == 0) { // no need to deal with class level annotations if the method has some
+ final RolesAllowed classRolesAllowed = (RolesAllowed) clazz.getAnnotation(RolesAllowed.class);
+ final PermitAll classPermitAll = (PermitAll) clazz.getAnnotation(PermitAll.class);
+ final DenyAll classDenyAll = (DenyAll) clazz.getAnnotation(DenyAll.class);
+
+ if (classRolesAllowed != null) {
+ Set<String> roles = new HashSet<String>();
+ final Set<String> previous = rolesAllowed.putIfAbsent(method, roles);
+ if (previous != null) {
+ roles = previous;
+ }
+ roles.addAll(Arrays.asList(classRolesAllowed.value()));
+ }
+
+ if (classPermitAll != null) {
+ permitAll.add(method);
+ }
+
+ if (classDenyAll != null) {
+ denyAll.add(method);
+ }
+ }
+
+ final RolesAllowed mthdRolesAllowed = method.getAnnotation(RolesAllowed.class);
+ final PermitAll mthdPermitAll = method.getAnnotation(PermitAll.class);
+ final DenyAll mthdDenyAll = method.getAnnotation(DenyAll.class);
+
+ if (mthdRolesAllowed != null) {
+ Set<String> roles = new HashSet<String>();
+ final Set<String> previous = rolesAllowed.putIfAbsent(method, roles);
+ if (previous != null) {
+ roles = previous;
+ }
+ roles.addAll(Arrays.asList(mthdRolesAllowed.value()));
+ }
+
+ if (mthdPermitAll != null) {
+ permitAll.add(method);
+ }
+
+ if (mthdDenyAll != null) {
+ denyAll.add(method);
+ }
+
+ return true;
+ }
+
+ private List<Class<? extends Annotation>[]> hasClassLevelAnnotations(final Class clazz, final Class<? extends Annotation>... annotationsToCheck) {
+ final List<Class<? extends Annotation>[]> list = new ArrayList<>();
+ for (Class<? extends Annotation> annotationToCheck : annotationsToCheck) {
+ if (clazz.isAnnotationPresent(annotationToCheck)) {
+ list.add(annotationsToCheck);
+ }
+ }
+ return list;
+ }
+
+ private List<Class<? extends Annotation>[]> hasMethodLevelAnnotations(final Method method, final Class<? extends Annotation>... annotationsToCheck) {
+ final List<Class<? extends Annotation>[]> list = new ArrayList<>();
+ for (Class<? extends Annotation> annotationToCheck : annotationsToCheck) {
+ if (method.isAnnotationPresent(annotationToCheck)) {
+ list.add(annotationsToCheck);
+ }
+ }
+ return list;
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipal.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipal.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipal.java
new file mode 100644
index 0000000..661fbde
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipal.java
@@ -0,0 +1,360 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt.principal;
+
+import org.eclipse.microprofile.jwt.Claims;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.MalformedClaimException;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+import javax.security.auth.Subject;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A default implementation of JWTCallerPrincipal using jose4j
+ * Another implementation could use nimbus and another plain JSON-P
+ */
+public class DefaultJWTCallerPrincipal extends JWTCallerPrincipal {
+
+ private static final Logger logger = Logger.getLogger(DefaultJWTCallerPrincipal.class.getName());
+ private final String jwt;
+ private final String type;
+ private final JwtClaims claimsSet;
+
+ /**
+ * Create the DefaultJWTCallerPrincipal from the parsed JWT token and the extracted principal name
+ *
+ * @param jwt - the parsed JWT token representation
+ * @param name - the extracted unqiue name to use as the principal name; from "upn", "preferred_username" or "sub" claim
+ */
+ public DefaultJWTCallerPrincipal(final String jwt, final String type, final JwtClaims claimsSet, final String name) {
+ super(name);
+ this.jwt = jwt;
+ this.type = type;
+ this.claimsSet = claimsSet;
+ fixJoseTypes();
+ }
+
+ @Override
+ public Set<String> getAudience() {
+ final Set<String> audSet = new HashSet<>();
+ try {
+ final List<String> audList = claimsSet.getStringListClaimValue("aud");
+ if (audList != null) {
+ audSet.addAll(audList);
+ }
+
+ } catch (final MalformedClaimException e) {
+ try {
+ final String aud = claimsSet.getStringClaimValue("aud");
+ audSet.add(aud);
+ } catch (final MalformedClaimException e1) {
+ logger.log(Level.FINEST, "Can't retrieve malformed 'aud' claim.", e);
+ }
+ }
+ return audSet.isEmpty() ? null : audSet;
+ }
+
+ @Override
+ public Set<String> getGroups() {
+ final HashSet<String> groups = new HashSet<>();
+ try {
+ final List<String> globalGroups = claimsSet.getStringListClaimValue("groups");
+ if (globalGroups != null) {
+ groups.addAll(globalGroups);
+ }
+
+ } catch (final MalformedClaimException e) {
+ logger.log(Level.FINEST, "Can't retrieve malformed 'groups' claim.", e);
+ }
+ return groups;
+ }
+
+
+ @Override
+ public Set<String> getClaimNames() {
+ return new HashSet<>(claimsSet.getClaimNames());
+ }
+
+ public String getRawToken() {
+ return jwt;
+ }
+
+ @Override
+ public Object getClaim(final String claimName) {
+ Claims claimType = Claims.UNKNOWN;
+ Object claim = null;
+ try {
+ claimType = Claims.valueOf(claimName);
+ } catch (IllegalArgumentException e) {
+ }
+ // Handle the jose4j NumericDate types and
+ switch (claimType) {
+ case exp:
+ case iat:
+ case auth_time:
+ case nbf:
+ case updated_at:
+ try {
+ claim = claimsSet.getClaimValue(claimType.name(), Long.class);
+ if (claim == null) {
+ claim = new Long(0);
+ }
+ } catch (final MalformedClaimException e) {
+ logger.log(Level.FINEST, "Can't retrieve 'updated_at' a malformed claim.", e);
+ }
+ break;
+ case groups:
+ claim = getGroups();
+ break;
+ case aud:
+ claim = getAudience();
+ break;
+ case UNKNOWN:
+ claim = claimsSet.getClaimValue(claimName);
+ break;
+ default:
+ claim = claimsSet.getClaimValue(claimType.name());
+ }
+ return claim;
+ }
+
+ @Override
+ public boolean implies(final Subject subject) {
+ return false;
+ }
+
+ public String toString() {
+ return toString(false);
+ }
+
+ /**
+ * TODO: showAll is ignored and currently assumed true
+ *
+ * @param showAll - should all claims associated with the JWT be displayed or should only those defined in the
+ * JsonWebToken interface be displayed.
+ * @return JWTCallerPrincipal string view
+ */
+ @Override
+ public String toString(boolean showAll) {
+ String toString = "DefaultJWTCallerPrincipal{" +
+ "id='" + getTokenID() + '\'' +
+ ", name='" + getName() + '\'' +
+ ", expiration=" + getExpirationTime() +
+ ", notBefore=" + getClaim(Claims.nbf.name()) +
+ ", issuedAt=" + getIssuedAtTime() +
+ ", issuer='" + getIssuer() + '\'' +
+ ", audience=" + getAudience() +
+ ", subject='" + getSubject() + '\'' +
+ ", type='" + type + '\'' +
+ ", issuedFor='" + getClaim("azp") + '\'' +
+ ", authTime=" + getClaim("auth_time") +
+ ", givenName='" + getClaim("given_name") + '\'' +
+ ", familyName='" + getClaim("family_name") + '\'' +
+ ", middleName='" + getClaim("middle_name") + '\'' +
+ ", nickName='" + getClaim("nickname") + '\'' +
+ ", preferredUsername='" + getClaim("preferred_username") + '\'' +
+ ", email='" + getClaim("email") + '\'' +
+ ", emailVerified=" + getClaim(Claims.email_verified.name()) +
+ ", allowedOrigins=" + getClaim("allowedOrigins") +
+ ", updatedAt=" + getClaim("updated_at") +
+ ", acr='" + getClaim("acr") + '\'';
+
+ final StringBuilder tmp = new StringBuilder(toString);
+ tmp.append(", groups=[");
+ for (String group : getGroups()) {
+ tmp.append(group);
+ tmp.append(',');
+ }
+ tmp.setLength(tmp.length() - 1);
+ tmp.append("]}");
+ return tmp.toString();
+ }
+
+ /**
+ * Convert the types jose4j uses for address, sub_jwk, and jwk
+ */
+ private void fixJoseTypes() {
+ if (claimsSet.hasClaim(Claims.address.name())) {
+ replaceMap(Claims.address.name());
+ }
+ if (claimsSet.hasClaim(Claims.jwk.name())) {
+ replaceMap(Claims.jwk.name());
+ }
+ if (claimsSet.hasClaim(Claims.sub_jwk.name())) {
+ replaceMap(Claims.sub_jwk.name());
+ }
+
+ // Handle custom claims
+ final Set<String> customClaimNames = filterCustomClaimNames(claimsSet.getClaimNames());
+ for (String name : customClaimNames) {
+ final Object claimValue = claimsSet.getClaimValue(name);
+ if (claimValue instanceof List) {
+ replaceList(name);
+
+ } else if (claimValue instanceof Map) {
+ replaceMap(name);
+
+ } else if (claimValue instanceof Number) {
+ replaceNumber(name);
+ }
+ }
+ }
+
+ /**
+ * Determine the custom claims in the set
+ *
+ * @param claimNames - the current set of claim names in this token
+ * @return the possibly empty set of names for non-Claims claims
+ */
+ private Set<String> filterCustomClaimNames(final Collection<String> claimNames) {
+ final HashSet<String> customNames = new HashSet<>(claimNames);
+ for (Claims claim : Claims.values()) {
+ customNames.remove(claim.name());
+ }
+ return customNames;
+ }
+
+ /**
+ * Replace the jose4j Map<String,Object> with a JsonObject
+ *
+ * @param name - claim name
+ */
+ private void replaceMap(final String name) {
+ try {
+ final Map<String, Object> map = claimsSet.getClaimValue(name, Map.class);
+ final JsonObject jsonObject = replaceMap(map);
+ claimsSet.setClaim(name, jsonObject);
+
+ } catch (final MalformedClaimException e) {
+ logger.log(Level.WARNING, "replaceMap failure for: " + name, e);
+ }
+ }
+
+ private JsonObject replaceMap(final Map<String, Object> map) {
+ final JsonObjectBuilder builder = Json.createObjectBuilder();
+
+ for (Map.Entry<String, Object> entry : map.entrySet()) {
+ final Object entryValue = entry.getValue();
+ if (entryValue instanceof Map) {
+ final JsonObject entryJsonObject = replaceMap((Map<String, Object>) entryValue);
+ builder.add(entry.getKey(), entryJsonObject);
+
+ } else if (entryValue instanceof List) {
+ final JsonArray array = (JsonArray) wrapValue(entryValue);
+ builder.add(entry.getKey(), array);
+
+ } else if (entryValue instanceof Long || entryValue instanceof Integer) {
+ final long lvalue = ((Number) entryValue).longValue();
+ builder.add(entry.getKey(), lvalue);
+
+ } else if (entryValue instanceof Double || entryValue instanceof Float) {
+ final double value = ((Number) entryValue).doubleValue();
+ builder.add(entry.getKey(), value);
+
+ } else if (entryValue instanceof Boolean) {
+ final boolean flag = ((Boolean) entryValue).booleanValue();
+ builder.add(entry.getKey(), flag);
+
+ } else if (entryValue instanceof String) {
+ builder.add(entry.getKey(), entryValue.toString());
+ }
+ }
+ return builder.build();
+ }
+
+ private JsonValue wrapValue(final Object value) {
+ JsonValue jsonValue = null;
+ if (value instanceof Number) {
+ final Number number = (Number) value;
+ if ((number instanceof Long) || (number instanceof Integer)) {
+ jsonValue = Json.createObjectBuilder()
+ .add("tmp", number.longValue())
+ .build()
+ .getJsonNumber("tmp");
+
+ } else {
+ jsonValue = Json.createObjectBuilder()
+ .add("tmp", number.doubleValue())
+ .build()
+ .getJsonNumber("tmp");
+ }
+
+ } else if (value instanceof Boolean) {
+ final Boolean flag = (Boolean) value;
+ jsonValue = flag ? JsonValue.TRUE : JsonValue.FALSE;
+
+ } else if (value instanceof List) {
+ final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+ final List list = (List) value;
+ for (Object element : list) {
+ if (element instanceof String) {
+ arrayBuilder.add(element.toString());
+
+ } else {
+ JsonValue jvalue = wrapValue(element);
+ arrayBuilder.add(jvalue);
+ }
+
+ }
+ jsonValue = arrayBuilder.build();
+
+ }
+ return jsonValue;
+ }
+
+
+ /**
+ * Replace the jose4j List<?> with a JsonArray
+ *
+ * @param name - claim name
+ */
+ private void replaceList(final String name) {
+ try {
+ final List list = claimsSet.getClaimValue(name, List.class);
+ final JsonArray array = (JsonArray) wrapValue(list);
+ claimsSet.setClaim(name, array);
+
+ } catch (final MalformedClaimException e) {
+ logger.log(Level.WARNING, "replaceList failure for: " + name, e);
+ }
+ }
+
+ private void replaceNumber(final String name) {
+ try {
+ final Number number = claimsSet.getClaimValue(name, Number.class);
+ final JsonNumber jsonNumber = (JsonNumber) wrapValue(number);
+ claimsSet.setClaim(name, jsonNumber);
+
+ } catch (final MalformedClaimException e) {
+ logger.log(Level.WARNING, "replaceNumber failure for: " + name, e);
+ }
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tomee/blob/c456f4be/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java
----------------------------------------------------------------------
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java
new file mode 100644
index 0000000..feb2008
--- /dev/null
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/principal/DefaultJWTCallerPrincipalFactory.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomee.microprofile.jwt.principal;
+
+import org.apache.tomee.microprofile.jwt.ParseException;
+import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo;
+import org.eclipse.microprofile.jwt.Claims;
+import org.jose4j.jwa.AlgorithmConstraints;
+import org.jose4j.jws.AlgorithmIdentifiers;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.MalformedClaimException;
+import org.jose4j.jwt.NumericDate;
+import org.jose4j.jwt.consumer.InvalidJwtException;
+import org.jose4j.jwt.consumer.JwtConsumer;
+import org.jose4j.jwt.consumer.JwtConsumerBuilder;
+import org.jose4j.jwt.consumer.JwtContext;
+
+/**
+ * A default implementation of the abstract JWTCallerPrincipalFactory that uses the Keycloak token parsing classes.
+ */
+public class DefaultJWTCallerPrincipalFactory extends JWTCallerPrincipalFactory {
+
+ /**
+ * Tries to load the JWTAuthContextInfo from CDI if the class level authContextInfo has not been set.
+ */
+ public DefaultJWTCallerPrincipalFactory() {
+ }
+
+ @Override
+ public JWTCallerPrincipal parse(final String token, final JWTAuthContextInfo authContextInfo) throws ParseException {
+ JWTCallerPrincipal principal;
+
+ try {
+ final JwtConsumerBuilder builder = new JwtConsumerBuilder()
+ .setRequireExpirationTime()
+ .setRequireSubject()
+ .setSkipDefaultAudienceValidation()
+ .setExpectedIssuer(authContextInfo.getIssuedBy())
+ .setVerificationKey(authContextInfo.getSignerKey())
+ .setJwsAlgorithmConstraints(
+ new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST,
+ AlgorithmIdentifiers.RSA_USING_SHA256));
+
+ if (authContextInfo.getExpGracePeriodSecs() > 0) {
+ builder.setAllowedClockSkewInSeconds(authContextInfo.getExpGracePeriodSecs());
+
+ } else {
+ builder.setEvaluationTime(NumericDate.fromSeconds(0));
+ }
+
+ final JwtConsumer jwtConsumer = builder.build();
+ final JwtContext jwtContext = jwtConsumer.process(token);
+ final String type = jwtContext.getJoseObjects().get(0).getHeader("typ");
+ // Validate the JWT and process it to the Claims
+ jwtConsumer.processContext(jwtContext);
+ JwtClaims claimsSet = jwtContext.getJwtClaims();
+
+ // We have to determine the unique name to use as the principal name. It comes from upn, preferred_username, sub in that order
+ String principalName = claimsSet.getClaimValue("upn", String.class);
+ if (principalName == null) {
+ principalName = claimsSet.getClaimValue("preferred_username", String.class);
+ if (principalName == null) {
+ principalName = claimsSet.getSubject();
+ }
+ }
+ claimsSet.setClaim(Claims.raw_token.name(), token);
+ principal = new DefaultJWTCallerPrincipal(token, type, claimsSet, principalName);
+
+ } catch (final InvalidJwtException e) {
+ throw new ParseException("Failed to verify token", e);
+
+ } catch (final MalformedClaimException e) {
+ throw new ParseException("Failed to verify token claims", e);
+ }
+
+ return principal;
+ }
+}
\ No newline at end of file