diff --git a/src/test/java/org/apache/sling/auth/core/AuthUtilTest.java b/src/test/java/org/apache/sling/auth/core/AuthUtilTest.java index 6d9de6a..60a83b1 100644 --- a/src/test/java/org/apache/sling/auth/core/AuthUtilTest.java +++ b/src/test/java/org/apache/sling/auth/core/AuthUtilTest.java @@ -18,19 +18,39 @@ */ package org.apache.sling.auth.core; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.sling.api.auth.Authenticator; import org.apache.sling.api.resource.NonExistingResource; +import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.SyntheticResource; +import org.apache.sling.auth.core.spi.JakartaAuthenticationHandler; import org.junit.Assert; import org.junit.Test; -import org.mockito.Mockito; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.contains; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.startsWith; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SuppressWarnings("deprecation") public class AuthUtilTest { - final ResourceResolver resolver = Mockito.mock(ResourceResolver.class); + final ResourceResolver resolver = mock(ResourceResolver.class); - final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + final HttpServletRequest request = mock(HttpServletRequest.class); @Test public void test_isRedirectValid_null_empty() { @@ -61,7 +81,7 @@ public void test_isRedirectValid_normalized() { @Test public void test_isRedirectValid_invalid_characters() { - Mockito.when(request.getContextPath()).thenReturn(""); + when(request.getContextPath()).thenReturn(""); Assert.assertFalse(AuthUtil.isRedirectValid(request, "/illegal//x")); @@ -73,7 +93,7 @@ public void test_isRedirectValid_invalid_characters() { @Test public void test_isRedirectValid_no_resource_resolver_root_context() { - Mockito.when(request.getContextPath()).thenReturn(""); + when(request.getContextPath()).thenReturn(""); Assert.assertFalse(AuthUtil.isRedirectValid(request, "relative/path")); Assert.assertTrue(AuthUtil.isRedirectValid(request, "/absolute/path")); @@ -82,7 +102,7 @@ public void test_isRedirectValid_no_resource_resolver_root_context() { @Test public void test_isRedirectValid_no_resource_resolver_non_root_context() { - Mockito.when(request.getContextPath()).thenReturn("/ctx"); + when(request.getContextPath()).thenReturn("/ctx"); Assert.assertFalse(AuthUtil.isRedirectValid(request, "relative/path")); Assert.assertFalse(AuthUtil.isRedirectValid(request, "/absolute/path")); @@ -96,15 +116,15 @@ public void test_isRedirectValid_no_resource_resolver_non_root_context() { @Test public void test_isRedirectValid_resource_resolver_root_context() { - Mockito.when(request.getContextPath()).thenReturn(""); - Mockito.when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)) + when(request.getContextPath()).thenReturn(""); + when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)) .thenReturn(resolver); - Mockito.when(resolver.resolve((HttpServletRequest) Mockito.any(), Mockito.eq("/absolute/path"))) + when(resolver.resolve((HttpServletRequest) any(), eq("/absolute/path"))) .thenReturn(new SyntheticResource(resolver, "/absolute/path", "test")); - Mockito.when(resolver.resolve((HttpServletRequest) Mockito.any(), Mockito.eq("relative/path"))) + when(resolver.resolve((HttpServletRequest) any(), eq("relative/path"))) .thenReturn(new NonExistingResource(resolver, "relative/path")); - Mockito.when(resolver.resolve((HttpServletRequest) Mockito.any(), Mockito.any())) + when(resolver.resolve((HttpServletRequest) any(), any())) .thenReturn(new NonExistingResource(resolver, "/absolute/missing")); Assert.assertFalse(AuthUtil.isRedirectValid(request, "relative/path")); @@ -120,15 +140,15 @@ public void test_isRedirectValid_resource_resolver_root_context() { @Test public void test_isRedirectValid_resource_resolver_non_root_context() { - Mockito.when(request.getContextPath()).thenReturn("/ctx"); - Mockito.when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)) + when(request.getContextPath()).thenReturn("/ctx"); + when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)) .thenReturn(resolver); - Mockito.when(resolver.resolve((HttpServletRequest) Mockito.any(), Mockito.eq("/absolute/path"))) + when(resolver.resolve((HttpServletRequest) any(), eq("/absolute/path"))) .thenReturn(new SyntheticResource(resolver, "/absolute/path", "test")); - Mockito.when(resolver.resolve((HttpServletRequest) Mockito.any(), Mockito.eq("relative/path"))) + when(resolver.resolve((HttpServletRequest) any(), eq("relative/path"))) .thenReturn(new NonExistingResource(resolver, "relative/path")); - Mockito.when(resolver.resolve((HttpServletRequest) Mockito.any(), Mockito.any())) + when(resolver.resolve((HttpServletRequest) any(), any())) .thenReturn(new NonExistingResource(resolver, "/absolute/missing")); Assert.assertFalse(AuthUtil.isRedirectValid(request, "relative/path")); @@ -147,19 +167,529 @@ public void test_isBrowserRequest_null() { @Test public void test_isBrowserRequest_Mozilla() { - Mockito.when(request.getHeader("User-Agent")).thenReturn("This is firefox (Mozilla)"); + when(request.getHeader("User-Agent")).thenReturn("This is firefox (Mozilla)"); Assert.assertTrue(AuthUtil.isBrowserRequest(request)); } @Test public void test_isBrowserRequest_Opera() { - Mockito.when(request.getHeader("User-Agent")).thenReturn("This is opera (Opera)"); + when(request.getHeader("User-Agent")).thenReturn("This is opera (Opera)"); Assert.assertTrue(AuthUtil.isBrowserRequest(request)); } @Test public void test_isBrowserRequest_WebDAV() { - Mockito.when(request.getHeader("User-Agent")).thenReturn("WebDAV Client"); + when(request.getHeader("User-Agent")).thenReturn("WebDAV Client"); Assert.assertFalse(AuthUtil.isBrowserRequest(request)); } + + // ---- Jakarta variants ---- + + @Test + public void test_getAttributeOrParameter_jakarta() { + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getAttribute("a")).thenReturn("attrValue"); + Assert.assertEquals("attrValue", AuthUtil.getAttributeOrParameter(req, "a", "def")); + + when(req.getAttribute("b")).thenReturn(null); + when(req.getParameter("b")).thenReturn("paramValue"); + Assert.assertEquals("paramValue", AuthUtil.getAttributeOrParameter(req, "b", "def")); + + when(req.getAttribute("c")).thenReturn(""); + when(req.getParameter("c")).thenReturn(""); + Assert.assertEquals("def", AuthUtil.getAttributeOrParameter(req, "c", "def")); + + // non-string attribute is ignored + when(req.getAttribute("d")).thenReturn(Integer.valueOf(1)); + when(req.getParameter("d")).thenReturn(null); + Assert.assertEquals("def", AuthUtil.getAttributeOrParameter(req, "d", "def")); + } + + @Test + public void test_getLoginResource_jakarta() { + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/res"); + Assert.assertEquals("/res", AuthUtil.getLoginResource(req, "/def")); + + HttpServletRequest request2 = mock(HttpServletRequest.class); + Assert.assertEquals("/def", AuthUtil.getLoginResource(request2, "/def")); + } + + @Test + public void test_getMappedLoginResourcePath_jakarta() { + HttpServletRequest req = mock(HttpServletRequest.class); + ResourceResolver rr = mock(ResourceResolver.class); + when(req.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)).thenReturn(rr); + when(req.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/res"); + when(rr.map(req, "/res")).thenReturn("/mapped/res"); + Assert.assertEquals("/mapped/res", AuthUtil.getMappedLoginResourcePath(req, "/def")); + } + + @Test + public void test_getMappedLoginResourcePath_null_jakarta() { + HttpServletRequest req = mock(HttpServletRequest.class); + // no resolver -> getResourceResolver returns null -> NPE guarded by null path only + // here defaultLoginResource is null and no param -> resourcePath null -> returns null + Assert.assertNull(AuthUtil.getMappedLoginResourcePath(req, null)); + } + + @Test + public void test_setLoginResourceAttribute_jakarta() { + HttpServletRequest req = mock(HttpServletRequest.class); + // attribute already set + when(req.getAttribute(Authenticator.LOGIN_RESOURCE)).thenReturn("/existing"); + Assert.assertEquals("/existing", AuthUtil.setLoginResourceAttribute(req, "/def")); + + // parameter set + HttpServletRequest request2 = mock(HttpServletRequest.class); + when(request2.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/param"); + Assert.assertEquals("/param", AuthUtil.setLoginResourceAttribute(request2, "/def")); + + // default used + HttpServletRequest request3 = mock(HttpServletRequest.class); + Assert.assertEquals("/def", AuthUtil.setLoginResourceAttribute(request3, "/def")); + + // fallback to "/" + HttpServletRequest request4 = mock(HttpServletRequest.class); + Assert.assertEquals("/", AuthUtil.setLoginResourceAttribute(request4, null)); + } + + @Test + public void test_sendRedirect_jakarta() throws Exception { + HttpServletRequest req = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(req.getContextPath()).thenReturn(""); + when(req.getRequestURI()).thenReturn("/current"); + when(req.getQueryString()).thenReturn("x=1"); + + Map params = new HashMap<>(); + params.put("k", "v v"); + AuthUtil.sendRedirect(req, response, "/valid/target", params); + + verify(response).sendRedirect(contains("/valid/target?")); + } + + @Test + public void test_sendRedirect_invalidTarget_jakarta() throws Exception { + HttpServletRequest req = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(req.getContextPath()).thenReturn("/ctx"); + when(req.getRequestURI()).thenReturn("/current"); + + AuthUtil.sendRedirect(req, response, "relative", null); + verify(response).sendRedirect(startsWith("/ctx?")); + } + + @Test + public void test_sendRedirect_invalidTarget_rootContext_jakarta() throws Exception { + HttpServletRequest req = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(req.getContextPath()).thenReturn(""); + when(req.getRequestURI()).thenReturn("/current"); + + AuthUtil.sendRedirect(req, response, "relative", null); + verify(response).sendRedirect(startsWith("/?")); + } + + @Test(expected = IllegalStateException.class) + public void test_sendRedirect_committed_jakarta() throws Exception { + HttpServletRequest req = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(response.isCommitted()).thenReturn(true); + AuthUtil.sendRedirect(req, response, "/target", null); + } + + @Test + public void test_isValidateRequest_jakarta() { + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getParameter(AuthConstants.PAR_J_VALIDATE)).thenReturn("TRUE"); + Assert.assertTrue(AuthUtil.isValidateRequest(req)); + when(req.getParameter(AuthConstants.PAR_J_VALIDATE)).thenReturn("no"); + Assert.assertFalse(AuthUtil.isValidateRequest(req)); + } + + @Test + public void test_sendValid_jakarta() { + HttpServletResponse response = mock(HttpServletResponse.class); + AuthUtil.sendValid(response); + verify(response).setStatus(HttpServletResponse.SC_OK); + verify(response).setContentLength(0); + } + + @Test + public void test_sendInvalid_withReason_jakarta() throws Exception { + HttpServletRequest req = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(req.getAttribute(JakartaAuthenticationHandler.FAILURE_REASON)).thenReturn("bad"); + when(req.getAttribute(JakartaAuthenticationHandler.FAILURE_REASON_CODE)).thenReturn("code1"); + StringWriter sw = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(sw)); + + AuthUtil.sendInvalid(req, response); + verify(response).setStatus(HttpServletResponse.SC_FORBIDDEN); + verify(response).setHeader(AuthConstants.X_REASON, "bad"); + verify(response).setHeader(AuthConstants.X_REASON_CODE, "code1"); + Assert.assertTrue(sw.toString().contains("bad")); + } + + @Test + public void test_sendInvalid_noReason_jakarta() { + HttpServletRequest req = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + AuthUtil.sendInvalid(req, response); + verify(response).setStatus(HttpServletResponse.SC_FORBIDDEN); + } + + @Test + public void test_checkReferer_jakarta() { + HttpServletRequest req = mock(HttpServletRequest.class); + // not a POST -> true + when(req.getMethod()).thenReturn("GET"); + Assert.assertTrue(AuthUtil.checkReferer(req, "/login")); + + // POST with no referer -> true + when(req.getMethod()).thenReturn("POST"); + Assert.assertTrue(AuthUtil.checkReferer(req, "/login")); + + // POST with matching referer -> true + when(req.getContextPath()).thenReturn(""); + when(req.getHeader("Referer")).thenReturn("http://host/login"); + Assert.assertTrue(AuthUtil.checkReferer(req, "/login")); + + // POST with non-matching referer -> false + when(req.getHeader("Referer")).thenReturn("http://host/other"); + Assert.assertFalse(AuthUtil.checkReferer(req, "/login")); + + // POST with malformed referer -> true (parse fails, falls through) + when(req.getHeader("Referer")).thenReturn("::not a url::"); + Assert.assertTrue(AuthUtil.checkReferer(req, "/login")); + } + + @Test + public void test_isAjaxRequest_jakarta() { + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getHeader("X-Requested-With")).thenReturn("XMLHttpRequest"); + Assert.assertTrue(AuthUtil.isAjaxRequest(req)); + when(req.getHeader("X-Requested-With")).thenReturn("other"); + Assert.assertFalse(AuthUtil.isAjaxRequest(req)); + } + + @Test + public void test_isRedirectValid_url_jakarta() { + Assert.assertFalse(AuthUtil.isRedirectValid((HttpServletRequest) null, "http://www.google.com")); + } + + @Test + public void test_isBrowserRequest_jakarta() { + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getHeader("User-Agent")).thenReturn("Mozilla/5.0"); + Assert.assertTrue(AuthUtil.isBrowserRequest(req)); + when(req.getHeader("User-Agent")).thenReturn("curl"); + Assert.assertFalse(AuthUtil.isBrowserRequest(req)); + } + + // ---- Deprecated javax variants ---- + + @SuppressWarnings("deprecation") + @Test + public void test_getAttributeOrParameter_javax() { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + when(req.getAttribute("a")).thenReturn("attrValue"); + Assert.assertEquals("attrValue", AuthUtil.getAttributeOrParameter(req, "a", "def")); + + when(req.getAttribute("b")).thenReturn(null); + when(req.getParameter("b")).thenReturn("paramValue"); + Assert.assertEquals("paramValue", AuthUtil.getAttributeOrParameter(req, "b", "def")); + + when(req.getAttribute("c")).thenReturn(null); + when(req.getParameter("c")).thenReturn(null); + Assert.assertEquals("def", AuthUtil.getAttributeOrParameter(req, "c", "def")); + } + + @SuppressWarnings("deprecation") + @Test + public void test_getLoginResource_javax() { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + when(req.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/res"); + Assert.assertEquals("/res", AuthUtil.getLoginResource(req, "/def")); + } + + @SuppressWarnings("deprecation") + @Test + public void test_getMappedLoginResourcePath_javax() { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + ResourceResolver rr = mock(ResourceResolver.class); + when(req.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)).thenReturn(rr); + when(req.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/res"); + when(rr.map(req, "/res")).thenReturn("/mapped/res"); + Assert.assertEquals("/mapped/res", AuthUtil.getMappedLoginResourcePath(req, "/def")); + + javax.servlet.http.HttpServletRequest request2 = mock(javax.servlet.http.HttpServletRequest.class); + Assert.assertNull(AuthUtil.getMappedLoginResourcePath(request2, null)); + } + + @SuppressWarnings("deprecation") + @Test + public void test_setLoginResourceAttribute_javax() { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + when(req.getAttribute(Authenticator.LOGIN_RESOURCE)).thenReturn("/existing"); + Assert.assertEquals("/existing", AuthUtil.setLoginResourceAttribute(req, "/def")); + + javax.servlet.http.HttpServletRequest request2 = mock(javax.servlet.http.HttpServletRequest.class); + when(request2.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/param"); + Assert.assertEquals("/param", AuthUtil.setLoginResourceAttribute(request2, "/def")); + + javax.servlet.http.HttpServletRequest request3 = mock(javax.servlet.http.HttpServletRequest.class); + Assert.assertEquals("/def", AuthUtil.setLoginResourceAttribute(request3, "/def")); + + javax.servlet.http.HttpServletRequest request4 = mock(javax.servlet.http.HttpServletRequest.class); + Assert.assertEquals("/", AuthUtil.setLoginResourceAttribute(request4, null)); + } + + @SuppressWarnings("deprecation") + @Test + public void test_sendRedirect_javax() throws Exception { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + javax.servlet.http.HttpServletResponse response = mock(javax.servlet.http.HttpServletResponse.class); + when(req.getContextPath()).thenReturn(""); + when(req.getRequestURI()).thenReturn("/current"); + when(req.getQueryString()).thenReturn(null); + + Map params = new HashMap<>(); + params.put("k", "v"); + AuthUtil.sendRedirect(req, response, "/valid/target", params); + verify(response).sendRedirect(contains("/valid/target?")); + } + + @SuppressWarnings("deprecation") + @Test + public void test_isValidateRequest_javax() { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + when(req.getParameter(AuthConstants.PAR_J_VALIDATE)).thenReturn("true"); + Assert.assertTrue(AuthUtil.isValidateRequest(req)); + } + + @SuppressWarnings("deprecation") + @Test + public void test_sendValid_javax() { + javax.servlet.http.HttpServletResponse response = mock(javax.servlet.http.HttpServletResponse.class); + AuthUtil.sendValid(response); + verify(response).setStatus(javax.servlet.http.HttpServletResponse.SC_OK); + } + + @SuppressWarnings("deprecation") + @Test + public void test_sendInvalid_javax() throws Exception { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + javax.servlet.http.HttpServletResponse response = mock(javax.servlet.http.HttpServletResponse.class); + when(req.getAttribute(JakartaAuthenticationHandler.FAILURE_REASON)).thenReturn("bad"); + StringWriter sw = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(sw)); + AuthUtil.sendInvalid(req, response); + verify(response).setStatus(javax.servlet.http.HttpServletResponse.SC_FORBIDDEN); + Assert.assertTrue(sw.toString().contains("bad")); + } + + @SuppressWarnings("deprecation") + @Test + public void test_checkReferer_javax() { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + when(req.getMethod()).thenReturn("POST"); + when(req.getContextPath()).thenReturn(""); + when(req.getHeader("Referer")).thenReturn("http://host/login"); + Assert.assertTrue(AuthUtil.checkReferer(req, "/login")); + when(req.getHeader("Referer")).thenReturn("http://host/other"); + Assert.assertFalse(AuthUtil.checkReferer(req, "/login")); + } + + @SuppressWarnings("deprecation") + @Test + public void test_isAjaxRequest_javax() { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + when(req.getHeader("X-Requested-With")).thenReturn("XMLHttpRequest"); + Assert.assertTrue(AuthUtil.isAjaxRequest(req)); + } + + @SuppressWarnings("deprecation") + @Test + public void test_isBrowserRequest_javax() { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + when(req.getHeader("User-Agent")).thenReturn("Opera/9"); + Assert.assertTrue(AuthUtil.isBrowserRequest(req)); + when(req.getHeader("User-Agent")).thenReturn(null); + Assert.assertFalse(AuthUtil.isBrowserRequest(req)); + } + + @SuppressWarnings("deprecation") + @Test + public void test_isRedirectValid_javax() { + Assert.assertFalse(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest) null, "http://host")); + Assert.assertTrue(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest) null, "/absolute/path")); + Assert.assertFalse(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest) null, "/unnormalized//x")); + } + + // ---- jakarta isRedirectValid, resolver resolves the target ---- + + @Test + public void test_isRedirectValid_resolvesToResource_jakarta() { + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getContextPath()).thenReturn(""); + final ResourceResolver rr = mock(ResourceResolver.class); + final Resource resource = mock(Resource.class); + when(resource.getResourceType()).thenReturn("some/type"); + when(rr.resolve(eq(req), anyString())).thenReturn(resource); + when(req.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)).thenReturn(rr); + + Assert.assertTrue(AuthUtil.isRedirectValid(req, "/valid/path")); + } + + // ---- javax isRedirectValid full branch coverage ---- + + @Test + public void test_isRedirectValid_empty_javax() { + Assert.assertFalse(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest) null, "")); + Assert.assertFalse(AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest) null, null)); + } + + @Test + public void test_isRedirectValid_invalidTargets_javax() { + // "/a b": space triggers a URISyntaxException, "http://host/x": absolute URL, + // "/a//b": path is not normalized -- each must be rejected. + final String[] invalidTargets = {"/a b", "http://host/x", "/a//b"}; + for (String target : invalidTargets) { + Assert.assertFalse( + "expected invalid redirect for target: " + target, + AuthUtil.isRedirectValid((javax.servlet.http.HttpServletRequest) null, target)); + } + } + + @Test + public void test_isRedirectValid_contextPath_javax() { + // With context path "/ctx": a mismatching path and a non-absolute continuation are + // rejected, while the context root itself is accepted. + final Object[][] cases = { + {"/other/path", false}, + {"/ctx", true}, + {"/ctxrelative", false}, + }; + for (Object[] c : cases) { + final String target = (String) c[0]; + final boolean expected = (Boolean) c[1]; + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + when(req.getContextPath()).thenReturn("/ctx"); + Assert.assertEquals("target: " + target, expected, AuthUtil.isRedirectValid(req, target)); + } + } + + @Test + public void test_isRedirectValid_illegalChars_javax() { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + when(req.getContextPath()).thenReturn(""); + Assert.assertFalse(AuthUtil.isRedirectValid(req, "/path'quote")); + } + + @Test + public void test_isRedirectValid_resolvesToResource_javax() { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + when(req.getContextPath()).thenReturn(""); + final ResourceResolver rr = mock(ResourceResolver.class); + final Resource resource = mock(Resource.class); + when(resource.getResourceType()).thenReturn("some/type"); + when(rr.resolve(eq(req), anyString())).thenReturn(resource); + when(req.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)).thenReturn(rr); + + Assert.assertTrue(AuthUtil.isRedirectValid(req, "/valid/path")); + } + + // ---- javax sendRedirect branch coverage ---- + + @Test + public void test_sendRedirect_invalidTarget_rootContext_javax() throws Exception { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + javax.servlet.http.HttpServletResponse response = mock(javax.servlet.http.HttpServletResponse.class); + when(req.getContextPath()).thenReturn(""); + when(req.getRequestURI()).thenReturn("/current"); + AuthUtil.sendRedirect(req, response, "relative", null); + verify(response).sendRedirect(startsWith("/?")); + } + + @Test + public void test_sendRedirect_invalidTarget_withContext_javax() throws Exception { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + javax.servlet.http.HttpServletResponse response = mock(javax.servlet.http.HttpServletResponse.class); + when(req.getContextPath()).thenReturn("/ctx"); + when(req.getRequestURI()).thenReturn("/ctx/current"); + when(req.getQueryString()).thenReturn("a=b"); + final Map params = new HashMap<>(); + AuthUtil.sendRedirect(req, response, "relative", params); + verify(response).sendRedirect(startsWith("/ctx?")); + } + + // ---- jakarta sendInvalid with reason code ---- + + @Test + public void test_sendInvalid_withReasonCode_jakarta() throws Exception { + HttpServletRequest req = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(req.getAttribute(JakartaAuthenticationHandler.FAILURE_REASON)).thenReturn("bad"); + when(req.getAttribute(JakartaAuthenticationHandler.FAILURE_REASON_CODE)).thenReturn("code42"); + when(response.getWriter()).thenReturn(new java.io.PrintWriter(new java.io.StringWriter())); + AuthUtil.sendInvalid(req, response); + verify(response).setHeader(AuthConstants.X_REASON_CODE, "code42"); + } + + // ---- IOException error paths ---- + + @Test + public void test_sendValid_ioexception_jakarta() throws Exception { + HttpServletResponse response = mock(HttpServletResponse.class); + doThrow(new IOException("boom")).when(response).flushBuffer(); + AuthUtil.sendValid(response); + // IOException from flushBuffer is caught internally; the OK response is still prepared. + verify(response).setStatus(HttpServletResponse.SC_OK); + verify(response).flushBuffer(); + } + + @Test + public void test_sendValid_ioexception_javax() throws Exception { + javax.servlet.http.HttpServletResponse response = mock(javax.servlet.http.HttpServletResponse.class); + doThrow(new IOException("boom")).when(response).flushBuffer(); + AuthUtil.sendValid(response); + // IOException from flushBuffer is caught internally; the OK response is still prepared. + verify(response).setStatus(javax.servlet.http.HttpServletResponse.SC_OK); + verify(response).flushBuffer(); + } + + @Test + public void test_sendInvalid_ioexception_jakarta() throws Exception { + HttpServletRequest req = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + doThrow(new IOException("boom")).when(response).flushBuffer(); + AuthUtil.sendInvalid(req, response); + // IOException from flushBuffer is caught internally; the FORBIDDEN response is still prepared. + verify(response).setStatus(HttpServletResponse.SC_FORBIDDEN); + verify(response).flushBuffer(); + } + + @Test + public void test_sendInvalid_ioexception_javax() throws Exception { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + javax.servlet.http.HttpServletResponse response = mock(javax.servlet.http.HttpServletResponse.class); + doThrow(new IOException("boom")).when(response).flushBuffer(); + AuthUtil.sendInvalid(req, response); + // IOException from flushBuffer is caught internally; the FORBIDDEN response is still prepared. + verify(response).setStatus(javax.servlet.http.HttpServletResponse.SC_FORBIDDEN); + verify(response).flushBuffer(); + } + + // ---- javax checkReferer malformed URL ---- + + @Test + public void test_checkReferer_malformedUrl_javax() { + javax.servlet.http.HttpServletRequest req = mock(javax.servlet.http.HttpServletRequest.class); + when(req.getMethod()).thenReturn("POST"); + when(req.getContextPath()).thenReturn(""); + when(req.getHeader("Referer")).thenReturn("::: not a url :::"); + Assert.assertTrue(AuthUtil.checkReferer(req, "/login")); + } } diff --git a/src/test/java/org/apache/sling/auth/core/impl/AuthenticationHandlerHolderTest.java b/src/test/java/org/apache/sling/auth/core/impl/AuthenticationHandlerHolderTest.java new file mode 100644 index 0000000..31bd0aa --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/impl/AuthenticationHandlerHolderTest.java @@ -0,0 +1,242 @@ +/* + * 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.sling.auth.core.impl; + +import java.io.IOException; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.sling.auth.core.AuthConstants; +import org.apache.sling.auth.core.spi.AuthenticationInfo; +import org.apache.sling.auth.core.spi.JakartaAuthenticationFeedbackHandler; +import org.apache.sling.auth.core.spi.JakartaAuthenticationHandler; +import org.junit.Test; +import org.mockito.InOrder; +import org.osgi.framework.ServiceReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class AuthenticationHandlerHolderTest { + + private static final String PATH = "/content"; + + private interface FeedbackHandler extends JakartaAuthenticationHandler, JakartaAuthenticationFeedbackHandler {} + + @SuppressWarnings("deprecation") + private interface LegacyFeedbackHandler + extends org.apache.sling.auth.core.spi.AuthenticationHandler, + org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler {} + + @Test + public void extractCredentialsSetsAndRestoresExistingPath() { + final JakartaAuthenticationHandler handler = mock(JakartaAuthenticationHandler.class); + final AuthenticationHandlerHolder holder = holder(handler, "form", null); + final HttpServletRequest request = mock(HttpServletRequest.class); + final HttpServletResponse response = mock(HttpServletResponse.class); + final AuthenticationInfo authInfo = new AuthenticationInfo("form"); + when(request.getAttribute(JakartaAuthenticationHandler.PATH_PROPERTY)).thenReturn("oldPath"); + when(handler.extractCredentials(request, response)).thenReturn(authInfo); + + assertSame(authInfo, holder.extractCredentials(request, response)); + + final InOrder order = inOrder(request, handler); + order.verify(request).getAttribute(JakartaAuthenticationHandler.PATH_PROPERTY); + order.verify(request).setAttribute(JakartaAuthenticationHandler.PATH_PROPERTY, PATH); + order.verify(handler).extractCredentials(request, response); + order.verify(request).setAttribute(JakartaAuthenticationHandler.PATH_PROPERTY, "oldPath"); + } + + @Test + public void dropCredentialsRemovesPathWhenNoPreviousPathExists() throws IOException { + final JakartaAuthenticationHandler handler = mock(JakartaAuthenticationHandler.class); + final AuthenticationHandlerHolder holder = holder(handler, "form", null); + final HttpServletRequest request = mock(HttpServletRequest.class); + final HttpServletResponse response = mock(HttpServletResponse.class); + + holder.dropCredentials(request, response); + + final InOrder order = inOrder(request, handler); + order.verify(request).setAttribute(JakartaAuthenticationHandler.PATH_PROPERTY, PATH); + order.verify(handler).dropCredentials(request, response); + order.verify(request).removeAttribute(JakartaAuthenticationHandler.PATH_PROPERTY); + } + + @Test + public void requestCredentialsDelegatesWhenRequestedLoginMatchesAuthType() throws IOException { + final JakartaAuthenticationHandler handler = mock(JakartaAuthenticationHandler.class); + final AuthenticationHandlerHolder holder = holder(handler, "form", null); + final HttpServletRequest request = requestWithLogin(null, "form"); + final HttpServletResponse response = mock(HttpServletResponse.class); + when(handler.requestCredentials(request, response)).thenReturn(true); + + assertTrue(holder.requestCredentials(request, response)); + + verify(handler).requestCredentials(request, response); + verify(request).removeAttribute(JakartaAuthenticationHandler.PATH_PROPERTY); + } + + @Test + public void requestCredentialsSkipsWhenRequestedLoginDoesNotMatchAuthType() throws IOException { + final JakartaAuthenticationHandler handler = mock(JakartaAuthenticationHandler.class); + final AuthenticationHandlerHolder holder = holder(handler, "form", null); + final HttpServletRequest request = requestWithLogin(null, "basic"); + + assertFalse(holder.requestCredentials(request, mock(HttpServletResponse.class))); + + verify(handler, never()).requestCredentials(any(HttpServletRequest.class), any(HttpServletResponse.class)); + verify(request).removeAttribute(JakartaAuthenticationHandler.PATH_PROPERTY); + } + + @Test + public void requestCredentialsUsesLoginAttributeBeforeParameter() throws IOException { + final JakartaAuthenticationHandler handler = mock(JakartaAuthenticationHandler.class); + final AuthenticationHandlerHolder holder = holder(handler, "form", null); + final HttpServletRequest request = requestWithLogin("form", "basic"); + final HttpServletResponse response = mock(HttpServletResponse.class); + when(handler.requestCredentials(request, response)).thenReturn(true); + + assertTrue(holder.requestCredentials(request, response)); + + verify(handler).requestCredentials(request, response); + } + + @Test + public void requestCredentialsWithoutAuthTypeIgnoresRequestedLogin() throws IOException { + final JakartaAuthenticationHandler handler = mock(JakartaAuthenticationHandler.class); + final AuthenticationHandlerHolder holder = holder(handler, null, null); + final HttpServletRequest request = requestWithLogin(null, "basic"); + final HttpServletResponse response = mock(HttpServletResponse.class); + when(handler.requestCredentials(request, response)).thenReturn(true); + + assertTrue(holder.requestCredentials(request, response)); + + verify(handler).requestCredentials(request, response); + } + + @Test + public void browserOnlyRequestCredentialsSkipsNonBrowserRequests() throws IOException { + final JakartaAuthenticationHandler handler = mock(JakartaAuthenticationHandler.class); + final AuthenticationHandlerHolder holder = holder(handler, "form", "true"); + final HttpServletRequest request = requestWithLogin(null, null); + when(request.getHeader("User-Agent")).thenReturn("curl/8.0"); + + assertFalse(holder.requestCredentials(request, mock(HttpServletResponse.class))); + + verify(handler, never()).requestCredentials(any(HttpServletRequest.class), any(HttpServletResponse.class)); + } + + @Test + public void browserOnlyRequestCredentialsAllowsBrowserRequestsForYesValue() throws IOException { + final JakartaAuthenticationHandler handler = mock(JakartaAuthenticationHandler.class); + final AuthenticationHandlerHolder holder = holder(handler, "form", "yes"); + final HttpServletRequest request = requestWithLogin(null, null); + final HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getHeader("User-Agent")).thenReturn("Mozilla/5.0"); + when(handler.requestCredentials(request, response)).thenReturn(true); + + assertTrue(holder.requestCredentials(request, response)); + + verify(handler).requestCredentials(request, response); + } + + @Test + public void getFeedbackHandlerReturnsJakartaFeedbackHandlerOnlyWhenImplemented() { + final FeedbackHandler feedbackHandler = mock(FeedbackHandler.class); + assertSame(feedbackHandler, holder(feedbackHandler, "form", null).getFeedbackHandler()); + assertNull( + holder(mock(JakartaAuthenticationHandler.class), "form", null).getFeedbackHandler()); + } + + @Test + @SuppressWarnings("deprecation") + public void legacyConstructorWrapsDeprecatedAuthenticationHandler() { + final org.apache.sling.auth.core.spi.AuthenticationHandler handler = mock(LegacyFeedbackHandler.class); + final AuthenticationHandlerHolder holder = + new AuthenticationHandlerHolder(PATH, handler, serviceReference("legacy", null)); + final HttpServletRequest request = mock(HttpServletRequest.class); + final HttpServletResponse response = mock(HttpServletResponse.class); + final AuthenticationInfo authInfo = new AuthenticationInfo("legacy"); + when(handler.extractCredentials( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class))) + .thenReturn(authInfo); + + assertSame(authInfo, holder.extractCredentials(request, response)); + } + + @Test + public void equalsHashCodeCompareToAndToStringIncludeHolderState() { + final JakartaAuthenticationHandler handler = mock(JakartaAuthenticationHandler.class); + when(handler.toString()).thenReturn("handlerString"); + final ServiceReference serviceReference = serviceReference("form", null); + final AuthenticationHandlerHolder holder = new AuthenticationHandlerHolder(PATH, handler, serviceReference); + final AuthenticationHandlerHolder same = new AuthenticationHandlerHolder(PATH, handler, serviceReference); + final AuthenticationHandlerHolder differentHandler = + new AuthenticationHandlerHolder(PATH, mock(JakartaAuthenticationHandler.class), serviceReference); + final AuthenticationHandlerHolder differentType = + new AuthenticationHandlerHolder(PATH, handler, serviceReference("basic", null)); + final AuthenticationHandlerHolder differentBrowserOnly = + new AuthenticationHandlerHolder(PATH, handler, serviceReference("form", "true")); + + assertEquals(holder, holder); + assertEquals(holder, same); + assertEquals(holder.hashCode(), same.hashCode()); + assertEquals(0, holder.compareTo(same)); + assertNotEquals(holder, null); + assertNotEquals(holder, differentHandler); + assertNotEquals(holder, differentType); + assertNotEquals(holder, differentBrowserOnly); + assertEquals("handlerString", holder.toString()); + assertTrue(holder.isPathRequiresHandler("/content/page")); + } + + private AuthenticationHandlerHolder holder( + final JakartaAuthenticationHandler handler, final String authType, final String browserOnly) { + return new AuthenticationHandlerHolder(PATH, handler, serviceReference(authType, browserOnly)); + } + + private HttpServletRequest requestWithLogin(final String attributeValue, final String parameterValue) { + final HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getAttribute(JakartaAuthenticationHandler.REQUEST_LOGIN_PARAMETER)) + .thenReturn(attributeValue); + when(request.getParameter(JakartaAuthenticationHandler.REQUEST_LOGIN_PARAMETER)) + .thenReturn(parameterValue); + return request; + } + + private ServiceReference serviceReference(final String authType, final String browserOnly) { + final ServiceReference serviceReference = mock(ServiceReference.class); + when(serviceReference.getProperty(JakartaAuthenticationHandler.TYPE_PROPERTY)) + .thenReturn(authType); + when(serviceReference.getProperty(AuthConstants.AUTH_HANDLER_BROWSER_ONLY)) + .thenReturn(browserOnly); + return serviceReference; + } +} diff --git a/src/test/java/org/apache/sling/auth/core/impl/AuthenticationHandlerWrapperTest.java b/src/test/java/org/apache/sling/auth/core/impl/AuthenticationHandlerWrapperTest.java new file mode 100644 index 0000000..58cbf36 --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/impl/AuthenticationHandlerWrapperTest.java @@ -0,0 +1,114 @@ +/* + * 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.sling.auth.core.impl; + +import java.io.IOException; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler; +import org.apache.sling.auth.core.spi.AuthenticationHandler; +import org.apache.sling.auth.core.spi.AuthenticationInfo; +import org.apache.sling.auth.core.spi.JakartaAuthenticationFeedbackHandler; +import org.apache.sling.auth.core.spi.JakartaAuthenticationHandler; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SuppressWarnings("deprecation") +public class AuthenticationHandlerWrapperTest { + + private interface FeedbackHandler extends AuthenticationHandler, AuthenticationFeedbackHandler {} + + @Test + public void plainWrapperDelegatesAuthenticationHandlerMethods() throws IOException { + final AuthenticationHandler handler = mock(AuthenticationHandler.class); + final JakartaAuthenticationHandler wrapper = AuthenticationHandlerWrapper.wrap(handler); + final HttpServletRequest request = mock(HttpServletRequest.class); + final HttpServletResponse response = mock(HttpServletResponse.class); + final AuthenticationInfo authInfo = new AuthenticationInfo("legacy"); + when(handler.extractCredentials( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class))) + .thenReturn(authInfo); + when(handler.requestCredentials( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class))) + .thenReturn(true); + + assertFalse(wrapper instanceof JakartaAuthenticationFeedbackHandler); + assertSame(authInfo, wrapper.extractCredentials(request, response)); + assertTrue(wrapper.requestCredentials(request, response)); + wrapper.dropCredentials(request, response); + + final ArgumentCaptor requestCaptor = + ArgumentCaptor.forClass(javax.servlet.http.HttpServletRequest.class); + final ArgumentCaptor responseCaptor = + ArgumentCaptor.forClass(javax.servlet.http.HttpServletResponse.class); + verify(handler).extractCredentials(requestCaptor.capture(), responseCaptor.capture()); + verify(handler) + .requestCredentials( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class)); + verify(handler) + .dropCredentials( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class)); + assertNotNull(requestCaptor.getValue()); + assertNotNull(responseCaptor.getValue()); + } + + @Test + public void feedbackWrapperDelegatesFeedbackMethods() { + final FeedbackHandler handler = mock(FeedbackHandler.class); + final JakartaAuthenticationHandler wrapper = AuthenticationHandlerWrapper.wrap(handler); + final JakartaAuthenticationFeedbackHandler feedbackWrapper = (JakartaAuthenticationFeedbackHandler) wrapper; + final HttpServletRequest request = mock(HttpServletRequest.class); + final HttpServletResponse response = mock(HttpServletResponse.class); + final AuthenticationInfo authInfo = new AuthenticationInfo("legacy"); + when(handler.authenticationSucceeded( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class), + any(AuthenticationInfo.class))) + .thenReturn(true); + + feedbackWrapper.authenticationFailed(request, response, authInfo); + assertTrue(feedbackWrapper.authenticationSucceeded(request, response, authInfo)); + + verify(handler) + .authenticationFailed( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class), + same(authInfo)); + verify(handler) + .authenticationSucceeded( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class), + same(authInfo)); + } +} diff --git a/src/test/java/org/apache/sling/auth/core/impl/AuthenticationRequirementHolderTest.java b/src/test/java/org/apache/sling/auth/core/impl/AuthenticationRequirementHolderTest.java new file mode 100644 index 0000000..c51ca32 --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/impl/AuthenticationRequirementHolderTest.java @@ -0,0 +1,71 @@ +/* + * 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.sling.auth.core.impl; + +import org.junit.Assert; +import org.junit.Test; + +public class AuthenticationRequirementHolderTest { + + @Test + public void test_fromConfig_plus() { + AuthenticationRequirementHolder h = AuthenticationRequirementHolder.fromConfig("+/secure", null); + Assert.assertTrue(h.requiresAuthentication()); + Assert.assertEquals("/secure", h.fullPath); + } + + @Test + public void test_fromConfig_minus() { + AuthenticationRequirementHolder h = AuthenticationRequirementHolder.fromConfig("-/open", null); + Assert.assertFalse(h.requiresAuthentication()); + Assert.assertEquals("/open", h.fullPath); + } + + @Test + public void test_fromConfig_plain() { + AuthenticationRequirementHolder h = AuthenticationRequirementHolder.fromConfig("/plain", null); + Assert.assertTrue(h.requiresAuthentication()); + Assert.assertEquals("/plain", h.fullPath); + } + + @Test(expected = IllegalArgumentException.class) + public void test_fromConfig_null() { + AuthenticationRequirementHolder.fromConfig(null, null); + } + + @Test(expected = IllegalArgumentException.class) + public void test_fromConfig_empty() { + AuthenticationRequirementHolder.fromConfig("", null); + } + + @Test + public void test_hashCode_equals() { + AuthenticationRequirementHolder a = new AuthenticationRequirementHolder("/p", true, null); + AuthenticationRequirementHolder b = new AuthenticationRequirementHolder("/p", true, null); + AuthenticationRequirementHolder c = new AuthenticationRequirementHolder("/p", false, null); + + Assert.assertEquals(a, b); + Assert.assertEquals(a.hashCode(), b.hashCode()); + Assert.assertNotEquals(a, c); + Assert.assertNotEquals(a.hashCode(), c.hashCode()); + Assert.assertEquals(a, a); + Assert.assertNotEquals(a, null); + Assert.assertNotEquals(a, "not a holder"); + } +} diff --git a/src/test/java/org/apache/sling/auth/core/impl/AuthenticatorWebConsolePluginTest.java b/src/test/java/org/apache/sling/auth/core/impl/AuthenticatorWebConsolePluginTest.java new file mode 100644 index 0000000..6ccf2d8 --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/impl/AuthenticatorWebConsolePluginTest.java @@ -0,0 +1,159 @@ +/* + * 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.sling.auth.core.impl; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.sling.auth.core.impl.hc.SetField; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class AuthenticatorWebConsolePluginTest { + + private SlingAuthenticator.Config config; + private AuthenticationHandlersManager handlersManager; + + @SuppressWarnings("unchecked") + private PathBasedHolderCache requirementsManager = + mock(PathBasedHolderCache.class); + + private HttpServletRequest request = mock(HttpServletRequest.class); + private HttpServletResponse response = mock(HttpServletResponse.class); + + @Before + public void setup() { + config = mock(SlingAuthenticator.Config.class); + when(config.sling_auth_anonymous_user()).thenReturn(""); + when(config.auth_sudo_cookie()).thenReturn("sling.sudo"); + when(config.auth_sudo_parameter()).thenReturn("sudo"); + + handlersManager = mock(AuthenticationHandlersManager.class); + Map> handlerMap = new LinkedHashMap<>(); + handlerMap.put("/path/a", Arrays.asList("HandlerA", "HandlerB")); + when(handlersManager.getAuthenticationHandlerMap()).thenReturn(handlerMap); + + when(requirementsManager.getHolders()) + .thenReturn(Arrays.asList( + new AuthenticationRequirementHolder("/secure", true, null), + new AuthenticationRequirementHolder("/public", false, null))); + } + + private AuthenticatorWebConsolePlugin newPlugin() throws Exception { + AuthenticatorWebConsolePlugin plugin = new AuthenticatorWebConsolePlugin(config); + SetField.set(plugin, "authenticationHoldersManager", handlersManager); + SetField.set(plugin, "authenticationRequirementsManager", requirementsManager); + return plugin; + } + + @Test + public void test_doGet_rendersAllSections() throws Exception { + AuthenticatorWebConsolePlugin plugin = newPlugin(); + StringWriter sw = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(sw)); + + plugin.doGet(request, response); + + String out = sw.toString(); + Assert.assertTrue(out.contains("Registered Authentication Handler")); + Assert.assertTrue(out.contains("/path/a")); + Assert.assertTrue(out.contains("HandlerA")); + Assert.assertTrue(out.contains("Authentication Requirement Configuration")); + Assert.assertTrue(out.contains("/secure")); + Assert.assertTrue(out.contains("Yes")); + Assert.assertTrue(out.contains("/public")); + Assert.assertTrue(out.contains("No")); + Assert.assertTrue(out.contains("Miscellaneous Configuration")); + Assert.assertTrue(out.contains("sling.sudo")); + Assert.assertTrue(out.contains("sudo")); + } + + @Test + public void test_config_default_anonymous_user() throws Exception { + when(config.sling_auth_anonymous_user()).thenReturn(null); + AuthenticatorWebConsolePlugin plugin = newPlugin(); + StringWriter sw = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(sw)); + + plugin.doGet(request, response); + Assert.assertTrue(sw.toString().contains("(default)")); + } + + @Test + public void test_service_get_dispatches() throws Exception { + AuthenticatorWebConsolePlugin plugin = newPlugin(); + StringWriter sw = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(sw)); + when(request.getMethod()).thenReturn("GET"); + when(request.getProtocol()).thenReturn("HTTP/1.1"); + + plugin.service(request, response); + Assert.assertTrue(sw.toString().contains("Registered Authentication Handler")); + } + + @Test + public void test_service_post_ignored() throws Exception { + AuthenticatorWebConsolePlugin plugin = newPlugin(); + when(request.getMethod()).thenReturn("POST"); + + plugin.service(request, response); + verify(response, never()).getWriter(); + } + + @Test + public void test_doGet_ioexception_sends_error() throws Exception { + AuthenticatorWebConsolePlugin plugin = newPlugin(); + jakarta.servlet.ServletConfig scfg = mock(jakarta.servlet.ServletConfig.class); + when(scfg.getServletContext()).thenReturn(mock(jakarta.servlet.ServletContext.class)); + plugin.init(scfg); + when(response.getWriter()).thenThrow(new IOException("no writer")); + + plugin.doGet(request, response); + verify(response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + + @Test + public void test_modified_updates_config() throws Exception { + AuthenticatorWebConsolePlugin plugin = newPlugin(); + SlingAuthenticator.Config newConfig = mock(SlingAuthenticator.Config.class); + when(newConfig.sling_auth_anonymous_user()).thenReturn(null); + when(newConfig.auth_sudo_cookie()).thenReturn("c2"); + when(newConfig.auth_sudo_parameter()).thenReturn("p2"); + plugin.modified(newConfig); + + StringWriter sw = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(sw)); + plugin.doGet(request, response); + Assert.assertTrue(sw.toString().contains("c2")); + Assert.assertTrue(sw.toString().contains("p2")); + } +} diff --git a/src/test/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandlerTest.java b/src/test/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandlerTest.java new file mode 100644 index 0000000..a4e7693 --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/impl/HttpBasicAuthenticationHandlerTest.java @@ -0,0 +1,183 @@ +/* + * 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.sling.auth.core.impl; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.sling.auth.core.spi.AuthenticationInfo; +import org.apache.sling.auth.core.spi.JakartaAuthenticationHandler; +import org.junit.Assert; +import org.junit.Test; + +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class HttpBasicAuthenticationHandlerTest { + + private HttpServletRequest request = mock(HttpServletRequest.class); + private HttpServletResponse response = mock(HttpServletResponse.class); + + private static String basic(String user, String pass) { + String raw = user + ":" + pass; + return "Basic " + Base64.getEncoder().encodeToString(raw.getBytes(StandardCharsets.ISO_8859_1)); + } + + @Test + public void test_extractCredentials_valid() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + when(request.getHeader("Authorization")).thenReturn(basic("admin", "secret")); + AuthenticationInfo info = handler.extractCredentials(request, response); + Assert.assertNotNull(info); + Assert.assertEquals("admin", info.getUser()); + Assert.assertArrayEquals("secret".toCharArray(), info.getPassword()); + } + + @Test + public void test_extractCredentials_no_colon() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + String header = "Basic " + Base64.getEncoder().encodeToString("justuser".getBytes(StandardCharsets.ISO_8859_1)); + when(request.getHeader("Authorization")).thenReturn(header); + AuthenticationInfo info = handler.extractCredentials(request); + Assert.assertNotNull(info); + Assert.assertEquals("justuser", info.getUser()); + Assert.assertEquals(0, info.getPassword().length); + } + + @Test + public void test_extractCredentials_no_header() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + Assert.assertNull(handler.extractCredentials(request)); + when(request.getHeader("Authorization")).thenReturn(""); + Assert.assertNull(handler.extractCredentials(request)); + } + + @Test + public void test_extractCredentials_no_blank() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + when(request.getHeader("Authorization")).thenReturn("Basic"); + Assert.assertNull(handler.extractCredentials(request)); + } + + @Test + public void test_extractCredentials_wrong_scheme() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + when(request.getHeader("Authorization")).thenReturn("Digest abc"); + Assert.assertNull(handler.extractCredentials(request)); + } + + @Test + public void test_extractCredentials_login_requested() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + when(request.getHeader("Authorization")).thenReturn(null); + when(request.getParameter(JakartaAuthenticationHandler.REQUEST_LOGIN_PARAMETER)) + .thenReturn("BASIC"); + AuthenticationInfo info = handler.extractCredentials(request, response); + Assert.assertSame(AuthenticationInfo.DOING_AUTH, info); + verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + + @Test + public void test_extractCredentials_no_login_requested() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + when(request.getHeader("Authorization")).thenReturn(null); + Assert.assertNull(handler.extractCredentials(request, response)); + } + + @Test + public void test_requestCredentials_fullSupport() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + Assert.assertTrue(handler.requestCredentials(request, response)); + verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + + @Test + public void test_requestCredentials_preemptive() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", false); + Assert.assertFalse(handler.requestCredentials(request, response)); + verify(response, never()).setStatus(anyInt()); + } + + @Test + public void test_dropCredentials_fullSupport_withHeader() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + when(request.getHeader("Authorization")).thenReturn("Basic xyz"); + handler.dropCredentials(request, response); + verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + + @Test + public void test_dropCredentials_noHeader() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + when(request.getHeader("Authorization")).thenReturn(null); + handler.dropCredentials(request, response); + verify(response, never()).setStatus(anyInt()); + } + + @Test + public void test_authenticationFailed_notValidate() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + handler.authenticationFailed(request, response, new AuthenticationInfo("BASIC")); + verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + + @Test + public void test_authenticationFailed_validate() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + when(request.getParameter("j_validate")).thenReturn("true"); + handler.authenticationFailed(request, response, new AuthenticationInfo("BASIC")); + verify(response, never()).setStatus(anyInt()); + } + + @Test + public void test_sendUnauthorized_committed() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + when(response.isCommitted()).thenReturn(true); + Assert.assertFalse(handler.sendUnauthorized(response)); + } + + @Test + public void test_sendUnauthorized_ok() { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + Assert.assertTrue(handler.sendUnauthorized(response)); + verify(response).setHeader("WWW-Authenticate", "Basic realm=\"realm\""); + } + + @Test + public void test_sendUnauthorized_ioexception() throws IOException { + HttpBasicAuthenticationHandler handler = new HttpBasicAuthenticationHandler("realm", true); + doThrow(new IOException("boom")).when(response).flushBuffer(); + Assert.assertFalse(handler.sendUnauthorized(response)); + } + + @Test + public void test_toString() { + Assert.assertTrue( + new HttpBasicAuthenticationHandler("r", true).toString().contains("enabled")); + Assert.assertTrue( + new HttpBasicAuthenticationHandler("r", false).toString().contains("preemptive")); + } +} diff --git a/src/test/java/org/apache/sling/auth/core/impl/LoginServletTest.java b/src/test/java/org/apache/sling/auth/core/impl/LoginServletTest.java new file mode 100644 index 0000000..9e10244 --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/impl/LoginServletTest.java @@ -0,0 +1,116 @@ +/* + * 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.sling.auth.core.impl; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.SlingHttpServletResponse; +import org.apache.sling.api.auth.Authenticator; +import org.apache.sling.api.auth.NoAuthenticationHandlerException; +import org.apache.sling.auth.core.impl.hc.SetField; +import org.junit.Test; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class LoginServletTest { + + private SlingHttpServletRequest request = mock(SlingHttpServletRequest.class); + private SlingHttpServletResponse response = mock(SlingHttpServletResponse.class); + + @Test + public void test_login_success() throws Exception { + LoginServlet servlet = new LoginServlet(); + Authenticator authenticator = mock(Authenticator.class); + SetField.set(servlet, "authenticator", authenticator); + + when(request.getAuthType()).thenReturn(null); + servlet.service(request, response); + + verify(authenticator) + .login((javax.servlet.http.HttpServletRequest) request, (javax.servlet.http.HttpServletResponse) + response); + } + + @Test + public void test_login_redirect_when_authenticated_and_self() throws Exception { + LoginServlet servlet = new LoginServlet(); + Authenticator authenticator = mock(Authenticator.class); + SetField.set(servlet, "authenticator", authenticator); + + when(request.getAuthType()).thenReturn("BASIC"); + when(request.getContextPath()).thenReturn("/ctx"); + // no login resource -> resourcePath null -> isSelf true + servlet.service(request, response); + + verify(response).sendRedirect("/ctx/"); + verify(authenticator, never()) + .login( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class)); + } + + @Test + public void test_login_no_authenticator() throws Exception { + LoginServlet servlet = new LoginServlet(); + when(request.getAuthType()).thenReturn(null); + servlet.service(request, response); + verify(response).sendError(eq(HttpServletResponse.SC_FORBIDDEN), anyString()); + } + + @Test + public void test_login_no_handler() throws Exception { + LoginServlet servlet = new LoginServlet(); + Authenticator authenticator = mock(Authenticator.class); + SetField.set(servlet, "authenticator", authenticator); + when(request.getAuthType()).thenReturn(null); + doThrow(new NoAuthenticationHandlerException()) + .when(authenticator) + .login( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class)); + + servlet.service(request, response); + verify(response).sendError(eq(HttpServletResponse.SC_FORBIDDEN), anyString()); + } + + @Test + public void test_login_response_committed() throws Exception { + LoginServlet servlet = new LoginServlet(); + Authenticator authenticator = mock(Authenticator.class); + SetField.set(servlet, "authenticator", authenticator); + when(request.getAuthType()).thenReturn(null); + doThrow(new IllegalStateException("committed")) + .when(authenticator) + .login( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class)); + + servlet.service(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } +} diff --git a/src/test/java/org/apache/sling/auth/core/impl/LogoutServletTest.java b/src/test/java/org/apache/sling/auth/core/impl/LogoutServletTest.java new file mode 100644 index 0000000..2f7b383 --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/impl/LogoutServletTest.java @@ -0,0 +1,74 @@ +/* + * 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.sling.auth.core.impl; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.SlingHttpServletResponse; +import org.apache.sling.api.auth.Authenticator; +import org.apache.sling.auth.core.impl.hc.SetField; +import org.junit.Test; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +public class LogoutServletTest { + + private SlingHttpServletRequest request = mock(SlingHttpServletRequest.class); + private SlingHttpServletResponse response = mock(SlingHttpServletResponse.class); + + @Test + public void test_logout_success() throws Exception { + LogoutServlet servlet = new LogoutServlet(); + Authenticator authenticator = mock(Authenticator.class); + SetField.set(servlet, "authenticator", authenticator); + + servlet.service(request, response); + verify(authenticator) + .logout((javax.servlet.http.HttpServletRequest) request, (javax.servlet.http.HttpServletResponse) + response); + } + + @Test + public void test_logout_no_authenticator() { + LogoutServlet servlet = new LogoutServlet(); + servlet.service(request, response); + verify(response).setStatus(HttpServletResponse.SC_NO_CONTENT); + } + + @Test + public void test_logout_response_committed() throws Exception { + LogoutServlet servlet = new LogoutServlet(); + Authenticator authenticator = mock(Authenticator.class); + SetField.set(servlet, "authenticator", authenticator); + doThrow(new IllegalStateException("committed")) + .when(authenticator) + .logout( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class)); + + servlet.service(request, response); + verify(response, never()).setStatus(anyInt()); + } +} diff --git a/src/test/java/org/apache/sling/auth/core/impl/PathBasedHolderTest.java b/src/test/java/org/apache/sling/auth/core/impl/PathBasedHolderTest.java index a128efa..a648b57 100644 --- a/src/test/java/org/apache/sling/auth/core/impl/PathBasedHolderTest.java +++ b/src/test/java/org/apache/sling/auth/core/impl/PathBasedHolderTest.java @@ -18,14 +18,23 @@ */ package org.apache.sling.auth.core.impl; +import org.junit.Assert; import org.junit.Test; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class PathBasedHolderTest { + private static PathBasedHolder holder(String url, ServiceReference ref) { + return new PathBasedHolder(url, ref) {}; + } + @Test public void TestIsPathRequiresHandlerRoot() { final PathBasedHolder holder = new PathBasedHolder("/", null) {}; @@ -116,4 +125,97 @@ public void test_emptyNodeAuthenticationHandlerPath() throws Throwable { final String handlerPath = ""; assertPathRequiresHandler(true, requestPath, handlerPath); } + + @Test + public void test_getProvider_noReference() { + assertEquals("Apache Sling Request Authenticator", holder("/x", null).getProvider()); + } + + @Test + public void test_getProvider_description() { + ServiceReference ref = mock(ServiceReference.class); + when(ref.getProperty(Constants.SERVICE_DESCRIPTION)).thenReturn("My Service"); + assertEquals("My Service", holder("/x", ref).getProvider()); + } + + @Test + public void test_getProvider_serviceId() { + ServiceReference ref = mock(ServiceReference.class); + when(ref.getProperty(Constants.SERVICE_DESCRIPTION)).thenReturn(null); + when(ref.getProperty(Constants.SERVICE_ID)).thenReturn(42L); + assertEquals("Service 42", holder("/x", ref).getProvider()); + } + + @Test + public void test_protocol_and_host_parsing() { + PathBasedHolder h = holder("http://example.com/some/path", null); + assertTrue(h.isPathRequiresHandler("/some/path")); + + // host only with trailing content + PathBasedHolder h2 = holder("//example.com/foo", null); + assertTrue(h2.isPathRequiresHandler("/foo")); + + // host only, no path + PathBasedHolder h3 = holder("//example.com", null); + assertTrue(h3.isPathRequiresHandler("/anything")); + + // just double slash + PathBasedHolder h4 = holder("//", null); + assertTrue(h4.isPathRequiresHandler("/anything")); + } + + @Test + public void test_hashCode_and_equals() { + PathBasedHolder a = holder("/x", null); + PathBasedHolder b = holder("/x", null); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(a, b); + Assert.assertNotEquals(a, holder("/y", null)); + Assert.assertNotEquals(a, null); + Assert.assertNotEquals(a, "not a holder"); + assertEquals(a, a); + } + + @Test + public void test_equals_differentServiceReference() { + ServiceReference ref = mock(ServiceReference.class); + Assert.assertNotEquals(holder("/x", ref), holder("/x", null)); + assertEquals(holder("/x", ref), holder("/x", ref)); + } + + @Test + public void test_compareTo_byPath() { + PathBasedHolder a = holder("/a", null); + PathBasedHolder b = holder("/b", null); + assertTrue(a.compareTo(b) > 0); + assertTrue(b.compareTo(a) < 0); + } + + @Test + public void test_compareTo_nullServiceReferences() { + PathBasedHolder a = holder("/same", null); + PathBasedHolder b = holder("/same", null); + // both null service references -> compared by class name (same class) -> 0 + assertEquals(0, a.compareTo(b)); + } + + @Test + public void test_compareTo_oneNullServiceReference() { + ServiceReference ref = mock(ServiceReference.class); + PathBasedHolder withRef = holder("/same", ref); + PathBasedHolder withoutRef = holder("/same", null); + assertEquals(-1, withoutRef.compareTo(withRef)); + assertEquals(1, withRef.compareTo(withoutRef)); + } + + @Test + @SuppressWarnings({"unchecked", "rawtypes"}) + public void test_compareTo_byServiceReference() { + ServiceReference refA = mock(ServiceReference.class); + ServiceReference refB = mock(ServiceReference.class); + when(refB.compareTo(refA)).thenReturn(5); + PathBasedHolder a = holder("/same", refA); + PathBasedHolder b = holder("/same", refB); + assertEquals(5, a.compareTo(b)); + } } diff --git a/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorOsgiJavaxTest.java b/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorOsgiJavaxTest.java index acec169..54afbb5 100644 --- a/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorOsgiJavaxTest.java +++ b/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorOsgiJavaxTest.java @@ -46,7 +46,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.mockito.Mockito; import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; @@ -115,8 +114,7 @@ public void testLoginEventDecoration() { AuthenticationInfo authInfo = new AuthenticationInfo("testing", "admin", "admin".toCharArray()); authInfo.put(AuthConstants.AUTH_INFO_LOGIN, Boolean.TRUE); when(req.getRequestURL()).thenReturn(new StringBuffer("/test")); - when(testAuthHandler.extractCredentials(Mockito.any(), Mockito.any())) - .thenReturn(authInfo); + when(testAuthHandler.extractCredentials(any(), any())).thenReturn(authInfo); }, () -> testEventHandler.collectedEvents(AuthConstants.TOPIC_LOGIN), event -> assertEquals("test1Value", event.getProperty("test1"))); @@ -132,8 +130,7 @@ public void testLoginFailedEventDecoration() { // provide invalid test authInfo AuthenticationInfo authInfo = new AuthenticationInfo("testing", "invalid", "invalid".toCharArray()); when(req.getRequestURL()).thenReturn(new StringBuffer("/testing")); - when(testAuthHandler.extractCredentials(Mockito.any(), Mockito.any())) - .thenReturn(authInfo); + when(testAuthHandler.extractCredentials(any(), any())).thenReturn(authInfo); // throw exception to trigger FailedLogin event try { when(resourceResolverFactory.getResourceResolver(authInfo)) diff --git a/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorTest.java b/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorTest.java index 053a6b9..7e67d49 100644 --- a/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorTest.java +++ b/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorTest.java @@ -29,6 +29,7 @@ import junitx.util.PrivateAccessor; import org.apache.sling.api.SlingJakartaHttpServletRequest; import org.apache.sling.api.SlingJakartaHttpServletResponse; +import org.apache.sling.api.auth.NoAuthenticationHandlerException; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceResolverFactory; import org.apache.sling.auth.core.AuthenticationSupport; @@ -38,14 +39,20 @@ import org.junit.Assert; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class SlingAuthenticatorTest { @@ -53,14 +60,14 @@ public class SlingAuthenticatorTest { * Helper method to create a default configuration */ public static SlingAuthenticator.Config createDefaultConfig() { - final SlingAuthenticator.Config config = Mockito.mock(SlingAuthenticator.Config.class); + final SlingAuthenticator.Config config = mock(SlingAuthenticator.Config.class); - Mockito.when(config.auth_sudo_cookie()).thenReturn("sling.sudo"); - Mockito.when(config.auth_sudo_parameter()).thenReturn("sudo"); - Mockito.when(config.auth_annonymous()).thenReturn(true); - Mockito.when(config.auth_http()).thenReturn(SlingAuthenticator.HTTP_AUTH_PREEMPTIVE); - Mockito.when(config.auth_http_realm()).thenReturn("Sling (Development)"); - Mockito.when(config.auth_uri_suffix()).thenReturn(new String[] {SlingAuthenticator.DEFAULT_AUTH_URI_SUFFIX}); + when(config.auth_sudo_cookie()).thenReturn("sling.sudo"); + when(config.auth_sudo_parameter()).thenReturn("sudo"); + when(config.auth_annonymous()).thenReturn(true); + when(config.auth_http()).thenReturn(SlingAuthenticator.HTTP_AUTH_PREEMPTIVE); + when(config.auth_http_realm()).thenReturn("Sling (Development)"); + when(config.auth_uri_suffix()).thenReturn(new String[] {SlingAuthenticator.DEFAULT_AUTH_URI_SUFFIX}); return config; } @@ -76,17 +83,17 @@ public SlingAuthenticator createSlingAuthenticator(final String... typeAndPathPa private static final long BUNDLE_ID = 732; private BundleContext createBundleContext() { - final BundleContext context = Mockito.mock(BundleContext.class); - final Bundle bundle = Mockito.mock(Bundle.class); - Mockito.when(bundle.getBundleId()).thenReturn(BUNDLE_ID); - Mockito.when(context.getBundle()).thenReturn(bundle); + final BundleContext context = mock(BundleContext.class); + final Bundle bundle = mock(Bundle.class); + when(bundle.getBundleId()).thenReturn(BUNDLE_ID); + when(context.getBundle()).thenReturn(bundle); return context; } private SlingAuthenticator createSlingAuthenticator( final SlingAuthenticator.Config config, final String... typeAndPathPairs) { final AuthenticationRequirementsManager requirements = - new AuthenticationRequirementsManager(createBundleContext(), null, config, callable -> callable.run()); + new AuthenticationRequirementsManager(createBundleContext(), null, config, Runnable::run); final AuthenticationHandlersManager handlers = new AuthenticationHandlersManager(config); if (typeAndPathPairs != null) { int i = 0; @@ -95,10 +102,7 @@ private SlingAuthenticator createSlingAuthenticator( i += 2; } } - final SlingAuthenticator slingAuthenticator = - new SlingAuthenticator(requirements, handlers, null, Mockito.mock(BundleContext.class), config); - - return slingAuthenticator; + return new SlingAuthenticator(requirements, handlers, null, mock(BundleContext.class), config); } @Test @@ -151,10 +155,10 @@ public void test_isAnonAllowed() throws Throwable { // anon is allowed by default final SlingAuthenticator slingAuthenticator = this.createSlingAuthenticator(); - final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); - Mockito.when(request.getServerName()).thenReturn("localhost"); - Mockito.when(request.getServerPort()).thenReturn(80); - Mockito.when(request.getScheme()).thenReturn("http"); + final HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getServerName()).thenReturn("localhost"); + when(request.getServerPort()).thenReturn(80); + when(request.getScheme()).thenReturn("http"); Assert.assertTrue(slingAuthenticator.isAnonAllowed(request)); } @@ -163,14 +167,14 @@ public void test_isAnonAllowed() throws Throwable { public void test_isAnonNotAllowed() throws Throwable { // anon is allowed by default final SlingAuthenticator.Config config = createDefaultConfig(); - Mockito.when(config.auth_annonymous()).thenReturn(false); + when(config.auth_annonymous()).thenReturn(false); final SlingAuthenticator slingAuthenticator = this.createSlingAuthenticator(config); - final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); - Mockito.when(request.getServerName()).thenReturn("localhost"); - Mockito.when(request.getServerPort()).thenReturn(80); - Mockito.when(request.getScheme()).thenReturn("http"); + final HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getServerName()).thenReturn("localhost"); + when(request.getServerPort()).thenReturn(80); + when(request.getScheme()).thenReturn("http"); Assert.assertFalse(slingAuthenticator.isAnonAllowed(request)); } @@ -179,14 +183,14 @@ private void assertAuthInfo(String protectedPath, String requestChildNode) throw final String authType = "AUTH_TYPE_TEST"; final SlingAuthenticator slingAuthenticator = this.createSlingAuthenticator(authType, protectedPath); - final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + final HttpServletRequest request = mock(HttpServletRequest.class); buildExpectationsForRequest(request, requestChildNode); AuthenticationInfo authInfo = (AuthenticationInfo) PrivateAccessor.invoke( slingAuthenticator, "getAuthenticationInfo", new Class[] {HttpServletRequest.class, HttpServletResponse.class}, - new Object[] {request, Mockito.mock(HttpServletResponse.class)}); + new Object[] {request, mock(HttpServletResponse.class)}); /** * The AUTH TYPE defined above should be used for the path /test and his children: eg /test/childnode. */ @@ -245,14 +249,14 @@ public void test_childNodeShouldHaveAuthenticationInfoLonger() throws Throwable final SlingAuthenticator slingAuthenticator = this.createSlingAuthenticator(AUTH_TYPE, PROTECTED_PATH, AUTH_TYPE_LONGER, PROTECTED_PATH_LONGER); - final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + final HttpServletRequest request = mock(HttpServletRequest.class); buildExpectationsForRequest(request, REQUEST_CHILD_NODE); AuthenticationInfo authInfo = (AuthenticationInfo) PrivateAccessor.invoke( slingAuthenticator, "getAuthenticationInfo", new Class[] {HttpServletRequest.class, HttpServletResponse.class}, - new Object[] {request, Mockito.mock(HttpServletResponse.class)}); + new Object[] {request, mock(HttpServletResponse.class)}); /** * The AUTH TYPE defined aboved should be used for the path /test and his children: eg /test/childnode. */ @@ -283,14 +287,14 @@ public void test_siblingNodeShouldNotHaveAuthenticationInfo() throws Throwable { final SlingAuthenticator slingAuthenticator = this.createSlingAuthenticator(AUTH_TYPE, PROTECTED_PATH); - final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + final HttpServletRequest request = mock(HttpServletRequest.class); buildExpectationsForRequest(request, REQUEST_NOT_PROTECTED_PATH); AuthenticationInfo authInfo = (AuthenticationInfo) PrivateAccessor.invoke( slingAuthenticator, "getAuthenticationInfo", new Class[] {HttpServletRequest.class, HttpServletResponse.class}, - new Object[] {request, Mockito.mock(HttpServletResponse.class)}); + new Object[] {request, mock(HttpServletResponse.class)}); /** * The AUTH TYPE defined aboved should not be used for the path /test2. */ @@ -300,20 +304,20 @@ public void test_siblingNodeShouldNotHaveAuthenticationInfo() throws Throwable { @Test public void testServletRequestListener() { final SlingAuthenticator slingAuthenticator = this.createSlingAuthenticator(); - final ServletRequestEvent event = Mockito.mock(ServletRequestEvent.class); - final ServletRequest request = Mockito.mock(ServletRequest.class); - Mockito.when(event.getServletRequest()).thenReturn(request); + final ServletRequestEvent event = mock(ServletRequestEvent.class); + final ServletRequest request = mock(ServletRequest.class); + when(event.getServletRequest()).thenReturn(request); slingAuthenticator.requestInitialized(event); - final ResourceResolver resolver = Mockito.mock(ResourceResolver.class); - Mockito.when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)) + final ResourceResolver resolver = mock(ResourceResolver.class); + when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)) .thenReturn(resolver); slingAuthenticator.requestDestroyed(event); // verify resolver close, attribute removed - Mockito.verify(resolver).close(); - Mockito.verify(request).removeAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER); + verify(resolver).close(); + verify(request).removeAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER); } @Test @@ -321,11 +325,11 @@ public void testSetSudoCookieNoSudoBeforeNoSudoAfter() { final SlingAuthenticator slingAuthenticator = this.createSlingAuthenticator(); final AuthenticationInfo info = new AuthenticationInfo("basic"); - final SlingJakartaHttpServletRequest req = Mockito.mock(SlingJakartaHttpServletRequest.class); - final SlingJakartaHttpServletResponse res = Mockito.mock(SlingJakartaHttpServletResponse.class); + final SlingJakartaHttpServletRequest req = mock(SlingJakartaHttpServletRequest.class); + final SlingJakartaHttpServletResponse res = mock(SlingJakartaHttpServletResponse.class); assertFalse(slingAuthenticator.setSudoCookie(req, res, info)); - Mockito.verify(res, never()).addCookie(Mockito.any()); + verify(res, never()).addCookie(any()); } @Test @@ -334,12 +338,12 @@ public void testSetSudoCookieNoSudoBeforeSudoAfter() { final AuthenticationInfo info = new AuthenticationInfo("basic"); info.put(ResourceResolverFactory.USER_IMPERSONATION, "newsudo"); - final SlingJakartaHttpServletRequest req = Mockito.mock(SlingJakartaHttpServletRequest.class); - final SlingJakartaHttpServletResponse res = Mockito.mock(SlingJakartaHttpServletResponse.class); + final SlingJakartaHttpServletRequest req = mock(SlingJakartaHttpServletRequest.class); + final SlingJakartaHttpServletResponse res = mock(SlingJakartaHttpServletResponse.class); assertTrue(slingAuthenticator.setSudoCookie(req, res, info)); ArgumentCaptor argument = ArgumentCaptor.forClass(Cookie.class); - Mockito.verify(res).addCookie(argument.capture()); + verify(res).addCookie(argument.capture()); assertEquals("\"newsudo\"", argument.getValue().getValue()); } @@ -349,13 +353,13 @@ public void testSetSudoCookieSudoBeforeSameSudoAfter() { final AuthenticationInfo info = new AuthenticationInfo("basic"); info.put(ResourceResolverFactory.USER_IMPERSONATION, "oldsudo"); - final SlingJakartaHttpServletRequest req = Mockito.mock(SlingJakartaHttpServletRequest.class); + final SlingJakartaHttpServletRequest req = mock(SlingJakartaHttpServletRequest.class); final Cookie cookie = new Cookie("sling.sudo", "\"oldsudo\""); - Mockito.when(req.getCookies()).thenReturn(new Cookie[] {cookie}); - final SlingJakartaHttpServletResponse res = Mockito.mock(SlingJakartaHttpServletResponse.class); + when(req.getCookies()).thenReturn(new Cookie[] {cookie}); + final SlingJakartaHttpServletResponse res = mock(SlingJakartaHttpServletResponse.class); assertFalse(slingAuthenticator.setSudoCookie(req, res, info)); - Mockito.verify(res, never()).addCookie(Mockito.any()); + verify(res, never()).addCookie(any()); } @Test @@ -364,14 +368,14 @@ public void testSetSudoCookieSudoBeforeNewSudoAfter() { final AuthenticationInfo info = new AuthenticationInfo("basic"); info.put(ResourceResolverFactory.USER_IMPERSONATION, "newsudo"); - final SlingJakartaHttpServletRequest req = Mockito.mock(SlingJakartaHttpServletRequest.class); + final SlingJakartaHttpServletRequest req = mock(SlingJakartaHttpServletRequest.class); final Cookie cookie = new Cookie("sling.sudo", "\"oldsudo\""); - Mockito.when(req.getCookies()).thenReturn(new Cookie[] {cookie}); - final SlingJakartaHttpServletResponse res = Mockito.mock(SlingJakartaHttpServletResponse.class); + when(req.getCookies()).thenReturn(new Cookie[] {cookie}); + final SlingJakartaHttpServletResponse res = mock(SlingJakartaHttpServletResponse.class); assertTrue(slingAuthenticator.setSudoCookie(req, res, info)); ArgumentCaptor argument = ArgumentCaptor.forClass(Cookie.class); - Mockito.verify(res).addCookie(argument.capture()); + verify(res).addCookie(argument.capture()); assertEquals("\"newsudo\"", argument.getValue().getValue()); } @@ -380,14 +384,14 @@ public void testSetSudoCookieSudoBeforeNoSudoAfter() { final SlingAuthenticator slingAuthenticator = this.createSlingAuthenticator(); final AuthenticationInfo info = new AuthenticationInfo("basic"); - final SlingJakartaHttpServletRequest req = Mockito.mock(SlingJakartaHttpServletRequest.class); + final SlingJakartaHttpServletRequest req = mock(SlingJakartaHttpServletRequest.class); final Cookie cookie = new Cookie("sling.sudo", "\"oldsudo\""); - Mockito.when(req.getCookies()).thenReturn(new Cookie[] {cookie}); - final SlingJakartaHttpServletResponse res = Mockito.mock(SlingJakartaHttpServletResponse.class); + when(req.getCookies()).thenReturn(new Cookie[] {cookie}); + final SlingJakartaHttpServletResponse res = mock(SlingJakartaHttpServletResponse.class); assertTrue(slingAuthenticator.setSudoCookie(req, res, info)); ArgumentCaptor argument = ArgumentCaptor.forClass(Cookie.class); - Mockito.verify(res).addCookie(argument.capture()); + verify(res).addCookie(argument.capture()); assertEquals("\"\"", argument.getValue().getValue()); } @@ -397,21 +401,21 @@ public void testSudoCookieFlags() { final AuthenticationInfo info = new AuthenticationInfo("basic"); info.put(ResourceResolverFactory.USER_IMPERSONATION, "newsudo"); - final SlingJakartaHttpServletRequest req = Mockito.mock(SlingJakartaHttpServletRequest.class); - Mockito.when(req.isSecure()).thenReturn(true); - SlingJakartaHttpServletResponse res = Mockito.mock(SlingJakartaHttpServletResponse.class); + final SlingJakartaHttpServletRequest req = mock(SlingJakartaHttpServletRequest.class); + when(req.isSecure()).thenReturn(true); + SlingJakartaHttpServletResponse res = mock(SlingJakartaHttpServletResponse.class); assertTrue(slingAuthenticator.setSudoCookie(req, res, info)); ArgumentCaptor argument1 = ArgumentCaptor.forClass(Cookie.class); - Mockito.verify(res).addCookie(argument1.capture()); + verify(res).addCookie(argument1.capture()); assertTrue(argument1.getValue().isHttpOnly()); assertTrue(argument1.getValue().getSecure()); - res = Mockito.mock(SlingJakartaHttpServletResponse.class); - Mockito.when(req.isSecure()).thenReturn(false); + res = mock(SlingJakartaHttpServletResponse.class); + when(req.isSecure()).thenReturn(false); assertTrue(slingAuthenticator.setSudoCookie(req, res, info)); ArgumentCaptor argument2 = ArgumentCaptor.forClass(Cookie.class); - Mockito.verify(res).addCookie(argument2.capture()); + verify(res).addCookie(argument2.capture()); assertTrue(argument2.getValue().isHttpOnly()); assertFalse(argument2.getValue().getSecure()); } @@ -426,10 +430,10 @@ public void testSudoCookieFlags() { */ private void buildExpectationsForRequest(final HttpServletRequest request, final String requestPath) { { - Mockito.when(request.getServletPath()).thenReturn(requestPath); - Mockito.when(request.getServerName()).thenReturn("localhost"); - Mockito.when(request.getServerPort()).thenReturn(80); - Mockito.when(request.getScheme()).thenReturn("http"); + when(request.getServletPath()).thenReturn(requestPath); + when(request.getServerName()).thenReturn("localhost"); + when(request.getServerPort()).thenReturn(80); + when(request.getScheme()).thenReturn("http"); } } @@ -462,7 +466,9 @@ protected boolean doRequestCredentials(HttpServletRequest request, HttpServletRe @Override protected void doDropCredentials(HttpServletRequest request, HttpServletResponse response) - throws IOException {} + throws IOException { + // Intentionally empty: this test stub never needs to drop credentials. + } }; } @@ -493,4 +499,384 @@ public void dropCredentials(HttpServletRequest request, HttpServletResponse resp throw new UnsupportedOperationException("Unimplemented method 'dropCredentials'"); } } + + private SlingAuthenticator createAuthenticator(AbstractAuthenticationHandlerHolder... holders) { + final SlingAuthenticator.Config config = SlingAuthenticatorTest.createDefaultConfig(); + final AuthenticationRequirementsManager requirements = + new AuthenticationRequirementsManager(createBundleContext(), null, config, Runnable::run); + final AuthenticationHandlersManager handlers = new AuthenticationHandlersManager(config); + for (AbstractAuthenticationHandlerHolder h : holders) { + handlers.addHolder(h); + } + return new SlingAuthenticator(requirements, handlers, null, mock(BundleContext.class), config); + } + + private SlingAuthenticator createAuthenticator( + final ResourceResolverFactory rrf, final AbstractAuthenticationHandlerHolder... holders) { + final SlingAuthenticator.Config config = SlingAuthenticatorTest.createDefaultConfig(); + final AuthenticationRequirementsManager requirements = + new AuthenticationRequirementsManager(createBundleContext(), null, config, Runnable::run); + final AuthenticationHandlersManager handlers = new AuthenticationHandlersManager(config); + for (AbstractAuthenticationHandlerHolder h : holders) { + handlers.addHolder(h); + } + return new SlingAuthenticator(requirements, handlers, rrf, mock(BundleContext.class), config); + } + + private AbstractAuthenticationHandlerHolder infoHolder(final String path, final AuthenticationInfo info) { + return new AbstractAuthenticationHandlerHolder(path, null) { + @Override + protected JakartaAuthenticationFeedbackHandler getFeedbackHandler() { + return null; + } + + @Override + protected AuthenticationInfo doExtractCredentials( + HttpServletRequest request, HttpServletResponse response) { + return info; + } + + @Override + protected boolean doRequestCredentials(HttpServletRequest request, HttpServletResponse response) + throws IOException { + return true; + } + + @Override + protected void doDropCredentials(HttpServletRequest request, HttpServletResponse response) + throws IOException { + // Intentionally empty: this test stub never needs to drop credentials. + } + }; + } + + private AbstractAuthenticationHandlerHolder holder( + final String path, final boolean requestResult, final boolean throwOnRequest) { + return new AbstractAuthenticationHandlerHolder(path, null) { + @Override + protected JakartaAuthenticationFeedbackHandler getFeedbackHandler() { + return null; + } + + @Override + protected AuthenticationInfo doExtractCredentials( + HttpServletRequest request, HttpServletResponse response) { + return null; + } + + @Override + protected boolean doRequestCredentials(HttpServletRequest request, HttpServletResponse response) + throws IOException { + if (throwOnRequest) { + throw new IOException("boom"); + } + return requestResult; + } + + @Override + protected void doDropCredentials(HttpServletRequest request, HttpServletResponse response) + throws IOException { + // Intentionally empty: this test stub never needs to drop credentials. + } + }; + } + + private HttpServletRequest requestFor(String path) { + final HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getServletPath()).thenReturn(path); + when(request.getServerName()).thenReturn("localhost"); + when(request.getServerPort()).thenReturn(80); + when(request.getScheme()).thenReturn("http"); + when(request.getContextPath()).thenReturn(""); + when(request.getRequestURI()).thenReturn(path); + return request; + } + + @Test + public void test_login_success() { + final SlingAuthenticator auth = createAuthenticator(holder("/", true, false)); + final HttpServletRequest request = requestFor("/content"); + final HttpServletResponse response = mock(HttpServletResponse.class); + auth.login(request, response); + // login checks the commit state before delegating to a handler + verify(response).isCommitted(); + } + + @Test + public void test_login_handler_throws_is_treated_as_done() { + final SlingAuthenticator auth = createAuthenticator(holder("/", false, true)); + final HttpServletRequest request = requestFor("/content"); + final HttpServletResponse response = mock(HttpServletResponse.class); + auth.login(request, response); + // an IOException from the handler is treated as "done", so no exception escapes + verify(response).isCommitted(); + } + + @Test(expected = NoAuthenticationHandlerException.class) + public void test_login_no_handler() { + final SlingAuthenticator auth = createAuthenticator(holder("/", false, false)); + final HttpServletRequest request = requestFor("/content"); + final HttpServletResponse response = mock(HttpServletResponse.class); + auth.login(request, response); + } + + @Test(expected = IllegalStateException.class) + public void test_login_response_committed() { + final SlingAuthenticator auth = createAuthenticator(holder("/", true, false)); + final HttpServletRequest request = requestFor("/content"); + final HttpServletResponse response = mock(HttpServletResponse.class); + when(response.isCommitted()).thenReturn(true); + auth.login(request, response); + } + + @Test + public void test_logout_success() { + final SlingAuthenticator auth = createAuthenticator(holder("/", true, false)); + final HttpServletRequest request = requestFor("/content"); + final HttpServletResponse response = mock(HttpServletResponse.class); + auth.logout(request, response); + // logout checks the commit state before dropping credentials + verify(response, atLeastOnce()).isCommitted(); + } + + @Test(expected = IllegalStateException.class) + public void test_logout_response_committed() { + final SlingAuthenticator auth = createAuthenticator(holder("/", true, false)); + final HttpServletRequest request = requestFor("/content"); + final HttpServletResponse response = mock(HttpServletResponse.class); + when(response.isCommitted()).thenReturn(true); + auth.logout(request, response); + } + + @Test + public void test_javax_login_wrapper() { + final SlingAuthenticator auth = createAuthenticator(holder("/", true, false)); + final javax.servlet.http.HttpServletRequest request = mock(javax.servlet.http.HttpServletRequest.class); + when(request.getServletPath()).thenReturn("/content"); + when(request.getServerName()).thenReturn("localhost"); + when(request.getServerPort()).thenReturn(80); + when(request.getScheme()).thenReturn("http"); + when(request.getContextPath()).thenReturn(""); + final javax.servlet.http.HttpServletResponse response = mock(javax.servlet.http.HttpServletResponse.class); + auth.login(request, response); + // the javax wrapper delegates to the jakarta login, which checks the commit state + verify(response).isCommitted(); + } + + @Test + public void test_javax_logout_wrapper() { + final SlingAuthenticator auth = createAuthenticator(holder("/", true, false)); + final javax.servlet.http.HttpServletRequest request = mock(javax.servlet.http.HttpServletRequest.class); + when(request.getServletPath()).thenReturn("/content"); + when(request.getServerName()).thenReturn("localhost"); + when(request.getServerPort()).thenReturn(80); + when(request.getScheme()).thenReturn("http"); + when(request.getContextPath()).thenReturn(""); + final javax.servlet.http.HttpServletResponse response = mock(javax.servlet.http.HttpServletResponse.class); + auth.logout(request, response); + // the javax wrapper delegates to the jakarta logout, which checks the commit state + verify(response, atLeastOnce()).isCommitted(); + } + + @Test + public void test_handleSecurity_already_authenticated() { + final SlingAuthenticator auth = createAuthenticator(); + final HttpServletRequest request = requestFor("/content"); + final ResourceResolver resolver = mock(ResourceResolver.class); + when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)) + .thenReturn(resolver); + final HttpServletResponse response = mock(HttpServletResponse.class); + Assert.assertTrue(auth.handleSecurity(request, response)); + } + + @Test + public void test_handleSecurity_valid_credentials() throws Exception { + final ResourceResolverFactory rrf = mock(ResourceResolverFactory.class); + final ResourceResolver resolver = mock(ResourceResolver.class); + when(rrf.getResourceResolver(any(java.util.Map.class))).thenReturn(resolver); + + final AuthenticationInfo info = new AuthenticationInfo("basic", "user", "pwd".toCharArray()); + final SlingAuthenticator auth = createAuthenticator(rrf, infoHolder("/", info)); + + final HttpServletRequest request = requestFor("/content"); + // a non-resolver attribute should be overwritten + when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)) + .thenReturn("not-a-resolver"); + final HttpServletResponse response = mock(HttpServletResponse.class); + + auth.handleSecurity(request, response); + verify(request, atLeastOnce()).setAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER, resolver); + } + + @Test + public void test_handleSecurity_valid_credentials_with_redirect() throws Exception { + final ResourceResolverFactory rrf = mock(ResourceResolverFactory.class); + final ResourceResolver resolver = mock(ResourceResolver.class); + when(rrf.getResourceResolver(any(java.util.Map.class))).thenReturn(resolver); + + final AuthenticationInfo info = new AuthenticationInfo("basic", "user", "pwd".toCharArray()); + final SlingAuthenticator auth = createAuthenticator(rrf, infoHolder("/", info)); + + final HttpServletRequest request = requestFor("/content"); + when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER)).thenReturn("/content/redirect"); + final HttpServletResponse response = mock(HttpServletResponse.class); + + // redirect requested -> request considered done, resolver closed + Assert.assertFalse(auth.handleSecurity(request, response)); + verify(resolver).close(); + } + + @Test + public void test_handleSecurity_doing_auth() { + final SlingAuthenticator auth = createAuthenticator(infoHolder("/", AuthenticationInfo.DOING_AUTH)); + final HttpServletRequest request = requestFor("/content"); + final HttpServletResponse response = mock(HttpServletResponse.class); + Assert.assertFalse(auth.handleSecurity(request, response)); + } + + @Test + public void test_handleSecurity_fail_auth_triggers_login() { + final SlingAuthenticator auth = createAuthenticator(infoHolder("/", AuthenticationInfo.FAIL_AUTH)); + final HttpServletRequest request = requestFor("/content"); + when(request.getRequestURI()).thenReturn("/content"); + final HttpServletResponse response = mock(HttpServletResponse.class); + Assert.assertFalse(auth.handleSecurity(request, response)); + } + + @Test + public void test_finishSecurity_closes_resolver() { + final SlingAuthenticator auth = createAuthenticator(); + final HttpServletRequest request = requestFor("/content"); + final ResourceResolver resolver = mock(ResourceResolver.class); + when(request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER)) + .thenReturn(resolver); + final HttpServletResponse response = mock(HttpServletResponse.class); + auth.finishSecurity(request, response); + verify(resolver).close(); + verify(request).removeAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER); + } + + @Test + public void test_finishSecurity_no_resolver() { + final SlingAuthenticator auth = createAuthenticator(); + final HttpServletRequest request = requestFor("/content"); + final HttpServletResponse response = mock(HttpServletResponse.class); + auth.finishSecurity(request, response); + // with no resolver attribute present, nothing is closed or removed + verify(request).getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER); + verify(request, never()).removeAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER); + } + + @Test + public void test_javax_handleSecurity_wrapper() { + final ResourceResolverFactory rrf = mock(ResourceResolverFactory.class); + final ResourceResolver resolver = mock(ResourceResolver.class); + try { + when(rrf.getResourceResolver(any(java.util.Map.class))).thenReturn(resolver); + } catch (Exception e) { + throw new RuntimeException(e); + } + final AuthenticationInfo info = new AuthenticationInfo("basic", "user", "pwd".toCharArray()); + final SlingAuthenticator auth = createAuthenticator(rrf, infoHolder("/", info)); + + final javax.servlet.http.HttpServletRequest request = mock(javax.servlet.http.HttpServletRequest.class); + when(request.getServletPath()).thenReturn("/content"); + when(request.getServerName()).thenReturn("localhost"); + when(request.getServerPort()).thenReturn(80); + when(request.getScheme()).thenReturn("http"); + when(request.getContextPath()).thenReturn(""); + when(request.getRequestURI()).thenReturn("/content"); + final javax.servlet.http.HttpServletResponse response = mock(javax.servlet.http.HttpServletResponse.class); + // valid credentials are supplied, so the javax wrapper authenticates successfully + Assert.assertTrue(auth.handleSecurity(request, response)); + } + + @Test + public void test_logout_dropCredentials_ioexception() { + final AbstractAuthenticationHandlerHolder dropThrows = new AbstractAuthenticationHandlerHolder("/", null) { + @Override + protected JakartaAuthenticationFeedbackHandler getFeedbackHandler() { + return null; + } + + @Override + protected AuthenticationInfo doExtractCredentials( + HttpServletRequest request, HttpServletResponse response) { + return null; + } + + @Override + protected boolean doRequestCredentials(HttpServletRequest request, HttpServletResponse response) + throws IOException { + return false; + } + + @Override + protected void doDropCredentials(HttpServletRequest request, HttpServletResponse response) + throws IOException { + throw new IOException("boom"); + } + }; + final SlingAuthenticator auth = createAuthenticator(dropThrows); + final HttpServletRequest request = requestFor("/content"); + final HttpServletResponse response = mock(HttpServletResponse.class); + auth.logout(request, response); + // an IOException while dropping credentials is caught, so logout still completes + verify(response, atLeastOnce()).isCommitted(); + } + + private Object invokeHandleLoginFailure( + SlingAuthenticator auth, + HttpServletRequest request, + HttpServletResponse response, + AuthenticationInfo info, + Exception reason) + throws Throwable { + return junitx.util.PrivateAccessor.invoke( + auth, + "handleLoginFailure", + new Class[] { + HttpServletRequest.class, HttpServletResponse.class, AuthenticationInfo.class, Exception.class + }, + new Object[] {request, response, info, reason}); + } + + @Test + public void test_handleLoginFailure_loginException_doLogin() throws Throwable { + final SlingAuthenticator auth = createAuthenticator(holder("/", true, false)); + final HttpServletRequest request = requestFor("/content"); + when(request.getParameter(org.apache.sling.auth.core.AuthConstants.PAR_J_VALIDATE)) + .thenReturn("true"); + final HttpServletResponse response = mock(HttpServletResponse.class); + final AuthenticationInfo info = new AuthenticationInfo("basic", "user"); + final Object result = invokeHandleLoginFailure( + auth, request, response, info, new org.apache.sling.api.resource.LoginException("bad")); + Assert.assertEquals(Boolean.FALSE, result); + } + + @Test + public void test_handleLoginFailure_tooManySessions() throws Throwable { + final SlingAuthenticator auth = createAuthenticator(); + final HttpServletRequest request = requestFor("/content"); + final HttpServletResponse response = mock(HttpServletResponse.class); + final AuthenticationInfo info = new AuthenticationInfo("basic", "user"); + invokeHandleLoginFailure(auth, request, response, info, new TooManySessionsException("too many")); + verify(response).sendError(eq(503), anyString()); + } + + @Test + public void test_handleLoginFailure_genericError() throws Throwable { + final SlingAuthenticator auth = createAuthenticator(); + final HttpServletRequest request = requestFor("/content"); + final HttpServletResponse response = mock(HttpServletResponse.class); + final AuthenticationInfo info = new AuthenticationInfo("basic", "user"); + invokeHandleLoginFailure(auth, request, response, info, new RuntimeException("kaboom")); + verify(response).sendError(eq(500), anyString()); + } + + static class TooManySessionsException extends RuntimeException { + TooManySessionsException(String msg) { + super(msg); + } + } } diff --git a/src/test/java/org/apache/sling/auth/core/impl/engine/EngineAuthenticationHandlerHolderTest.java b/src/test/java/org/apache/sling/auth/core/impl/engine/EngineAuthenticationHandlerHolderTest.java new file mode 100644 index 0000000..71cf6c3 --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/impl/engine/EngineAuthenticationHandlerHolderTest.java @@ -0,0 +1,210 @@ +/* + * 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.sling.auth.core.impl.engine; + +import javax.jcr.Credentials; + +import java.io.IOException; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import junitx.util.PrivateAccessor; +import org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler; +import org.apache.sling.auth.core.spi.AuthenticationInfo; +import org.apache.sling.auth.core.spi.JakartaAuthenticationFeedbackHandler; +import org.apache.sling.engine.auth.AuthenticationHandler; +import org.junit.Test; +import org.osgi.framework.ServiceReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SuppressWarnings("deprecation") +public class EngineAuthenticationHandlerHolderTest { + + private static final String PATH = "/content"; + + private interface FeedbackEngineHandler extends AuthenticationHandler, AuthenticationFeedbackHandler {} + + @Test + public void extractCredentialsReturnsNullWhenEngineHandlerReturnsNull() { + final AuthenticationHandler handler = mock(AuthenticationHandler.class); + final EngineAuthenticationHandlerHolder holder = holder(handler); + + assertNull(holder.doExtractCredentials(mock(HttpServletRequest.class), mock(HttpServletResponse.class))); + } + + @Test + public void extractCredentialsConvertsDoingAuthSingleton() { + final AuthenticationHandler handler = mock(AuthenticationHandler.class); + final EngineAuthenticationHandlerHolder holder = holder(handler); + when(handler.authenticate( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class))) + .thenReturn(org.apache.sling.engine.auth.AuthenticationInfo.DOING_AUTH); + + assertSame( + AuthenticationInfo.DOING_AUTH, + holder.doExtractCredentials(mock(HttpServletRequest.class), mock(HttpServletResponse.class))); + } + + @Test + public void extractCredentialsConvertsLegacyAuthenticationInfo() { + final AuthenticationHandler handler = mock(AuthenticationHandler.class); + final EngineAuthenticationHandlerHolder holder = holder(handler); + final Credentials credentials = mock(Credentials.class); + final org.apache.sling.engine.auth.AuthenticationInfo engineInfo = + new org.apache.sling.engine.auth.AuthenticationInfo("engine", credentials, "workspace"); + when(handler.authenticate( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class))) + .thenReturn(engineInfo); + + final AuthenticationInfo info = + holder.doExtractCredentials(mock(HttpServletRequest.class), mock(HttpServletResponse.class)); + + assertEquals("engine", info.getAuthType()); + assertSame(credentials, info.get("user.jcr.credentials")); + assertEquals("workspace", info.get("user.jcr.workspace")); + } + + @Test + public void requestCredentialsDelegatesToLegacyHandler() throws IOException { + final AuthenticationHandler handler = mock(AuthenticationHandler.class); + final EngineAuthenticationHandlerHolder holder = holder(handler); + when(handler.requestAuthentication( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class))) + .thenReturn(true); + + assertTrue(holder.doRequestCredentials(mock(HttpServletRequest.class), mock(HttpServletResponse.class))); + + verify(handler) + .requestAuthentication( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class)); + } + + @Test + public void dropCredentialsDoesNotCallLegacyHandler() throws Throwable { + final AuthenticationHandler handler = mock(AuthenticationHandler.class); + + holder(handler).doDropCredentials(mock(HttpServletRequest.class), mock(HttpServletResponse.class)); + + verify(handler, never()) + .authenticate( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class)); + verify(handler, never()) + .requestAuthentication( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class)); + } + + @Test + public void getFeedbackHandlerReturnsNullForPlainEngineHandler() throws Throwable { + assertNull(getFeedbackHandler(holder(mock(AuthenticationHandler.class)))); + } + + @Test + public void feedbackHandlerAdaptsJakartaCallsToLegacyFeedbackHandler() throws Throwable { + final FeedbackEngineHandler handler = mock(FeedbackEngineHandler.class); + final JakartaAuthenticationFeedbackHandler feedbackHandler = getFeedbackHandler(holder(handler)); + final HttpServletRequest request = mock(HttpServletRequest.class); + final HttpServletResponse response = mock(HttpServletResponse.class); + final AuthenticationInfo authInfo = new AuthenticationInfo("engine"); + when(handler.authenticationSucceeded( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class), + same(authInfo))) + .thenReturn(true); + + assertNotNull(feedbackHandler); + feedbackHandler.authenticationFailed(request, response, authInfo); + assertTrue(feedbackHandler.authenticationSucceeded(request, response, authInfo)); + + verify(handler) + .authenticationFailed( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class), + same(authInfo)); + verify(handler) + .authenticationSucceeded( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class), + same(authInfo)); + } + + @Test + public void finalMethodsSetAndResetPathAroundEngineDelegation() { + final AuthenticationHandler handler = mock(AuthenticationHandler.class); + final EngineAuthenticationHandlerHolder holder = holder(handler); + final HttpServletRequest request = mock(HttpServletRequest.class); + final HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getAttribute(org.apache.sling.auth.core.spi.JakartaAuthenticationHandler.PATH_PROPERTY)) + .thenReturn("oldPath"); + + holder.extractCredentials(request, response); + + verify(request).setAttribute(org.apache.sling.auth.core.spi.JakartaAuthenticationHandler.PATH_PROPERTY, PATH); + verify(request) + .setAttribute(org.apache.sling.auth.core.spi.JakartaAuthenticationHandler.PATH_PROPERTY, "oldPath"); + } + + @Test + public void equalsHashCodeCompareToAndToStringIncludeEngineHandler() { + final AuthenticationHandler handler = mock(AuthenticationHandler.class); + when(handler.toString()).thenReturn("engineHandler"); + final ServiceReference reference = mock(ServiceReference.class); + final EngineAuthenticationHandlerHolder holder = + new EngineAuthenticationHandlerHolder(PATH, handler, reference); + final EngineAuthenticationHandlerHolder same = new EngineAuthenticationHandlerHolder(PATH, handler, reference); + final EngineAuthenticationHandlerHolder differentHandler = + new EngineAuthenticationHandlerHolder(PATH, mock(AuthenticationHandler.class), reference); + + assertEquals(holder, holder); + assertEquals(holder, same); + assertEquals(holder.hashCode(), same.hashCode()); + assertEquals(0, holder.compareTo(same)); + assertNotEquals(holder, null); + assertNotEquals(holder, differentHandler); + assertNotEquals(holder, new Object()); + assertEquals("engineHandler (Legacy API Handler)", holder.toString()); + } + + private EngineAuthenticationHandlerHolder holder(final AuthenticationHandler handler) { + return new EngineAuthenticationHandlerHolder(PATH, handler, mock(ServiceReference.class)); + } + + private JakartaAuthenticationFeedbackHandler getFeedbackHandler(final EngineAuthenticationHandlerHolder holder) + throws Throwable { + return (JakartaAuthenticationFeedbackHandler) + PrivateAccessor.invoke(holder, "getFeedbackHandler", new Class[0], new Object[0]); + } +} diff --git a/src/test/java/org/apache/sling/auth/core/impl/engine/EngineSlingAuthenticatorTest.java b/src/test/java/org/apache/sling/auth/core/impl/engine/EngineSlingAuthenticatorTest.java new file mode 100644 index 0000000..60355bf --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/impl/engine/EngineSlingAuthenticatorTest.java @@ -0,0 +1,81 @@ +/* + * 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.sling.auth.core.impl.engine; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.sling.auth.core.impl.hc.SetField; +import org.apache.sling.engine.auth.NoAuthenticationHandlerException; +import org.junit.Assert; +import org.junit.Test; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@SuppressWarnings("deprecation") +public class EngineSlingAuthenticatorTest { + + private HttpServletRequest request = mock(HttpServletRequest.class); + private HttpServletResponse response = mock(HttpServletResponse.class); + + @Test + public void test_login_delegates() throws Exception { + EngineSlingAuthenticator bridge = new EngineSlingAuthenticator(); + org.apache.sling.api.auth.Authenticator delegate = mock(org.apache.sling.api.auth.Authenticator.class); + SetField.set(bridge, "slingAuthenticator", delegate); + + bridge.login(request, response); + verify(delegate).login((javax.servlet.http.HttpServletRequest) request, (javax.servlet.http.HttpServletResponse) + response); + } + + @Test + public void test_login_wraps_exception() throws Exception { + EngineSlingAuthenticator bridge = new EngineSlingAuthenticator(); + org.apache.sling.api.auth.Authenticator delegate = mock(org.apache.sling.api.auth.Authenticator.class); + SetField.set(bridge, "slingAuthenticator", delegate); + doThrow(new org.apache.sling.api.auth.NoAuthenticationHandlerException()) + .when(delegate) + .login( + any(javax.servlet.http.HttpServletRequest.class), + any(javax.servlet.http.HttpServletResponse.class)); + + try { + bridge.login(request, response); + Assert.fail("Expected NoAuthenticationHandlerException"); + } catch (NoAuthenticationHandlerException expected) { + Assert.assertNotNull(expected.getCause()); + } + } + + @Test + public void test_logout_delegates() throws Exception { + EngineSlingAuthenticator bridge = new EngineSlingAuthenticator(); + org.apache.sling.api.auth.Authenticator delegate = mock(org.apache.sling.api.auth.Authenticator.class); + SetField.set(bridge, "slingAuthenticator", delegate); + + bridge.logout(request, response); + verify(delegate) + .logout((javax.servlet.http.HttpServletRequest) request, (javax.servlet.http.HttpServletResponse) + response); + } +} diff --git a/src/test/java/org/apache/sling/auth/core/impl/hc/DefaultLoginsHealthCheckTest.java b/src/test/java/org/apache/sling/auth/core/impl/hc/DefaultLoginsHealthCheckTest.java index fce5c29..061a91d 100644 --- a/src/test/java/org/apache/sling/auth/core/impl/hc/DefaultLoginsHealthCheckTest.java +++ b/src/test/java/org/apache/sling/auth/core/impl/hc/DefaultLoginsHealthCheckTest.java @@ -28,13 +28,14 @@ import org.apache.felix.hc.api.Result; import org.apache.sling.jcr.api.SlingRepository; import org.junit.Test; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class DefaultLoginsHealthCheckTest { @@ -46,10 +47,10 @@ private Result getTestResult(String login) throws Exception { SetField.set(c, "logins", Arrays.asList(new String[] {login})); } - final SlingRepository repo = Mockito.mock(SlingRepository.class); + final SlingRepository repo = mock(SlingRepository.class); SetField.set(c, "repository", repo); - final Session s = Mockito.mock(Session.class); - Mockito.when(repo.login(ArgumentMatchers.any(Credentials.class))).thenAnswer(new Answer() { + final Session s = mock(Session.class); + when(repo.login(any(Credentials.class))).thenAnswer(new Answer() { @Override public Session answer(InvocationOnMock invocation) throws LoginException { final SimpleCredentials c = (SimpleCredentials) invocation.getArguments()[0]; diff --git a/src/test/java/org/apache/sling/auth/core/spi/AbstractAuthenticationFormServletTest.java b/src/test/java/org/apache/sling/auth/core/spi/AbstractAuthenticationFormServletTest.java new file mode 100644 index 0000000..bd5eb1d --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/spi/AbstractAuthenticationFormServletTest.java @@ -0,0 +1,150 @@ +/* + * 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.sling.auth.core.spi; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.apache.sling.api.auth.Authenticator; +import org.junit.Assert; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SuppressWarnings("deprecation") +public class AbstractAuthenticationFormServletTest { + + private static class TestFormServlet extends AbstractAuthenticationFormServlet { + private final String reason; + + TestFormServlet(String reason) { + this.reason = reason; + } + + @Override + protected String getReason(HttpServletRequest request) { + return reason; + } + + @Override + protected String getDefaultFormPath() { + return "test-login-form.html"; + } + + @Override + protected String getCustomFormPath() { + return "does-not-exist.html"; + } + } + + private HttpServletRequest request = mock(HttpServletRequest.class); + private HttpServletResponse response = mock(HttpServletResponse.class); + + @Test + public void test_doGet_rendersForm() throws Exception { + TestFormServlet servlet = new TestFormServlet("Reason!"); + when(request.getContextPath()).thenReturn(""); + StringWriter sw = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(sw)); + + servlet.doGet(request, response); + + String out = sw.toString(); + Assert.assertTrue(out.contains("reason=[Reason!]")); + verify(response).setContentType("text/html"); + verify(response).flushBuffer(); + } + + @Test + public void test_doPost_rendersForm() throws Exception { + TestFormServlet servlet = new TestFormServlet(""); + when(request.getContextPath()).thenReturn(""); + StringWriter sw = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(sw)); + + servlet.doPost(request, response); + Assert.assertTrue(sw.toString().contains("")); + } + + @Test + public void test_getForm_substitutes_and_escapes() throws Exception { + TestFormServlet servlet = new TestFormServlet("&\"'"); + when(request.getContextPath()).thenReturn(""); + when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/valid/path"); + + String form = servlet.getForm(request); + Assert.assertTrue(form.contains("resource=[/valid/path]")); + Assert.assertTrue(form.contains("<b>&%22%27")); + } + + @Test + public void test_getForm_invalid_resource_cleansed() throws Exception { + TestFormServlet servlet = new TestFormServlet(""); + when(request.getContextPath()).thenReturn(""); + when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/invalid//path"); + + String form = servlet.getForm(request); + Assert.assertTrue(form.contains("resource=[]")); + } + + @Test + public void test_getContextPath_from_resource() { + TestFormServlet servlet = new TestFormServlet(""); + when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/foo/bar/?x=1"); + Assert.assertEquals("/foo/bar", servlet.getContextPath(request)); + } + + @Test + public void test_getContextPath_fallback_to_servlet_context() { + TestFormServlet servlet = new TestFormServlet(""); + when(request.getContextPath()).thenReturn("/ctx"); + Assert.assertEquals("/ctx", servlet.getContextPath(request)); + } + + @Test + public void test_handle_ioexception_sends_error() throws Exception { + TestFormServlet servlet = new TestFormServlet(""); + javax.servlet.ServletConfig config = mock(javax.servlet.ServletConfig.class); + when(config.getServletContext()).thenReturn(mock(javax.servlet.ServletContext.class)); + servlet.init(config); + when(request.getContextPath()).thenReturn(""); + when(response.getWriter()).thenThrow(new IOException("no writer")); + + servlet.doGet(request, response); + verify(response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + + @Test + public void test_default_paths() { + AbstractAuthenticationFormServlet servlet = new AbstractAuthenticationFormServlet() { + @Override + protected String getReason(HttpServletRequest request) { + return ""; + } + }; + Assert.assertEquals("login.html", servlet.getDefaultFormPath()); + Assert.assertEquals("custom_login.html", servlet.getCustomFormPath()); + } +} diff --git a/src/test/java/org/apache/sling/auth/core/spi/AbstractAuthenticationHandlerTest.java b/src/test/java/org/apache/sling/auth/core/spi/AbstractAuthenticationHandlerTest.java new file mode 100644 index 0000000..a26c044 --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/spi/AbstractAuthenticationHandlerTest.java @@ -0,0 +1,96 @@ +/* + * 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.sling.auth.core.spi; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.api.auth.Authenticator; +import org.apache.sling.auth.core.AuthConstants; +import org.junit.Assert; +import org.junit.Test; + +import static org.mockito.Mockito.contains; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Verifies that the deprecated {@link AbstractAuthenticationHandler} helper + * methods delegate to {@link org.apache.sling.auth.core.AuthUtil}. + */ +@SuppressWarnings("deprecation") +public class AbstractAuthenticationHandlerTest { + + private HttpServletRequest request = mock(HttpServletRequest.class); + private HttpServletResponse response = mock(HttpServletResponse.class); + + @Test + public void test_getAttributeOrParameter() { + when(request.getParameter("p")).thenReturn("v"); + Assert.assertEquals("v", AbstractAuthenticationHandler.getAttributeOrParameter(request, "p", "def")); + } + + @Test + public void test_getLoginResource() { + when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/res"); + Assert.assertEquals("/res", AbstractAuthenticationHandler.getLoginResource(request, "/def")); + } + + @Test + public void test_setLoginResourceAttribute() { + Assert.assertEquals("/def", AbstractAuthenticationHandler.setLoginResourceAttribute(request, "/def")); + } + + @Test + public void test_sendRedirect() throws Exception { + when(request.getContextPath()).thenReturn(""); + when(request.getRequestURI()).thenReturn("/current"); + Map params = new HashMap<>(); + AbstractAuthenticationHandler.sendRedirect(request, response, "/target", params); + verify(response).sendRedirect(contains("/target?")); + } + + @Test + public void test_isRedirectValid() { + Assert.assertTrue(AbstractAuthenticationHandler.isRedirectValid(null, "/absolute/path")); + Assert.assertFalse(AbstractAuthenticationHandler.isRedirectValid(null, "http://host")); + } + + @Test + public void test_isValidateRequest() { + when(request.getParameter(AuthConstants.PAR_J_VALIDATE)).thenReturn("true"); + Assert.assertTrue(AbstractAuthenticationHandler.isValidateRequest(request)); + } + + @Test + public void test_sendValid() { + AbstractAuthenticationHandler.sendValid(response); + verify(response).setStatus(HttpServletResponse.SC_OK); + } + + @Test + public void test_sendInvalid() { + AbstractAuthenticationHandler.sendInvalid(request, response); + verify(response).setStatus(HttpServletResponse.SC_FORBIDDEN); + } +} diff --git a/src/test/java/org/apache/sling/auth/core/spi/AbstractJakartaAuthenticationFormServletTest.java b/src/test/java/org/apache/sling/auth/core/spi/AbstractJakartaAuthenticationFormServletTest.java new file mode 100644 index 0000000..4c41903 --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/spi/AbstractJakartaAuthenticationFormServletTest.java @@ -0,0 +1,156 @@ +/* + * 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.sling.auth.core.spi; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.sling.api.auth.Authenticator; +import org.junit.Assert; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class AbstractJakartaAuthenticationFormServletTest { + + private static class TestFormServlet extends AbstractJakartaAuthenticationFormServlet { + private final String reason; + + TestFormServlet(String reason) { + this.reason = reason; + } + + @Override + protected String getReason(HttpServletRequest request) { + return reason; + } + + @Override + protected String getDefaultFormPath() { + return "test-login-form.html"; + } + + @Override + protected String getCustomFormPath() { + return "does-not-exist.html"; + } + } + + private HttpServletRequest request = mock(HttpServletRequest.class); + private HttpServletResponse response = mock(HttpServletResponse.class); + + @Test + public void test_doGet_rendersForm() throws Exception { + TestFormServlet servlet = new TestFormServlet("Reason!"); + when(request.getContextPath()).thenReturn(""); + StringWriter sw = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(sw)); + + servlet.doGet(request, response); + + String out = sw.toString(); + Assert.assertTrue(out.contains("reason=[Reason!]")); + verify(response).setContentType("text/html"); + verify(response).flushBuffer(); + } + + @Test + public void test_doPost_rendersForm() throws Exception { + TestFormServlet servlet = new TestFormServlet(""); + when(request.getContextPath()).thenReturn(""); + StringWriter sw = new StringWriter(); + when(response.getWriter()).thenReturn(new PrintWriter(sw)); + + servlet.doPost(request, response); + Assert.assertTrue(sw.toString().contains("")); + } + + @Test + public void test_getForm_substitutes_and_escapes() throws Exception { + TestFormServlet servlet = new TestFormServlet("&\"'"); + when(request.getContextPath()).thenReturn(""); + when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/valid/path"); + + String form = servlet.getForm(request); + Assert.assertTrue(form.contains("resource=[/valid/path]")); + // reason escaped + Assert.assertTrue(form.contains("<b>&%22%27")); + } + + @Test + public void test_getForm_invalid_resource_cleansed() throws Exception { + TestFormServlet servlet = new TestFormServlet(""); + when(request.getContextPath()).thenReturn(""); + // an invalid (non-normalized) redirect target must be cleansed to empty + when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/invalid//path"); + + String form = servlet.getForm(request); + Assert.assertTrue(form.contains("resource=[]")); + } + + @Test + public void test_getResource_default_empty() { + TestFormServlet servlet = new TestFormServlet(""); + Assert.assertEquals("", servlet.getResource(request)); + } + + @Test + public void test_getContextPath_from_resource() { + TestFormServlet servlet = new TestFormServlet(""); + when(request.getParameter(Authenticator.LOGIN_RESOURCE)).thenReturn("/foo/bar/?x=1"); + Assert.assertEquals("/foo/bar", servlet.getContextPath(request)); + } + + @Test + public void test_getContextPath_fallback_to_servlet_context() { + TestFormServlet servlet = new TestFormServlet(""); + when(request.getContextPath()).thenReturn("/ctx"); + Assert.assertEquals("/ctx", servlet.getContextPath(request)); + } + + @Test + public void test_handle_ioexception_sends_error() throws Exception { + TestFormServlet servlet = new TestFormServlet(""); + jakarta.servlet.ServletConfig config = mock(jakarta.servlet.ServletConfig.class); + when(config.getServletContext()).thenReturn(mock(jakarta.servlet.ServletContext.class)); + servlet.init(config); + when(request.getContextPath()).thenReturn(""); + when(response.getWriter()).thenThrow(new IOException("no writer")); + + servlet.doGet(request, response); + verify(response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + + @Test + public void test_default_paths() { + AbstractJakartaAuthenticationFormServlet servlet = new AbstractJakartaAuthenticationFormServlet() { + @Override + protected String getReason(HttpServletRequest request) { + return ""; + } + }; + Assert.assertEquals("login.html", servlet.getDefaultFormPath()); + Assert.assertEquals("custom_login.html", servlet.getCustomFormPath()); + } +} diff --git a/src/test/java/org/apache/sling/auth/core/spi/DefaultAuthenticationFeedbackHandlerTest.java b/src/test/java/org/apache/sling/auth/core/spi/DefaultAuthenticationFeedbackHandlerTest.java new file mode 100644 index 0000000..0ae6527 --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/spi/DefaultAuthenticationFeedbackHandlerTest.java @@ -0,0 +1,100 @@ +/* + * 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.sling.auth.core.spi; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.sling.auth.core.AuthenticationSupport; +import org.junit.Assert; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@SuppressWarnings("deprecation") +public class DefaultAuthenticationFeedbackHandlerTest { + + @Test + public void test_no_redirect() { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + Assert.assertFalse(DefaultAuthenticationFeedbackHandler.handleRedirect(request, response)); + } + + @Test + public void test_redirect_true_uses_requestUri() throws Exception { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER)).thenReturn("true"); + when(request.getRequestURI()).thenReturn("/same/uri"); + Assert.assertTrue(DefaultAuthenticationFeedbackHandler.handleRedirect(request, response)); + verify(response).sendRedirect("/same/uri"); + } + + @Test + public void test_redirect_absolute_valid() throws Exception { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER)).thenReturn("/valid/path"); + when(request.getContextPath()).thenReturn(""); + Assert.assertTrue(DefaultAuthenticationFeedbackHandler.handleRedirect(request, response)); + verify(response).sendRedirect("/valid/path"); + } + + @Test + public void test_redirect_relative_made_absolute() throws Exception { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER)).thenReturn("rel"); + when(request.getRequestURI()).thenReturn("/base/page"); + when(request.getContextPath()).thenReturn(""); + Assert.assertTrue(DefaultAuthenticationFeedbackHandler.handleRedirect(request, response)); + verify(response).sendRedirect("/base/rel"); + } + + @Test + public void test_redirect_invalid_falls_back_to_root() throws Exception { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER)).thenReturn("/invalid//path"); + when(request.getContextPath()).thenReturn(""); + Assert.assertTrue(DefaultAuthenticationFeedbackHandler.handleRedirect(request, response)); + verify(response).sendRedirect("/"); + } + + @Test + public void test_authenticationFailed_noop() { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + DefaultAuthenticationFeedbackHandler handler = new DefaultAuthenticationFeedbackHandler(); + handler.authenticationFailed(request, response, new AuthenticationInfo("test")); + verifyNoInteractions(response); + } + + @Test + public void test_authenticationSucceeded_delegates() { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + DefaultAuthenticationFeedbackHandler handler = new DefaultAuthenticationFeedbackHandler(); + Assert.assertFalse(handler.authenticationSucceeded(request, response, new AuthenticationInfo("test"))); + } +} diff --git a/src/test/java/org/apache/sling/auth/core/spi/DefaultJakartaAuthenticationFeedbackHandlerTest.java b/src/test/java/org/apache/sling/auth/core/spi/DefaultJakartaAuthenticationFeedbackHandlerTest.java new file mode 100644 index 0000000..be79b7a --- /dev/null +++ b/src/test/java/org/apache/sling/auth/core/spi/DefaultJakartaAuthenticationFeedbackHandlerTest.java @@ -0,0 +1,121 @@ +/* + * 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.sling.auth.core.spi; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.sling.auth.core.AuthenticationSupport; +import org.junit.Assert; +import org.junit.Test; + +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +public class DefaultJakartaAuthenticationFeedbackHandlerTest { + + @Test + public void test_no_redirect() { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + // no redirect parameter and no login resource -> false + Assert.assertFalse(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request, response)); + } + + @Test + public void test_redirect_true_uses_requestUri() throws Exception { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER)).thenReturn("true"); + when(request.getRequestURI()).thenReturn("/same/uri"); + Assert.assertTrue(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request, response)); + verify(response).sendRedirect("/same/uri"); + } + + @Test + public void test_redirect_empty_uses_requestUri() throws Exception { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER)).thenReturn(""); + when(request.getRequestURI()).thenReturn("/same/uri"); + Assert.assertTrue(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request, response)); + verify(response).sendRedirect("/same/uri"); + } + + @Test + public void test_redirect_absolute_valid() throws Exception { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER)).thenReturn("/valid/path"); + when(request.getContextPath()).thenReturn(""); + Assert.assertTrue(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request, response)); + verify(response).sendRedirect("/valid/path"); + } + + @Test + public void test_redirect_relative_made_absolute() throws Exception { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER)).thenReturn("rel"); + when(request.getRequestURI()).thenReturn("/base/page"); + when(request.getContextPath()).thenReturn(""); + Assert.assertTrue(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request, response)); + verify(response).sendRedirect("/base/rel"); + } + + @Test + public void test_redirect_invalid_falls_back_to_root() throws Exception { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER)).thenReturn("/invalid//path"); + when(request.getContextPath()).thenReturn(""); + Assert.assertTrue(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request, response)); + verify(response).sendRedirect("/"); + } + + @Test + public void test_redirect_send_failure_is_swallowed() throws Exception { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getParameter(AuthenticationSupport.REDIRECT_PARAMETER)).thenReturn("/valid"); + when(request.getContextPath()).thenReturn(""); + doThrow(new java.io.IOException("boom")).when(response).sendRedirect(anyString()); + Assert.assertTrue(DefaultJakartaAuthenticationFeedbackHandler.handleRedirect(request, response)); + } + + @Test + public void test_authenticationFailed_noop() { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + DefaultJakartaAuthenticationFeedbackHandler handler = new DefaultJakartaAuthenticationFeedbackHandler(); + handler.authenticationFailed(request, response, new AuthenticationInfo("test")); + verifyNoInteractions(response); + } + + @Test + public void test_authenticationSucceeded_delegates() { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + DefaultJakartaAuthenticationFeedbackHandler handler = new DefaultJakartaAuthenticationFeedbackHandler(); + Assert.assertFalse(handler.authenticationSucceeded(request, response, new AuthenticationInfo("test"))); + } +} diff --git a/src/test/resources/org/apache/sling/auth/core/spi/test-login-form.html b/src/test/resources/org/apache/sling/auth/core/spi/test-login-form.html new file mode 100644 index 0000000..697fee2 --- /dev/null +++ b/src/test/resources/org/apache/sling/auth/core/spi/test-login-form.html @@ -0,0 +1,19 @@ + +resource=[${resource}] reason=[${j_reason}] rcp=[${requestContextPath}] cp=[${contextPath}] \ No newline at end of file