Add a bit more in terms of wiring - still designing and working on it

Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/d987d3ae
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/d987d3ae
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/d987d3ae

Branch: refs/heads/master
Commit: d987d3aedf89abf68e2975cd3c2be4274616a2fc
Parents: 672b422
Author: Jean-Louis Monteiro <jeano...@gmail.com>
Authored: Thu Feb 22 22:00:27 2018 +0100
Committer: Jean-Louis Monteiro <jeano...@gmail.com>
Committed: Thu Feb 22 22:00:27 2018 +0100

----------------------------------------------------------------------
 .../server/cxf/rs/MPJWTSecurityContextTest.java | 183 ++++++++++++++-----
 .../tomee/microprofile/jwt/MPJWTContext.java    | 134 ++++++++++++++
 .../tomee/microprofile/jwt/MPJWTFilter.java     |  22 ++-
 .../microprofile/jwt/MPJWTInitializer.java      |  19 +-
 .../src/main/resources/META-INF/beans.xml       |   1 +
 5 files changed, 305 insertions(+), 54 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tomee/blob/d987d3ae/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/MPJWTSecurityContextTest.java
----------------------------------------------------------------------
diff --git 
a/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/MPJWTSecurityContextTest.java
 
b/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/MPJWTSecurityContextTest.java
index 62565b4..10a1305 100644
--- 
a/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/MPJWTSecurityContextTest.java
+++ 
b/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/MPJWTSecurityContextTest.java
@@ -21,71 +21,81 @@ import org.apache.cxf.endpoint.Server;
 import org.apache.cxf.jaxrs.model.ApplicationInfo;
 import org.apache.openejb.jee.WebApp;
 import org.apache.openejb.junit.ApplicationComposer;
-import org.apache.openejb.loader.SystemInstance;
 import org.apache.openejb.observer.Observes;
 import org.apache.openejb.server.cxf.rs.event.ServerCreated;
 import org.apache.openejb.server.rest.InternalApplication;
-import org.apache.openejb.spi.SecurityService;
 import org.apache.openejb.testing.Classes;
 import org.apache.openejb.testing.Configuration;
 import org.apache.openejb.testing.EnableServices;
 import org.apache.openejb.testing.Module;
+import org.apache.openejb.testing.RandomPort;
 import org.apache.openejb.testng.PropertiesBuilder;
-import org.apache.openejb.util.NetworkUtil;
 import org.eclipse.microprofile.auth.LoginConfig;
-import org.junit.BeforeClass;
+import org.eclipse.microprofile.jwt.JsonWebToken;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+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.ws.rs.ApplicationPath;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.client.ClientBuilder;
-import javax.ws.rs.container.ContainerRequestContext;
-import javax.ws.rs.container.ContainerRequestFilter;
 import javax.ws.rs.core.Application;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.SecurityContext;
-import javax.ws.rs.ext.Provider;
 import java.io.IOException;
+import java.lang.annotation.Annotation;
 import java.security.Principal;
-import java.util.Objects;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
 import java.util.Properties;
