org.wildfly.security.permission.PermissionUtil Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wildfly.security.permission;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.AllPermission;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.function.LongFunction;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import org.wildfly.common.Assert;
/**
* General permission utility methods and constants.
*
* @author David M. Lloyd
*/
public final class PermissionUtil {
private PermissionUtil() {
}
/**
* A shared {@link AllPermission} instance.
*/
public static final Permission ALL_PERMISSION = new AllPermission();
/**
* A read-only permission collection which implies {@link AllPermission}.
*/
public static final PermissionCollection ALL_PERMISSIONS;
/**
* A permission collection which is empty.
*/
public static final PermissionCollection EMPTY_PERMISSION_COLLECTION;
/**
* An array with no permissions in it.
*/
public static final Permission[] NO_PERMISSIONS = new Permission[0];
static {
Permissions permissions = new Permissions();
permissions.add(ALL_PERMISSION);
permissions.setReadOnly();
ALL_PERMISSIONS = permissions;
permissions = new Permissions();
permissions.setReadOnly();
EMPTY_PERMISSION_COLLECTION = permissions;
}
/**
* Parse an actions string, using the given function to map action strings to bits.
*
* @param actionsString the actions string (must not be {@code null})
* @param function the mapping function (must not be {@code null})
* @return the union of all the action bits
* @throws IllegalArgumentException if {@code function} throws this exception (indicating an invalid action string)
*/
public static int parseActions(String actionsString, ToIntFunction function) throws IllegalArgumentException {
Assert.checkNotNullParam("actionsString", actionsString);
Assert.checkNotNullParam("function", function);
int actions = 0;
int pos = 0;
int idx = actionsString.indexOf(',');
for (;;) {
String str;
if (idx == -1) {
str = actionsString.substring(pos, actionsString.length()).trim();
if (! str.isEmpty()) actions |= function.applyAsInt(str);
return actions;
} else {
str = actionsString.substring(pos, idx).trim();
pos = idx + 1;
if (! str.isEmpty()) actions |= function.applyAsInt(str);
idx = actionsString.indexOf(',', pos);
}
}
}
/**
* Parse an actions string, using the given function to map action strings to bits.
*
* @param actionsString the actions string (must not be {@code null})
* @param function the mapping function (must not be {@code null})
* @return the union of all the action bits
* @throws IllegalArgumentException if {@code function} throws this exception (indicating an invalid action string)
*/
public static long parseActions(String actionsString, ToLongFunction function) throws IllegalArgumentException {
Assert.checkNotNullParam("actionsString", actionsString);
Assert.checkNotNullParam("function", function);
long actions = 0;
int pos = 0;
int idx = actionsString.indexOf(',');
for (;;) {
String str;
if (idx == -1) {
str = actionsString.substring(pos, actionsString.length()).trim();
if (! str.isEmpty()) actions |= function.applyAsLong(str);
return actions;
} else {
str = actionsString.substring(pos, idx).trim();
pos = idx + 1;
if (! str.isEmpty()) actions |= function.applyAsLong(str);
idx = actionsString.indexOf(',', pos);
}
}
}
/**
* Deparse an action bit set, using the given function to map action bits to strings. If the bits are all clear,
* the empty string {@code ""} is returned.
*
* @param actionBits the action bit set
* @param mappingFunction the mapping function (must not be {@code null})
* @return the actions string (not {@code null})
*/
public static String toActionsString(int actionBits, IntFunction mappingFunction) {
Assert.checkNotNullParam("mappingFunction", mappingFunction);
final StringBuilder sb = new StringBuilder();
if (actionBits == 0) return "";
int lb = Integer.highestOneBit(actionBits);
sb.append(mappingFunction.apply(lb));
actionBits &= ~lb;
while (actionBits != 0) {
lb = Integer.highestOneBit(actionBits);
sb.append(',').append(mappingFunction.apply(lb));
actionBits &= ~lb;
}
return sb.toString();
}
/**
* Deparse an action bit set, using the given function to map action bits to strings. If the bits are all clear,
* the empty string {@code ""} is returned.
*
* @param actionBits the action bit set
* @param mappingFunction the mapping function (must not be {@code null})
* @return the actions string (not {@code null})
*/
public static String toActionsString(long actionBits, LongFunction mappingFunction) {
Assert.checkNotNullParam("mappingFunction", mappingFunction);
final StringBuilder sb = new StringBuilder();
if (actionBits == 0) return "";
long lb = Long.highestOneBit(actionBits);
sb.append(mappingFunction.apply(lb));
actionBits &= ~lb;
while (actionBits != 0) {
lb = Long.highestOneBit(actionBits);
sb.append(',').append(mappingFunction.apply(lb));
actionBits &= ~lb;
}
return sb.toString();
}
/**
* Create an iterable view over a permission collection.
*
* @param pc the permission collection (must not be {@code null})
* @return the iterable view (not {@code null})
*/
public static Iterable iterable(PermissionCollection pc) {
return () -> {
final Enumeration elements = pc.elements();
return new Iterator() {
public boolean hasNext() {
return elements.hasMoreElements();
}
public Permission next() {
return elements.nextElement();
}
};
};
}
/**
* Perform an action for each permission in the given collection.
*
* @param collection the collection (must not be {@code null})
* @param consumer the consumer to which each permission should be passed (must not be {@code null})
*/
public static void forEachIn(PermissionCollection collection, Consumer consumer) {
Assert.checkNotNullParam("collection", collection);
Assert.checkNotNullParam("consumer", consumer);
final Enumeration elements = collection.elements();
while (elements.hasMoreElements()) {
consumer.accept(elements.nextElement());
}
}
/**
* Perform an action for each permission in the given collection.
*
* @param collection the collection (must not be {@code null})
* @param parameter the parameter to pass to the consumer
* @param consumer the consumer to which each permission should be passed (must not be {@code null})
* @param the type of the parameter
* @return the {@code parameter} that was passed in
*/
public static
P forEachIn(PermissionCollection collection, BiConsumer
consumer, P parameter) {
Assert.checkNotNullParam("collection", collection);
Assert.checkNotNullParam("consumer", consumer);
final Enumeration elements = collection.elements();
while (elements.hasMoreElements()) {
consumer.accept(parameter, elements.nextElement());
}
return parameter;
}
/**
* Run a test for each permission in the given collection. If the predicate returns {@code false} for any element,
* {@code false} is returned; otherwise, {@code true} is returned.
*
* @param collection the collection (must not be {@code null})
* @param predicate the predicate to apply to each element (must not be {@code null})
* @return {@code true} if the predicate matched all the permissions in the collection, {@code false} otherwise
*/
public static boolean forEachIn(PermissionCollection collection, Predicate predicate) {
Assert.checkNotNullParam("collection", collection);
Assert.checkNotNullParam("predicate", predicate);
final Enumeration elements = collection.elements();
while (elements.hasMoreElements()) {
if (! predicate.test(elements.nextElement())) {
return false;
}
}
return true;
}
/**
* Run a test for each permission in the given collection. If the predicate returns {@code false} for any element,
* {@code false} is returned; otherwise, {@code true} is returned.
*
* @param collection the collection (must not be {@code null})
* @param parameter the parameter to pass to the consumer
* @param predicate the predicate to apply to each element (must not be {@code null})
* @param the type of the parameter
* @return {@code true} if the predicate matched all the permissions in the collection, {@code false} otherwise
*/
public static
boolean forEachIn(PermissionCollection collection, BiPredicate
predicate, P parameter) {
Assert.checkNotNullParam("collection", collection);
Assert.checkNotNullParam("predicate", predicate);
final Enumeration elements = collection.elements();
while (elements.hasMoreElements()) {
if (! predicate.test(parameter, elements.nextElement())) {
return false;
}
}
return true;
}
/**
* Create a permission collection that is the union of two permission collections. The permission
* collections must be read-only.
*
* @param pc1 the first permission collection (must not be {@code null})
* @param pc2 the second permission collection (must not be {@code null})
* @return a new permission collection that is the union of the two collections (not {@code null})
*/
public static PermissionCollection union(PermissionCollection pc1, PermissionCollection pc2) {
Assert.checkNotNullParam("pc1", pc1);
Assert.checkNotNullParam("pc2", pc2);
if (! pc1.isReadOnly() || ! pc2.isReadOnly()) {
throw ElytronMessages.log.permissionCollectionMustBeReadOnly();
}
if (pc1.implies(ALL_PERMISSION) || pc2.implies(ALL_PERMISSION)) {
return ALL_PERMISSIONS;
} else {
return new UnionPermissionCollection(pc1, pc2);
}
}
/**
* Create a permission collection that is the intersection of two permission collections. The permission
* collections must be read-only.
*
* @param pc1 the first permission collection (must not be {@code null})
* @param pc2 the second permission collection (must not be {@code null})
* @return a new permission collection that is the intersection of the two collections (not {@code null})
*/
public static PermissionCollection intersection(PermissionCollection pc1, PermissionCollection pc2) {
Assert.checkNotNullParam("pc1", pc1);
Assert.checkNotNullParam("pc2", pc2);
if (! pc1.isReadOnly() || ! pc2.isReadOnly()) {
throw ElytronMessages.log.permissionCollectionMustBeReadOnly();
}
if (pc1.implies(ALL_PERMISSION)) {
return pc2;
} else if (pc2.implies(ALL_PERMISSION)) {
return pc1;
} else {
return new IntersectionPermissionCollection(pc1, pc2);
}
}
/**
* Determine if one collection implies all the permissions in the other collection.
*
* @param collection the collection to check against (must not be {@code null})
* @param testCollection the collection whose permissions are to be tested (must not be {@code null})
* @return {@code true} if {@code collection} implies all of the permissions in {@code testCollection}, {@code false} otherwise
*/
public static boolean impliesAll(PermissionCollection collection, PermissionCollection testCollection) {
return forEachIn(collection, PermissionCollection::implies, testCollection);
}
/**
* Determine if two permission collections are equal, that is, each collection implies all of the permissions in the
* other collection.
*
* @param pc1 the first collection (must not be {@code null})
* @param pc2 the second collection (must not be {@code null})
* @return {@code true} if the collections imply one another, {@code false} otherwise
*/
public static boolean equals(PermissionCollection pc1, PermissionCollection pc2) {
return impliesAll(pc1, pc2) && impliesAll(pc2, pc1);
}
/**
* Add all of the permissions from the source collection to the target collection.
*
* @param target the target collection (must not be {@code null})
* @param source the source collection (must not be {@code null})
* @return the target collection (not {@code null})
*/
public static PermissionCollection addAll(PermissionCollection target, PermissionCollection source) {
return forEachIn(source, PermissionCollection::add, target);
}
/**
* Add all of the permissions from the source collection to the target collection.
*
* @param target the target collection (must not be {@code null})
* @param source the source collection (must not be {@code null})
* @return the target collection (not {@code null})
*/
public static PermissionCollection addAll(PermissionCollection target, Collection source) {
source.forEach(target::add);
return target;
}
/**
* Add a permission to a collection, returning the target collection. If the permission is {@code null}, it is
* not added.
*
* @param target the target collection (must not be {@code null})
* @param source the permission to add
* @return the target collection (not {@code null})
*/
public static PermissionCollection add(PermissionCollection target, Permission source) {
Assert.checkNotNullParam("target", target);
if (source != null) target.add(source);
return target;
}
/**
* Instantiate a permission with the given class name, permission name, and actions.
*
* @param classLoader the class loader to search in ({@code null} indicates the system class loader)
* @param className the name of the permission class to instantiate (must not be {@code null})
* @param name the permission name (may be {@code null} if allowed by the permission class)
* @param actions the permission actions (may be {@code null} if allowed by the permission class)
* @return the permission object (not {@code null})
* @throws InvalidPermissionClassException if the permission class does not exist or is not valid
* @throws ClassCastException if the class name does not refer to a subclass of {@link Permission}
*/
public static Permission createPermission(final ClassLoader classLoader, final String className, final String name, final String actions) {
Assert.checkNotNullParam("className", className);
final Class extends Permission> permissionClass;
try {
permissionClass = Class.forName(className, true, classLoader).asSubclass(Permission.class);
} catch (ClassNotFoundException e) {
throw ElytronMessages.log.permissionClassMissing(className, e);
}
return createPermission(permissionClass, name, actions);
}
/**
* Instantiate a permission with the given class, permission name, and actions.
*
* @param permissionClass the permission class to instantiate (must not be {@code null})
* @param name the permission name (may be {@code null} if allowed by the permission class)
* @param actions the permission actions (may be {@code null} if allowed by the permission class)
* @return the permission object (not {@code null})
* @throws InvalidPermissionClassException if the permission class does not exist or is not valid
*/
public static Permission createPermission(final Class extends Permission> permissionClass, final String name, final String actions) {
Assert.checkNotNullParam("permissionClass", permissionClass);
Constructor extends Permission> noArgs = null;
Constructor extends Permission> oneArg = null;
Constructor extends Permission> twoArg = null;
for (Constructor> raw : permissionClass.getConstructors()) {
@SuppressWarnings("unchecked")
Constructor extends Permission> ctor = (Constructor extends Permission>) raw;
final Class>[] parameterTypes = ctor.getParameterTypes();
if (parameterTypes.length == 2 && parameterTypes[0] == String.class && parameterTypes[1] == String.class) {
twoArg = ctor;
} else if (parameterTypes.length == 1 && parameterTypes[0] == String.class) {
oneArg = ctor;
} else if (parameterTypes.length == 0) {
noArgs = ctor;
}
}
try {
if (twoArg != null) {
return twoArg.newInstance(name, actions);
} else if (oneArg != null) {
return oneArg.newInstance(name);
} else if (noArgs != null) {
return noArgs.newInstance();
} else {
throw ElytronMessages.log.noPermissionConstructor(permissionClass.getName());
}
} catch (IllegalAccessException e) {
throw new IllegalAccessError(e.getMessage());
} catch (InstantiationException e) {
throw ElytronMessages.log.permissionInstantiation(permissionClass.getName(), e);
} catch (InvocationTargetException e) {
try {
throw e.getCause();
} catch (Error | RuntimeException cause) {
throw cause;
} catch (Throwable cause) {
throw new UndeclaredThrowableException(cause);
}
}
}
/**
* Get a read-only collection of the given permissions.
*
* @param permissions the permissions to assign
* @return the read-only collection
*/
public static PermissionCollection readOnlyCollectionOf(Permission... permissions) {
final int length = permissions.length;
if (length == 0) {
return EMPTY_PERMISSION_COLLECTION;
} else {
Permissions collection = new Permissions();
addAll(collection, Arrays.asList(permissions));
collection.setReadOnly();
return collection;
}
}
}