package org.eclipse.osgi.internal.permadmin;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.Permission;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.osgi.framework.Bundle;
import org.osgi.service.condpermadmin.Condition;
import org.osgi.service.condpermadmin.ConditionInfo;
import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
import org.osgi.service.permissionadmin.PermissionInfo;
public final class SecurityRow implements ConditionalPermissionInfo {
static final Class<?>[] conditionMethodArgs = new Class[] {Bundle.class, ConditionInfo.class};
static Condition[] ABSTAIN_LIST = new Condition[0];
static Condition[] SATISFIED_LIST = new Condition[0];
static final Decision DECISION_ABSTAIN = new Decision(SecurityTable.ABSTAIN, null, null, null);
static final Decision DECISION_GRANTED = new Decision(SecurityTable.GRANTED, null, null, null);
static final Decision DECISION_DENIED = new Decision(SecurityTable.DENIED, null, null, null);
private final SecurityAdmin securityAdmin;
private final String name;
private final ConditionInfo[] conditionInfos;
private final PermissionInfoCollection permissionInfoCollection;
private final boolean deny;
final Map<BundlePermissions, Condition[]> bundleConditions;
final Object bundleConditionsLock = new Object();
public SecurityRow(SecurityAdmin securityAdmin, String name, ConditionInfo[] conditionInfos, PermissionInfo[] permissionInfos, String decision) {
if (permissionInfos == null || permissionInfos.length == 0)
throw new IllegalArgumentException("It is invalid to have empty permissionInfos");
this.securityAdmin = securityAdmin;
this.conditionInfos = conditionInfos == null ? new ConditionInfo[0] : conditionInfos;
decision = decision.toLowerCase();
boolean d = ConditionalPermissionInfo.DENY.equals(decision);
boolean a = ConditionalPermissionInfo.ALLOW.equals(decision);
if (!(d | a))
throw new IllegalArgumentException("Invalid decision: " + decision);
this.deny = d;
this.name = name;
this.permissionInfoCollection = new PermissionInfoCollection(permissionInfos);
if (conditionInfos == null || conditionInfos.length == 0)
bundleConditions = null;
else
bundleConditions = new HashMap<>();
}
static SecurityRowSnapShot createSecurityRowSnapShot(String encoded) {
return (SecurityRowSnapShot) createConditionalPermissionInfo(null, encoded);
}
static SecurityRow createSecurityRow(SecurityAdmin securityAdmin, String encoded) {
return (SecurityRow) createConditionalPermissionInfo(securityAdmin, encoded);
}
private static ConditionalPermissionInfo createConditionalPermissionInfo(SecurityAdmin securityAdmin, String encoded) {
encoded = encoded.trim();
if (encoded.length() == 0)
throw new IllegalArgumentException("Empty encoded string is invalid");
char[] chars = encoded.toCharArray();
int end = encoded.length() - 1;
char lastChar = chars[end];
if (lastChar != '}' && lastChar != '"')
throw new IllegalArgumentException(encoded);
String encodedName = null;
if (lastChar == '"') {
if (chars.length < 2)
throw new IllegalArgumentException(encoded);
int endName = encoded.length() - 1;
int startName = endName - 1;
while (startName > 0) {
if (chars[startName] == '"') {
startName--;
if (startName > 0 && chars[startName] == '\\')
startName--;
else {
startName++;
break;
}
}
startName--;
}
if (chars[startName] != '"')
throw new IllegalArgumentException(encoded);
encodedName = unescapeString(encoded.substring(startName + 1, endName));
end = encoded.lastIndexOf('}', startName);
}
int start = encoded.indexOf('{');
if (start < 0 || end < start)
throw new IllegalArgumentException(encoded);
String decision = encoded.substring(0, start);
decision = decision.trim();
if (decision.length() == 0 || (!ConditionalPermissionInfo.DENY.equalsIgnoreCase(decision) && !ConditionalPermissionInfo.ALLOW.equalsIgnoreCase(decision)))
throw new IllegalArgumentException(encoded);
List<ConditionInfo> condList = new ArrayList<>();
List<PermissionInfo> permList = new ArrayList<>();
int pos = start + 1;
while (pos < end) {
while (pos < end && chars[pos] != '[' && chars[pos] != '(')
pos++;
if (pos == end)
break;
int startPos = pos;
char endChar = chars[startPos] == '[' ? ']' : ')';
while (pos < end && chars[pos] != endChar) {
if (chars[pos] == '"') {
pos++;
while (chars[pos] != '"') {
if (chars[pos] == '\\')
pos++;
pos++;
}
}
pos++;
}
int endPos = pos;
String token = new String(chars, startPos, endPos - startPos + 1);
if (endChar == ']')
condList.add(new ConditionInfo(token));
else
permList.add(new PermissionInfo(token));
pos++;
}
if (permList.size() == 0)
throw new IllegalArgumentException("No Permission infos: " + encoded);
ConditionInfo[] conds = condList.toArray(new ConditionInfo[condList.size()]);
PermissionInfo[] perms = permList.toArray(new PermissionInfo[permList.size()]);
if (securityAdmin == null)
return new SecurityRowSnapShot(encodedName, conds, perms, decision);
return new SecurityRow(securityAdmin, encodedName, conds, perms, decision);
}
static Object cloneArray(Object[] array) {
if (array == null)
return null;
Object result = Array.newInstance(array.getClass().getComponentType(), array.length);
System.arraycopy(array, 0, result, 0, array.length);
return result;
}
private static void escapeString(String str, StringBuilder output) {
int len = str.length();
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
switch (c) {
case '"' :
case '\\' :
output.append('\\');
output.append(c);
break;
case '\r' :
output.append("\\r");
break;
case '\n' :
output.append("\\n");
break;
default :
output.append(c);
break;
}
}
}
private static String unescapeString(String str) {
StringBuilder output = new StringBuilder(str.length());
int end = str.length();
for (int i = 0; i < end; i++) {
char c = str.charAt(i);
if (c == '\\') {
i++;
if (i < end) {
c = str.charAt(i);
switch (c) {
case '"' :
case '\\' :
break;
case 'r' :
c = '\r';
break;
case 'n' :
c = '\n';
break;
default :
c = '\\';
i--;
break;
}
}
}
output.append(c);
}
return output.toString();
}
@Override
public String getName() {
return name;
}
@Override
public ConditionInfo[] getConditionInfos() {
return (ConditionInfo[]) cloneArray(conditionInfos);
}
ConditionInfo[] internalGetConditionInfos() {
return conditionInfos;
}
@Override
public String getAccessDecision() {
return deny ? ConditionalPermissionInfo.DENY : ConditionalPermissionInfo.ALLOW;
}
@Override
public PermissionInfo[] getPermissionInfos() {
return (PermissionInfo[]) cloneArray(permissionInfoCollection.getPermissionInfos());
}
PermissionInfo[] internalGetPermissionInfos() {
return permissionInfoCollection.getPermissionInfos();
}
@Override
public void delete() {
securityAdmin.delete(this, true);
}
Condition[] getConditions(BundlePermissions bundlePermissions) {
synchronized (bundleConditionsLock) {
Condition[] conditions = null;
if (bundleConditions != null) {
conditions = bundleConditions.get(bundlePermissions);
}
if (conditions == null) {
conditions = new Condition[conditionInfos.length];
for (int i = 0; i < conditionInfos.length; i++) {
Class<?> clazz;
try {
clazz = Class.forName(conditionInfos[i].getType());
} catch (ClassNotFoundException e) {
return null;
}
Constructor<?> constructor = null;
Method method = getConditionMethod(clazz);
if (method == null) {
constructor = getConditionConstructor(clazz);
if (constructor == null) {
conditions[i] = Condition.FALSE;
continue;
}
}
Object[] args = {bundlePermissions.getBundle(), conditionInfos[i]};
try {
if (method != null)
conditions[i] = (Condition) method.invoke(null, args);
else
conditions[i] = (Condition) constructor.newInstance(args);
} catch (Exception e) {
conditions[i] = Condition.FALSE;
}
}
if (bundleConditions != null) {
bundleConditions.put(bundlePermissions, conditions);
}
}
return conditions;
}
}
private Method getConditionMethod(Class<?> clazz) {
for (Method checkMethod : clazz.getMethods()) {
if (checkMethod.getName().equals("getCondition")
&& (checkMethod.getModifiers() & Modifier.STATIC) == Modifier.STATIC
&& checkParameterTypes(checkMethod.getParameterTypes())) {
return checkMethod;
}
}
return null;
}
private Constructor<?> getConditionConstructor(Class<?> clazz) {
for (Constructor<?> checkConstructor : clazz.getConstructors()) {
if (checkParameterTypes(checkConstructor.getParameterTypes())) {
return checkConstructor;
}
}
return null;
}
private boolean checkParameterTypes(Class<?>[] foundTypes) {
if (foundTypes.length != conditionMethodArgs.length) {
return false;
}
for (int i = 0; i < foundTypes.length; i++) {
if (!foundTypes[i].isAssignableFrom(conditionMethodArgs[i])) {
return false;
}
}
return true;
}
Decision evaluate(BundlePermissions bundlePermissions, Permission permission) {
if (bundleConditions == null || bundlePermissions == null)
return evaluatePermission(permission);
Condition[] conditions = getConditions(bundlePermissions);
if (conditions == ABSTAIN_LIST)
return DECISION_ABSTAIN;
if (conditions == SATISFIED_LIST)
return evaluatePermission(permission);
boolean empty = true;
List<Condition> postponedConditions = null;
Decision postponedPermCheck = null;
for (int i = 0; i < conditions.length; i++) {
Condition condition = conditions[i];
if (condition == null)
continue;
if (!isPostponed(condition)) {
boolean mutable = condition.isMutable();
if (condition.isSatisfied()) {
if (!mutable)
conditions[i] = null;
} else {
if (!mutable)
synchronized (bundleConditionsLock) {
bundleConditions.put(bundlePermissions, ABSTAIN_LIST);
}
return DECISION_ABSTAIN;
}
} else {
if (postponedPermCheck == null)
postponedPermCheck = evaluatePermission(permission);
if (postponedPermCheck == DECISION_ABSTAIN)
return postponedPermCheck;
if (postponedConditions == null)
postponedConditions = new ArrayList<>(1);
postponedConditions.add(condition);
}
empty &= conditions[i] == null;
}
if (empty) {
synchronized (bundleConditionsLock) {
bundleConditions.put(bundlePermissions, SATISFIED_LIST);
}
}
if (postponedPermCheck != null)
return new Decision(postponedPermCheck.decision | SecurityTable.POSTPONED, postponedConditions.toArray(new Condition[postponedConditions.size()]), this, bundlePermissions);
return evaluatePermission(permission);
}
private boolean isPostponed(Condition condition) {
return condition.isPostponed() && securityAdmin.getSupportedSecurityManager() != null;
}
private Decision evaluatePermission(Permission permission) {
return permissionInfoCollection.implies(permission) ? (deny ? DECISION_DENIED : DECISION_GRANTED) : DECISION_ABSTAIN;
}
@Override
public String toString() {
return getEncoded();
}
@Override
public String getEncoded() {
return getEncoded(name, conditionInfos, internalGetPermissionInfos(), deny);
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof ConditionalPermissionInfo))
return false;
return getEncoded().equals(((ConditionalPermissionInfo) obj).getEncoded());
}
@Override
public int hashCode() {
return getHashCode(name, internalGetConditionInfos(), internalGetPermissionInfos(), getAccessDecision());
}
static int getHashCode(String name, ConditionInfo[] conds, PermissionInfo[] perms, String decision) {
int h = 31 * 17 + decision.hashCode();
for (ConditionInfo cond : conds) {
h = 31 * h + cond.hashCode();
}
for (PermissionInfo perm : perms) {
h = 31 * h + perm.hashCode();
}
if (name != null)
h = 31 * h + name.hashCode();
return h;
}
static String getEncoded(String name, ConditionInfo[] conditionInfos, PermissionInfo[] permissionInfos, boolean deny) {
StringBuilder result = new StringBuilder();
if (deny)
result.append(ConditionalPermissionInfo.DENY);
else
result.append(ConditionalPermissionInfo.ALLOW);
result.append(" { ");
if (conditionInfos != null)
for (ConditionInfo conditionInfo : conditionInfos) {
result.append(conditionInfo.getEncoded()).append(' ');
}
if (permissionInfos != null)
for (PermissionInfo permissionInfo : permissionInfos) {
result.append(permissionInfo.getEncoded()).append(' ');
}
result.append('}');
if (name != null) {
result.append(" \"");
escapeString(name, result);
result.append('"');
}
return result.toString();
}
PermissionInfoCollection getPermissionInfoCollection() {
return permissionInfoCollection;
}
void clearCaches() {
permissionInfoCollection.clearPermissionCache();
if (bundleConditions != null)
synchronized (bundleConditionsLock) {
bundleConditions.clear();
}
}
static class Decision {
final int decision;
final Condition[] postponed;
private final SecurityRow row;
private final BundlePermissions bundlePermissions;
Decision(int decision, Condition[] postponed, SecurityRow row, BundlePermissions bundlePermissions) {
this.decision = decision;
this.postponed = postponed;
this.row = row;
this.bundlePermissions = bundlePermissions;
}
void handleImmutable(Condition condition, boolean isSatisfied, boolean mutable) {
if (mutable || !condition.isPostponed())
return;
if (isSatisfied) {
synchronized (row.bundleConditionsLock) {
Condition[] rowConditions = row.bundleConditions.get(bundlePermissions);
boolean isEmpty = true;
for (int i = 0; i < rowConditions.length; i++) {
if (rowConditions[i] == condition)
if (isSatisfied)
rowConditions[i] = null;
isEmpty &= rowConditions[i] == null;
}
if (isEmpty)
row.bundleConditions.put(bundlePermissions, SATISFIED_LIST);
}
} else {
synchronized (row.bundleConditionsLock) {
row.bundleConditions.put(bundlePermissions, ABSTAIN_LIST);
}
}
}
}
}