+ * This tests asserts the ability to define multiple beans using
+ * a {@code ...Definition} annotation. It specifically asserts that
+ * those can come from a repeatable annotation ("realm2" and "realm3")
+ * and that those can be combined with one defined elsewhere ("realm1").
+ *
+ */
+@BasicAuthenticationMechanismDefinition(
+ realmName="realm2",
+ qualifiers = { BasicAuthenticationMechanism2.class}
+)
+
+@BasicAuthenticationMechanismDefinition(
+ realmName="realm3",
+ qualifiers = { BasicAuthenticationMechanism3.class}
+)
+
+@Alternative
+@Priority(APPLICATION)
+@ApplicationScoped
+public class CustomAuthenticationMechanismHandler implements HttpAuthenticationMechanismHandler {
+
+ @Inject
+ @BasicAuthenticationMechanism
+ HttpAuthenticationMechanism authenticationMechanism1;
+
+ @Inject
+ @BasicAuthenticationMechanism2
+ HttpAuthenticationMechanism authenticationMechanism2;
+
+ @Inject
+ @BasicAuthenticationMechanism3
+ HttpAuthenticationMechanism authenticationMechanism3;
+
+ @Override
+ public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response,
+ HttpMessageContext httpMessageContext) throws AuthenticationException {
+
+ if (getRequestRelativeURI(request).startsWith("/protectedServlet1")) {
+ return authenticationMechanism1.validateRequest(request, response, httpMessageContext);
+ }
+
+ if (getRequestRelativeURI(request).startsWith("/protectedServlet2")) {
+ return authenticationMechanism2.validateRequest(request, response, httpMessageContext);
+ }
+
+ return authenticationMechanism3.validateRequest(request, response, httpMessageContext);
+ }
+
+ public static String getRequestRelativeURI(HttpServletRequest request) {
+ return request.getRequestURI().substring(request.getContextPath().length());
+ }
+
+}
diff --git a/tck/app-custom-authentication-mechanism-handler2/src/main/java/ee/jakarta/tck/security/test/ProtectedServlet1.java b/tck/app-custom-authentication-mechanism-handler2/src/main/java/ee/jakarta/tck/security/test/ProtectedServlet1.java
new file mode 100644
index 0000000..bbabbb9
--- /dev/null
+++ b/tck/app-custom-authentication-mechanism-handler2/src/main/java/ee/jakarta/tck/security/test/ProtectedServlet1.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation.
+ * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package ee.jakarta.tck.security.test;
+
+import jakarta.annotation.security.DeclareRoles;
+import jakarta.inject.Inject;
+import jakarta.security.enterprise.SecurityContext;
+import jakarta.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.HttpConstraint;
+import jakarta.servlet.annotation.ServletSecurity;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Test Servlet that prints out the name of the authenticated caller and whether
+ * this caller is in any of the roles {foo, bar, kaz}
+ *
+ */
+@BasicAuthenticationMechanismDefinition(
+ realmName="realm1"
+)
+@WebServlet("/protectedServlet1")
+@ServletSecurity(@HttpConstraint(rolesAllowed = "foo"))
+@DeclareRoles({"bar", "kaz"})
+public class ProtectedServlet1 extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Inject
+ private SecurityContext securityContext;
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+
+ response.getWriter().write("This is a servlet \n");
+
+ String webName = null;
+ if (request.getUserPrincipal() != null) {
+ webName = request.getUserPrincipal().getName();
+ }
+
+ response.getWriter().write("web username: " + webName + "\n");
+
+ response.getWriter().write("web user has role \"foo\": " + request.isUserInRole("foo") + "\n");
+ response.getWriter().write("web user has role \"bar\": " + request.isUserInRole("bar") + "\n");
+ response.getWriter().write("web user has role \"kaz\": " + request.isUserInRole("kaz") + "\n");
+
+ String contextName = null;
+ if (securityContext.getCallerPrincipal() != null) {
+ contextName = securityContext.getCallerPrincipal().getName();
+ }
+
+ response.getWriter().write("context username: " + contextName + "\n");
+
+ response.getWriter().write("context user has role \"foo\": " + securityContext.isCallerInRole("foo") + "\n");
+ response.getWriter().write("context user has role \"bar\": " + securityContext.isCallerInRole("bar") + "\n");
+ response.getWriter().write("context user has role \"kaz\": " + securityContext.isCallerInRole("kaz") + "\n");
+
+ response.getWriter().write("has access " + securityContext.hasAccessToWebResource("/servlets"));
+
+ }
+
+}
diff --git a/tck/app-custom-authentication-mechanism-handler2/src/main/java/ee/jakarta/tck/security/test/ProtectedServlet2.java b/tck/app-custom-authentication-mechanism-handler2/src/main/java/ee/jakarta/tck/security/test/ProtectedServlet2.java
new file mode 100644
index 0000000..c6734b3
--- /dev/null
+++ b/tck/app-custom-authentication-mechanism-handler2/src/main/java/ee/jakarta/tck/security/test/ProtectedServlet2.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation.
+ * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package ee.jakarta.tck.security.test;
+
+import jakarta.inject.Inject;
+import jakarta.security.enterprise.SecurityContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.HttpConstraint;
+import jakarta.servlet.annotation.ServletSecurity;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Test Servlet that prints out the name of the authenticated caller and whether
+ * this caller is in any of the roles {foo, bar, kaz}
+ *
+ */
+@WebServlet("/protectedServlet2")
+@ServletSecurity(@HttpConstraint(rolesAllowed = "foo"))
+public class ProtectedServlet2 extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Inject
+ private SecurityContext securityContext;
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+
+ response.getWriter().write("This is a servlet \n");
+
+ String webName = null;
+ if (request.getUserPrincipal() != null) {
+ webName = request.getUserPrincipal().getName();
+ }
+
+ response.getWriter().write("web username: " + webName + "\n");
+
+ response.getWriter().write("web user has role \"foo\": " + request.isUserInRole("foo") + "\n");
+ response.getWriter().write("web user has role \"bar\": " + request.isUserInRole("bar") + "\n");
+ response.getWriter().write("web user has role \"kaz\": " + request.isUserInRole("kaz") + "\n");
+
+ String contextName = null;
+ if (securityContext.getCallerPrincipal() != null) {
+ contextName = securityContext.getCallerPrincipal().getName();
+ }
+
+ response.getWriter().write("context username: " + contextName + "\n");
+
+ response.getWriter().write("context user has role \"foo\": " + securityContext.isCallerInRole("foo") + "\n");
+ response.getWriter().write("context user has role \"bar\": " + securityContext.isCallerInRole("bar") + "\n");
+ response.getWriter().write("context user has role \"kaz\": " + securityContext.isCallerInRole("kaz") + "\n");
+
+ response.getWriter().write("has access " + securityContext.hasAccessToWebResource("/servlets"));
+
+ }
+
+}
diff --git a/tck/app-custom-authentication-mechanism-handler2/src/main/java/ee/jakarta/tck/security/test/ProtectedServlet3.java b/tck/app-custom-authentication-mechanism-handler2/src/main/java/ee/jakarta/tck/security/test/ProtectedServlet3.java
new file mode 100644
index 0000000..509e1b6
--- /dev/null
+++ b/tck/app-custom-authentication-mechanism-handler2/src/main/java/ee/jakarta/tck/security/test/ProtectedServlet3.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation.
+ * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package ee.jakarta.tck.security.test;
+
+import jakarta.inject.Inject;
+import jakarta.security.enterprise.SecurityContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.HttpConstraint;
+import jakarta.servlet.annotation.ServletSecurity;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Test Servlet that prints out the name of the authenticated caller and whether
+ * this caller is in any of the roles {foo, bar, kaz}
+ *
+ */
+@WebServlet("/protectedServlet3")
+@ServletSecurity(@HttpConstraint(rolesAllowed = "foo"))
+public class ProtectedServlet3 extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Inject
+ private SecurityContext securityContext;
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+
+ response.getWriter().write("This is a servlet \n");
+
+ String webName = null;
+ if (request.getUserPrincipal() != null) {
+ webName = request.getUserPrincipal().getName();
+ }
+
+ response.getWriter().write("web username: " + webName + "\n");
+
+ response.getWriter().write("web user has role \"foo\": " + request.isUserInRole("foo") + "\n");
+ response.getWriter().write("web user has role \"bar\": " + request.isUserInRole("bar") + "\n");
+ response.getWriter().write("web user has role \"kaz\": " + request.isUserInRole("kaz") + "\n");
+
+ String contextName = null;
+ if (securityContext.getCallerPrincipal() != null) {
+ contextName = securityContext.getCallerPrincipal().getName();
+ }
+
+ response.getWriter().write("context username: " + contextName + "\n");
+
+ response.getWriter().write("context user has role \"foo\": " + securityContext.isCallerInRole("foo") + "\n");
+ response.getWriter().write("context user has role \"bar\": " + securityContext.isCallerInRole("bar") + "\n");
+ response.getWriter().write("context user has role \"kaz\": " + securityContext.isCallerInRole("kaz") + "\n");
+
+ response.getWriter().write("has access " + securityContext.hasAccessToWebResource("/servlets"));
+
+ }
+
+}
diff --git a/tck/app-custom-authentication-mechanism-handler2/src/main/java/ee/jakarta/tck/security/test/TestIdentityStore.java b/tck/app-custom-authentication-mechanism-handler2/src/main/java/ee/jakarta/tck/security/test/TestIdentityStore.java
new file mode 100644
index 0000000..ca319fc
--- /dev/null
+++ b/tck/app-custom-authentication-mechanism-handler2/src/main/java/ee/jakarta/tck/security/test/TestIdentityStore.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package ee.jakarta.tck.security.test;
+
+import static java.util.Arrays.asList;
+import static jakarta.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
+
+import java.util.HashSet;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.security.enterprise.credential.UsernamePasswordCredential;
+import jakarta.security.enterprise.identitystore.CredentialValidationResult;
+import jakarta.security.enterprise.identitystore.IdentityStore;
+
+@ApplicationScoped
+public class TestIdentityStore implements IdentityStore {
+
+ public CredentialValidationResult validate(UsernamePasswordCredential usernamePasswordCredential) {
+
+ if (usernamePasswordCredential.compareTo("reza", "secret1")) {
+ return new CredentialValidationResult("reza", new HashSet<>(asList("foo", "bar")));
+ }
+
+ return INVALID_RESULT;
+ }
+
+}
diff --git a/tck/app-custom-authentication-mechanism-handler2/src/main/webapp/WEB-INF/beans.xml b/tck/app-custom-authentication-mechanism-handler2/src/main/webapp/WEB-INF/beans.xml
new file mode 100644
index 0000000..0711884
--- /dev/null
+++ b/tck/app-custom-authentication-mechanism-handler2/src/main/webapp/WEB-INF/beans.xml
@@ -0,0 +1,6 @@
+
+
+ * In this test, three instances of the build-in Basic HTTP authentication mechanism are used. The custom + * handler invokes each one depending on the request URI that was used. + */ +@RunWith(Arquillian.class) +public class AppCustomAuthenticationMechanismHandler2IT extends ArquillianBase { + + @Deployment(testable = false) + public static Archive> createDeployment() { + return mavenWar(); + } + + /** + * Test that for path {@code /protectedServlet1} in effect {@link TestAuthenticationMechanism1} is used. + */ + @Test + public void testAuthenticated1() { + WebResponse response = responseFromServer("/protectedServlet1"); + + assertEquals(401, response.getStatusCode()); + + assertTrue( + "Response did not contain the \"WWW-Authenticate\" header, but should have", + response.getResponseHeaderValue("WWW-Authenticate") != null); + + + // Most important part of the test: check that we have the correct authentication mechanism instance used + + assertTrue( + "Response did not contain \"realm1\" in the \"WWW-Authenticate\" header value, but should have", + response.getResponseHeaderValue("WWW-Authenticate").contains("realm1")); + + + // For completion, check that authentication mechanism also actually authenticates + + DefaultCredentialsProvider credentialsProvider = new DefaultCredentialsProvider(); + credentialsProvider.addCredentials("reza", "secret1"); + + getWebClient().setCredentialsProvider(credentialsProvider); + + assertDefaultAuthenticated( + readFromServer("/protectedServlet1")); + } + + /** + * Test that for path {@code /protectedServlet2} in effect {@link TestAuthenticationMechanism2} is used. + */ + @Test + public void testAuthenticated2() { + WebResponse response = responseFromServer("/protectedServlet2"); + + assertEquals(401, response.getStatusCode()); + + assertTrue( + "Response did not contain the \"WWW-Authenticate\" header, but should have", + response.getResponseHeaderValue("WWW-Authenticate") != null); + + + // Most important part of the test: check that we have the correct authentication mechanism instance used + + assertTrue( + "Response did not contain \"realm1\" in the \"WWW-Authenticate\" header value, but should have", + response.getResponseHeaderValue("WWW-Authenticate").contains("realm2")); + + + // For completion, check that authentication mechanism also actually authenticates + + DefaultCredentialsProvider credentialsProvider = new DefaultCredentialsProvider(); + credentialsProvider.addCredentials("reza", "secret1"); + + getWebClient().setCredentialsProvider(credentialsProvider); + + assertDefaultAuthenticated( + readFromServer("/protectedServlet2")); + } + + /** + * Test that for path {@code /protectedServlet3} in effect {@link TestAuthenticationMechanism3} is used. + */ + @Test + public void testAuthenticated3() { + WebResponse response = responseFromServer("/protectedServlet3"); + + assertEquals(401, response.getStatusCode()); + + assertTrue( + "Response did not contain the \"WWW-Authenticate\" header, but should have", + response.getResponseHeaderValue("WWW-Authenticate") != null); + + + // Most important part of the test: check that we have the correct authentication mechanism instance used + + assertTrue( + "Response did not contain \"realm3\" in the \"WWW-Authenticate\" header value, but should have", + response.getResponseHeaderValue("WWW-Authenticate").contains("realm3")); + + + // For completion, check that authentication mechanism also actually authenticates + + DefaultCredentialsProvider credentialsProvider = new DefaultCredentialsProvider(); + credentialsProvider.addCredentials("reza", "secret1"); + + getWebClient().setCredentialsProvider(credentialsProvider); + + assertDefaultAuthenticated( + readFromServer("/protectedServlet3")); + } + +}