diff --git a/api/src/main/java/jakarta/security/jacc/Policy.java b/api/src/main/java/jakarta/security/jacc/Policy.java index 05ebed2..cb27f33 100644 --- a/api/src/main/java/jakarta/security/jacc/Policy.java +++ b/api/src/main/java/jakarta/security/jacc/Policy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Contributors to Eclipse Foundation. All rights reserved. + * Copyright (c) 2023, 2024 Contributors to Eclipse Foundation. 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 @@ -37,9 +37,6 @@ * a given policy context. In a Jakarta EE Servlet environment these contain the transformed security constraints as expressed by XML * in web.xml, via annotations, or which are programmatically set using the Jakarta Servlet APIs. * - *

- * NOTE: DRAFT API. SUBJECT TO CHANGE - * * @author Arjan Tijms */ public interface Policy { diff --git a/api/src/main/java/jakarta/security/jacc/PolicyFactory.java b/api/src/main/java/jakarta/security/jacc/PolicyFactory.java index 9183a93..f4ee5eb 100644 --- a/api/src/main/java/jakarta/security/jacc/PolicyFactory.java +++ b/api/src/main/java/jakarta/security/jacc/PolicyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Contributors to Eclipse Foundation. All rights reserved. + * Copyright (c) 2023, 2024 Contributors to Eclipse Foundation. 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 @@ -19,9 +19,6 @@ * Abstract factory and finder class for obtaining the instance of the class that implements the * PolicyFactory of a provider. The factory will be used to instantiate Policy objects. * - *

- * NOTE: DRAFT API. SUBJECT TO CHANGE - * * @see Policy * * @author Arjan Tijms diff --git a/api/src/main/java/jakarta/security/jacc/PrincipalMapper.java b/api/src/main/java/jakarta/security/jacc/PrincipalMapper.java index 0f84798..f0d60d4 100644 --- a/api/src/main/java/jakarta/security/jacc/PrincipalMapper.java +++ b/api/src/main/java/jakarta/security/jacc/PrincipalMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Contributors to Eclipse Foundation. All rights reserved. + * Copyright (c) 2023, 2024 Contributors to Eclipse Foundation. 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 @@ -37,9 +37,6 @@ * A PrincipalMapper is intended to be used by a {@link Policy}, but should work * outside a {@link Policy} (for instance, during request processing in a Servlet container). * - *

- * NOTE: DRAFT API. SUBJECT TO CHANGE - * * @author Arjan Tijms */ public interface PrincipalMapper { diff --git a/spec/src/main/asciidoc/chapters/2_provider-configuration.adoc b/spec/src/main/asciidoc/chapters/2_provider-configuration.adoc index 8873a96..9e67049 100644 --- a/spec/src/main/asciidoc/chapters/2_provider-configuration.adoc +++ b/spec/src/main/asciidoc/chapters/2_provider-configuration.adoc @@ -14,6 +14,26 @@ An application server that supports this contract must allow replacement of the `jakarta.security.jacc.Policy` object used by the application server for all applications running on it, in addition to supporting a `jakarta.security.jacc.Policy` object for individual applications. +Replacement is done via the `jakarta.security.jacc.PolicyFactory` abstract factory class. A +default static method, `getPolicyFactory` is provided that uses the system property +`jakarta.security.jacc.PolicyFactory.provider` to locate a concrete implementation. The +container or an application can alternatively set a custom `PolicyFactory` using the +`setPolicyFactory` method. In that case the `PolicyFactory` implementation +can come from a container specific configuration, or in case of the Servlet Container +from the web application's servlet context initialization parameter (context-param in web.xml) `jakarta.security.jacc.PolicyFactory.provider`. + +If the `PolicyFactory` has a public constructor with one argument of type `PolicyFactory`, +then the container should call this constructor with as argument the + `PolicyFactory` instance that is being replaced. This allows a replacement `PolicyFactory` to wrap + the existing one and selectively provide extra functionality. + +From this factory class a concrete implementation of the Policy of type `jakarta.security.jacc.Policy` can be +obtained using the method `getPolicy`. + +In addition to replacing the `PolicyFactory`, the default `PolicyFactory` must also allow replacing just the +`Policy` instance. This is detailed below in <>. + + === Permission Implementation Classes This contract defines the package, `jakarta.security.jacc`, that contains (among other things) `Permission` @@ -31,8 +51,9 @@ default static method, `getPolicyConfigurationFactory` is provided that uses the container can alternatively set a custom `PolicyConfigurationFactory` using the `setPolicyConfigurationFactory` method. In that case the `PolicyConfigurationFactory` implementation can come from a container specific configuration, or in case of the Servlet Container -from the context property `jakarta.security.jacc.PolicyConfigurationFactory.provider`. If the -`PolicyConfigurationFactory` has a public constructor with one argument of type `PolicyConfigurationFactory`, +from the web application's servlet context initialization parameter (context-param in web.xml) `jakarta.security.jacc.PolicyConfigurationFactory.provider`. + +If the `PolicyConfigurationFactory` has a public constructor with one argument of type `PolicyConfigurationFactory`, then the container should call this constructor with as argument the `PolicyConfigurationFactory` instance that is being replaced. This allows a replacement `PolicyConfigurationFactory` to wrap the existing one and selectively provide extra functionality. diff --git a/tck/app-custom-policy/pom.xml b/tck/app-custom-policy/pom.xml index 5323a04..87ee263 100644 --- a/tck/app-custom-policy/pom.xml +++ b/tck/app-custom-policy/pom.xml @@ -47,6 +47,6 @@ - app-mem-policy + app-custom-policy diff --git a/tck/app-custom-policyfactory/pom.xml b/tck/app-custom-policyfactory/pom.xml new file mode 100644 index 0000000..bb914f9 --- /dev/null +++ b/tck/app-custom-policyfactory/pom.xml @@ -0,0 +1,55 @@ + + + + + 4.0.0 + + + org.eclipse.ee4j.authorization.tck + jakarta-authorization-tck + 4.0.0-SNAPSHOT + + + app-custom-policyfactory + war + + + Like app-custom-policy, but uses a custom PolicyFactory defined in web.xml to supply a custom Policy. + Note that this only tests for the PolicyFactory being replaceable and wrappable, and + is not an example of how to easily supply a custom Policy or how to write a realistic + PolicyFactory. + + + + false + + + + + org.eclipse.ee4j.authorization.tck + common + ${project.version} + + + + + app-custom-policyfactory + + diff --git a/tck/app-custom-policyfactory/src/main/java/ee/jakarta/tck/authorization/test/ProtectedServlet.java b/tck/app-custom-policyfactory/src/main/java/ee/jakarta/tck/authorization/test/ProtectedServlet.java new file mode 100644 index 0000000..683c3b6 --- /dev/null +++ b/tck/app-custom-policyfactory/src/main/java/ee/jakarta/tck/authorization/test/ProtectedServlet.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 Contributors to 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.authorization.test; + +import jakarta.annotation.security.DeclareRoles; +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; + +/** + * Protected Servlet that prints out the name of the authenticated caller and whether + * this caller is in any of the roles {foo, bar, kaz} + * + *

+ * The role "foo" is required to access this Servlet. "bar" is a role assigned by the + * native identity store, "kaz" doesn't exist (but we should still be able to test for it). + * + */ +@WebServlet("/protectedServlet/*") +@DeclareRoles("bar") +@ServletSecurity(@HttpConstraint(rolesAllowed = "foo")) +public class ProtectedServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @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"); + } + +} diff --git a/tck/app-custom-policyfactory/src/main/java/ee/jakarta/tck/authorization/test/TestPolicy.java b/tck/app-custom-policyfactory/src/main/java/ee/jakarta/tck/authorization/test/TestPolicy.java new file mode 100644 index 0000000..23739d4 --- /dev/null +++ b/tck/app-custom-policyfactory/src/main/java/ee/jakarta/tck/authorization/test/TestPolicy.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * 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.authorization.test; + +import jakarta.security.jacc.Policy; +import jakarta.security.jacc.WebResourcePermission; +import java.security.Permission; +import java.security.PermissionCollection; +import java.util.logging.Logger; +import javax.security.auth.Subject; + +/** + * Policy implementation that uses a custom permission check + * to grant access to {@code /protectedServlet/[*]/test} to + * the unauthenticated caller. + */ +public class TestPolicy implements Policy { + + private static final Logger LOGGER = Logger.getLogger(TestPolicy.class.getName()); + + private final Policy originalPolicy; + + public TestPolicy(Policy policy) { + this.originalPolicy = policy; + } + + public boolean implies(Permission permissionToBeChecked, Subject subject) { + LOGGER.info(permissionToBeChecked.toString()); + LOGGER.info(subject.toString()); + + // First try our custom permission checking + if (impliesCustom(permissionToBeChecked)) { + return true; + } + + // If custom doesn't grant access, try the original policy so we + // keep all normal checks in place. + return originalPolicy.implies(permissionToBeChecked, subject); + } + + public PermissionCollection getPermissionCollection(Subject subject) { + return originalPolicy.getPermissionCollection(subject); + } + + private boolean impliesCustom(Permission permissionToBeChecked) { + return + permissionToBeChecked instanceof WebResourcePermission && + permissionToBeChecked.getName().startsWith("/protectedServlet/") && + permissionToBeChecked.getName().endsWith("/test"); + } + +} diff --git a/tck/app-custom-policyfactory/src/main/java/ee/jakarta/tck/authorization/test/TestPolicyFactory.java b/tck/app-custom-policyfactory/src/main/java/ee/jakarta/tck/authorization/test/TestPolicyFactory.java new file mode 100644 index 0000000..e664102 --- /dev/null +++ b/tck/app-custom-policyfactory/src/main/java/ee/jakarta/tck/authorization/test/TestPolicyFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Contributors to Eclipse Foundation. + * + * 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.authorization.test; + +import jakarta.security.jacc.Policy; +import jakarta.security.jacc.PolicyFactory; + +/** + * Test policy configuration factory. + * + *

+ * This factort is solely used to test for replacement and wrapping of the PolicyFactory. + * It ignores the contextId which is not something real factories should + * do in most cases, and therefor should not be used as an example of how to create + * a custom PolicyFactory. + */ +public class TestPolicyFactory extends PolicyFactory { + + private Policy policy; + + public TestPolicyFactory(PolicyFactory policyFactory) { + super(policyFactory); + policy = new TestPolicy(policyFactory.getPolicy()); + } + + public Policy getPolicy(String contextId) { + return policy; + } + + @Override + public void setPolicy(String contextId, Policy policy) { + this.policy = new TestPolicy(policy); + } +} diff --git a/tck/app-custom-policyfactory/src/main/webapp/WEB-INF/beans.xml b/tck/app-custom-policyfactory/src/main/webapp/WEB-INF/beans.xml new file mode 100644 index 0000000..7b7b7ad --- /dev/null +++ b/tck/app-custom-policyfactory/src/main/webapp/WEB-INF/beans.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/tck/app-custom-policyfactory/src/main/webapp/WEB-INF/web.xml b/tck/app-custom-policyfactory/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..bbd18cc --- /dev/null +++ b/tck/app-custom-policyfactory/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,33 @@ + + + + + + jakarta.security.jacc.PolicyFactory.provider + ee.jakarta.tck.authorization.test.TestPolicyFactory + + + + BASIC + file + + diff --git a/tck/app-custom-policyfactory/src/test/java/ee/jakarta/tck/authorization/test/AppCustomPolicyFactoryIT.java b/tck/app-custom-policyfactory/src/test/java/ee/jakarta/tck/authorization/test/AppCustomPolicyFactoryIT.java new file mode 100644 index 0000000..4c932ce --- /dev/null +++ b/tck/app-custom-policyfactory/src/test/java/ee/jakarta/tck/authorization/test/AppCustomPolicyFactoryIT.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024 Contributors to 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.authorization.test; + +import static ee.jakarta.tck.authorization.util.Assert.assertDefaultAccess; +import static ee.jakarta.tck.authorization.util.Assert.assertDefaultAuthenticated; +import static ee.jakarta.tck.authorization.util.Assert.assertDefaultNoAccess; +import static ee.jakarta.tck.authorization.util.Assert.assertDefaultNotAuthenticated; +import static ee.jakarta.tck.authorization.util.ShrinkWrap.mavenWar; + +import ee.jakarta.tck.authorization.util.ArquillianBase; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.junit.Test; +import org.junit.runner.RunWith; + + +@RunWith(Arquillian.class) +public class AppCustomPolicyFactoryIT extends ArquillianBase { + + @Deployment(testable = false) + public static Archive createDeployment() { + return mavenWar(); + } + + // Test several general conditions to make sure security + // works in the normal way + + /** + * Normally authenticated for a request to the default path. + * Should have access via the role foo + */ + @Test + public void testAuthenticated() { + assertDefaultAuthenticated( + readFromServerWithCredentials("/protectedServlet", "reza", "secret1")); + } + + /** + * Not authenticated on the default path. + * Should not have access, since not in the required role foo + */ + @Test + public void testNotAuthenticated() { + assertDefaultNoAccess( + readFromServer("/protectedServlet")); + } + + /** + * Wrongly authenticated on the default path. + * Should not have access, since not in the required role foo + */ + @Test + public void testNotAuthenticatedWrongName() { + assertDefaultNoAccess( + readFromServer("/protectedServlet?name=romo&password=secret1")); + } + + // Test on the special test path which a custom policy is observing + + /** + * Should have access, despite not being in the required role foo. + * The custom policy made an exception here. + * + * But, the caller should not be in any roles (specially, should not be in role foo) + */ + @Test + public void testNotAuthenticatedSpecial() { + String response = readFromServer("/protectedServlet/foo/test"); + + assertDefaultAccess(response); + assertDefaultNotAuthenticated(response); + } + +} diff --git a/tck/pom.xml b/tck/pom.xml index 98ed6cb..4f9698b 100644 --- a/tck/pom.xml +++ b/tck/pom.xml @@ -58,11 +58,17 @@ app-custom-policy app-custom-policy2 + + app-custom-policyfactory + + app-custom-trace-policy app-custom-trace-policyconfiguration app-custom-trace-policyconfigurationfactory - app-policy-within-servlet @@ -71,6 +77,7 @@ app-servlet-constraints + app-ejb-constraints