Allow securiting asynchronous endpoints 60/89860/11
authorLukas Baca <lbaca@luminanetworks.com>
Mon, 18 May 2020 22:02:13 +0000 (00:02 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 7 Jul 2020 11:07:54 +0000 (13:07 +0200)
We need the possibility to allow async requests by AAA because SSE
(Server Sent Events) use async communication.

Previous behavior is set as default. New behavior is possible set by
new parameter in FilterDetails and ServletDetails. These are also
covered by an explicit test suite.

ShiroWebContextSecurer is also updated to take advantage of this
new capability.

JIRA: AAA-199
Change-Id: I83d579bbe33cfeb33f44150ecbe3619654bebe36
Signed-off-by: Lukas Baca <lbaca@luminanetworks.com>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
aaa-shiro/impl/src/main/java/org/opendaylight/aaa/shiro/web/env/ShiroWebContextSecurer.java
web/api/src/main/java/org/opendaylight/aaa/web/FilterDetails.java
web/api/src/main/java/org/opendaylight/aaa/web/ServletDetails.java
web/api/src/main/java/org/opendaylight/aaa/web/WebContextSecurer.java
web/api/src/test/java/org/opendaylight/aaa/web/tests/FilterDetailsTest.java [new file with mode: 0644]
web/api/src/test/java/org/opendaylight/aaa/web/tests/ServletDetailsTest.java [new file with mode: 0644]
web/impl-osgi/src/main/java/org/opendaylight/aaa/web/osgi/PaxWebServer.java
web/testutils/src/main/java/org/opendaylight/aaa/web/testutils/WebTestModule.java

index c1fd809f251ddd3f99c4e74c8028d6f65c8be601..2a922bf7324c79830da260856e62b2965e89a778 100644 (file)
@@ -29,19 +29,27 @@ public class ShiroWebContextSecurer implements WebContextSecurer {
     }
 
     @Override
-    public void requireAuthentication(WebContextBuilder webContextBuilder, String... urlPatterns) {
-        webContextBuilder
-            .addListener(shiroEnvironmentLoaderListener)
-
-            // AAA filter in front of these REST web services as well as for moon endpoints
-            .addFilter(FilterDetails.builder().filter(new AAAShiroFilter()).addUrlPatterns(urlPatterns).build())
-
-            // CORS filter
-            .addFilter(FilterDetails.builder().filter(new CrossOriginFilter()).addUrlPatterns(urlPatterns)
-               .putInitParam("allowedOrigins", "*")
-               .putInitParam("allowedMethods", "GET,POST,OPTIONS,DELETE,PUT,HEAD")
-               .putInitParam("allowedHeaders", "origin, content-type, accept, authorization")
-               .build());
+    public void requireAuthentication(WebContextBuilder webContextBuilder, boolean asyncSupported,
+            String... urlPatterns) {
+        webContextBuilder.addListener(shiroEnvironmentLoaderListener)
+
+                // AAA filter in front of these REST web services as well as for moon endpoints
+                .addFilter(FilterDetails.builder()
+                        .filter(new AAAShiroFilter())
+                        .addUrlPatterns(urlPatterns)
+                        .asyncSupported(asyncSupported)
+                        .build())
+
+                // CORS filter
+                .addFilter(FilterDetails.builder()
+                        .filter(new CrossOriginFilter())
+                        .addUrlPatterns(urlPatterns)
+                        .asyncSupported(asyncSupported)
+                        .putInitParam("allowedOrigins", "*")
+                        .putInitParam("allowedMethods", "GET,POST,OPTIONS,DELETE,PUT,HEAD")
+                        .putInitParam("allowedHeaders", "origin, content-type, accept, authorization")
+                        .build());
+
     }
 
 }
index b67d45acbeaa6048fa46d0e83c20759193610d5e..a5d848ea90c1ff318d440ce6fae0e1a5a64b085d 100644 (file)
@@ -36,4 +36,8 @@ public interface FilterDetails {
 
     Map<String, String> initParams();
 
+    @Default default Boolean getAsyncSupported() {
+        return false;
+    }
+
 }
index 2e90606b66d1b76fce3fd319a5d7730a73150eac..2f3aa0cf821ad7a84736bd3ab8eca3844914f262 100644 (file)
@@ -36,4 +36,7 @@ public interface ServletDetails {
 
     Map<String, String> initParams();
 
+    @Default default Boolean getAsyncSupported() {
+        return false;
+    }
 }
index 52facc12112f00141c0d2ecb21451244236a717a..2b7f6e7e708291b80bc7795cf96e3dc5f5609ff8 100644 (file)
@@ -13,19 +13,34 @@ package org.opendaylight.aaa.web;
  * @author Michael Vorburger.ch
  */
 public interface WebContextSecurer {
+    /**
+     * Configures the WebContext in an implementation specific manner so that it requires authentication to access the
+     * given URL Patterns. Typically, this will be done by adding a {@code javax.servlet.Filter} (or several, and
+     * whatever else they need).
+     *
+     * @param webContextBuilder builder to secure
+     * @param asyncSupported true if asynchronous communication should also be supported
+     * @param urlPatterns URL patterns that require authentication
+     */
+    void requireAuthentication(WebContextBuilder webContextBuilder, boolean asyncSupported, String... urlPatterns);
 
     /**
-     * Configures the WebContext in an implementation specific manner so that it requires
-     * authentication to access the given URL Patterns.  Typically, this will be done by
-     * adding a <code>javax.servlet.Filter</code> (or several, and whatever else they need).
+     * Configures the WebContext in an implementation specific manner so that it requires authentication to access the
+     * given URL Patterns. Typically, this will be done by adding a {@code javax.servlet.Filter} (or several, and
+     * whatever else they need).
+     *
+     * <p>
+     * This method is equivalent to {@code requireAuthentication(webContextBuilder, false, urlPatterns}.
      */
-    void requireAuthentication(WebContextBuilder webContextBuilder, String... urlPatterns);
+    default void requireAuthentication(final WebContextBuilder webContextBuilder, final String... urlPatterns) {
+        requireAuthentication(webContextBuilder, false, urlPatterns);
+    }
 
     /**
-     * Configures the WebContext so that all its URL patterns (<code>/**</code>) require authentication.
+     * Configures the WebContext so that all its URL patterns ({@code/**}) require authentication.
      * @see #requireAuthentication(WebContextBuilder, String...)
      */
-    default void requireAuthentication(WebContextBuilder webContextBuilder) {
+    default void requireAuthentication(final WebContextBuilder webContextBuilder) {
         requireAuthentication(webContextBuilder, "/*");
     }
 }
diff --git a/web/api/src/test/java/org/opendaylight/aaa/web/tests/FilterDetailsTest.java b/web/api/src/test/java/org/opendaylight/aaa/web/tests/FilterDetailsTest.java
new file mode 100644 (file)
index 0000000..b5fea29
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2020 Lumina Networks, Inc. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.web.tests;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import javax.servlet.Filter;
+import org.junit.Test;
+import org.opendaylight.aaa.web.FilterDetails;
+
+public class FilterDetailsTest {
+
+    @Test
+    public void testDefaultValue() {
+        FilterDetails filterDetails = FilterDetails.builder()
+                .filter(mock(Filter.class))
+                .addUrlPattern("test")
+                .addUrlPattern("another")
+                .name("custom")
+                .putInitParam("key", "value")
+                .build();
+
+        assertFalse(filterDetails.getAsyncSupported());
+    }
+
+    @Test
+    public void testAsyncFalse() {
+        FilterDetails filterDetails = FilterDetails.builder()
+                .filter(mock(Filter.class))
+                .addUrlPattern("test")
+                .addUrlPattern("another")
+                .name("custom")
+                .putInitParam("key", "value")
+                .asyncSupported(false)
+                .build();
+
+        assertFalse(filterDetails.getAsyncSupported());
+    }
+
+    @Test
+    public void testAsyncTrue() {
+        FilterDetails filterDetails = FilterDetails.builder()
+                .filter(mock(Filter.class))
+                .addUrlPattern("test")
+                .addUrlPattern("another")
+                .name("custom")
+                .putInitParam("key", "value")
+                .asyncSupported(true)
+                .build();
+
+        assertTrue(filterDetails.getAsyncSupported());
+    }
+
+    @Test
+    public void testException() {
+        assertThrows(IllegalStateException.class, () -> {
+            FilterDetails.builder().build();
+        });
+    }
+
+}
diff --git a/web/api/src/test/java/org/opendaylight/aaa/web/tests/ServletDetailsTest.java b/web/api/src/test/java/org/opendaylight/aaa/web/tests/ServletDetailsTest.java
new file mode 100644 (file)
index 0000000..5f20186
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020 Lumina Networks, Inc. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.web.tests;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import javax.servlet.Servlet;
+import org.junit.Test;
+import org.opendaylight.aaa.web.ServletDetails;
+
+public class ServletDetailsTest {
+
+    @Test
+    public void testDefaultValue() {
+        ServletDetails servletDetails = ServletDetails.builder()
+                .servlet(mock(Servlet.class))
+                .addUrlPattern("test")
+                .addUrlPattern("another")
+                .name("custom")
+                .putInitParam("key", "value")
+                .build();
+
+        assertFalse(servletDetails.getAsyncSupported());
+    }
+
+    @Test
+    public void testAsyncFalse() {
+        ServletDetails servletDetails = ServletDetails.builder()
+                .servlet(mock(Servlet.class))
+                .addUrlPattern("test")
+                .addUrlPattern("another")
+                .name("custom")
+                .putInitParam("key", "value")
+                .asyncSupported(false)
+                .build();
+
+        assertFalse(servletDetails.getAsyncSupported());
+    }
+
+    @Test
+    public void testAsyncTrue() {
+        ServletDetails servletDetails = ServletDetails.builder()
+                .servlet(mock(Servlet.class))
+                .addUrlPattern("test")
+                .addUrlPattern("another")
+                .name("custom")
+                .putInitParam("key", "value")
+                .asyncSupported(true)
+                .build();
+
+        assertTrue(servletDetails.getAsyncSupported());
+    }
+
+    @Test
+    public void testException() {
+        assertThrows(IllegalStateException.class, () -> {
+            ServletDetails.builder().build();
+        });
+    }
+}
index 1c3a3e065e4c2e844353da53b17b7417ea1188a3..07d9ef53fce19f51330eea22978c277935516d64 100644 (file)
@@ -164,13 +164,13 @@ public class PaxWebServer implements ServiceFactory<WebServer> {
             // 3. Filters - because subsequent servlets should already be covered by the filters
             for (FilterDetails filter : webContext.filters()) {
                 registerFilter(osgiHttpContext, filter.urlPatterns(), filter.name(), filter.filter(),
-                        filter.initParams());
+                        filter.initParams(), filter.getAsyncSupported());
             }
 
             // 4. servlets - 'bout time for 'em by now, don't you think? ;)
             for (ServletDetails servlet : webContext.servlets()) {
                 registerServlet(osgiHttpContext, servlet.urlPatterns(), servlet.name(), servlet.servlet(),
-                        servlet.initParams());
+                        servlet.initParams(), servlet.getAsyncSupported());
             }
 
             try {
@@ -191,10 +191,11 @@ public class PaxWebServer implements ServiceFactory<WebServer> {
         }
 
         private void registerFilter(final HttpContext osgiHttpContext, final List<String> urlPatterns,
-                final String name, final Filter filter, final Map<String, String> params) {
-            boolean asyncSupported = false;
+                final String name, final Filter filter, final Map<String, String> params,
+                Boolean asyncSupported) {
             String[] absUrlPatterns = absolute(urlPatterns);
-            LOG.info("Registering Filter for aliases {}: {}", absUrlPatterns, filter);
+            LOG.info("Registering Filter for aliases {}: {} with async: {}", absUrlPatterns,
+                    filter, asyncSupported);
             paxWeb.registerFilter(filter, absUrlPatterns, new String[] { name }, new MapDictionary<>(params),
                     asyncSupported, osgiHttpContext);
             registeredFilters.add(filter);
@@ -205,11 +206,12 @@ public class PaxWebServer implements ServiceFactory<WebServer> {
         }
 
         private void registerServlet(final HttpContext osgiHttpContext, final List<String> urlPatterns,
-                final String name, final Servlet servlet, final Map<String, String> params) throws ServletException {
+                final String name, final Servlet servlet, final Map<String, String> params,
+                Boolean asyncSupported) throws ServletException {
             int loadOnStartup = 1;
-            boolean asyncSupported = false;
             String[] absUrlPatterns = absolute(urlPatterns);
-            LOG.info("Registering Servlet for aliases {}: {}", absUrlPatterns, servlet);
+            LOG.info("Registering Servlet for aliases {}: {} with async: {}", absUrlPatterns,
+                    servlet, asyncSupported);
             paxWeb.registerServlet(servlet, name, absUrlPatterns, new MapDictionary<>(params), loadOnStartup,
                     asyncSupported, osgiHttpContext);
             registeredServlets.add(servlet);
index 21e881af9e3f715ce0252f9d120b02b00e9c039f..87fb1dc352e64dd1c2a4664240785c8835ea657b 100644 (file)
@@ -31,6 +31,6 @@ public class WebTestModule extends AbstractModule {
 
         // NB: This is a NOOP WebContextSecurer
         // TODO: LATER offer one with a fixed uid/pwd for HTTP BASIC, using Jetty's Filter
-        bind(WebContextSecurer.class).toInstance((webContextBuilder, urlPatterns) -> { });
+        bind(WebContextSecurer.class).toInstance((webContextBuilder, asyncSupported, urlPatterns) -> { });
     }
 }