org.glassfish.ejb.security.application.EJBSecurityManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
// Portions Copyright [2016-2021] [Payara Foundation and/or its affiliates]
package org.glassfish.ejb.security.application;
import com.sun.ejb.EjbInvocation;
import com.sun.enterprise.deployment.EjbIORConfigurationDescriptor;
import com.sun.enterprise.deployment.RunAsIdentityDescriptor;
import com.sun.enterprise.security.SecurityContext;
import com.sun.enterprise.security.SecurityManager;
import com.sun.enterprise.security.auth.WebAndEjbToJaasBridge;
import com.sun.enterprise.security.common.AppservAccessController;
import com.sun.enterprise.security.ee.SecurityUtil;
import com.sun.enterprise.security.ee.audit.AppServerAuditManager;
import com.sun.enterprise.security.jacc.JaccEJBConstraintsTranslator;
import com.sun.enterprise.security.jacc.cache.CachedPermission;
import com.sun.enterprise.security.jacc.cache.CachedPermissionImpl;
import com.sun.enterprise.security.jacc.cache.PermissionCache;
import com.sun.enterprise.security.jacc.cache.PermissionCacheFactory;
import com.sun.enterprise.security.jacc.context.PolicyContextHandlerImpl;
import com.sun.logging.LogDomains;
import org.glassfish.api.invocation.ComponentInvocation;
import org.glassfish.api.invocation.InvocationException;
import org.glassfish.api.invocation.InvocationManager;
import org.glassfish.deployment.common.SecurityRoleMapperFactory;
import org.glassfish.ejb.deployment.descriptor.EjbDescriptor;
import org.glassfish.ejb.security.factory.EJBSecurityManagerFactory;
import org.glassfish.external.probe.provider.PluginPoint;
import org.glassfish.external.probe.provider.StatsProviderManager;
import javax.security.auth.Subject;
import javax.security.auth.SubjectDomainCombiner;
import jakarta.security.jacc.*;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.sun.enterprise.security.common.AppservAccessController.doPrivileged;
import static com.sun.enterprise.security.common.AppservAccessController.privileged;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.SEVERE;
/**
* This class is used by the EJB server to manage security. All the container object only call into this object for
* managing security. This class cannot be subclassed.
*
* An instance of this class should be created per deployment unit.
*
* @author Harpreet Singh, monzillo
*/
public final class EJBSecurityManager implements SecurityManager {
private static final Logger _logger = LogDomains.getLogger(EJBSecurityManager.class, LogDomains.EJB_LOGGER);
private AppServerAuditManager auditManager;
private static final PolicyContextHandlerImpl pcHandlerImpl = (PolicyContextHandlerImpl) PolicyContextHandlerImpl.getInstance();
private final SecurityRoleMapperFactory roleMapperFactory;
private final EjbDescriptor deploymentDescriptor;
// Objects required for Run-AS
private final RunAsIdentityDescriptor runAs;
// JACC related
private static PolicyConfigurationFactory policyConfigurationFactory;
private String ejbName;
// contextId id is the same as an appname. This will be used to get
// a PolicyConfiguration object per application.
private String contextId;
private CodeSource codesource;
private String realmName;
// We use two protection domain caches until we decide how to
// set the codesource in the protection domain of system apps.
// PD's in protectionDomainCache have the (privileged) codesource
// of the EJBSecurityManager class. The PD used in pre-dispatch
// authorization decisions MUST not be constructed using a privileged
// codesource (or else all pre-distpatch access decisions will be granted).
private final Map cacheProtectionDomain = Collections.synchronizedMap(new WeakHashMap());
private final Map protectionDomainCache = Collections.synchronizedMap(new WeakHashMap());
private final Map accessControlContextCache = Collections.synchronizedMap(new WeakHashMap());
private PermissionCache uncheckedMethodPermissionCache;
private final Policy policy;
private static final CodeSource managerCodeSource = EJBSecurityManager.class.getProtectionDomain().getCodeSource();
private final InvocationManager invocationManager;
private final EJBSecurityManagerFactory securityManagerFactory;
private final EjbSecurityProbeProvider probeProvider = new EjbSecurityProbeProvider();
private static volatile EjbSecurityStatsProvider ejbStatsProvider;
public EJBSecurityManager(EjbDescriptor ejbDescriptor, InvocationManager invocationManager, EJBSecurityManagerFactory fact) throws Exception {
this.deploymentDescriptor = ejbDescriptor;
this.invocationManager = invocationManager;
roleMapperFactory = SecurityUtil.getRoleMapperFactory();
// get the default policy
policy = Policy.getPolicy();
securityManagerFactory = fact;
boolean runas = !(deploymentDescriptor.getUsesCallerIdentity());
if (runas) {
runAs = deploymentDescriptor.getRunAsIdentity();
// Note: runAs may be null even when runas==true if this EJB
// is an MDB.
if (runAs != null) {
if (_logger.isLoggable(FINE)) {
_logger.fine(deploymentDescriptor.getEjbClassName() + " will run-as: " + runAs.getPrincipal() + " ("
+ runAs.getRoleName() + ")");
}
}
} else {
runAs = null;
}
initialize();
}
public static String getContextID(EjbDescriptor ejbDescriptor) {
return SecurityUtil.getContextID(ejbDescriptor.getEjbBundleDescriptor());
}
/**
* This method is used by MDB Container - Invocation Manager to setup the run-as identity information. It has to be
* coupled with the postSetRunAsIdentity method. This method is called for EJB/MDB Containers
*/
@Override
public void preInvoke(ComponentInvocation inv) {
// Optimization to avoid the expensive call
if (runAs == null) {
inv.setPreInvokeDone(true);
return;
}
boolean isWebService = false;
if (inv instanceof EjbInvocation) {
isWebService = ((EjbInvocation) inv).isWebService;
}
// If it is not a webservice or successful authorization
// and preInvoke is not call before
if ((!isWebService || (inv.getAuth() != null && inv.getAuth().booleanValue())) && !inv.isPreInvokeDone()) {
inv.setOldSecurityContext(SecurityContext.getCurrent());
loginForRunAs();
inv.setPreInvokeDone(true);
}
}
/**
* This method is used by Message Driven Bean Container to remove the run-as identity information that was set up using
* the preSetRunAsIdentity method
*/
@Override
public void postInvoke(ComponentInvocation inv) {
if (runAs != null && inv.isPreInvokeDone()) {
privileged(() -> SecurityContext.setCurrent((SecurityContext) inv.getOldSecurityContext()));
}
}
public boolean getUsesCallerIdentity() {
return runAs == null;
}
public void loadPolicyConfiguration(EjbDescriptor ejbDescriptor) throws Exception {
PolicyConfigurationFactory factory = getPolicyFactory();
boolean inService = factory.inService(contextId);
// Only load the policy configuration if it isn't already in service.
//
// Consequently, all things that deploy modules (as apposed to/ loading already
// deployed modules) must make sure a pre-existing PolicyConfiguration
// is either in deleted or open state before this method is called.
//
// Note that policy statements are not removed to allow multiple EJBs to be
// represented by same PolicyConfiguration.
if (!inService) {
// Translate the deployment descriptor to configure the policy rules
JaccEJBConstraintsTranslator.translateConstraintsToPermissions(ejbDescriptor, factory.getPolicyConfiguration(contextId, false));
if (_logger.isLoggable(FINE)) {
_logger.fine("JACC: policy translated for policy context:" + contextId);
}
}
}
/**
* This method is called by the EJB container to decide whether or not a method specified in the Invocation should be
* allowed.
*
* @param componentInvocation invocation object that contains all the details of the invocation.
* @return A boolean value indicating if the client should be allowed to invoke the EJB.
*/
@Override
public boolean authorize(ComponentInvocation componentInvocation) {
if (!(componentInvocation instanceof EjbInvocation)) {
return false;
}
EjbInvocation ejbInvocation = (EjbInvocation) componentInvocation; // FIXME: Param type should be EjbInvocation
if (ejbInvocation.getAuth() != null) {
return ejbInvocation.getAuth().booleanValue();
}
boolean isAuthorized = false;
CachedPermission cachedPermission = null;
Permission permission = null;
if (ejbInvocation.invocationInfo == null || ejbInvocation.invocationInfo.cachedPermission == null) {
permission = new EJBMethodPermission(ejbName, ejbInvocation.getMethodInterface(), ejbInvocation.method);
cachedPermission = new CachedPermissionImpl(uncheckedMethodPermissionCache, permission);
if (ejbInvocation.invocationInfo != null) {
ejbInvocation.invocationInfo.cachedPermission = cachedPermission;
if (_logger.isLoggable(FINE)) {
_logger.fine("JACC: permission initialized in InvocationInfo: EJBMethodPermission (Name) = " + permission.getName()
+ " (Action) = " + permission.getActions());
}
}
} else {
cachedPermission = ejbInvocation.invocationInfo.cachedPermission;
permission = cachedPermission.getPermission();
}
String caller = null;
SecurityContext securityContext = null;
pcHandlerImpl.getHandlerData().setInvocation(ejbInvocation);
isAuthorized = cachedPermission.checkPermission();
if (!isAuthorized) {
securityContext = SecurityContext.getCurrent();
try {
// Set the policy context in the TLS.
String oldContextId = setPolicyContext(contextId);
try {
isAuthorized = policy.implies(getCachedProtectionDomain(securityContext.getPrincipalSet(), true), permission);
} catch (Throwable t) {
_logger.log(SEVERE, "jacc_access_exception", t);
isAuthorized = false;
} finally {
resetPolicyContext(oldContextId, contextId);
}
} catch (Throwable t) {
_logger.log(SEVERE, "jacc_policy_context_exception", t);
isAuthorized = false;
}
}
ejbInvocation.setAuth(isAuthorized);
if (auditManager.isAuditOn()) {
if (securityContext == null) {
securityContext = SecurityContext.getCurrent();
}
caller = securityContext.getCallerPrincipal().getName();
auditManager.ejbInvocation(caller, ejbName, ejbInvocation.method.toString(), isAuthorized);
}
if (isAuthorized && ejbInvocation.isWebService && !ejbInvocation.isPreInvokeDone()) {
preInvoke(ejbInvocation);
}
if (_logger.isLoggable(FINE)) {
_logger.fine(
"JACC: Access Control Decision Result: " + isAuthorized +
" EJBMethodPermission (Name) = " + permission.getName() +
" (Action) = " + permission.getActions() +
" (Caller) = " + caller);
}
return isAuthorized;
}
/**
* This method returns a boolean value indicating whether or not the caller is in the specified role.
*
* @param role role name in the form of java.lang.String
* @return A boolean true/false depending on whether or not the caller has the specified role.
*/
@Override
public boolean isCallerInRole(String role) {
/*
* In case of Run As - Should check isCallerInRole with respect to the old security context.
*/
boolean isCallerInRole = false;
if (_logger.isLoggable(FINE)) {
_logger.entering("EJBSecurityManager", "isCallerInRole", role);
}
EJBRoleRefPermission ejbRoleRefPermission = new EJBRoleRefPermission(ejbName, role);
SecurityContext securityContext;
if (runAs != null) {
securityContext = (SecurityContext) invocationManager.getCurrentInvocation().getOldSecurityContext();
} else {
securityContext = SecurityContext.getCurrent();
}
Set principalSet = securityContext != null ? securityContext.getPrincipalSet() : null;
ProtectionDomain prdm = getCachedProtectionDomain(principalSet, true);
String oldContextId = null;
try {
// set the policy context in the TLS.
oldContextId = setPolicyContext(this.contextId);
isCallerInRole = policy.implies(prdm, ejbRoleRefPermission);
} catch (Throwable t) {
_logger.log(Level.SEVERE, "jacc_is_caller_in_role_exception", t);
isCallerInRole = false;
} finally {
try {
resetPolicyContext(oldContextId, contextId);
} catch (Throwable ex) {
_logger.log(Level.SEVERE, "jacc_policy_context_exception", ex);
isCallerInRole = false;
}
}
if (_logger.isLoggable(Level.FINE)) {
_logger.fine("JACC: isCallerInRole Result: " + isCallerInRole + " EJBRoleRefPermission (Name) = " + ejbRoleRefPermission.getName() + " (Action) = "
+ ejbRoleRefPermission.getActions() + " (Codesource) = " + prdm.getCodeSource());
}
return isCallerInRole;
}
/**
* This method is similiar to the runMethod, except it keeps the semantics same as the one in reflection. On failure, if
* the exception is caused due to reflection, it returns the InvocationTargetException. This method is called from the
* containers for ejbTimeout, WebService and MDBs.
*
* @param beanClassMethod, the bean class method to be invoked
* @param isLocal, true if this invocation is through the local EJB view
* @param beanObject the object on which this method is to be invoked in this case the ejb,
* @param parameters the parameters for the method,
* @param c, the container instance can be a null value, where in the container will be queried to find its security
* manager.
* @return Object, the result of the execution of the method.
*/
@Override
public Object invoke(Method beanClassMethod, boolean isLocal, Object beanObject, Object[] parameters) throws Throwable {
// Optimization. Skip doAsPrivileged call if this is a local invocation and the target EJB
// uses caller identity or the System Security Manager is disabled.
//
// Still need to execute it within the target bean's policy context.
// see CR 6331550
if ((isLocal && getUsesCallerIdentity()) || System.getSecurityManager() == null) {
return runMethod(beanClassMethod, beanObject, parameters);
}
try {
return doAsPrivileged(()-> beanClassMethod.invoke(beanObject, parameters));
} catch (PrivilegedActionException pae) {
throw pae.getCause();
}
}
@Override
public void resetPolicyContext() {
try {
doPrivileged(new PrivilegedExceptionAction