JAMES-1960 Allow CORS request on WebAdmin (configuration defined)

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

Branch: refs/heads/master
Commit: 1463487763dc70f801922067a1d5e7b0f933bd6f
Parents: 0b5d95b
Author: benwa <btell...@linagora.com>
Authored: Thu Mar 9 14:40:56 2017 +0700
Committer: benwa <btell...@linagora.com>
Committed: Wed Mar 15 09:02:26 2017 +0700

----------------------------------------------------------------------
 .../destination/conf/webadmin.properties        |  6 +-
 .../destination/conf/webadmin.properties        |  6 +-
 .../modules/server/WebAdminServerModule.java    |  2 +
 .../org/apache/james/webadmin/CORSFilter.java   | 40 +++++++++++
 .../james/webadmin/WebAdminConfiguration.java   | 49 ++++++++++++--
 .../apache/james/webadmin/WebAdminServer.java   | 30 ++++++---
 .../webadmin/authentication/JwtFilter.java      | 15 +++--
 .../apache/james/webadmin/routes/CORSRoute.java | 42 ++++++++++++
 .../webadmin/WebAdminConfigurationTest.java     | 70 +++++++++++++++++++-
 9 files changed, 236 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/14634877/dockerfiles/run/guice/cassandra-ldap/destination/conf/webadmin.properties
----------------------------------------------------------------------
diff --git 
a/dockerfiles/run/guice/cassandra-ldap/destination/conf/webadmin.properties 
b/dockerfiles/run/guice/cassandra-ldap/destination/conf/webadmin.properties
index a9aced0..38e2ba0 100644
--- a/dockerfiles/run/guice/cassandra-ldap/destination/conf/webadmin.properties
+++ b/dockerfiles/run/guice/cassandra-ldap/destination/conf/webadmin.properties
@@ -33,4 +33,8 @@ https.enabled=false
 #https.trust.password
 
 # Defaults to false
-#jwt.enabled=true
\ No newline at end of file
+#jwt.enabled=true
+
+# Defaults to false
+#cors.enable=true
+#cors.origin
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/14634877/dockerfiles/run/guice/cassandra/destination/conf/webadmin.properties
----------------------------------------------------------------------
diff --git 
a/dockerfiles/run/guice/cassandra/destination/conf/webadmin.properties 
b/dockerfiles/run/guice/cassandra/destination/conf/webadmin.properties
index a9aced0..38e2ba0 100644
--- a/dockerfiles/run/guice/cassandra/destination/conf/webadmin.properties
+++ b/dockerfiles/run/guice/cassandra/destination/conf/webadmin.properties
@@ -33,4 +33,8 @@ https.enabled=false
 #https.trust.password
 
 # Defaults to false
-#jwt.enabled=true
\ No newline at end of file
+#jwt.enabled=true
+
+# Defaults to false
+#cors.enable=true
+#cors.origin
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/14634877/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/WebAdminServerModule.java
----------------------------------------------------------------------
diff --git 
a/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/WebAdminServerModule.java
 
