diff --git a/core/src/test/java/io/confluent/rest/ApplicationServerTest.java b/core/src/test/java/io/confluent/rest/ApplicationServerTest.java index 4c48e0c7ab..4a4abcab73 100644 --- a/core/src/test/java/io/confluent/rest/ApplicationServerTest.java +++ b/core/src/test/java/io/confluent/rest/ApplicationServerTest.java @@ -83,6 +83,26 @@ private TestRestConfig configBasic() { return new TestRestConfig(props); } + private TestRestConfig configBasicWithWildcardSkipPath() { + Properties props = new Properties(); + props.put(RestConfig.AUTHENTICATION_METHOD_CONFIG, RestConfig.AUTHENTICATION_METHOD_BASIC); + props.put(RestConfig.AUTHENTICATION_REALM_CONFIG, "c3"); + props.put(RestConfig.AUTHENTICATION_ROLES_CONFIG, Collections.singletonList("Administrators")); + props.put(RestConfig.AUTHENTICATION_SKIP_PATHS, "/*"); + + return new TestRestConfig(props); + } + + private TestRestConfig configBasicWithSkipPaths(String skipPaths) { + Properties props = new Properties(); + props.put(RestConfig.AUTHENTICATION_METHOD_CONFIG, RestConfig.AUTHENTICATION_METHOD_BASIC); + props.put(RestConfig.AUTHENTICATION_REALM_CONFIG, "c3"); + props.put(RestConfig.AUTHENTICATION_ROLES_CONFIG, Collections.singletonList("Administrators")); + props.put(RestConfig.AUTHENTICATION_SKIP_PATHS, skipPaths); + + return new TestRestConfig(props); + } + /* Ensure security handlers are confined to a single context */ @Test public void testSecurityHandlerIsolation() throws Exception { @@ -97,6 +117,47 @@ public void testSecurityHandlerIsolation() throws Exception { assertThat(makeGetRequest( "/app2/resource"), is(Code.UNAUTHORIZED)); } + /* + * Regression test: authentication.skip.paths="/*" must actually disable authentication for + * all paths, even though this skip pathSpec is identical to the hardcoded global auth + * constraint's pathSpec ("/*"). Before the fix in AuthUtil#createConstraint, an unauthenticated + * request here was rejected because Jetty's ConstraintSecurityHandler merges two mappings that + * share the same pathSpec, and the skip mapping's default Authorization.INHERIT deferred to the + * global constraint's real BASIC auth requirement instead of actually allowing the request. + */ + @Test + public void testUnsecuredWildcardSkipPathOverridesGlobalAuthConstraint() throws Exception { + TestApp app1 = new TestApp("/app1"); + TestApp app2 = new TestApp(configBasicWithWildcardSkipPath(), "/app2"); + + server.registerApplication(app1); + server.registerApplication(app2); + server.start(); + + // app1 has no auth configured at all; sanity check that it stays open. + assertThat(makeGetRequest("/app1/resource"), is(Code.OK)); + // app2 has BASIC auth enabled globally, but skips it for "/*": an unauthenticated request + // must still succeed. + assertThat(makeGetRequest("/app2/resource"), is(Code.OK)); + } + + /* + * Non-wildcard skip path: only the exact configured pathSpec should bypass authentication; + * every other path under the same app must remain protected by the global auth constraint. + */ + @Test + public void testUnsecuredSpecificSkipPathLeavesOtherPathsSecured() throws Exception { + TestApp app = new TestApp(configBasicWithSkipPaths("/resource"), "/app"); + + server.registerApplication(app); + server.start(); + + // "/resource" is an explicit skip path: unauthenticated requests must succeed. + assertThat(makeGetRequest("/app/resource"), is(Code.OK)); + // "/exception" was not listed as a skip path, so it must still require authentication. + assertThat(makeGetRequest("/app/exception"), is(Code.UNAUTHORIZED)); + } + /* Test Exception Mapper isolation */ @Test public void testExceptionMapperIsolation() throws Exception {