| /******************************************************************************* |
| * Copyright (c) 2008, 2012 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 |
| * Connexta, LLC - evaluation cache implementation |
| *******************************************************************************/ |
| package org.eclipse.osgi.internal.permadmin; |
| |
| import java.security.Permission; |
| import java.security.PermissionCollection; |
| import java.util.Enumeration; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import org.eclipse.osgi.internal.permadmin.SecurityRow.Decision; |
| import org.osgi.service.condpermadmin.Condition; |
| |
| public class SecurityTable extends PermissionCollection { |
| private static final long serialVersionUID = -1800193310096318060L; |
| static final int GRANTED = 0x0001; |
| static final int DENIED = 0x0002; |
| static final int ABSTAIN = 0x0004; |
| static final int POSTPONED = 0x0008; |
| |
| private static final int MUTABLE = 0x0016; |
| |
| private final SecurityRow[] rows; |
| private final SecurityAdmin securityAdmin; |
| |
| private final transient Map<EvaluationCacheKey, Integer> evaluationCache = new ConcurrentHashMap<>(10000); |
| |
| public SecurityTable(SecurityAdmin securityAdmin, SecurityRow[] rows) { |
| if (rows == null) |
| throw new NullPointerException("rows cannot be null!!"); //$NON-NLS-1$ |
| this.rows = rows; |
| this.securityAdmin = securityAdmin; |
| } |
| |
| boolean isEmpty() { |
| return rows.length == 0; |
| } |
| |
| int evaluate(BundlePermissions bundlePermissions, Permission permission) { |
| if (bundlePermissions == null) { |
| return ABSTAIN; |
| } |
| EvaluationCacheKey evaluationCacheKey = new EvaluationCacheKey(bundlePermissions, permission); |
| if (isEmpty()) { |
| evaluationCache.put(evaluationCacheKey, ABSTAIN); |
| return ABSTAIN; |
| } |
| |
| //can't short-circuit early, so try cache |
| Integer result = evaluationCache.get(evaluationCacheKey); |
| boolean hasMutable = false; |
| if (result != null) { |
| hasMutable = (result & MUTABLE) == MUTABLE; |
| if (!hasMutable) { |
| return result; |
| } |
| } |
| //cache miss or has mutable rows |
| boolean postponed = false; |
| Decision[] results = new Decision[rows.length]; |
| int immediateDecisionIdx = -1; |
| // evaluate each row |
| for (int i = 0; i < rows.length && immediateDecisionIdx == -1; i++) { |
| if (result == null) { |
| //check all conditions for any that are mutable, this will turn off the cache |
| hasMutable |= checkMutable(bundlePermissions, evaluationCacheKey, rows[i]); |
| } |
| try { |
| results[i] = rows[i].evaluate(bundlePermissions, permission); |
| } catch (Exception e) { |
| // TODO log? |
| results[i] = SecurityRow.DECISION_ABSTAIN; |
| } |
| if ((results[i].decision & ABSTAIN) == ABSTAIN) |
| continue; // ignore this row and continue to next row |
| if ((results[i].decision & POSTPONED) == POSTPONED) { |
| // row is postponed; we can no longer return quickly on a denied decision |
| postponed = true; |
| continue; // continue to next row |
| } |
| if (!postponed) { |
| // no postpones encountered yet; we can return the decision quickly |
| if (!hasMutable) { |
| evaluationCache.put(evaluationCacheKey, results[i].decision); |
| } |
| return results[i].decision; // return GRANTED or DENIED |
| } |
| // got an immediate answer; but it is after a postponed condition. |
| // no need to process the rest of the rows |
| immediateDecisionIdx = i; |
| } |
| Integer immediateDecision = handlePostponedConditions(evaluationCacheKey, hasMutable, postponed, results, immediateDecisionIdx); |
| if (immediateDecision != null) |
| return immediateDecision; |
| int finalDecision = postponed ? POSTPONED : ABSTAIN; |
| if (!hasMutable && (finalDecision & POSTPONED) != POSTPONED) { |
| evaluationCache.put(evaluationCacheKey, finalDecision); |
| } |
| return finalDecision; |
| } |
| |
| private boolean checkMutable(BundlePermissions bundlePermissions, EvaluationCacheKey evaluationCacheKey, SecurityRow row) { |
| Condition[] conditions = row.getConditions(bundlePermissions); |
| if (conditions != null) { |
| for (Condition condition : conditions) { |
| if (condition != null && condition.isMutable()) { |
| evaluationCache.put(evaluationCacheKey, MUTABLE); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private Integer handlePostponedConditions(EvaluationCacheKey evaluationCacheKey, boolean hasMutable, boolean postponed, Decision[] results, int immediateDecisionIdx) { |
| if (postponed) { |
| int immediateDecision = immediateDecisionIdx < 0 ? DENIED : results[immediateDecisionIdx].decision; |
| // iterate over all postponed conditions; |
| // if they all provide the same decision as the immediate decision then return the immediate decision |
| boolean allSameDecision = true; |
| int i = immediateDecisionIdx < 0 ? results.length - 1 : immediateDecisionIdx - 1; |
| for (; i >= 0 && allSameDecision; i--) { |
| if ((results[i].decision & POSTPONED) == POSTPONED) { |
| if ((results[i].decision & immediateDecision) == 0) |
| allSameDecision = false; |
| else |
| results[i] = SecurityRow.DECISION_ABSTAIN; // we can clear postpones with the same decision as the immediate |
| } |
| } |
| if (allSameDecision) { |
| if (!hasMutable) { |
| evaluationCache.put(evaluationCacheKey, immediateDecision); |
| } |
| return immediateDecision; |
| } |
| |
| // we now are forced to postpone; we need to also remember the postponed decisions and |
| // the immediate decision if there is one. |
| EquinoxSecurityManager equinoxManager = securityAdmin.getSupportedSecurityManager(); |
| if (equinoxManager == null) { |
| // TODO this is really an error condition. |
| // This should never happen. We checked for a supported manager when the row was postponed |
| if (!hasMutable) { |
| evaluationCache.put(evaluationCacheKey, ABSTAIN); |
| } |
| return ABSTAIN; |
| } |
| equinoxManager.addConditionsForDomain(results); |
| } |
| return null; |
| } |
| |
| void clearEvaluationCache() { |
| evaluationCache.clear(); |
| } |
| |
| SecurityRow getRow(int i) { |
| return rows.length <= i || i < 0 ? null : rows[i]; |
| } |
| |
| SecurityRow getRow(String name) { |
| for (SecurityRow row : rows) { |
| if (name.equals(row.getName())) { |
| return row; |
| } |
| } |
| return null; |
| } |
| |
| SecurityRow[] getRows() { |
| return rows; |
| } |
| |
| String[] getEncodedRows() { |
| String[] encoded = new String[rows.length]; |
| for (int i = 0; i < rows.length; i++) |
| encoded[i] = rows[i].getEncoded(); |
| return encoded; |
| } |
| |
| @Override |
| public void add(Permission permission) { |
| throw new SecurityException(); |
| } |
| |
| @Override |
| public Enumeration<Permission> elements() { |
| return BundlePermissions.EMPTY_ENUMERATION; |
| } |
| |
| @Override |
| public boolean implies(Permission permission) { |
| return (evaluate(null, permission) & SecurityTable.GRANTED) != 0; |
| } |
| } |