| /******************************************************************************* |
| * Copyright (c) 2008, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.osgi.internal.permadmin; |
| |
| import java.security.*; |
| import java.util.*; |
| import org.eclipse.osgi.internal.permadmin.SecurityRow.Decision; |
| import org.osgi.service.condpermadmin.Condition; |
| |
| /** |
| * |
| * This security manager implements the ConditionalPermission processing for |
| * OSGi. It is to be used with ConditionalPermissionAdmin. |
| * |
| */ |
| public class EquinoxSecurityManager extends SecurityManager { |
| /* |
| * This is super goofy, but we need to make sure that the CheckContext and |
| * CheckPermissionAction classes load early. Otherwise, we run into problems later. |
| */ |
| static { |
| Class<?> c; |
| c = CheckPermissionAction.class; |
| c = CheckContext.class; |
| c.getName(); // to prevent compiler warnings |
| } |
| |
| static class CheckContext { |
| // A non zero depth indicates that we are doing a recursive permission check. |
| List<List<Decision[]>> depthCondSets = new ArrayList<>(2); |
| List<AccessControlContext> accs = new ArrayList<>(2); |
| List<Class<?>> CondClassSet; |
| |
| public int getDepth() { |
| return depthCondSets.size() - 1; |
| } |
| } |
| |
| static class CheckPermissionAction implements PrivilegedAction<Void> { |
| Permission perm; |
| Object context; |
| EquinoxSecurityManager fsm; |
| |
| CheckPermissionAction(EquinoxSecurityManager fsm, Permission perm, Object context) { |
| this.fsm = fsm; |
| this.perm = perm; |
| this.context = context; |
| } |
| |
| @Override |
| public Void run() { |
| fsm.internalCheckPermission(perm, context); |
| return null; |
| } |
| } |
| |
| private final ThreadLocal<CheckContext> localCheckContext = new ThreadLocal<>(); |
| |
| boolean addConditionsForDomain(Decision[] results) { |
| CheckContext cc = localCheckContext.get(); |
| if (cc == null) { |
| // We are being invoked in a weird way. Perhaps the ProtectionDomain is |
| // getting invoked directly. |
| return false; |
| } |
| List<Decision[]> condSets = cc.depthCondSets.get(cc.getDepth()); |
| if (condSets == null) { |
| condSets = new ArrayList<>(1); |
| cc.depthCondSets.set(cc.getDepth(), condSets); |
| } |
| condSets.add(results); |
| return true; |
| } |
| |
| boolean inCheckPermission() { |
| return localCheckContext.get() != null; |
| } |
| |
| @Override |
| public void checkPermission(Permission perm, Object context) { |
| AccessController.doPrivileged(new CheckPermissionAction(this, perm, context)); |
| } |
| |
| /** |
| * Gets the AccessControlContext currently being evaluated by |
| * the SecurityManager. |
| * |
| * @return the AccessControlContext currently being evaluated by the SecurityManager, or |
| * null if no AccessControlContext is being evaluated. Note: this method will |
| * return null if the permission check is being done directly on the AccessControlContext |
| * rather than the SecurityManager. |
| */ |
| public AccessControlContext getContextToBeChecked() { |
| CheckContext cc = localCheckContext.get(); |
| if (cc != null && cc.accs != null && !cc.accs.isEmpty()) |
| return cc.accs.get(cc.accs.size() - 1); |
| return null; |
| } |
| |
| void internalCheckPermission(Permission perm, Object context) { |
| AccessControlContext acc = (AccessControlContext) context; |
| CheckContext cc = localCheckContext.get(); |
| if (cc == null) { |
| cc = new CheckContext(); |
| localCheckContext.set(cc); |
| } |
| cc.depthCondSets.add(null); // initialize postponed condition set to null |
| cc.accs.add(acc); |
| try { |
| acc.checkPermission(perm); |
| // We want to pop the first set of postponed conditions and process them |
| List<Decision[]> conditionSets = cc.depthCondSets.get(cc.getDepth()); |
| if (conditionSets == null) |
| return; |
| // TODO the spec seems impossible to implement just doing the simple thing for now |
| Map<Class<? extends Condition>, Dictionary<Object, Object>> conditionDictionaries = new HashMap<>(); |
| for (Decision[] domainDecisions : conditionSets) { |
| boolean grant = false; |
| for (Decision domainDecision : domainDecisions) { |
| if (domainDecision == null) { |
| break; |
| } |
| if ((domainDecision.decision & SecurityTable.ABSTAIN) != 0) { |
| continue; |
| } |
| if ((domainDecision.decision & SecurityTable.POSTPONED) == 0) { |
| // hit an immediate decision; use it |
| if ((domainDecision.decision & SecurityTable.GRANTED) != 0) { |
| grant = true; |
| } |
| break; |
| } |
| int decision = getPostponedDecision(domainDecision, conditionDictionaries, cc); |
| if ((decision & SecurityTable.ABSTAIN) != 0) |
| continue; |
| if ((decision & SecurityTable.GRANTED) != 0) |
| grant = true; |
| break; |
| } |
| if (!grant) |
| // did not find a condition to grant the permission for this domain |
| throw new SecurityException("Conditions not satisfied"); //$NON-NLS-1$ |
| // continue to next domain |
| } |
| |
| } finally { |
| cc.depthCondSets.remove(cc.getDepth()); |
| cc.accs.remove(cc.accs.size() - 1); |
| } |
| } |
| |
| private int getPostponedDecision(Decision decision, Map<Class<? extends Condition>, Dictionary<Object, Object>> conditionDictionaries, CheckContext cc) { |
| Condition[] postponed = decision.postponed; |
| for (Condition postponedCond : postponed) { |
| Dictionary<Object, Object> condContext = conditionDictionaries.get(postponedCond.getClass()); |
| if (condContext == null) { |
| condContext = new Hashtable<>(); |
| conditionDictionaries.put(postponedCond.getClass(), condContext); |
| } |
| // prevent recursion into Condition |
| if (cc.CondClassSet == null) |
| cc.CondClassSet = new ArrayList<>(2); |
| if (cc.CondClassSet.contains(postponedCond.getClass())) { |
| return SecurityTable.ABSTAIN; |
| } |
| cc.CondClassSet.add(postponedCond.getClass()); |
| try { |
| // must call isMutable before calling isSatisfied according to the specification |
| boolean mutable = postponedCond.isMutable(); |
| boolean isSatisfied = postponedCond.isSatisfied(new Condition[]{postponedCond}, condContext); |
| decision.handleImmutable(postponedCond, isSatisfied, mutable); |
| if (!isSatisfied) |
| return SecurityTable.ABSTAIN; |
| } finally { |
| cc.CondClassSet.remove(postponedCond.getClass()); |
| } |
| } |
| // call postponed conditions are satisfied return the decision |
| return decision.decision; |
| } |
| |
| @Override |
| public void checkPermission(Permission perm) { |
| checkPermission(perm, getSecurityContext()); |
| } |
| |
| @Override |
| public Object getSecurityContext() { |
| return AccessController.getContext(); |
| } |
| } |