This is an automated email from the ASF dual-hosted git repository.

jungm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomee.git


The following commit(s) were added to refs/heads/main by this push:
     new adce8b90b5 completed jakarta authorization 3.0 impl
adce8b90b5 is described below

commit adce8b90b51519f752e4285c501cc6d0d9c6e5a8
Author: Markus Jung <[email protected]>
AuthorDate: Sat Aug 22 17:34:29 2026 +0200

    completed jakarta authorization 3.0 impl
---
 .../src/test/resources/arquillian.xml              |  18 ---
 .../src/test/resources/arquillian.xml              |  16 --
 .../src/test/resources/arquillian.xml              |  16 --
 .../src/test/resources/arquillian.xml              |  17 --
 .../src/test/resources/arquillian.xml              |  25 ---
 .../src/test/resources/arquillian.xml              |  24 ---
 arquillian/arquillian-tomee-tests/pom.xml          |  17 --
 .../assembler/classic/JaccPermissionsBuilder.java  |  16 +-
 .../core/security/AbstractSecurityService.java     | 151 +++++++++---------
 .../apache/openejb/core/security/JaccProvider.java | 171 ++++++++++++++++-----
 .../apache/openejb/core/security/PolicyJDK24.java  |  46 ------
 .../core/security/jacc/BasicJaccProvider.java      |  15 +-
 .../classic/JaccPermissionsBuilderTest.java        | 157 +++++++++++++++++++
 .../core/security/BasicJaccProviderTest.java       |  70 ---------
 .../core/security/JaccProviderPolicyTest.java      | 136 ++++++++++++++++
 .../openejb/core/security/PolicyProviderTest.java  | 119 ++++++++++++++
 .../apache/openejb/core/security/SecurityTest.java |  22 +++
 .../tomee/catalina/TomcatSecurityService.java      |   1 +
 .../apache/tomee/catalina/TomcatWebAppBuilder.java |  23 ++-
 19 files changed, 677 insertions(+), 383 deletions(-)

diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-config-tests/src/test/resources/arquillian.xml
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-config-tests/src/test/resources/arquillian.xml
index 675a0258fa..86306a16a6 100644
--- 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-config-tests/src/test/resources/arquillian.xml
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-config-tests/src/test/resources/arquillian.xml
@@ -50,24 +50,6 @@
       </property>
     </configuration>
   </container>
-  <container qualifier="tomee-remote-secpol">
-    <configuration>
-      <property name="httpPort">-1</property>
-      <property name="ajpPort">-1</property>
-      <property name="stopPort">-1</property>
-      <property name="dir">target/tomee-remote</property>
-      <property 
name="appWorkingDir">target/arquillian-remote-working-dir</property>
-      <property name="portRange">20001-30000</property>
-      <property name="cleanOnStartUp">true</property>
-      <property name="simpleLog">true</property>
-      <property name="properties">
-        My\ DataSource.JdbcUrl = jdbc:hsqldb:mem:hsqldb
-        My\ Unmanaged\ DataSource.JdbcUrl = jdbc:hsqldb:mem:hsqldb
-        openejb.classloader.forced-load=org.apache.openejb.arquillian.tests
-        jakarta.security.jacc.policy.provider=sun.security.provider.PolicyFile
-      </property>
-    </configuration>
-  </container>
   <container qualifier="tomee-webapp">
     <configuration>
       <property name="httpPort">-1</property>
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/resources/arquillian.xml
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/resources/arquillian.xml
index 87d2c1c1c0..c402963dca 100644
--- 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/resources/arquillian.xml
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-data-tests/src/test/resources/arquillian.xml
@@ -46,20 +46,4 @@
       </property>
     </configuration>
   </container>
-  <container qualifier="tomee-remote-secpol">
-    <configuration>
-      <property name="httpPort">-1</property>
-      <property name="ajpPort">-1</property>
-      <property name="stopPort">-1</property>
-      <property name="dir">target/tomee-remote</property>
-      <property 
name="appWorkingDir">target/arquillian-remote-working-dir</property>
-      <property name="portRange">33001-36000</property>
-      <property name="cleanOnStartUp">true</property>
-      <property name="simpleLog">true</property>
-      <property name="properties">
-        openejb.classloader.forced-load=org.apache.openejb.arquillian.tests
-        jakarta.security.jacc.policy.provider=sun.security.provider.PolicyFile
-      </property>
-    </configuration>
-  </container>
 </arquillian>
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/resources/arquillian.xml
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/resources/arquillian.xml
index 7d6684a6fa..1e57eda520 100644
--- 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/resources/arquillian.xml
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/resources/arquillian.xml
@@ -47,22 +47,6 @@
       </property>
     </configuration>
   </container>
-  <container qualifier="tomee-remote-secpol">
-    <configuration>
-      <property name="httpPort">-1</property>
-      <property name="ajpPort">-1</property>
-      <property name="stopPort">-1</property>
-      <property name="dir">target/apache-tomee-remote</property>
-      <property 
name="appWorkingDir">target/arquillian-test-working-dir</property>
-      <property name="simpleLog">true</property>
-      <property name="properties">
-        My\ DataSource.JdbcUrl = jdbc:hsqldb:mem:hsqldb
-        My\ Unmanaged\ DataSource.JdbcUrl = jdbc:hsqldb:mem:hsqldb
-        openejb.classloader.forced-load=org.apache.openejb.arquillian.tests.
-        jakarta.security.jacc.policy.provider=sun.security.provider.PolicyFile
-      </property>
-    </configuration>
-  </container>
   <container qualifier="tomee-webapp">
     <configuration>
       <property name="httpPort">-1</property>
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxws-tests/src/test/resources/arquillian.xml
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxws-tests/src/test/resources/arquillian.xml
index c5f6405eb8..e42b337c95 100644
--- 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxws-tests/src/test/resources/arquillian.xml
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxws-tests/src/test/resources/arquillian.xml
@@ -50,23 +50,6 @@
       </property>
     </configuration>
   </container>
-  <container qualifier="tomee-remote-secpol">
-    <configuration>
-      <property name="httpPort">-1</property>
-      <property name="ajpPort">-1</property>
-      <property name="stopPort">-1</property>
-      <property name="dir">target/apache-tomee-remote</property>
-      <property 
name="appWorkingDir">target/arquillian-test-working-dir</property>
-      <property name="simpleLog">true</property>
-      <property name="properties">
-        My\ DataSource.JdbcUrl = jdbc:hsqldb:mem:hsqldb
-        My\ Unmanaged\ DataSource.JdbcUrl = jdbc:hsqldb:mem:hsqldb
-        openejb.classloader.forced-load=org.apache.openejb.arquillian.tests
-        jakarta.security.jacc.policy.provider=sun.security.provider.PolicyFile
-        tomee.webapp.classloader.forced-skip=org.apache.commons.logging.
-      </property>
-    </configuration>
-  </container>
   <container qualifier="tomee-webapp">
     <configuration>
       <property name="httpPort">-1</property>
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-jms-tests/src/test/resources/arquillian.xml
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-jms-tests/src/test/resources/arquillian.xml
index 1ee8741887..103465ef72 100644
--- 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-jms-tests/src/test/resources/arquillian.xml
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-jms-tests/src/test/resources/arquillian.xml
@@ -64,31 +64,6 @@
       </property>
     </configuration>
   </container>