+import java.util.Set;
+import java.util.function.Predicate;
 
 import static org.junit.Assert.assertEquals;
 
 @EnableServices("jax-rs")
 @RunWith(ApplicationComposer.class)
 public class MPJWTSecurityContextTest {
-
-    private static int port = -1;
-
-    @BeforeClass
-    public static void beforeClass() {
-        port = NetworkUtil.getNextAvailablePort();
-    }
+    @RandomPort("http")
+    private int port;
 
     @Configuration
     public Properties props() {
         return new PropertiesBuilder()
-                .p("httpejbd.port", Integer.toString(port))
                 .p("observer", "new://Service?class-name=" + 
Observer.class.getName()) // properly packaged and auto registered
                 .build();
     }
 
     @Module
-    @Classes(cdi = true, value = {Res.class, RestApplication.class})
+    @Classes(value = {Res.class, RestApplication.class, MPFilter.class, 
MPContext.class}, cdi = true)
     public WebApp war() {
         return new WebApp()
                 .contextRoot("foo");
     }
 
     @Test
-    public void check() throws IOException {
+    public void check() {
+        // todo: close the client (just to stay clean even in tests and avoid 
to potentially leak)
         assertEquals("true", ClientBuilder.newClient()
                 .target("http://127.0.0.1:"; + port)
-                .path("api/foo/sc")
+                .path("foo/api/sc")
                 .queryParam("role", "therole")
                 .request()
                 .accept(MediaType.TEXT_PLAIN_TYPE)
@@ -93,7 +103,7 @@ public class MPJWTSecurityContextTest {
 
         assertEquals("false", ClientBuilder.newClient()
                 .target("http://127.0.0.1:"; + port)
-                .path("api/foo/sc")
+                .path("foo/api/sc")
                 .queryParam("role", "another")
                 .request()
                 .accept(MediaType.TEXT_PLAIN_TYPE)
@@ -107,13 +117,12 @@ public class MPJWTSecurityContextTest {
     }
 
     @Path("sc")
+    @ApplicationScoped
     public static class Res {
-        @Context
-        private SecurityContext sc;
-
         @GET
-        public boolean f() {
-            return sc.isUserInRole("therole");
+        @Produces(MediaType.TEXT_PLAIN)
+        public boolean f(@Context final SecurityContext ctx, 
@QueryParam("role") final String role) {
+            return ctx.isUserInRole(role);
         }
     }
 
@@ -132,46 +141,120 @@ public class MPJWTSecurityContextTest {
             final LoginConfig annotation = 
application.getClass().getAnnotation(LoginConfig.class);
             if (annotation != null && 
"MP-JWT".equals(annotation.authMethod())) {
                 // add the ContainerRequestFilter on the fly
-                // todo how to add this on the fly
             }
         }
     }
 
-    // this should also be packaged into the same module and delegate to the 
security service
-    @Provider
-    public static class MPJWTSecurityContext implements ContainerRequestFilter 
{
-
-        private final SecurityService securityService;
-
-        public MPJWTSecurityContext() {
-            securityService = 
SystemInstance.get().getComponent(SecurityService.class);
-            Objects.requireNonNull(securityService, "A security context needs 
to be properly configured to enforce security in REST services");
-        }
+    // todo: industrialize that but idea is to fill that during startup to let 
it be usable at runtime
+    // note: the bean must be added through an extension in a real impl
+    @ApplicationScoped
+    public static class MPContext {
+        // todo: login config model, not the raw annot
+        private Map<String, LoginConfig> configs = new HashMap<>();
 
-        @Override
-        public void filter(final ContainerRequestContext 
containerRequestContext) throws IOException {
-            containerRequestContext.setSecurityContext(new SecurityContext() {
-                @Override
-                public Principal getUserPrincipal() {
-                    return securityService.getCallerPrincipal();
-                }
+        @PostConstruct
+        private void init() {
+            // todo: drop and replace by actual init
+            configs.put("/api", new LoginConfig() {
 
                 @Override
-                public boolean isUserInRole(final String s) {
-                    return securityService.isCallerInRole(s);
+                public Class<? extends Annotation> annotationType() {
+                    return LoginConfig.class;
                 }
 
                 @Override
-                public boolean isSecure() {
-                    return false;
+                public String authMethod() {
+                    return "MP-JWT";
                 }
 
                 @Override
-                public String getAuthenticationScheme() {
-                    return "MP-JWT";
+                public String realmName() {
+                    return "";
                 }
             });
         }
+
+        public Map<String, LoginConfig> getConfigs() {
+            return configs;
+        }
     }
 
-}
+    @WebFilter(asyncSupported = true, urlPatterns = "/*") // addbefore from an 
initializer
+    public static class MPFilter implements Filter {
+        @Inject
+        private MPContext context;
+
+        @Override
+        public void init(final FilterConfig filterConfig) throws 
ServletException {
+            // no-op
+        }
+
+        @Override
+        public void doFilter(final ServletRequest request, final 
ServletResponse response, final FilterChain chain)
+                throws IOException, ServletException {
+            final HttpServletRequest httpServletRequest = 
HttpServletRequest.class.cast(request);
+            final String uri = 
httpServletRequest.getRequestURI().substring(httpServletRequest.getContextPath().length());
+
+            // todo: better handling of conflicting app paths?
+            final Optional<Map.Entry<String, LoginConfig>> first = 
context.getConfigs()
+                    .entrySet()
+                    .stream()
+                    .filter(new Predicate<Map.Entry<String, LoginConfig>>() {
+                        @Override
+                        public boolean test(final Map.Entry<String, 
LoginConfig> e) {
+                            return uri.startsWith(e.getKey());
+                        }
+                    })
+                    .findFirst();
+
+            if (first.isPresent()) {
+                chain.doFilter(new 
HttpServletRequestWrapper(httpServletRequest) {
+                    private final MPPcp pcp = new MPPcp();
+
+                    @Override
+                    public Principal getUserPrincipal() {
+                        return pcp;
+                    }
+
+                    @Override
+                    public boolean isUserInRole(final String role) {
+                        return pcp.getGroups().contains(role);
+                    }
+                }, response);
+
+            } else {
+                chain.doFilter(request, response);
+
+            }
+        }
+
+        @Override
+        public void destroy() {
+            // no-op
+        }
+    }
+
+    // todo
+    public static class MPPcp implements JsonWebToken {
+
+        @Override
+        public String getName() {
+            return "mp";
+        }
+
+        @Override
+        public Set<String> getClaimNames() {
+            return Collections.singleton("test");
+        }
+
+        @Override
+        public <T> T getClaim(String claimName) {
+            return (T) "foo";
+        }
+
+        @Override
+        public Set<String> getGroups() {
+            return Collections.singleton("therole");
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/d987d3ae/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java
----------------------------------------------------------------------
diff --git 
a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java
 
b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java
new file mode 100644
index 0000000..2197745
--- /dev/null
+++ 
b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java
@@ -0,0 +1,134 @@
+/*
+ *     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.enterprise.context.ApplicationScoped;
+import java.net.URI;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Predicate;
+
+/**
+ * Responsible for holding the runtime model
+ */
+@ApplicationScoped
+public class MPJWTContext {
+
+    private final ConcurrentMap<MPJWTConfigKey, MPJWTConfigValue> 
configuration = new ConcurrentHashMap<>();
+
+    public MPJWTConfigValue addMapping(final MPJWTConfigKey key, final 
MPJWTConfigValue value) {
+        Objects.requireNonNull(key, "MP JWT Key is required");
+        Objects.requireNonNull(value, "MP JWT Value is required");
+
+        final MPJWTConfigValue oldValue = configuration.putIfAbsent(key, 
value);
+        if (oldValue != null) {
+            throw new IllegalStateException("A mapping has already been 
defined for the key " + key);
+        }
+
+        return value;
+    }
+
+    public Optional<MPJWTConfigValue> get(final MPJWTConfigKey key) {
+        Objects.requireNonNull(key, "MP JWT Key is required to retrieve the 
configuration");
+        return Optional.ofNullable(configuration.get(key));
+    }
+
+    public Optional<Map.Entry<MPJWTConfigKey, MPJWTConfigValue>> 
findFirst(final String path) {
+        return configuration.entrySet()
+                .stream()
+                .filter(new Predicate<ConcurrentMap.Entry<MPJWTConfigKey, 
MPJWTConfigValue>>() {
+                    @Override
+                    public boolean test(final 
ConcurrentMap.Entry<MPJWTConfigKey, MPJWTConfigValue> e) {
+                        return path.startsWith(e.getKey().toURI());
+                    }
+                })
+                .findFirst();
+    }
+
+
+    public static class MPJWTConfigValue {
+        private final String authMethod;
+        private final String realm;
+
+        public MPJWTConfigValue(final String authMethod, final String realm) {
+            this.authMethod = authMethod;
+            this.realm = realm;
+        }
+
+        public String getAuthMethod() {
+            return authMethod;
+        }
+
+        public String getRealm() {
+            return realm;
+        }
+    }
+
+    public static class MPJWTConfigKey {
+        private final String contextPath;
+        private final String applicationPath;
+
+        public MPJWTConfigKey(final String contextPath, final String 
applicationPath) {
+            this.contextPath = contextPath;
+            this.applicationPath = applicationPath;
+        }
+
+        public String getApplicationPath() {
+            return applicationPath;
+        }
+
+        public String getContextPath() {
+            return contextPath;
+        }
+
+        public String toURI() {
+            return ("/" + contextPath + "/" + 
applicationPath).replaceAll("//", "/");
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            final MPJWTConfigKey that = (MPJWTConfigKey) o;
+
+            if (contextPath != null ? !contextPath.equals(that.contextPath) : 
that.contextPath != null) return false;
+            return !(applicationPath != null ? 
!applicationPath.equals(that.applicationPath) : that.applicationPath != null);
+
+        }
+
+        @Override
+        public int hashCode() {
+            int result = contextPath != null ? contextPath.hashCode() : 0;
+            result = 31 * result + (applicationPath != null ? 
applicationPath.hashCode() : 0);
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "MPJWTConfigKey{" +
+                    "applicationPath='" + applicationPath + '\'' +
+                    ", contextPath='" + contextPath + '\'' +
+                    '}';
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tomee/blob/d987d3ae/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
----------------------------------------------------------------------
diff --git 
a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
 
b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
index 9b57b51..cb235fa 100644
--- 
a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
+++ 
b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
@@ -16,6 +16,9 @@
  */
 package org.apache.tomee.microprofile.jwt;
 
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+import javax.inject.Inject;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -27,11 +30,15 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequestWrapper;
 import java.io.IOException;
 import java.security.Principal;
+import java.util.Map;
+import java.util.Optional;
 
 // 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 MPJWTContext context;
 
     @Override
     public void init(final FilterConfig filterConfig) throws ServletException {
@@ -42,21 +49,30 @@ public class MPJWTFilter implements Filter {
     @Override
     public void doFilter(final ServletRequest request, final ServletResponse 
response, final FilterChain chain) throws IOException, ServletException {
 
-        final HttpServletRequest httpServletRequest = 
(HttpServletRequest)request;
+        final HttpServletRequest httpServletRequest = (HttpServletRequest) 
request;
+        final Optional<Map.Entry<MPJWTContext.MPJWTConfigKey, 
MPJWTContext.MPJWTConfigValue>> first =
+                context.findFirst(httpServletRequest.getRequestURI());
+
+        if (first.isPresent()) { // nothing found in the context
+            chain.doFilter(request, response);
+        }
 
         // todo get JWT and do validation
+        // todo not sure what to do with the realm
+
+        final JsonWebToken jsonWebToken = new 
DefaultJWTCallerPrincipal("bla"); // will be build during validation
 
         // now wrap the httpServletRequest and override the principal so CXF 
can propagate into the SecurityContext
         chain.doFilter(new HttpServletRequestWrapper(httpServletRequest) {
 
             @Override
             public Principal getUserPrincipal() {
-                return null; // todo, during parsing and validation, we need 
to convert into the JWT Principal as specified by the spec
+                return jsonWebToken;
             }
 
             @Override
             public boolean isUserInRole(String role) {
-                return true; // replace with a check based on the claims 
content
+                return jsonWebToken.getGroups().contains(role);
             }
 
             @Override

http://git-wip-us.apache.org/repos/asf/tomee/blob/d987d3ae/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java
----------------------------------------------------------------------
diff --git 
a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java
 
b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java
index e91047d..dc3d7ba 100644
--- 
a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java
+++ 
b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java
@@ -18,11 +18,13 @@ package org.apache.tomee.microprofile.jwt;
 
 import org.eclipse.microprofile.auth.LoginConfig;
 
+import javax.inject.Inject;
 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.ApplicationPath;
 import javax.ws.rs.core.Application;
 import java.util.Set;
 
@@ -32,6 +34,9 @@ import java.util.Set;
 @HandlesTypes(LoginConfig.class)
 public class MPJWTInitializer implements ServletContainerInitializer {
 
+    @Inject
+    private MPJWTContext context;
+
     @Override
     public void onStartup(final Set<Class<?>> classes, final ServletContext 
ctx) throws ServletException {
 
@@ -46,12 +51,24 @@ public class MPJWTInitializer implements 
ServletContainerInitializer {
                 continue;
             }
 
-            if (!Application.class.isInstance(clazz)) {
+            if (!Application.class.isAssignableFrom(clazz)) {
                 continue; // do we really want Application?
             }
 
+            final ApplicationPath applicationPath = 
clazz.getAnnotation(ApplicationPath.class);
+
             final FilterRegistration.Dynamic mpJwtFilter = 
ctx.addFilter("mp-jwt-filter", MPJWTFilter.class);
             mpJwtFilter.setAsyncSupported(true);
+
+            context.addMapping(
+                    new MPJWTContext.MPJWTConfigKey(
+                            ctx.getContextPath(),
+                            applicationPath == null ? "" : 
applicationPath.value()),
+                    new MPJWTContext.MPJWTConfigValue(
+                            loginConfig.authMethod(),
+                            loginConfig.realmName())
+            );
+
         }
 
     }

http://git-wip-us.apache.org/repos/asf/tomee/blob/d987d3ae/tck/mp-jwt-embedded/src/main/resources/META-INF/beans.xml
----------------------------------------------------------------------
diff --git a/tck/mp-jwt-embedded/src/main/resources/META-INF/beans.xml 
b/tck/mp-jwt-embedded/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..330c7f6
--- /dev/null
+++ b/tck/mp-jwt-embedded/src/main/resources/META-INF/beans.xml
@@ -0,0 +1 @@
+<beans/>
\ No newline at end of file

Reply via email to