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&persistent=false&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) {