-  <container qualifier="tomee-remote-secpol">
-    <configuration>
-      <property name="httpPort">-1</property>
-      <property name="ajpPort">-1</property>
-      <property name="stopPort">-1</property>
-      <property name="dir">target/apache-tomee-remote</property>
-      <property 
name="appWorkingDir">target/arquillian-test-working-dir</property>
-      <property name="simpleLog">true</property>
-      <property name="properties">
-        My\ DataSource.JdbcUrl = jdbc:hsqldb:mem:hsqldb
-        My\ Unmanaged\ DataSource.JdbcUrl = jdbc:hsqldb:mem:hsqldb
-
-        openejb.classloader.forced-load=org.apache.openejb.arquillian.tests.jms
-
-        AMQResourceAdapter = new://Resource?type=ActiveMQResourceAdapter
-        AMQResourceAdapter.BrokerXmlConfig = 
broker:(vm://localhost)?useJmx=false&amp;persistent=false&amp;deleteAllMessagesOnStartup=true
-        AMQMessageContainer = new://Container?type=MESSAGE
-        AMQMessageContainer.ResourceAdapter = AMQResourceAdapter
-        AMQConnectionFactory = 
new://Resource?type=jakarta.jms.ConnectionFactory
-        AMQConnectionFactory.ResourceAdapter = AMQResourceAdapter
-        
-        jakarta.security.jacc.policy.provider=sun.security.provider.PolicyFile
-      </property>
-    </configuration>
-  </container>
   <container qualifier="tomee-webapp">
     <configuration>
       <property name="httpPort">-1</property>
diff --git 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-webprofile-tests/src/test/resources/arquillian.xml
 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-webprofile-tests/src/test/resources/arquillian.xml
index abe0ab844c..eb72feef95 100644
--- 
a/arquillian/arquillian-tomee-tests/arquillian-tomee-webprofile-tests/src/test/resources/arquillian.xml
+++ 
b/arquillian/arquillian-tomee-tests/arquillian-tomee-webprofile-tests/src/test/resources/arquillian.xml
@@ -137,30 +137,6 @@
       </property>
     </configuration>
   </container>
-  <container qualifier="tomee-remote-secpol">
-    <configuration>
-      <property name="httpPort">-1</property>
-      <property name="ajpPort">-1</property>
-      <property name="stopPort">-1</property>
-      <property name="dir">target/tomee-remote</property>
-      <property 
name="appWorkingDir">target/arquillian-remote-working-dir</property>
-      <property name="portRange">33001-36000</property>
-      <property name="cleanOnStartUp">true</property>
-      <property name="simpleLog">true</property>
-      <property name="properties">
-        My\ DataSource.JdbcUrl = jdbc:hsqldb:mem:hsqldb
-        My\ Unmanaged\ DataSource.JdbcUrl = jdbc:hsqldb:mem:hsqldb
-        openejb.classloader.forced-load=org.apache.openejb.arquillian.tests
-        openejb.ear.use-as-webcontext-base=true
-        embedded = false
-
-        # try to save some permgen mem
-        openejb.cdi.activated-on-ejb = false
-        openejb.descriptors.output = true
-        jakarta.security.jacc.policy.provider=sun.security.provider.PolicyFile
-      </property>
-    </configuration>
-  </container>
   <container qualifier="tomee-webapp">
     <configuration>
       <property name="httpPort">-1</property>
diff --git a/arquillian/arquillian-tomee-tests/pom.xml 
b/arquillian/arquillian-tomee-tests/pom.xml
index 0e11f037d1..1ba762624e 100644
--- a/arquillian/arquillian-tomee-tests/pom.xml
+++ b/arquillian/arquillian-tomee-tests/pom.xml
@@ -322,23 +322,6 @@
                   </systemPropertyVariables>
                 </configuration>
               </execution>
-              <execution>
-                <id>test-tomee-remote-secpol</id>
-                <phase>test</phase>
-                <goals>
-                  <goal>test</goal>
-                </goals>
-                <configuration>
-                  <skip>${skip.remote.webprofile}</skip>
-                  <systemPropertyVariables>
-                    <openejb.arquillian.debug>true</openejb.arquillian.debug>
-                    <tomee.version>${project.version}</tomee.version>
-                    <tomee.classifier>webprofile</tomee.classifier>
-                    <arquillian.launch>tomee-remote-secpol</arquillian.launch>
-                    
<openejb.arquillian.adapter>tomee-remote</openejb.arquillian.adapter>
-                  </systemPropertyVariables>
-                </configuration>
-              </execution>
               <execution>
                 <id>test-tomee-embedded</id>
                 <phase>test</phase>
diff --git 
a/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/JaccPermissionsBuilder.java
 
b/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/JaccPermissionsBuilder.java
index 21981363ca..e64b127ba4 100644
--- 
a/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/JaccPermissionsBuilder.java
+++ 
b/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/JaccPermissionsBuilder.java
@@ -30,6 +30,7 @@ import jakarta.security.jacc.EJBRoleRefPermission;
 import jakarta.security.jacc.PolicyConfiguration;
 import jakarta.security.jacc.PolicyConfigurationFactory;
 import jakarta.security.jacc.PolicyContextException;
+import jakarta.security.jacc.PolicyFactory;
 import java.lang.reflect.Method;
 import java.security.Permission;
 import java.security.PermissionCollection;
@@ -60,7 +61,10 @@ public class JaccPermissionsBuilder {
             final PolicyConfigurationFactory factory = 
PolicyConfigurationFactory.getPolicyConfigurationFactory();
 
             // final boolean needsCommit = factory.inService(contextID);
-            final PolicyConfiguration policy = 
factory.getPolicyConfiguration(contextID, false);
+            // open with remove=true: each install carries the complete 
permission set for its
+            // policy context, so a re-install (e.g. a context reload with 
changed constraints)
+            // replaces the previous permissions instead of accumulating them
+            final PolicyConfiguration policy = 
factory.getPolicyConfiguration(contextID, true);
 
             policy.addToExcludedPolicy(policyContext.getExcludedPermissions());
 
@@ -74,6 +78,16 @@ public class JaccPermissionsBuilder {
             // if (needsCommit) {
                 policy.commit();
             // }
+
+            // A committed configuration is not visible to a Policy until that 
Policy
+            // has had an opportunity to assimilate its in-service 
configurations.
+            final PolicyFactory policyFactory = 
PolicyFactory.getPolicyFactory();
+            if (policyFactory != null) {
+                final jakarta.security.jacc.Policy selectedPolicy = 
policyFactory.getPolicy(contextID);
+                if (selectedPolicy != null) {
+                    selectedPolicy.refresh();
+                }
+            }
         } catch (final ClassNotFoundException e) {
             throw new OpenEJBException("PolicyConfigurationFactory class not 
found", e);
 
diff --git 
a/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java
 
b/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java
index b9a0d44082..2434af3a47 100644
--- 
a/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java
+++ 
b/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java
@@ -45,10 +45,8 @@ import javax.security.auth.login.LoginException;
 import java.io.Serializable;
 import java.lang.reflect.Method;
 import java.security.AccessControlContext;
-import java.security.AccessControlException;
 import java.security.AccessController;
 import java.security.CodeSource;
-import java.security.Policy;
 import java.security.Principal;
 import java.security.PrivilegedAction;
 import java.security.ProtectionDomain;
@@ -62,7 +60,6 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.util.Arrays.asList;
 
@@ -87,7 +84,6 @@ public abstract class AbstractSecurityService implements 
DestroyableResource, Se
     private String realmName = "PropertiesLogin";
     protected Subject defaultSubject;
     protected SecurityContext defaultContext;
-    private static final AtomicBoolean jaccWarningLogged = new 
AtomicBoolean(false);
     private final PrincipalMapper principalMapper = new 
DefaultPrincipalMapper();
 
     public AbstractSecurityService() {
@@ -177,9 +173,46 @@ public abstract class AbstractSecurityService implements 
DestroyableResource, Se
                 roles.add(name);
             }
         }
+        addAnyAuthenticatedUserRole(principals, logicalRoles, roles);
         return roles;
     }
 
+    protected void addAnyAuthenticatedUserRole(final Principal[] principals, 
final Set<String> logicalRoles,
+                                               final Set<String> roles) {
+        if (principals == null || principals.length == 0 || 
!logicalRoles.contains("**")
+                || principalMapper.isAnyAuthenticatedUserRoleMapped()) {
+            return;
+        }
+
+        final Set<Principal> principalSet = new 
LinkedHashSet<>(asList(principals));
+        final Subject subject = new Subject();
+        subject.getPrincipals().addAll(principalSet);
+
+        if (!isDefaultIdentity(principals) && 
principalMapper.getCallerPrincipal(subject) != null) {
+            roles.add("**");
+        }
+    }
+
+    private boolean isDefaultIdentity(final Principal[] principals) {
+        if (defaultSubject == null || defaultSubject.getPrincipals().size() != 
principals.length) {
+            return false;
+        }
+
+        for (final Principal principal : principals) {
+            boolean found = false;
+            for (final Principal defaultPrincipal : 
defaultSubject.getPrincipals()) {
+                if (principal == defaultPrincipal) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     @Override
     public void contextEntered(final ThreadContext oldContext, final 
ThreadContext newContext) {
         final String moduleID = newContext.getBeanContext().getModuleID();
@@ -377,41 +410,40 @@ public abstract class AbstractSecurityService implements 
DestroyableResource, Se
 
     @Override
     public boolean isCallerAuthorized(final Method method, final InterfaceType 
type) {
-        if 
(System.getProperty("java.vm.specification.version").compareTo("21") < 0) {
-            final ThreadContext threadContext = 
ThreadContext.getThreadContext();
-            final BeanContext beanContext = threadContext.getBeanContext();
-            try {
+        final ThreadContext threadContext = ThreadContext.getThreadContext();
+        final BeanContext beanContext = threadContext.getBeanContext();
 
-                final String ejbName = beanContext.getEjbName();
-                String name = type == null ? null : type.getSpecName();
-                if ("LocalBean".equals(name) || "LocalBeanHome".equals(name)) {
-                    name = null;
-                }
+        final String ejbName = beanContext.getEjbName();
+        String name = type == null ? null : type.getSpecName();
+        if ("LocalBean".equals(name) || "LocalBeanHome".equals(name)) {
+            name = null;
+        }
 
-                final Identity currentIdentity = clientIdentity.get();
-                final SecurityContext securityContext;
-                if (currentIdentity == null) {
-                    securityContext = threadContext.get(SecurityContext.class);
-                } else {
-                    securityContext = new 
SecurityContext(currentIdentity.getSubject());
-                }
+        final Identity currentIdentity = clientIdentity.get();
+        final SecurityContext securityContext;
+        if (currentIdentity == null) {
+            securityContext = threadContext.get(SecurityContext.class);
+        } else {
+            securityContext = new 
SecurityContext(currentIdentity.getSubject());
+        }
 
-                securityContext.getAccessControlContext().checkPermission(new 
EJBMethodPermission(ejbName, name, method));
-            } catch (final AccessControlException e) {
+        // evaluate the EJBMethodPermission via the Jakarta Authorization 
policy;
+        // the AccessControlContext.checkPermission() route is unavailable on 
JDK 24+ (JEP 486)
+        try {
+            final PolicyFactory policyFactory = 
PolicyFactory.getPolicyFactory();
+            if (policyFactory == null) {
                 return false;
             }
-        } else {
-            if (!jaccWarningLogged.getAndSet(true)) {
-                LOGGER.warning("Skipping JACC authorization checks as TomEE 
running on JDK 21+ does not support method security at the moment.");
-            }
-            if (LOGGER.isDebugEnabled()) {
-                LOGGER.debug("Skipping JACC authorization checks for method '"
-                        + (method == null ? "null" : method.getName())
-                        + "' on type '" + (type == null ? "null" : 
type.getSpecName())
-                        + "'.");
+
+            final jakarta.security.jacc.Policy policy = 
policyFactory.getPolicy();
+            if (policy == null) {
+                return false;
             }
+
+            return policy.implies(new EJBMethodPermission(ejbName, name, 
method), securityContext.subject);
+        } catch (final SecurityException e) {
+            return false;
         }
-        return true;
     }
 
     protected static String autoJaccProvider() {
@@ -449,50 +481,23 @@ public abstract class AbstractSecurityService implements 
DestroyableResource, Se
             Thread.currentThread().setContextClassLoader(contextClassLoader);
         }
 
-        // check the system provided provider first - if for some reason it 
isn't loaded, load it
-        final String systemPolicyProvider = 
SystemInstance.get().getOptions().getProperties().getProperty("jakarta.security.jacc.policy.provider");
-        if (systemPolicyProvider != null && getPolicy() == null) {
-            installPolicy(systemPolicyProvider);
-        }
-
-        if (! 
JaccProvider.Policy.class.getName().equals(getPolicy().getClass().getName())) {
-            // this should delegate to the policy installed above
-            installPolicy(JaccProvider.Policy.class.getName());
-        }
-    }
-
-    private static void installPolicy(String policyProvider) {
-        try {
-            final ClassLoader classLoader = 
Thread.currentThread().getContextClassLoader();
-            final Class policyClass = Class.forName(policyProvider, true, 
classLoader);
-            final Policy policy = (Policy) 
policyClass.getDeclaredConstructor().newInstance();
-            policy.refresh();
-            setPolicy(policy);
-        } catch (final Exception e) {
-            throw new IllegalStateException("Could not install JACC Policy 
Provider: " + policyProvider, e);
-        }
-    }
-
-
-    public static Policy getPolicy() {
-        Policy policy = PolicyJDK24.getPolicy();
-        if (policy == null) {
-            policy = Policy.getPolicy();
-        }
-
-        return policy;
-    }
-
-    public static void setPolicy(Policy policy) {
-        try {
-            Policy.setPolicy(policy);
-        } catch (UnsupportedOperationException e) {
-            //we are running JDK 24 or later, so no system-wide policy 
possible.
-            PolicyJDK24.setPolicy(policy);
+        // Jakarta Authorization requires 
jakarta.security.jacc.policy.provider to name a
+        // jakarta.security.jacc.Policy implementation registered via 
PolicyFactory#setPolicy
+        final String policyProvider = 
SystemInstance.get().getOptions().getProperties().getProperty("jakarta.security.jacc.policy.provider");
+        if (policyProvider != null) {
+            try {
+                final Object policy = 
Thread.currentThread().getContextClassLoader()
+                        
.loadClass(policyProvider).getDeclaredConstructor().newInstance();
+                if (!(policy instanceof jakarta.security.jacc.Policy)) {
+                    throw new IllegalArgumentException(policyProvider + " is 
not a " + jakarta.security.jacc.Policy.class.getName());
+                }
+                
PolicyFactory.getPolicyFactory().setPolicy((jakarta.security.jacc.Policy) 
policy);
+            } catch (final Exception e) {
+                throw new IllegalStateException("Could not install JACC Policy 
Provider: " + policyProvider, e);
+            }
         }
     }
 
-
     protected Subject createSubject(final String name, final String groupName) 
{
         if (name == null) {
             return null;
diff --git 
a/container/openejb-core/src/main/java/org/apache/openejb/core/security/JaccProvider.java
 
b/container/openejb-core/src/main/java/org/apache/openejb/core/security/JaccProvider.java
index 3c2d0d2a72..7f75e368c3 100644
--- 
a/container/openejb-core/src/main/java/org/apache/openejb/core/security/JaccProvider.java
+++ 
b/container/openejb-core/src/main/java/org/apache/openejb/core/security/JaccProvider.java
@@ -30,11 +30,16 @@ import java.security.ProtectionDomain;
 import java.security.cert.Certificate;
 import java.security.Permissions;
 import java.security.Principal;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import javax.security.auth.Subject;
 
+import org.apache.openejb.core.security.jacc.BasicPolicyConfiguration;
+import org.apache.openejb.loader.SystemInstance;
+
 /**
  * @version $Rev$ $Date$
  */
@@ -172,25 +177,6 @@ public abstract class JaccProvider {
         }
     }
 
-    public static class Policy extends java.security.Policy {
-
-        public Policy() throws PolicyContextException, ClassNotFoundException {
-            install();
-        }
-
-        public PermissionCollection getPermissions(final CodeSource 
codesource) {
-            return get().getPermissions(codesource);
-        }
-
-        public void refresh() {
-            get().refresh();
-        }
-
-        public boolean implies(final ProtectionDomain domain, final Permission 
permission) {
-            return get().implies(domain, permission);
-        }
-    }
-
     public static boolean isSentinelPolicy(final jakarta.security.jacc.Policy 
policy) {
         return policy instanceof DefaultPolicy;
     }
@@ -202,33 +188,149 @@ public abstract class JaccProvider {
 
         @Override
         public boolean implies(final Permission permissionToBeChecked, final 
Subject subject) {
+            final JaccProvider provider = get();
+            if (provider == null) {
+                return false;
+            }
+
             final Principal[] principals = subject == null ? new Principal[0] 
: subject.getPrincipals().toArray(new Principal[0]);
             final ProtectionDomain protectionDomain = new ProtectionDomain(
                     new CodeSource(null, (Certificate[]) null), null, null, 
principals);
 
-            final java.security.Policy policy = getPolicyProvider();
-            return policy != null && policy.implies(protectionDomain, 
permissionToBeChecked);
+            return provider.implies(protectionDomain, permissionToBeChecked);
+        }
+
+        @Override
+        public boolean isExcluded(final Permission permission) {
+            final PolicyConfiguration configuration = currentConfiguration();
+            if (configuration == null) {
+                return false;
+            }
+            final PermissionCollection excluded = 
configuration.getExcludedPermissions();
+            return excluded != null && excluded.implies(permission);
+        }
+
+        @Override
+        public boolean isUnchecked(final Permission permission) {
+            final PolicyConfiguration configuration = currentConfiguration();
+            if (configuration == null) {
+                return false;
+            }
+            final PermissionCollection unchecked = 
configuration.getUncheckedPermissions();
+            return unchecked != null && unchecked.implies(permission);
+        }
+
+        @Override
+        public boolean impliesByRole(final Permission permission, final 
Subject subject) {
+            final PolicyConfiguration configuration = currentConfiguration();
+            if (configuration == null || subject == null) {
+                return false;
+            }
+
+            final Map<String, PermissionCollection> perRolePermissions = 
configuration.getPerRolePermissions();
+            for (final String role : rolesOf(subject, 
perRolePermissions.keySet())) {
+                final PermissionCollection permissions = 
perRolePermissions.get(role);
+                if (permissions != null && permissions.implies(permission)) {
+                    return true;
+                }
+            }
+            return false;
         }
 
         @Override
         public PermissionCollection getPermissionCollection(final Subject 
subject) {
-            final Permissions permissions = new Permissions();
-            final java.security.Policy policy = getPolicyProvider();
-            if (policy == null) {
-                return permissions;
+            final PolicyConfiguration configuration = currentConfiguration();
+            if (configuration == null) {
+                return new Permissions();
             }
 
-            final PermissionCollection providerPermissions =
-                    policy.getPermissions(new CodeSource(null, (Certificate[]) 
null));
-            if (providerPermissions == null) {
-                return permissions;
+            final PermissionCollection excluded = 
copyOf(configuration.getExcludedPermissions());
+            final PermissionCollection permissions = new 
ExcludingPermissionCollection(excluded);
+
+            addAll(permissions, configuration.getUncheckedPermissions());
+
+            final Map<String, PermissionCollection> perRolePermissions = 
configuration.getPerRolePermissions();
+            if (subject != null) {
+                for (final String role : rolesOf(subject, 
perRolePermissions.keySet())) {
+                    addAll(permissions, perRolePermissions.get(role));
+                }
+            }
+            permissions.setReadOnly();
+            return permissions;
+        }
+
+        private static Set<String> rolesOf(final Subject subject, final 
Set<String> declaredRoles) {
+            final Principal[] principals = subject.getPrincipals().toArray(new 
Principal[0]);
+            if (principals.length == 0) {
+                return Collections.emptySet();
+            }
+
+            final BasicPolicyConfiguration.RoleResolver roleResolver =
+                    
SystemInstance.get().getComponent(BasicPolicyConfiguration.RoleResolver.class);
+            if (roleResolver == null) {
+                return Collections.emptySet();
             }
+            return roleResolver.getLogicalRoles(principals, declaredRoles);
+        }
 
-            final Enumeration<Permission> elements = 
providerPermissions.elements();
+        private static void addAll(final PermissionCollection target, final 
PermissionCollection source) {
+            if (source == null) {
+                return;
+            }
+            final Enumeration<Permission> elements = source.elements();
             while (elements.hasMoreElements()) {
-                permissions.add(elements.nextElement());
+                target.add(elements.nextElement());
+            }
+        }
+
+        private static PermissionCollection copyOf(final PermissionCollection 
source) {
+            final Permissions copy = new Permissions();
+            addAll(copy, source);
+            copy.setReadOnly();
+            return copy;
+        }
+
+        private static PolicyConfiguration currentConfiguration() {
+            final JaccProvider provider = get();
+            if (provider == null) {
+                return null;
+            }
+
+            final PolicyConfiguration configuration = 
provider.getPolicyConfiguration();
+            try {
+                return configuration != null && configuration.inService() ? 
configuration : null;
+            } catch (final PolicyContextException e) {
+                return null;
+            }
+        }
+
+        private static final class ExcludingPermissionCollection extends 
PermissionCollection {
+            private static final long serialVersionUID = 1L;
+
+            private final PermissionCollection excluded;
+            private final Permissions granted = new Permissions();
+
+            private ExcludingPermissionCollection(final PermissionCollection 
excluded) {
+                this.excluded = excluded;
+            }
+
+            @Override
+            public void add(final Permission permission) {
+                if (isReadOnly()) {
+                    throw new SecurityException("attempt to add a Permission 
to a readonly PermissionCollection");
+                }
+                granted.add(permission);
+            }
+
+            @Override
+            public boolean implies(final Permission permission) {
+                return !excluded.implies(permission) && 
granted.implies(permission);
+            }
+
+            @Override
+            public Enumeration<Permission> elements() {
+                return granted.elements();
             }
-            return permissions;
         }
     }
 
@@ -236,11 +338,6 @@ public abstract class JaccProvider {
         return contextID == null ? DEFAULT_CONTEXT_ID : contextID;
     }
 
-    private static java.security.Policy getPolicyProvider() {
-        final java.security.Policy policy = PolicyJDK24.getPolicy();
-        return policy != null ? policy : java.security.Policy.getPolicy();
-    }
-
     public abstract PolicyConfiguration getPolicyConfiguration(String 
contextID, boolean remove) throws PolicyContextException;
 
     public abstract PolicyConfiguration getPolicyConfiguration(String 
contextID);
diff --git 
a/container/openejb-core/src/main/java/org/apache/openejb/core/security/PolicyJDK24.java
 
b/container/openejb-core/src/main/java/org/apache/openejb/core/security/PolicyJDK24.java
deleted file mode 100644
index 6db9ccf4cb..0000000000
--- 
a/container/openejb-core/src/main/java/org/apache/openejb/core/security/PolicyJDK24.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.openejb.core.security;
-
-import java.security.Policy;
-
-/**
- * A utility class to manage the Java Security Policy in a thread-safe manner.
- * This class provides methods to get and set the security policy, ensuring
- * that changes are synchronized across threads.
- */
-public class PolicyJDK24 {
-
-    private static volatile Policy policy;
-
-    /**
-     * @return the policy
-     */
-    public static synchronized Policy getPolicy() {
-        return policy;
-    }
-
-    /**
-     * @param policy the policy to set
-     */
-    public static synchronized void setPolicy(Policy policy) {
-        PolicyJDK24.policy = policy;
-    }
-
-
-
-}
diff --git 
a/container/openejb-core/src/main/java/org/apache/openejb/core/security/jacc/BasicJaccProvider.java
 
b/container/openejb-core/src/main/java/org/apache/openejb/core/security/jacc/BasicJaccProvider.java
index 7d61a36ed6..131ae96d9d 100644
--- 
a/container/openejb-core/src/main/java/org/apache/openejb/core/security/jacc/BasicJaccProvider.java
+++ 
b/container/openejb-core/src/main/java/org/apache/openejb/core/security/jacc/BasicJaccProvider.java
@@ -18,7 +18,6 @@
 package org.apache.openejb.core.security.jacc;
 
 import org.apache.openejb.core.security.JaccProvider;
-import org.apache.openejb.core.security.PolicyJDK24;
 import org.apache.openejb.loader.SystemInstance;
 import org.apache.openejb.spi.SecurityService;
 
@@ -64,12 +63,6 @@ public class BasicJaccProvider extends JaccProvider {
 
     private final Map<String, BasicPolicyConfiguration> configurations = new 
HashMap<>();
 
-    private final java.security.Policy systemPolicy;
-
-    public BasicJaccProvider() {
-        systemPolicy = PolicyJDK24.getPolicy();
-    }
-
     public PolicyConfiguration getPolicyConfiguration(final String contextID, 
final boolean remove) throws PolicyContextException {
         if (contextID == null) {
             throw new IllegalArgumentException("contextID can't be null;");
@@ -106,12 +99,12 @@ public class BasicJaccProvider extends JaccProvider {
     }
 
     public boolean inService(final String contextID) throws 
PolicyContextException {
-        final PolicyConfiguration configuration = 
getPolicyConfiguration(contextID, false);
-        return configuration.inService();
+        final PolicyConfiguration configuration = 
getPolicyConfiguration(contextID);
+        return configuration != null && configuration.inService();
     }
 
     public PermissionCollection getPermissions(final CodeSource codesource) {
-        return systemPolicy == null ? null : 
systemPolicy.getPermissions(codesource);
+        return null;
     }
 
     public void refresh() {
@@ -134,7 +127,7 @@ public class BasicJaccProvider extends JaccProvider {
             }
         }
 
-        return systemPolicy != null && systemPolicy.implies(domain, 
permission);
+        return false;
     }
 
     public boolean hasAccessToWebResource(final String resource, final 
String... methods) {
diff --git 
a/container/openejb-core/src/test/java/org/apache/openejb/assembler/classic/JaccPermissionsBuilderTest.java
 
b/container/openejb-core/src/test/java/org/apache/openejb/assembler/classic/JaccPermissionsBuilderTest.java
new file mode 100644
index 0000000000..64b9aa2039
--- /dev/null
+++ 
b/container/openejb-core/src/test/java/org/apache/openejb/assembler/classic/JaccPermissionsBuilderTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.openejb.assembler.classic;
+
+import jakarta.security.jacc.Policy;
+import jakarta.security.jacc.PolicyConfiguration;
+import jakarta.security.jacc.PolicyConfigurationFactory;
+import jakarta.security.jacc.PolicyContextException;
+import jakarta.security.jacc.PolicyFactory;
+import org.apache.openejb.core.security.jacc.BasicPolicyConfiguration;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.security.auth.Subject;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+public class JaccPermissionsBuilderTest {
+
+    private static final String CONTEXT_ID = "JaccPermissionsBuilderTest";
+
+    private PolicyConfigurationFactory previousConfigurationFactory;
+    private PolicyFactory previousPolicyFactory;
+
+    @Before
+    public void rememberFactories() throws Exception {
+        try {
+            previousConfigurationFactory = 
PolicyConfigurationFactory.getPolicyConfigurationFactory();
+        } catch (final ClassNotFoundException | PolicyContextException e) {
+            previousConfigurationFactory = null;
+        }
+        previousPolicyFactory = PolicyFactory.getPolicyFactory();
+    }
+
+    @After
+    public void restoreFactories() {
+        
PolicyConfigurationFactory.setPolicyConfigurationFactory(previousConfigurationFactory);
+        PolicyFactory.setPolicyFactory(previousPolicyFactory);
+    }
+
+    @Test
+    public void refreshesContextPolicyAfterCommit() throws Exception {
+        final List<String> events = new ArrayList<>();
+        final RecordingConfiguration configuration = new 
RecordingConfiguration(events);
+        final RecordingPolicy policy = new RecordingPolicy(events);
+        final RecordingPolicyFactory policyFactory = new 
RecordingPolicyFactory(policy);
+
+        PolicyConfigurationFactory.setPolicyConfigurationFactory(new 
RecordingConfigurationFactory(configuration));
+        PolicyFactory.setPolicyFactory(policyFactory);
+
+        new JaccPermissionsBuilder().install(new PolicyContext(CONTEXT_ID));
+
+        assertEquals(Arrays.asList("commit", "refresh"), events);
+        assertEquals(CONTEXT_ID, policyFactory.requestedContextId);
+    }
+
+    private static final class RecordingConfigurationFactory extends 
PolicyConfigurationFactory {
+        private final PolicyConfiguration configuration;
+
+        private RecordingConfigurationFactory(final PolicyConfiguration 
configuration) {
+            this.configuration = configuration;
+        }
+
+        @Override
+        public PolicyConfiguration getPolicyConfiguration(final String 
contextID, final boolean remove) {
+            return configuration;
+        }
+
+        @Override
+        public PolicyConfiguration getPolicyConfiguration(final String 
contextID) {
+            return configuration;
+        }
+
+        @Override
+        public PolicyConfiguration getPolicyConfiguration() {
+            return configuration;
+        }
+
+        @Override
+        public boolean inService(final String contextID) throws 
PolicyContextException {
+            return configuration.inService();
+        }
+    }
+
+    private static final class RecordingConfiguration extends 
BasicPolicyConfiguration {
+        private final List<String> events;
+
+        private RecordingConfiguration(final List<String> events) {
+            super(CONTEXT_ID);
+            this.events = events;
+        }
+
+        @Override
+        public void commit() throws PolicyContextException {
+            super.commit();
+            events.add("commit");
+        }
+    }
+
+    private static final class RecordingPolicyFactory extends PolicyFactory {
+        private final Policy policy;
+        private String requestedContextId;
+
+        private RecordingPolicyFactory(final Policy policy) {
+            this.policy = policy;
+        }
+
+        @Override
+        public Policy getPolicy(final String contextId) {
+            requestedContextId = contextId;
+            return policy;
+        }
+
+        @Override
+        public void setPolicy(final String contextId, final Policy policy) {
+            // no-op
+        }
+    }
+
+    private static final class RecordingPolicy implements Policy {
+        private final List<String> events;
+
+        private RecordingPolicy(final List<String> events) {
+            this.events = events;
+        }
+
+        @Override
+        public PermissionCollection getPermissionCollection(final Subject 
subject) {
+            return new Permissions();
+        }
+
+        @Override
+        public void refresh() {
+            events.add("refresh");
+        }
+    }
+}
diff --git 
a/container/openejb-core/src/test/java/org/apache/openejb/core/security/BasicJaccProviderTest.java
 
b/container/openejb-core/src/test/java/org/apache/openejb/core/security/BasicJaccProviderTest.java
deleted file mode 100644
index 11b8a3d0ae..0000000000
--- 
a/container/openejb-core/src/test/java/org/apache/openejb/core/security/BasicJaccProviderTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.openejb.core.security;
-
-import org.apache.openejb.junit.ApplicationComposer;
-import org.apache.openejb.testing.Classes;
-import org.apache.openejb.testing.ContainerProperties;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import jakarta.ejb.EJB;
-import jakarta.ejb.Singleton;
-import java.security.Policy;
-
-
-@Classes(innerClassesAsBean = true)
-@RunWith(ApplicationComposer.class)
-@ContainerProperties(
-        @ContainerProperties.Property(
-                name = "jakarta.security.jacc.policy.provider",
-                value = 
"org.apache.openejb.core.security.BasicJaccProviderTest$MyPolicy"))
-public class BasicJaccProviderTest {
-
-    @EJB
-    private SimpleSingleton myBean;
-
-    @Test
-    public void run() throws Exception {
-        Assert.assertNotNull("Singleton bean could not be created", myBean);
-        Assert.assertEquals("tset", myBean.reverse("test"));
-    }
-
-    public static class MyPolicy extends Policy {
-    }
-
-    @Singleton
-    public static class SimpleSingleton {
-        public String reverse(final String input) {
-            if (input == null) {
-                return null;
-            }
-
-            if (input.length() == 0) {
-                return "";
-            }
-
-            char[] chars = new char[input.length()];
-            for (int i = 0; i < input.length(); i++) {
-                chars[i] = input.charAt((input.length() - 1) - i);
-            }
-
-            return new String(chars);
-        }
-    }
-}
diff --git 
a/container/openejb-core/src/test/java/org/apache/openejb/core/security/JaccProviderPolicyTest.java
 
b/container/openejb-core/src/test/java/org/apache/openejb/core/security/JaccProviderPolicyTest.java
new file mode 100644
index 0000000000..f5971b56a4
--- /dev/null
+++ 
b/container/openejb-core/src/test/java/org/apache/openejb/core/security/JaccProviderPolicyTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.openejb.core.security;
+
+import jakarta.security.jacc.Policy;
+import jakarta.security.jacc.PolicyConfiguration;
+import jakarta.security.jacc.PolicyContext;
+import org.apache.openejb.core.security.jacc.BasicJaccProvider;
+import org.apache.openejb.core.security.jacc.BasicPolicyConfiguration;
+import org.apache.openejb.loader.SystemInstance;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.security.auth.Subject;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class JaccProviderPolicyTest {
+
+    private static final String CONTEXT_ID = "JaccProviderPolicyTest";
+
+    private JaccProvider previousProvider;
+    private BasicPolicyConfiguration.RoleResolver previousRoleResolver;
+    private BasicJaccProvider provider;
+    private Policy policy;
+    private String previousContextId;
+
+    @Before
+    public void setUp() throws Exception {
+        previousProvider = JaccProvider.get();
+        previousRoleResolver = 
SystemInstance.get().getComponent(BasicPolicyConfiguration.RoleResolver.class);
+        previousContextId = PolicyContext.getContextID();
+
+        provider = new BasicJaccProvider();
+        JaccProvider.set(provider);
+        
SystemInstance.get().setComponent(BasicPolicyConfiguration.RoleResolver.class,
+                (principals, roles) -> principals.length == 0 ? 
Collections.emptySet() : new HashSet<>(roles));
+        PolicyContext.setContextID(CONTEXT_ID);
+        policy = new JaccProvider.PolicyFactory().getPolicy(CONTEXT_ID);
+    }
+
+    @After
+    public void tearDown() {
+        PolicyContext.setContextID(previousContextId);
+        JaccProvider.set(previousProvider);
+        if (previousRoleResolver == null) {
+            
SystemInstance.get().removeComponent(BasicPolicyConfiguration.RoleResolver.class);
+        } else {
+            
SystemInstance.get().setComponent(BasicPolicyConfiguration.RoleResolver.class, 
previousRoleResolver);
+        }
+    }
+
+    @Test
+    public void onlyInServiceConfigurationParticipatesInDecisions() throws 
Exception {
+        final Permission excluded = new RuntimePermission("excluded");
+        final Permission unchecked = new RuntimePermission("unchecked");
+        final Permission byRole = new RuntimePermission("byRole");
+        final Subject subject = subject("caller");
+
+        final PolicyConfiguration configuration = 
provider.getPolicyConfiguration(CONTEXT_ID, true);
+        configuration.addToExcludedPolicy(excluded);
+        configuration.addToUncheckedPolicy(unchecked);
+        configuration.addToRole("user", byRole);
+
+        assertConfigurationHidden(excluded, unchecked, byRole, subject);
+
+        configuration.commit();
+        assertTrue(provider.inService(CONTEXT_ID));
+        assertTrue(policy.isExcluded(excluded));
+        assertTrue(policy.isUnchecked(unchecked));
+        assertTrue(policy.impliesByRole(byRole, subject));
+        assertTrue(policy.getPermissionCollection(subject).implies(unchecked));
+        assertTrue(policy.getPermissionCollection(subject).implies(byRole));
+
+        provider.getPolicyConfiguration(CONTEXT_ID, false);
+        assertFalse(provider.inService(CONTEXT_ID));
+        assertConfigurationHidden(excluded, unchecked, byRole, subject);
+
+        configuration.commit();
+        configuration.delete();
+        assertConfigurationHidden(excluded, unchecked, byRole, subject);
+    }
+
+    @Test
+    public void exclusionsTakePrecedenceInPermissionCollection() throws 
Exception {
+        final PolicyConfiguration configuration = 
provider.getPolicyConfiguration(CONTEXT_ID, true);
+        configuration.addToUncheckedPolicy(new 
RuntimePermission("unchecked.*"));
+        configuration.addToRole("user", new RuntimePermission("role.*"));
+        configuration.addToExcludedPolicy(new 
RuntimePermission("unchecked.blocked"));
+        configuration.addToExcludedPolicy(new 
RuntimePermission("role.blocked"));
+        configuration.commit();
+
+        final PermissionCollection permissions = 
policy.getPermissionCollection(subject("caller"));
+        assertTrue(permissions.implies(new 
RuntimePermission("unchecked.allowed")));
+        assertTrue(permissions.implies(new RuntimePermission("role.allowed")));
+        assertFalse(permissions.implies(new 
RuntimePermission("unchecked.blocked")));
+        assertFalse(permissions.implies(new 
RuntimePermission("role.blocked")));
+    }
+
+    private void assertConfigurationHidden(final Permission excluded, final 
Permission unchecked,
+                                           final Permission byRole, final 
Subject subject) {
+        assertFalse(policy.isExcluded(excluded));
+        assertFalse(policy.isUnchecked(unchecked));
+        assertFalse(policy.impliesByRole(byRole, subject));
+        
assertFalse(policy.getPermissionCollection(subject).implies(unchecked));
+        assertFalse(policy.getPermissionCollection(subject).implies(byRole));
+    }
+
+    private static Subject subject(final String name) {
+        final Set<Principal> principals = new HashSet<>();
+        principals.add(() -> name);
+        return new Subject(true, principals, Collections.emptySet(), 
Collections.emptySet());
+    }
+}
diff --git 
a/container/openejb-core/src/test/java/org/apache/openejb/core/security/PolicyProviderTest.java
 
b/container/openejb-core/src/test/java/org/apache/openejb/core/security/PolicyProviderTest.java
new file mode 100644
index 0000000000..ef1fa0fd6a
--- /dev/null
+++ 
b/container/openejb-core/src/test/java/org/apache/openejb/core/security/PolicyProviderTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.openejb.core.security;
+
+import org.apache.openejb.junit.ApplicationComposer;
+import org.apache.openejb.testing.Classes;
+import org.apache.openejb.testing.ContainerProperties;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import jakarta.annotation.security.DenyAll;
+import jakarta.ejb.EJB;
+import jakarta.ejb.EJBAccessException;
+import jakarta.ejb.Singleton;
+import jakarta.security.jacc.Policy;
+import jakarta.security.jacc.PolicyFactory;
+import javax.security.auth.Subject;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@Classes(innerClassesAsBean = true)
+@RunWith(ApplicationComposer.class)
+@ContainerProperties(
+        @ContainerProperties.Property(
+                name = "jakarta.security.jacc.policy.provider",
+                value = 
"org.apache.openejb.core.security.PolicyProviderTest$PermitEverythingPolicy"))
+public class PolicyProviderTest {
+
+    private static volatile boolean throwOnDecision;
+
+    @EJB
+    private Guarded guarded;
+
+    @After
+    public void resetDecisionFailure() {
+        throwOnDecision = false;
+    }
+
+    @AfterClass
+    public static void resetPolicy() {
+        PolicyFactory.getPolicyFactory().setPolicy(null);
+    }
+
+    @Test
+    public void policyIsInstalled() {
+        assertTrue(PolicyFactory.getPolicyFactory().getPolicy() instanceof 
PermitEverythingPolicy);
+    }
+
+    @Test
+    public void policyDecidesEjbAuthorization() {
+        final PermitEverythingPolicy policy =
+                (PermitEverythingPolicy) 
PolicyFactory.getPolicyFactory().getPolicy();
+        assertTrue("Policy was not refreshed after its configuration was 
committed", policy.refreshCount.get() > 0);
+        assertEquals("ok", guarded.restricted());
+    }
+
+    @Test
+    public void securityExceptionFromPolicyIsTreatedAsDenial() {
+        throwOnDecision = true;
+        try {
+            guarded.restricted();
+            fail("A policy SecurityException must deny the EJB invocation");
+        } catch (final EJBAccessException expected) {
+            // expected
+        }
+    }
+
+    public static class PermitEverythingPolicy implements Policy {
+        private final AtomicInteger refreshCount = new AtomicInteger();
+
+        @Override
+        public boolean implies(final Permission permissionToBeChecked, final 
Subject subject) {
+            if (throwOnDecision) {
+                throw new SecurityException("policy decision failed");
+            }
+            return refreshCount.get() > 0;
+        }
+
+        @Override
+        public PermissionCollection getPermissionCollection(final Subject 
subject) {
+            return new Permissions();
+        }
+
+        @Override
+        public void refresh() {
+            refreshCount.incrementAndGet();
+        }
+    }
+
+    @Singleton
+    public static class Guarded {
+        @DenyAll
+        public String restricted() {
+            return "ok";
+        }
+    }
+}
diff --git 
a/container/openejb-core/src/test/java/org/apache/openejb/core/security/SecurityTest.java
 
b/container/openejb-core/src/test/java/org/apache/openejb/core/security/SecurityTest.java
index 0839d99680..6372be9044 100644
--- 
a/container/openejb-core/src/test/java/org/apache/openejb/core/security/SecurityTest.java
+++ 
b/container/openejb-core/src/test/java/org/apache/openejb/core/security/SecurityTest.java
@@ -36,6 +36,7 @@ import jakarta.annotation.security.DenyAll;
 import jakarta.annotation.security.PermitAll;
 import jakarta.annotation.security.RolesAllowed;
 import jakarta.annotation.security.RunAs;
+import jakarta.ejb.EJBAccessException;
 import jakarta.ejb.SessionContext;
 import jakarta.ejb.Stateless;
 import javax.naming.Context;
@@ -102,6 +103,7 @@ public class SecurityTest extends TestCase {
 
         foo.svnCheckout("");
         foo.svnCommit("");
+        assertEquals("authenticated", foo.authenticatedOnly());
 
         try {
             foo.deleteProject("");
@@ -153,6 +155,12 @@ public class SecurityTest extends TestCase {
         final Project foo = (Project) ctx.lookup("FooBeanLocal");
 
         foo.svnCheckout("");
+        try {
+            foo.authenticatedOnly();
+            fail("The default unauthenticated identity must not have the ** 
role");
+        } catch (final EJBAccessException e) {
+            // good.
+        }
         try {
             foo.svnCommit("");
             fail("Should not be allowed");
@@ -230,6 +238,12 @@ public class SecurityTest extends TestCase {
             return s;
         }
 
+        @Override
+        @RolesAllowed({"**"})
+        public String authenticatedOnly() {
+            return "authenticated";
+        }
+
         @Override
         public boolean isCallerInRole(final String role) {
             return context.isCallerInRole(role);
@@ -273,6 +287,12 @@ public class SecurityTest extends TestCase {
             return s;
         }
 
+        @Override
+        @RolesAllowed({"**"})
+        public String authenticatedOnly() {
+            return "authenticated";
+        }
+
         @Override
         @PermitAll
         public boolean isCallerInRole(final String role) {
@@ -296,6 +316,8 @@ public class SecurityTest extends TestCase {
 
         public String deleteProject(String s);
 
+        public String authenticatedOnly();
+
         public boolean isCaller(String s);
 
         public boolean isCallerInRole(String s);
diff --git 
a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatSecurityService.java
 
b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatSecurityService.java
index 9fc2291149..593d6c8972 100644
--- 
a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatSecurityService.java
+++ 
b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatSecurityService.java
@@ -160,6 +160,7 @@ public class TomcatSecurityService extends 
AbstractSecurityService {
                 }
             }
         }
+        addAnyAuthenticatedUserRole(principals, logicalRoles, roles);
         return roles;
     }
 
diff --git 
a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatWebAppBuilder.java
 
b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatWebAppBuilder.java
index dd0079959d..33dae5986c 100644
--- 
a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatWebAppBuilder.java
+++ 
b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatWebAppBuilder.java
@@ -78,7 +78,6 @@ import org.apache.openejb.assembler.classic.JndiEncBuilder;
 import org.apache.openejb.assembler.classic.OpenEjbConfiguration;
 import org.apache.openejb.assembler.classic.OpenEjbConfigurationFactory;
 import org.apache.openejb.assembler.classic.PersistenceUnitInfo;
-import org.apache.openejb.assembler.classic.PolicyContext;
 import org.apache.openejb.assembler.classic.ReloadableEntityManagerFactory;
 import org.apache.openejb.assembler.classic.ResourceInfo;
 import org.apache.openejb.assembler.classic.ServletInfo;
@@ -608,17 +607,6 @@ public class TomcatWebAppBuilder implements WebAppBuilder, 
ContextListener, Pare
                     } else { // force a normal deployment with lazy building 
of AppInfo
                         deployWar(standardContext, host, null);
                     }
-
-                    // TODO should we copy the information in the appInfo 
using the jee object tree or add more to the info tree
-                    // this might then move to the assembler after webapp is 
deployed so we can read information from info tree
-                    // and build up all policy context from there instead of 
from Tomcat internal objects
-                    final TomcatSecurityConstaintsToJaccPermissionsTransformer 
transformer =
-                        new 
TomcatSecurityConstaintsToJaccPermissionsTransformer(standardContext);
-                    final PolicyContext policyContext = 
transformer.createResourceAndDataPermissions();
-
-                    final JaccPermissionsBuilder jaccPermissionsBuilder = new 
JaccPermissionsBuilder();
-                    jaccPermissionsBuilder.install(policyContext);
-
                 }
             }
         } finally { // cleanup temp var passing
@@ -1739,6 +1727,17 @@ public class TomcatWebAppBuilder implements 
WebAppBuilder, ContextListener, Pare
         }
         contextInfo.module = null; // shouldn't be there after startup 
(actually we shouldn't need it from info tree but our scanning does)
 
+        // build the Jakarta Authorization policy context from the merged 
Tomcat security
+        // constraints (web.xml and @ServletSecurity); runs here so it covers 
every deployment
+        // path, including wars Tomcat picks up from webapps/
+        try {
+            final TomcatSecurityConstaintsToJaccPermissionsTransformer 
transformer =
+                new 
TomcatSecurityConstaintsToJaccPermissionsTransformer(standardContext);
+            new 
JaccPermissionsBuilder().install(transformer.createResourceAndDataPermissions());
+        } catch (final Exception e) {
+            LOGGER.error("Could not install Jakarta Authorization permissions 
for " + standardContext.getName(), e);
+        }
+
         final String id = getId(standardContext);
         WebAppInfo currentWebAppInfo = null;
         for (final WebAppInfo webAppInfo : contextInfo.appInfo.webApps) {

Reply via email to