b/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/WebAdminServerModule.java
index 68e1b43..1532471 100644
--- 
a/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/WebAdminServerModule.java
+++ 
b/server/container/guice/protocols/webadmin/src/main/java/org/apache/james/modules/server/WebAdminServerModule.java
@@ -80,6 +80,8 @@ public class WebAdminServerModule extends AbstractModule {
                 .enable(configurationFile.getBoolean("enabled", false))
                 .port(new FixedPort(configurationFile.getInt("port", 
WebAdminServer.DEFAULT_PORT)))
                 .https(readHttpsConfiguration(configurationFile))
+                .enableCORS(configurationFile.getBoolean("cors.enable", false))
+                .urlCORSOrigin(configurationFile.getString("cors.origin", 
null))
                 .build();
         } catch (FileNotFoundException e) {
             return WebAdminConfiguration.builder()

http://git-wip-us.apache.org/repos/asf/james-project/blob/14634877/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/CORSFilter.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/CORSFilter.java
 
b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/CORSFilter.java
new file mode 100644
index 0000000..c4cfb29
--- /dev/null
+++ 
b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/CORSFilter.java
@@ -0,0 +1,40 @@
+/****************************************************************
+ * 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.james.webadmin;
+
+import spark.Filter;
+import spark.Request;
+import spark.Response;
+
+
+public class CORSFilter implements Filter {
+    private final String urlCORSOrigin;
+
+    public CORSFilter(String urlCORSOrigin) {
+        this.urlCORSOrigin = urlCORSOrigin;
+    }
+
+    @Override
+    public void handle(Request request, Response response) throws Exception {
+            response.header("Access-Control-Allow-Origin", urlCORSOrigin);
+            response.header("Access-Control-Request-Method", "DELETE, GET, 
POST, PUT");
+            response.header("Access-Control-Allow-Headers", "Content-Type, 
Authorization, Accept");
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/14634877/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/WebAdminConfiguration.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/WebAdminConfiguration.java
 
b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/WebAdminConfiguration.java
index 8cb022a..c87589b 100644
--- 
a/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/WebAdminConfiguration.java
+++ 
b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/WebAdminConfiguration.java
@@ -28,6 +28,9 @@ import com.google.common.base.Preconditions;
 
 public class WebAdminConfiguration {
 
+    public static final boolean DEFAULT_CORS_DISABLED = false;
+    public static final String CORS_ALL_ORIGINS = "*";
+
     public static Builder builder() {
         return new Builder();
     }
@@ -35,7 +38,9 @@ public class WebAdminConfiguration {
     public static class Builder {
         private Optional<Boolean> enabled = Optional.empty();
         private Port port;
+        private Optional<Boolean> enableCORS = Optional.empty();
         private Optional<HttpsConfiguration> httpsConfiguration = 
Optional.empty();
+        private Optional<String> urlCORSOrigin = Optional.empty();
 
         public Builder https(HttpsConfiguration httpsConfiguration) {
             this.httpsConfiguration = Optional.of(httpsConfiguration);
@@ -51,7 +56,6 @@ public class WebAdminConfiguration {
             this.enabled = Optional.of(isEnabled);
             return this;
         }
-
         public Builder enabled() {
             return enable(true);
         }
@@ -60,6 +64,24 @@ public class WebAdminConfiguration {
             return enable(false);
         }
 
+        public Builder urlCORSOrigin(String origin) {
+            this.urlCORSOrigin = Optional.ofNullable(origin);
+            return this;
+        }
+
+        public Builder enableCORS(boolean isEnabled) {
+            this.enableCORS = Optional.of(isEnabled);
+            return this;
+        }
+
+        public Builder CORSenabled() {
+            return enableCORS(true);
+        }
+
+        public Builder CORSdisabled() {
+            return enableCORS(false);
+        }
+
         public WebAdminConfiguration build() {
             Preconditions.checkState(enabled.isPresent(), "You need to 
explicitly enable or disable WebAdmin server");
             Preconditions.checkState(!enabled.get() || port != null, "You need 
to specify a port for WebAdminConfiguration");
@@ -68,25 +90,35 @@ public class WebAdminConfiguration {
                 httpsConfiguration.orElse(
                     HttpsConfiguration.builder()
                         .disabled()
-                        .build()));
+                        .build()),
+                enableCORS.orElse(DEFAULT_CORS_DISABLED),
+                urlCORSOrigin.orElse(CORS_ALL_ORIGINS));
         }
     }
 
     private final boolean enabled;
     private final Port port;
     private final HttpsConfiguration httpsConfiguration;
+    private final boolean enableCORS;
+    private final String urlCORSOrigin;
 
     @VisibleForTesting
-    WebAdminConfiguration(boolean enabled, Port port, HttpsConfiguration 
httpsConfiguration) {
+    WebAdminConfiguration(boolean enabled, Port port, HttpsConfiguration 
httpsConfiguration, boolean enableCORS, String urlCORSOrigin) {
         this.enabled = enabled;
         this.port = port;
         this.httpsConfiguration = httpsConfiguration;
+        this.enableCORS = enableCORS;
+        this.urlCORSOrigin = urlCORSOrigin;
     }
 
     public boolean isEnabled() {
         return enabled;
     }
 
+    public String getUrlCORSOrigin() {
+        return urlCORSOrigin;
+    }
+
     public Port getPort() {
         return port;
     }
@@ -95,19 +127,26 @@ public class WebAdminConfiguration {
         return httpsConfiguration;
     }
 
+    public boolean isEnableCORS() {
+        return enableCORS;
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof WebAdminConfiguration) {
             WebAdminConfiguration that = (WebAdminConfiguration) o;
 
             return Objects.equals(this.enabled, that.enabled)
-                && Objects.equals(this.port, that.port);
+                && Objects.equals(this.port, that.port)
+                && Objects.equals(this.httpsConfiguration, 
that.httpsConfiguration)
+                && Objects.equals(this.enableCORS, that.enableCORS)
+                && Objects.equals(this.urlCORSOrigin, that.urlCORSOrigin);
         }
         return false;
     }
 
     @Override
     public final int hashCode() {
-        return Objects.hash(enabled, port);
+        return Objects.hash(enabled, port, httpsConfiguration, enableCORS, 
urlCORSOrigin);
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/14634877/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/WebAdminServer.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/WebAdminServer.java
 
b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/WebAdminServer.java
index 7457219..9c398b4 100644
--- 
a/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/WebAdminServer.java
+++ 
b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/WebAdminServer.java
@@ -30,6 +30,7 @@ import 
org.apache.commons.configuration.HierarchicalConfiguration;
 import org.apache.james.lifecycle.api.Configurable;
 import org.apache.james.webadmin.authentication.AuthenticationFilter;
 import org.apache.james.webadmin.authentication.NoAuthenticationFilter;
+import org.apache.james.webadmin.routes.CORSRoute;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -72,20 +73,33 @@ public class WebAdminServer implements Configurable {
     public void configure(HierarchicalConfiguration config) throws 
ConfigurationException {
         if (configuration.isEnabled()) {
             service.port(configuration.getPort().toInt());
-            HttpsConfiguration httpsConfiguration = 
configuration.getHttpsConfiguration();
-            if (httpsConfiguration.isEnabled()) {
-                service.secure(httpsConfiguration.getKeystoreFilePath(),
-                    httpsConfiguration.getKeystorePassword(),
-                    httpsConfiguration.getTruststoreFilePath(),
-                    httpsConfiguration.getTruststorePassword());
-                LOGGER.info("Web admin set up to use HTTPS");
-            }
+            configureHTTPS();
+            configureCORS();
             service.before(authenticationFilter);
             routesList.forEach(routes -> routes.define(service));
             LOGGER.info("Web admin server started");
         }
     }
 
+    private void configureHTTPS() {
+        HttpsConfiguration httpsConfiguration = 
configuration.getHttpsConfiguration();
+        if (httpsConfiguration.isEnabled()) {
+            service.secure(httpsConfiguration.getKeystoreFilePath(),
+                httpsConfiguration.getKeystorePassword(),
+                httpsConfiguration.getTruststoreFilePath(),
+                httpsConfiguration.getTruststorePassword());
+            LOGGER.info("Web admin set up to use HTTPS");
+        }
+    }
+
+    private void configureCORS() {
+        if (configuration.isEnabled()) {
+            service.before(new CORSFilter(configuration.getUrlCORSOrigin()));
+            new CORSRoute().define(service);
+            LOGGER.info("Web admin set up to enable CORS from " + 
configuration.getUrlCORSOrigin());
+        }
+    }
+
     @PreDestroy
     public void destroy() {
         if (configuration.isEnabled()) {

http://git-wip-us.apache.org/repos/asf/james-project/blob/14634877/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/authentication/JwtFilter.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/authentication/JwtFilter.java
 
b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/authentication/JwtFilter.java
index 991f412..4832d6f 100644
--- 
a/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/authentication/JwtFilter.java
+++ 
b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/authentication/JwtFilter.java
@@ -33,6 +33,7 @@ import spark.Response;
 public class JwtFilter implements AuthenticationFilter {
     public static final String AUTHORIZATION_HEADER_PREFIX = "Bearer ";
     public static final String AUTHORIZATION_HEADER_NAME = "Authorization";
+    public static final String OPTIONS = "OPTIONS";
 
     private final JwtTokenVerifier jwtTokenVerifier;
 
@@ -43,13 +44,15 @@ public class JwtFilter implements AuthenticationFilter {
 
     @Override
     public void handle(Request request, Response response) throws Exception {
-        Optional<String> bearer = 
Optional.ofNullable(request.headers(AUTHORIZATION_HEADER_NAME))
-            .filter(value -> value.startsWith(AUTHORIZATION_HEADER_PREFIX))
-            .map(value -> 
value.substring(AUTHORIZATION_HEADER_PREFIX.length()));
+        if (request.requestMethod() != OPTIONS) {
+            Optional<String> bearer = 
Optional.ofNullable(request.headers(AUTHORIZATION_HEADER_NAME))
+                .filter(value -> value.startsWith(AUTHORIZATION_HEADER_PREFIX))
+                .map(value -> 
value.substring(AUTHORIZATION_HEADER_PREFIX.length()));
 
-        checkHeaderPresent(bearer);
-        checkValidSignature(bearer);
-        checkIsAdmin(bearer);
+            checkHeaderPresent(bearer);
+            checkValidSignature(bearer);
+            checkIsAdmin(bearer);
+        }
     }
 
     private void checkHeaderPresent(Optional<String> bearer) {

http://git-wip-us.apache.org/repos/asf/james-project/blob/14634877/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/routes/CORSRoute.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/routes/CORSRoute.java
 
b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/routes/CORSRoute.java
new file mode 100644
index 0000000..39e9bd7
--- /dev/null
+++ 
b/server/protocols/webadmin/src/main/java/org/apache/james/webadmin/routes/CORSRoute.java
@@ -0,0 +1,42 @@
+/****************************************************************
+ * 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.james.webadmin.routes;
+
+import org.apache.james.webadmin.Routes;
+
+import spark.Service;
+
+public class CORSRoute implements Routes {
+
+    @Override
+    public void define(Service service) {
+        service.options("/*", (request, response) -> {
+            String accessControlRequestHeaders = 
request.headers("Access-Control-Request-Headers");
+            if (accessControlRequestHeaders != null) {
+                response.header("Access-Control-Allow-Headers", 
accessControlRequestHeaders);
+            }
+            String accessControlRequestMethod = 
request.headers("Access-Control-Request-Method");
+            if (accessControlRequestMethod != null) {
+                response.header("Access-Control-Allow-Methods", 
accessControlRequestMethod);
+            }
+            return "";
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/14634877/server/protocols/webadmin/src/test/java/org/apache/james/webadmin/WebAdminConfigurationTest.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/src/test/java/org/apache/james/webadmin/WebAdminConfigurationTest.java
 
b/server/protocols/webadmin/src/test/java/org/apache/james/webadmin/WebAdminConfigurationTest.java
index d20100c..4606e94 100644
--- 
a/server/protocols/webadmin/src/test/java/org/apache/james/webadmin/WebAdminConfigurationTest.java
+++ 
b/server/protocols/webadmin/src/test/java/org/apache/james/webadmin/WebAdminConfigurationTest.java
@@ -46,7 +46,11 @@ public class WebAdminConfigurationTest {
         assertThat(WebAdminConfiguration.builder()
             .disabled()
             .build())
-            .isEqualTo(new WebAdminConfiguration(false, null, 
HttpsConfiguration.builder().disabled().build()));
+            .isEqualTo(new WebAdminConfiguration(false,
+                null,
+                HttpsConfiguration.builder().disabled().build(),
+                false,
+                "*"));
     }
 
     @Test
@@ -63,7 +67,12 @@ public class WebAdminConfigurationTest {
                 .enabled()
                 .port(PORT)
                 .build())
-            .isEqualTo(new WebAdminConfiguration(true, PORT, 
HttpsConfiguration.builder().disabled().build()));
+            .isEqualTo(new WebAdminConfiguration(
+                true,
+                PORT,
+                HttpsConfiguration.builder().disabled().build(),
+                false,
+                "*"));
     }
 
     @Test
@@ -79,7 +88,62 @@ public class WebAdminConfigurationTest {
                 .https(httpsConfiguration)
                 .port(PORT)
                 .build())
-            .isEqualTo(new WebAdminConfiguration(true, PORT, 
httpsConfiguration));
+            .isEqualTo(new WebAdminConfiguration(
+                true,
+                PORT,
+                httpsConfiguration,
+                false,
+                "*"));
+    }
+
+    @Test
+    public void builderShouldCORSEnabled() {
+        assertThat(
+            WebAdminConfiguration.builder()
+                .enabled()
+                .port(PORT)
+                .CORSenabled()
+                .build())
+            .isEqualTo(new WebAdminConfiguration(
+                true,
+                PORT,
+                HttpsConfiguration.builder().disabled().build(),
+                true,
+                "*"));
+    }
+
+    @Test
+    public void builderShouldCORSDisabled() {
+        assertThat(
+            WebAdminConfiguration.builder()
+                .enabled()
+                .port(PORT)
+                .CORSdisabled()
+                .build())
+            .isEqualTo(new WebAdminConfiguration(
+                true,
+                PORT,
+                HttpsConfiguration.builder().disabled().build(),
+                false,
+                "*"));
+    }
+
+    @Test
+    public void builderShouldCORSWithOrigin() {
+        String origin = "linagora.com";
+        assertThat(
+            WebAdminConfiguration.builder()
+                .enabled()
+                .port(PORT)
+                .CORSenabled()
+                .urlCORSOrigin(origin)
+                .build())
+            .isEqualTo(new WebAdminConfiguration(
+                true,
+                PORT,
+                HttpsConfiguration.builder().disabled().build(),
+                true,
+                origin));
     }
 
     @Test


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org
For additional commands, e-mail: server-dev-h...@james.apache.org

Reply via email to