
com.sun.enterprise.security.provider.PolicyConfigurationImpl Maven / Gradle / Ivy
/*
* 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 [2018] [Payara Foundation and/or its affiliates]
package com.sun.enterprise.security.provider;
import static com.sun.enterprise.security.provider.PolicyParser.PrincipalEntry.WILDCARD_CLASS;
import static com.sun.enterprise.security.provider.PolicyParser.PrincipalEntry.WILDCARD_NAME;
import static com.sun.logging.LogDomains.SECURITY_LOGGER;
import static java.lang.System.getSecurityManager;
import static java.security.AccessController.doPrivileged;
import static java.util.Collections.list;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINEST;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.Security;
import java.security.SecurityPermission;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.jacc.PolicyConfiguration;
import javax.security.jacc.PolicyContextException;
import org.glassfish.deployment.common.SecurityRoleMapper;
import org.glassfish.deployment.common.SecurityRoleMapperFactory;
import com.sun.enterprise.security.SecurityRoleMapperFactoryGen;
import com.sun.enterprise.security.provider.PolicyParser.GrantEntry;
import com.sun.enterprise.security.provider.PolicyParser.ParsingException;
import com.sun.enterprise.security.provider.PolicyParser.PermissionEntry;
import com.sun.enterprise.security.provider.PolicyParser.PrincipalEntry;
import com.sun.enterprise.util.LocalStringManagerImpl;
import sun.security.provider.PolicyFile;
/**
* Implementation of Jacc PolicyConfiguration Interface
*
* @author Harpreet Singh ([email protected])
* @author Ron Monzillo
*/
public class PolicyConfigurationImpl implements PolicyConfiguration {
private static Logger logger = Logger.getLogger(SECURITY_LOGGER);
private static LocalStringManagerImpl localStrings = new LocalStringManagerImpl(PolicyConfigurationImpl.class);
private static final String policySuffix = ".policy";
private static final String PROVIDER_URL = "policy.url.";
private static final Class>[] permissionParams = { String.class, String.class };
// These are the 3 possible states that this object can be in.
public static final int OPEN_STATE = 0;
public static final int INSERVICE_STATE = 2;
public static final int DELETED_STATE = 3;
// Package access
String CONTEXT_ID;
// Excluded permissions
private Permissions excludedPermissions;
// Unchecked permissions
private Permissions uncheckedPermissions;
// Permissions mapped to roles.
private Map roleToPermissionsMap;
// New instances are created in the open state.
protected int state = OPEN_STATE;
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true);
private Lock rLock = rwLock.readLock();
private Lock wLock = rwLock.writeLock();
// This bit is used to optimize commit processing
private boolean writeOnCommit = true;
// This bit is used to optimize refresh processing
private boolean wasRefreshed;
private Policy policy;
private String policyUrlValue;
// Policy file mod times
private long[] lastModTimes = new long[2];
private final Object refreshLock = new Object();
private String repository;
private Permission setPolicyPermission = new SecurityPermission("setPolicy");
private PolicyConfigurationFactoryImpl configurationFactory;
protected PolicyConfigurationImpl(String contextId, PolicyConfigurationFactoryImpl configurationFactory) {
CONTEXT_ID = contextId;
this.configurationFactory = configurationFactory;
repository = configurationFactory.getRepository();
initialize(true, true, false);
}
/**
* @param applicationPolicyDirectory, need to have absolute path
* @param open, then mark state as open
* @param remove, then remove any existing policy statements
*/
protected PolicyConfigurationImpl(File applicationPolicyDirectory, boolean open, boolean remove, PolicyConfigurationFactoryImpl configurationFactory) {
this.configurationFactory = configurationFactory;
CONTEXT_ID = applicationPolicyDirectory.getParentFile().getName() + '/' + applicationPolicyDirectory.getName();
repository = configurationFactory.getRepository();
String policyFileName = getPolicyFileName(true);
File policyFile = new File(policyFileName);
if (!policyFile.exists()) {
String defMsg = "Unable to open Policy file: " + policyFileName;
logger.log(SEVERE, localStrings.getLocalString("pc.file_not_found", defMsg, new Object[] { policyFileName }));
throw new RuntimeException(defMsg);
}
initialize(open, remove, true);
}
/**
* This method returns this object's policy context identifier.
*
* @return this object's policy context identifier.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the getContextID method signature. The exception thrown by the implementation class will be
* encapsulated (during construction) in the thrown PolicyContextException.
*/
@Override
public String getContextID() throws PolicyContextException {
checkSetPolicyPermission();
return CONTEXT_ID;
}
/**
* Used to add permissions to a named role in this PolicyConfiguration. If the named Role does not exist in the
* PolicyConfiguration, it is created as a result of the call to this function.
*
* It is the job of the Policy provider to ensure that all the permissions added to a role are granted to principals
* "mapped to the role".
*
*
* @param roleName the name of the Role to which the permissions are to be added.
*
* @param permissions the collection of permissions to be added to the role. The collection may be either a homogenous
* or heterogenous collection.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws java.lang.UnsupportedOperationException if the state of the policy context whose interface is this
* PolicyConfiguration Object is "deleted" or "inService" when this method is called.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the addToRole method signature. The exception thrown by the implementation class will be
* encapsulated (during construction) in the thrown PolicyContextException.
*/
@Override
public void addToRole(String roleName, PermissionCollection permissions) throws PolicyContextException {
assertStateIsOpen();
if (roleName != null && permissions != null) {
checkSetPolicyPermission();
for (Permission permission : list(permissions.elements())) {
getRolePermissions(roleName).add(permission);
writeOnCommit = true;
}
}
}
/**
* Used to add a single permission to a named role in this PolicyConfiguration. If the named Role does not exist in the
* PolicyConfiguration, it is created as a result of the call to this function.
*
* It is the job of the Policy provider to ensure that all the permissions added to a role are granted to principals
* "mapped to the role".
*
*
* @param roleName the name of the Role to which the permission is to be added.
*
* @param permission the permission to be added to the role.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws java.lang.UnsupportedOperationException if the state of the policy context whose interface is this
* PolicyConfiguration Object is "deleted" or "inService" when this method is called.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the addToRole method signature. The exception thrown by the implementation class will be
* encapsulated (during construction) in the thrown PolicyContextException.
*/
@Override
public void addToRole(String roleName, Permission permission) throws PolicyContextException {
assertStateIsOpen();
if (roleName != null && permission != null) {
checkSetPolicyPermission();
getRolePermissions(roleName).add(permission);
writeOnCommit = true;
}
}
/**
* Used to add unchecked policy statements to this PolicyConfiguration.
*
*
* @param permissions the collection of permissions to be added as unchecked policy statements. The collection may be
* either a homogenous or heterogenous collection.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws java.lang.UnsupportedOperationException if the state of the policy context whose interface is this
* PolicyConfiguration Object is "deleted" or "inService" when this method is called.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the addToUncheckedPolicy method signature. The exception thrown by the implementation class will be
* encapsulated (during construction) in the thrown PolicyContextException.
*/
@Override
public void addToUncheckedPolicy(PermissionCollection permissions) throws PolicyContextException {
assertStateIsOpen();
if (permissions != null) {
checkSetPolicyPermission();
for (Permission permission : list(permissions.elements())) {
getUncheckedPermissions().add(permission);
writeOnCommit = true;
}
}
}
/**
* Used to add a single unchecked policy statement to this PolicyConfiguration.
*
*
* @param permission the permission to be added to the unchecked policy statements.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws java.lang.UnsupportedOperationException if the state of the policy context whose interface is this
* PolicyConfiguration Object is "deleted" or "inService" when this method is called.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the addToUncheckedPolicy method signature. The exception thrown by the implementation class will be
* encapsulated (during construction) in the thrown PolicyContextException.
*/
@Override
public void addToUncheckedPolicy(Permission permission) throws PolicyContextException {
assertStateIsOpen();
if (permission != null) {
checkSetPolicyPermission();
getUncheckedPermissions().add(permission);
writeOnCommit = true;
}
}
/**
* Used to add excluded policy statements to this PolicyConfiguration.
*
*
* @param permissions the collection of permissions to be added to the excluded policy statements. The collection may be
* either a homogenous or heterogenous collection.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws java.lang.UnsupportedOperationException if the state of the policy context whose interface is this
* PolicyConfiguration Object is "deleted" or "inService" when this method is called.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the addToExcludedPolicy method signature. The exception thrown by the implementation class will be
* encapsulated (during construction) in the thrown PolicyContextException.
*/
@Override
public void addToExcludedPolicy(PermissionCollection permissions) throws PolicyContextException {
assertStateIsOpen();
if (permissions != null) {
checkSetPolicyPermission();
for (Permission permission : list(permissions.elements())) {
getExcludedPermissions().add(permission);
writeOnCommit = true;
}
}
}
/**
* Used to add a single excluded policy statement to this PolicyConfiguration.
*
*
* @param permission the permission to be added to the excluded policy statements.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission. fa
*
* @throws java.lang.UnsupportedOperationException if the state of the policy context whose interface is this
* PolicyConfiguration Object is "deleted" or "inService" when this method is called.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the addToExcludedPolicy method signature. The exception thrown by the implementation class will be
* encapsulated (during construction) in the thrown PolicyContextException.
*/
@Override
public void addToExcludedPolicy(Permission permission) throws PolicyContextException {
assertStateIsOpen();
if (permission != null) {
checkSetPolicyPermission();
getExcludedPermissions().add(permission);
writeOnCommit = true;
}
}
/**
* Used to remove a role and all its permissions from this PolicyConfiguration.
*
*
* @param roleName the name of the role to remove from this PolicyConfiguration. If the value of the roleName parameter
* is "*" and no role with name "*" exists in this PolicyConfiguration, then all roles must be removed from this
* PolicyConfiguration.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws java.lang.UnsupportedOperationException if the state of the policy context whose interface is this
* PolicyConfiguration Object is "deleted" or "inService" when this method is called.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the removeRole method signature. The exception thrown by the implementation class will be
* encapsulated (during construction) in the thrown PolicyContextException.
*/
@Override
public void removeRole(String roleName) throws PolicyContextException {
assertStateIsOpen();
if (roleName != null && roleToPermissionsMap != null) {
checkSetPolicyPermission();
if (roleToPermissionsMap.remove(roleName) != null) {
if (roleToPermissionsMap.isEmpty()) {
roleToPermissionsMap = null;
}
writeOnCommit = true;
} else if (roleName.equals("*")) {
boolean wasEmpty = roleToPermissionsMap.isEmpty();
if (!wasEmpty) {
roleToPermissionsMap.clear();
}
roleToPermissionsMap = null;
if (!wasEmpty) {
writeOnCommit = true;
}
}
}
}
/**
* Used to remove any unchecked policy statements from this PolicyConfiguration.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws java.lang.UnsupportedOperationException if the state of the policy context whose interface is this
* PolicyConfiguration Object is "deleted" or "inService" when this method is called.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the removeUncheckedPolicy method signature. The exception thrown by the implementation class will be
* encapsulated (during construction) in the thrown PolicyContextException.
*/
@Override
public void removeUncheckedPolicy() throws PolicyContextException {
assertStateIsOpen();
checkSetPolicyPermission();
if (uncheckedPermissions != null) {
uncheckedPermissions = null;
writeOnCommit = true;
}
}
/**
* Used to remove any excluded policy statements from this PolicyConfiguration.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws java.lang.UnsupportedOperationException if the state of the policy context whose interface is this
* PolicyConfiguration Object is "deleted" or "inService" when this method is called.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the removeExcludedPolicy method signature. The exception thrown by the implementation class will be
* encapsulated (during construction) in the thrown PolicyContextException.
*/
@Override
public void removeExcludedPolicy() throws PolicyContextException {
assertStateIsOpen();
checkSetPolicyPermission();
if (excludedPermissions != null) {
excludedPermissions = null;
writeOnCommit = true;
}
}
/**
* This method is used to set to "inService" the state of the policy context whose interface is this PolicyConfiguration
* Object. Only those policy contexts whose state is "inService" will be included in the policy contexts processed by
* the Policy.refresh method. A policy context whose state is "inService" may be returned to the "open" state by calling
* the getPolicyConfiguration method of the PolicyConfiguration factory with the policy context identifier of the policy
* context.
*
* When the state of a policy context is "inService", calling any method other than commit, delete, getContextID, or
* inService on its PolicyConfiguration Object will cause an UnsupportedOperationException to be thrown.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws java.lang.UnsupportedOperationException if the state of the policy context whose interface is this
* PolicyConfiguration Object is "deleted" when this method is called.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the commit method signature. The exception thrown by the implementation class will be encapsulated
* (during construction) in the thrown PolicyContextException.
*/
@Override
public void commit() throws PolicyContextException {
synchronized (refreshLock) {
if (stateIs(DELETED_STATE)) {
String defMsg = "Cannot perform Operation on a deleted PolicyConfiguration";
logger.log(WARNING, localStrings.getLocalString("pc.invalid_op_for_state_delete", defMsg));
throw new UnsupportedOperationException(defMsg);
} else {
try {
checkSetPolicyPermission();
if (stateIs(OPEN_STATE)) {
generatePermissions();
setState(INSERVICE_STATE);
}
} catch (Exception e) {
String defMsg = "commit fail for contextod " + CONTEXT_ID;
logger.log(SEVERE, localStrings.getLocalString("pc.commit_failure", defMsg, new Object[] { CONTEXT_ID, e }));
throw new PolicyContextException(e);
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("JACC Policy Provider: PC.commit " + CONTEXT_ID);
}
}
}
}
/**
* Creates a relationship between this configuration and another such that they share the same principal-to-role
* mappings. PolicyConfigurations are linked to apply a common principal-to-role mapping to multiple seperately
* manageable PolicyConfigurations, as is required when an application is composed of multiple modules.
*
* Note that the policy statements which comprise a role, or comprise the excluded or unchecked policy collections in a
* PolicyConfiguration are unaffected by the configuration being linked to another.
*
*
* @param link a reference to a different PolicyConfiguration than this PolicyConfiguration.
*
* The relationship formed by this method is symetric, transitive and idempotent. If the argument PolicyConfiguration
* does not have a different Policy context identifier than this PolicyConfiguration no relationship is formed, and an
* exception, as described below, is thrown.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws java.lang.UnsupportedOperationException if the state of the policy context whose interface is this
* PolicyConfiguration Object is "deleted" or "inService" when this method is called.
*
* @throws java.lang.IllegalArgumentException if called with an argument PolicyConfiguration whose Policy context is
* equivalent to that of this PolicyConfiguration.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the linkConfiguration method signature. The exception thrown by the implementation class will be
* encapsulated (during construction) in the thrown PolicyContextException.
*/
@Override
public void linkConfiguration(PolicyConfiguration link) throws PolicyContextException {
assertStateIsOpen();
String linkId = link.getContextID();
if (CONTEXT_ID.equals(linkId)) {
String defMsg = "Operation attempted to link PolicyConfiguration to itself.";
logger.log(Level.WARNING, localStrings.getLocalString("pc.unsupported_link_operation", defMsg));
throw new IllegalArgumentException(defMsg);
}
checkSetPolicyPermission();
updateLinkTable(linkId);
}
/**
* Causes all policy statements to be deleted from this PolicyConfiguration and sets its internal state such that
* calling any method, other than delete, getContextID, or inService on the PolicyConfiguration will be rejected and
* cause an UnsupportedOperationException to be thrown.
*
* This operation has no affect on any linked PolicyConfigurations other than removing any links involving the deleted
* PolicyConfiguration.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the delete method signature. The exception thrown by the implementation class will be encapsulated
* (during construction) in the thrown PolicyContextException.
*/
@Override
public void delete() throws PolicyContextException {
checkSetPolicyPermission();
synchronized (refreshLock) {
try {
removePolicy();
} finally {
setState(DELETED_STATE);
}
}
}
/**
* This method is used to determine if the policy context whose interface is this PolicyConfiguration Object is in the
* "inService" state.
*
* @return true if the state of the associated policy context is "inService"; false otherwise.
*
* @throws java.lang.SecurityException if called by an AccessControlContext that has not been granted the "setPolicy"
* SecurityPermission.
*
* @throws javax.security.jacc.PolicyContextException if the implementation throws a checked exception that has not been
* accounted for by the inService method signature. The exception thrown by the implementation class will be
* encapsulated (during construction) in the thrown PolicyContextException.
*/
@Override
public boolean inService() throws PolicyContextException {
checkSetPolicyPermission();
boolean isInService = stateIs(INSERVICE_STATE);
if (logger.isLoggable(FINE)) {
logger.fine("JACC Policy Provider: inService: " + (isInService ? "true " : "false ") + CONTEXT_ID);
}
return isInService;
}
// ### The following methods are implementation specific
protected void checkSetPolicyPermission() {
SecurityManager securityManager = getSecurityManager();
if (securityManager != null) {
securityManager.checkPermission(setPolicyPermission);
}
}
// Get the policy object
protected Policy getPolicy() {
if (stateIs(INSERVICE_STATE)) {
return policy;
}
if (logger.isLoggable(FINEST)) {
logger.finest("JACC Policy Provider: getPolicy (" + CONTEXT_ID + ") is NOT in service");
}
return null;
}
// get the policy object
protected Permissions getExcludedPolicy() {
return stateIs(INSERVICE_STATE) ? this.excludedPermissions : null;
}
// called by PolicyWrapper to refresh context specific policy object.
protected void refresh(boolean force) {
synchronized (refreshLock) {
if (stateIs(INSERVICE_STATE) && (wasRefreshed == false || force || filesChanged())) {
// find open policy.url
int i = 0;
String value = null;
String urlKey = null;
while (true) {
urlKey = PROVIDER_URL + (++i);
value = getSecurityProperty(urlKey);
if (value == null || value.equals("")) {
break;
}
}
try {
setSecurityProperty(urlKey, policyUrlValue);
if (fileChanged(false)) {
excludedPermissions = loadExcludedPolicy();
}
// capture time before load, to ensure that we
// have a time that precedes load
captureFileTime(true);
if (policy == null) {
policy = getNewPolicy();
} else {
policy.refresh();
if (logger.isLoggable(FINE)) {
logger.fine("JACC Policy Provider: Called Policy.refresh on contextId: " + CONTEXT_ID + " policyUrlValue was "
+ policyUrlValue);
}
}
wasRefreshed = true;
} finally {
// can't setProperty back to null, workaround is to
// use empty string
setSecurityProperty(urlKey, "");
}
}
}
}
private Policy getNewPolicy() {
Object wrapper = Policy.getPolicy();
if (wrapper != null && wrapper instanceof JDKPolicyFileWrapper) {
return ((JDKPolicyFileWrapper) wrapper).getNewPolicy();
}
return new PolicyFile();
}
private void captureFileTime(boolean granted) {
lastModTimes[granted ? 1 : 0] = new File(getPolicyFileName(granted)).lastModified();
}
private boolean filesChanged() {
return fileChanged(true) || fileChanged(false);
}
private boolean fileChanged(boolean granted) {
return _fileChanged(granted, new File(getPolicyFileName(granted)));
}
private boolean _fileChanged(boolean granted, File f) {
return !(lastModTimes[granted ? 1 : 0] == f.lastModified());
}
/**
* tests if policy file has arrived (via synchronization system). if File exists, also checks last modified time, in
* case file was not deleted on transition out of inservice state. Called when context is not inService to determine if
* it was needs to be transitioned because of file distribution.
*
* @param granted selects granted or excluded policy file
* @return true if new file has arrived.
*/
private boolean fileArrived(boolean granted) {
File policyFile = new File(getPolicyFileName(granted));
boolean hasFileArrived = policyFile.exists() && _fileChanged(granted, policyFile);
if (logger.isLoggable(FINE)) {
logger.fine(
"JACC Policy Provider: file arrival check" + " type: " + (granted ? "granted " : "excluded ") +
" arrived: " + hasFileArrived + " exists: " + policyFile.exists() +
" lastModified: " + policyFile.lastModified() + " storedTime: " + lastModTimes[granted ? 1 : 0] +
" state: " + (this.state == OPEN_STATE ? "open " : "deleted ") + CONTEXT_ID);
}
return hasFileArrived;
}
// initilaize the internal data structures.
// if open, then mark state as open
// if remove, then remove any existing policy statements
// if fromFile (and not remove), then mark state as in service,
// and not requiring write on commit
// if fromFile (and remove), then remove and mark state as open
protected void initialize(boolean open, boolean remove, boolean fromFile) {
synchronized (refreshLock) {
String name = getPolicyFileName(true);
if (open || remove) {
setState(OPEN_STATE);
} else {
setState(INSERVICE_STATE);
}
try {
if (remove) {
removePolicy();
}
policyUrlValue = sun.net.www.ParseUtil.fileToEncodedURL(new File(name)).toString();
if (fromFile && !remove) {
uncheckedPermissions = null;
roleToPermissionsMap = null;
excludedPermissions = loadExcludedPolicy();
initLinkTable();
captureFileTime(true);
writeOnCommit = false;
}
wasRefreshed = false;
} catch (MalformedURLException e) {
String defMsg = "Unable to convert Policy file Name to URL: " + name;
logger.log(Level.SEVERE, localStrings.getLocalString("pc.file_to_url", defMsg, new Object[] { name, e }));
throw new RuntimeException(defMsg);
}
}
}
private String getPolicyFileName(boolean granted) {
return granted ? getContextDirectoryName() + File.separator + "granted" + policySuffix
: getContextDirectoryName() + File.separator + "excluded" + policySuffix;
}
private String getContextDirectoryName() {
if (repository == null) {
throw new RuntimeException("JACC Policy provider: repository not initialized");
}
return configurationFactory.getContextDirectoryName(CONTEXT_ID);
}
// remove the directory used ot hold the context's policy files
private void removePolicyContextDirectory() {
String directoryName = getContextDirectoryName();
File contextDirectory = new File(directoryName);
if (contextDirectory.exists()) {
// WORKAROUND: due to existence of timestamp file in given directory
// for SE/EE synchronization
File[] files = contextDirectory.listFiles();
if (files != null && files.length > 0) {
for (int i = 0; i < files.length; i++) {
if (!files[i].delete()) {
String msg = localStrings.getLocalString("pc.file_delete_error", "Error while deleting policy file");
logger.log(SEVERE, msg);
throw new RuntimeException(msg);
}
}
}
// WORKAROUND: End
if (!contextDirectory.delete()) {
String defMsg = "Failure removing policy context directory: " + directoryName;
logger.log(SEVERE, localStrings.getLocalString("pc.file_delete_error", defMsg));
throw new RuntimeException(defMsg);
} else if (logger.isLoggable(FINE)) {
logger.fine("JACC Policy Provider: Policy context directory removed: " + directoryName);
}
File appDir = contextDirectory.getParentFile();
// WORKAROUND: due to existence of timestamp file in given directory
// for SE/EE synchronization
File[] fs = appDir.listFiles();
if (fs != null && fs.length > 0) {
boolean hasDir = false;
for (int i = 0; i < fs.length; i++) {
if (fs[i].isDirectory()) {
hasDir = true;
break;
}
}
if (!hasDir) {
for (int i = 0; i < fs.length; i++) {
fs[i].delete();
}
}
}
// WORKAROUND: End
File[] moduleDirs = appDir.listFiles();
if (moduleDirs == null || moduleDirs.length == 0) {
if (!appDir.delete()) {
String defMsg = "Failure removing policy context directory: " + appDir;
logger.log(Level.SEVERE, localStrings.getLocalString("pc.file_delete_error", defMsg));
throw new RuntimeException(defMsg);
}
}
}
}
// Remove the external (file) policy statements.
private void removePolicyFile(boolean granted) {
String fileName = getPolicyFileName(granted);
File policyFile = new File(fileName);
if (policyFile.exists()) {
if (!policyFile.delete()) {
String defMsg = "Failure removing policy file: " + fileName;
logger.log(Level.SEVERE, localStrings.getLocalString("pc.file_delete_error", defMsg, new Object[] { fileName }));
throw new RuntimeException(defMsg);
}
if (logger.isLoggable(FINE)) {
logger.fine("JACC Policy Provider: Policy file removed: " + fileName);
}
}
}
// remove the internal and external (file) policy statements.
private void removePolicy() {
excludedPermissions = null;
uncheckedPermissions = null;
roleToPermissionsMap = null;
removePolicyFile(true);
removePolicyFile(false);
removePolicyContextDirectory();
initLinkTable();
policy = null;
writeOnCommit = true;
}
private void initLinkTable() {
synchronized (refreshLock) {
// Get the linkSet corresponding to this context.
Set linkSet = configurationFactory.getLinkTable().get(CONTEXT_ID);
// Remove this context id from the linkSet (which may be shared
// with other contexts), and unmap the linkSet form this context.
if (linkSet != null) {
linkSet.remove(CONTEXT_ID);
configurationFactory.getLinkTable().remove(CONTEXT_ID);
}
// Create a new linkSet with onlythis context id, and put it in the table.
linkSet = new HashSet();
linkSet.add(CONTEXT_ID);
configurationFactory.getLinkTable().put(CONTEXT_ID, linkSet);
}
}
private void updateLinkTable(String otherId) {
synchronized (refreshLock) {
// Get the linkSet corresponding to this context
Set thisLinkSet = configurationFactory.getLinkTable().get(CONTEXT_ID);
// Get the linkSet corresponding to the context being linked to this
Set otherLinkSet = configurationFactory.getLinkTable().get(otherId);
if (otherLinkSet == null) {
logger.log(SEVERE, "pc.invalid_link_target", otherId);
throw new RuntimeException("Linked policy configuration (" + otherId + ") does not exist");
}
// For each context (id) linked to the context being linked to this
for (String otherContextId : otherLinkSet) {
// Add the id to this linkSet
thisLinkSet.add(otherContextId);
// Replace the linkset mapped to all the contexts being linked
// to this context, with this linkset.
configurationFactory.getLinkTable().put(otherContextId, thisLinkSet);
}
}
}
private void setState(int stateValue) {
wLock.lock();
try {
this.state = stateValue;
} finally {
wLock.unlock();
}
}
private boolean _stateIs(int stateValue) {
rLock.lock();
try {
return state == stateValue;
} finally {
rLock.unlock();
}
}
/**
* checks if PolicyContex is in agrument state. Detects implicpit state changes resulting from distribution of policy
* files by synchronization system.
*
* @param stateValue state the context is tested for
* @return true if in state.
*/
private boolean stateIs(int stateValue) {
boolean inState = _stateIs(stateValue);
if (stateValue == INSERVICE_STATE && !inState) {
if (fileArrived(true) || fileArrived(false)) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("JACC Policy Provider: file arrived transition to inService: " + " state: "
+ (this.state == OPEN_STATE ? "open " : "deleted ") + CONTEXT_ID);
}
// initialize(!open,!remove,fromFile)
initialize(false, false, true);
}
inState = _stateIs(INSERVICE_STATE);
}
return inState;
}
private void assertStateIsOpen() {
if (!stateIs(OPEN_STATE)) {
String defMsg = "Operation invoked on closed or deleted PolicyConfiguration.";
logger.log(WARNING, localStrings.getLocalString("pc.op_requires_state_open", defMsg));
throw new UnsupportedOperationException(defMsg);
}
}
private Permissions getUncheckedPermissions() {
if (uncheckedPermissions == null) {
uncheckedPermissions = new Permissions();
}
return uncheckedPermissions;
}
private Permissions getExcludedPermissions() {
if (excludedPermissions == null) {
excludedPermissions = new Permissions();
}
return excludedPermissions;
}
private Permissions getRolePermissions(String roleName) {
if (roleToPermissionsMap == null) {
roleToPermissionsMap = new HashMap<>();
}
return roleToPermissionsMap.computeIfAbsent(roleName, key -> new Permissions());
}
// This method workarounds a bug in PolicyParser.write(...).
private String escapeName(String name) {
return (name != null && name.indexOf('"') > 0) ? name.replaceAll("\"", "\\\\\"") : name;
}
private void generatePermissions() throws java.io.FileNotFoundException, java.io.IOException {
// Optimization - return immediately if the rules have not changed
if (!writeOnCommit) {
return;
}
// Otherwise proceed to write policy file
Map roleToSubjectMap = getRoleToSubjectMap();
// When there are role based permissions, a role mapper is required
checkNotNullWhenNeeded(roleToSubjectMap);
PolicyParser parser = new PolicyParser(false);
// Add unchecked grants to parser
if (uncheckedPermissions != null) {
addUncheckedGrantsToParser(parser);
}
// Add role based grants to parser
if (roleToPermissionsMap != null) {
addRoleBasedGrantsToParser(roleToSubjectMap, parser);
}
writeOnCommit = createPolicyFile(true, parser, writeOnCommit);
// Add excluded permissions to a new, excluded, parser
if (excludedPermissions != null) {
PolicyParser excludedParser = new PolicyParser(false);
addExcludedPermissionsToParser(excludedParser);
writeOnCommit = createPolicyFile(false, excludedParser, writeOnCommit);
}
if (!writeOnCommit) {
wasRefreshed = false;
}
}
private Map getRoleToSubjectMap() {
if (roleToPermissionsMap == null) {
return null;
}
Map roleToSubjectMap = null;
SecurityRoleMapperFactory factory = SecurityRoleMapperFactoryGen.getSecurityRoleMapperFactory();
// Make sure a role to subject map has been defined for the Policy Context
if (factory != null) {
// The role mapper is stored against the application naeme.
// For a web app get the appname for this contextid
SecurityRoleMapper securityRoleMapper = factory.getRoleMapper(CONTEXT_ID);
if (securityRoleMapper != null) {
roleToSubjectMap = securityRoleMapper.getRoleToSubjectMapping();
}
if (roleToSubjectMap != null) {
// Make sure all linked PCs have the same roleToSubjectMap
Set linkContextIds = configurationFactory.getLinkTable().get(CONTEXT_ID);
if (linkContextIds != null) {
for (String contextId : linkContextIds) {
if (!CONTEXT_ID.equals(contextId)) {
SecurityRoleMapper otherSecurityRoleMapper = factory.getRoleMapper(contextId);
Map otherRoleToSubjectMap = null;
if (otherSecurityRoleMapper != null) {
otherRoleToSubjectMap = otherSecurityRoleMapper.getRoleToSubjectMapping();
}
if (otherRoleToSubjectMap != roleToSubjectMap) {
String defMsg = "Linked policy contexts have different roleToSubjectMaps (" + CONTEXT_ID + ")<->(" + contextId + ")";
logger.log(SEVERE,
localStrings.getLocalString(
"pc.linked_with_different_role_maps",
defMsg, new Object[] { CONTEXT_ID, contextId }));
throw new RuntimeException(defMsg);
}
}
}
}
}
}
return roleToSubjectMap;
}
private void checkNotNullWhenNeeded(Map roleToSubjectMap) {
if (roleToSubjectMap == null && roleToPermissionsMap != null) {
String defMsg = "This application has no role mapper factory defined";
logger.log(SEVERE,
localStrings.getLocalString(
"pc.role_map_not_defined_at_commit", defMsg, new Object[] { CONTEXT_ID }));
throw new RuntimeException(localStrings.getLocalString("enterprise.deployment.deployment.norolemapperfactorydefine", defMsg));
}
}
private void addUncheckedGrantsToParser(PolicyParser parser) {
List permissions = list(uncheckedPermissions.elements());
if (!permissions.isEmpty()) {
GrantEntry grant = new GrantEntry();
addPermissionsToGrant(permissions, grant);
parser.add(grant);
}
}
@SuppressWarnings("unchecked")
private void addRoleBasedGrantsToParser(Map roleToSubjectMap, PolicyParser parser) {
for (String roleName : roleToPermissionsMap.keySet()) {
boolean withPrincipals = false;
Permissions rolePermissions = getRolePermissions(roleName);
Subject subject = roleToSubjectMap.get(roleName);
if (subject != null) {
for (Principal principal : subject.getPrincipals()) {
if (principal != null) {
withPrincipals = true;
GrantEntry grant = new GrantEntry();
grant.principals.add(
new PrincipalEntry(
principal.getClass().
getName(),
escapeName(principal.getName())));
addPermissionsToGrant(rolePermissions, grant);
parser.add(grant);
} else {
logger.log(WARNING, localStrings.getLocalString("pc.non_principal_mapped_to_role",
"non principal mapped to role " + roleName, new Object[] { principal, roleName }));
}
}
}
/**
* JACC MR8 add grant for the any authenticated user role '**'
*/
if (!withPrincipals && ("**".equals(roleName))) {
withPrincipals = true;
GrantEntry grant = new GrantEntry();
grant.principals.add(
new PrincipalEntry(
WILDCARD_CLASS,
WILDCARD_NAME));
addPermissionsToGrant(rolePermissions, grant);
parser.add(grant);
if (logger.isLoggable(FINE)) {
logger.fine("JACC Policy Provider: added role grant for any authenticated user");
}
}
if (!withPrincipals) {
logger.log(WARNING, localStrings.getLocalString("pc.no_principals_mapped_to_role", "no principals mapped to role " + roleName,
new Object[] { roleName }));
}
}
}
private void addExcludedPermissionsToParser(PolicyParser parser) {
List permissions = list(excludedPermissions.elements());
if (!permissions.isEmpty()) {
GrantEntry grant = new GrantEntry();
addPermissionsToGrant(permissions, grant);
parser.add(grant);
}
}
private void addPermissionsToGrant(Permissions permissions, GrantEntry grant) {
addPermissionsToGrant(list(permissions.elements()), grant);
}
private void addPermissionsToGrant(List permissions, GrantEntry grant) {
for (Permission permission : permissions) {
grant.add(
new PermissionEntry(
permission.getClass().getName(),
permission.getName(),
permission.getActions()));
}
}
private void createPolicyContextDirectory() {
String contextDirectoryName = getContextDirectoryName();
File contectDirectory = new File(contextDirectoryName);
String defMsg = "unable to create policy context directory";
String msg = localStrings.getLocalString("pc.unable_to_create_context_directory", defMsg, new Object[] { contextDirectoryName });
if (contectDirectory.exists()) {
if (!contectDirectory.isDirectory()) {
logger.log(SEVERE, msg);
throw new RuntimeException(defMsg);
}
} else {
if (!contectDirectory.mkdirs()) {
logger.log(Level.SEVERE, msg);
throw new RuntimeException(defMsg);
}
}
}
// Returns false if write succeeds. otherwise returns input woc (i.e. writeOnCommit)
private boolean createPolicyFile(boolean granted, PolicyParser parser, boolean woc) throws IOException {
boolean result = woc;
createPolicyContextDirectory();
removePolicyFile(granted);
String name = getPolicyFileName(granted);
OutputStreamWriter writer = null;
try {
if (logger.isLoggable(Level.FINE)) {
logger.fine("JACC Policy Provider: Writing grant statements to policy file: " + name);
}
writer = new OutputStreamWriter(new FileOutputStream(name), "UTF-8");
parser.write(writer);
result = false;
} catch (FileNotFoundException fnfe) {
logger.log(Level.SEVERE, localStrings.getLocalString("pc.file_error", "file not found " + name, new Object[] { name, fnfe }));
throw fnfe;
} catch (IOException ioe) {
logger.log(SEVERE, localStrings.getLocalString("pc.file_write_error", "file IO error on file " + name, new Object[] { name, ioe }));
throw ioe;
} finally {
if (writer != null) {
try {
writer.close();
captureFileTime(granted);
} catch (Exception e) {
String defMsg = "Unable to close Policy file: " + name;
logger.log(Level.SEVERE, localStrings.getLocalString("pc.file_close_error", defMsg, new Object[] { name, e }));
throw new RuntimeException(defMsg);
}
}
}
return result;
}
private Permission loadPermission(String className, String name, String actions) {
Class clazz = null;
Permission permission = null;
try {
clazz = Class.forName(className);
Constructor c = clazz.getConstructor(permissionParams);
permission = (Permission) c.newInstance(new Object[] { name, actions });
} catch (Exception e) {
String defMsg = "PolicyConfiguration error loading permission";
String msg = localStrings.getLocalString("pc.permission_load_error", defMsg, new Object[] { className, e });
logger.log(Level.SEVERE, msg);
throw new RuntimeException(defMsg, e);
}
return permission;
}
@SuppressWarnings("unchecked")
private Permissions loadExcludedPolicy() {
Permissions result = null;
String name = getPolicyFileName(false);
PolicyParser parser = new PolicyParser(false);
FileReader reader = null;
try {
captureFileTime(false);
reader = new FileReader(name);
parser.read(reader);
} catch (FileNotFoundException fnf) {
// Just means there is no excluded Policy file, which
// is the typical case
parser = null;
} catch (IOException ioe) {
String defMsg = "Error reading Policy file: " + name;
logger.log(SEVERE, localStrings.getLocalString("pc.file_read_error", defMsg, new Object[] { name, ioe }));
throw new RuntimeException(defMsg);
} catch (ParsingException pe) {
String defMsg = "Unable to parse Policy file: " + name;
logger.log(SEVERE, localStrings.getLocalString("pc.policy_parsing_exception", defMsg, new Object[] { name, pe }));
throw new RuntimeException(defMsg);
} finally {
if (reader != null) {
try {
reader.close();
} catch (Exception e) {
String defMsg = "Unable to close Policy file: " + name;
logger.log(SEVERE, localStrings.getLocalString("pc.file_close_error", defMsg, new Object[] { name, e }));
throw new RuntimeException(defMsg);
}
}
}
if (parser != null) {
for (GrantEntry grant : list((Enumeration)parser.grantElements())) {
if (grant.codeBase != null || grant.signedBy != null || grant.principals.size() != 0) {
logger.log(WARNING, localStrings.getLocalString("pc.excluded_grant_context_ignored", "ignore excluded grant context", new Object[] { grant }));
} else {
for (PermissionEntry entry : list((Enumeration) grant.permissionEntries.elements())) {
Permission permission = loadPermission(entry.permission, entry.name, entry.action);
if (result == null) {
result = new Permissions();
}
result.add(permission);
}
}
}
}
return result;
}
private void setSecurityProperty(final String key, final String value) {
if (System.getSecurityManager() == null) {
Security.setProperty(key, value);
}
doPrivileged(new PrivilegedAction