
org.jboss.as.naming.JndiPermission Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2010, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.naming;
import static org.jboss.as.naming.NamingMessages.MESSAGES;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.Permission;
import java.security.PermissionCollection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import javax.naming.Name;
/**
* This class represents access to a path in the JNDI tree. A JndiPermission
* consists of a pathname and a set of actions valid for that pathname.
*
* Pathname is the pathname of the file or directory granted the specified
* actions. A pathname that ends in "/*" indicates all the files and directories
* contained in that directory. A pathname that ends with "/-" indicates
* (recursively) all files and subdirectories contained in that directory. A
* pathname consisting of the special token "<<ALL BINDINGS>>" matches
* any file.
*
* The actions to be granted are passed to the constructor in an array of
* {@code Action} instances. The possible actions are "bind", "rebind",
* "unbind", "lookup", "list", "listBindings", and "createSubcontext".
* Their meaning is defined as follows:
*
*
* - bind
*
- Context.bind permission
*
- rebind
*
- Context.rebind permission
*
- unbind
*
- Context.unbind permission.
*
- lookup
*
- Context.lookup permission.
*
- list
*
- Context.list permission.
*
- listBindings
*
- Context.listBindings permission.
*
- createSubcontext
*
- Context.createSubcontext permission.
*
- destroySubcontext
*
- Context.destroySubcontext permission.
*
- addNamingListener
*
- EventContext.addNamingListener permission.
*
*
* Be careful when granting JndiPermissions. Think about the implications of
* granting read and especially write access to various files and directories.
* The "<<ALL BINDINGS>>" permission with write action is especially
* dangerous. This grants permission to write to the entire file system. One
* thing this effectively allows is replacement of the system binary, including
* the JVM runtime environment.
*
*
* Please note: Code can always read a file from the same directory it's in (or
* a subdirectory of that directory); it does not need explicit permission to do
* so.
*
* @author Marianne Mueller
* @author Roland Schemers
* @author [email protected]
* @version $Revision: 81310 $
* @serial exclude
* @see java.security.Permission
* @see java.security.Permissions
* @see java.security.PermissionCollection
*/
public final class JndiPermission extends Permission
implements Serializable {
private static final long serialVersionUID = 1;
public enum Action {
NONE("none", 0x0),
BIND("bind", 1),
REBIND("rebind", 2),
UNBIND("unbind", 4),
LOOKUP("lookup", 8),
LIST("list", 16),
LIST_BINDINGS("listBindings", 32),
CREATE_SUBCONTEXT("createSubcontext", 64),
DESTROY_SUBCONTEXT("destroySubcontext", 128),
ADD_NAMING_LISTENER("addNamingListener", 256),
ALL("all", BIND.mask | REBIND.mask | UNBIND.mask | LOOKUP.mask | LIST.mask | LIST_BINDINGS.mask |
CREATE_SUBCONTEXT.mask | DESTROY_SUBCONTEXT.mask | ADD_NAMING_LISTENER.mask);
private String actionName;
private int mask;
private Action(String actionName, int mask) {
this.actionName = actionName;
this.mask = mask;
}
public static Action forName(final String actionName) {
for(Action action : Action.values()) {
if(action.actionName.equals(actionName))
return action;
}
return null;
}
}
/**
* A utility method to check if the current access control context possesses
* the JndiPermission for given JNDI name and actions.
*
* @param name the JNDI name
* @param actions the actions to check the permission for
*/
public static void check(Name name, Action... actions) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new JndiPermission(name, actions));
}
}
// the actions mask
private transient int mask;
// does path indicate a directory? (wildcard or recursive)
private transient boolean directory;
// is it a recursive directory specification?
private transient boolean recursive;
/**
* the actions string.
*
* @serial
*/
private String actions; // Left null as long as possible, then
// created and re-used in the getAction function.
// canonicalized dir path. In the case of
// directories, it is the name "/blah/*" or "/blah/-" without
// the last character (the "*" or "-").
private transient String cpath;
// static Strings used by init(int mask)
private static final char RECURSIVE_CHAR = '-';
private static final char WILD_CHAR = '*';
/**
* initialize a JndiPermission object. Common to all constructors. Also
* called during de-serialization.
*
* @param mask the actions mask to use.
*/
private void init(int mask) {
if ((mask & Action.ALL.mask) != mask)
throw MESSAGES.invalidActionMask();
if (mask == Action.NONE.mask)
throw MESSAGES.invalidActionMask();
if ((cpath = getName()) == null)
throw new NullPointerException(MESSAGES.cannotBeNull("name"));
this.mask = mask;
if (cpath.equals("<>")) {
directory = true;
recursive = true;
cpath = "";
return;
}
int len = cpath.length();
char last = ((len > 0) ? cpath.charAt(len - 1) : 0);
if (last == RECURSIVE_CHAR && cpath.charAt(len - 2) == '/') {
directory = true;
recursive = true;
cpath = cpath.substring(0, --len);
} else if (last == WILD_CHAR && cpath.charAt(len - 2) == '/') {
directory = true;
// recursive = false;
cpath = cpath.substring(0, --len);
} else {
// overkill since they are initialized to false, but
// commented out here to remind us...
// directory = false;
// recursive = false;
}
}
/**
* Creates a new JndiPermission object with the specified actions. path
* is the pathname of a file or directory, and actions contains a
* comma-separated list of the desired actions granted on the file or
* directory. Possible actions are "bind", "rebind", "unbind", "lookup",
* "list", "listBindings", "createSubcontext", "destroySubcontext" and "addNamingListener".
*
*
* A pathname that ends in "/*" (where "/" is the file separator character,
* '/'
) indicates all the files and directories contained in
* that directory. A pathname that ends with "/-" indicates (recursively) all
* files and subdirectories contained in that directory. The special pathname
* "<<ALL BINDINGS>>" matches any file.
*
*
* A pathname consisting of a single "*" indicates all the files in the
* current directory, while a pathname consisting of a single "-" indicates
* all the files in the current directory and (recursively) all files and
* subdirectories contained in the current directory.
*
*
* A pathname containing an empty string represents an empty path.
*
* @param path the pathname of the file/directory.
* @param actions the action string.
* @throws IllegalArgumentException If actions is null
, empty or contains an action
* other than the specified possible actions.
*/
public JndiPermission(String path, Action... actions) {
super(path);
init(getMask(actions));
}
public JndiPermission(Name path, Action... actions) {
this(path.toString(), actions);
}
/**
* @see #JndiPermission(String, Action...)
*
* The default policy file parser ({@code sun.security.provider.PolicyFile}) requires this constructor.
*
* @param path the pathname to grant the permission
* @param actions the comma separated string of actions granted
*/
public JndiPermission(String path, String actions) {
super(path);
init(getMask(actions));
}
/**
* Checks if this JndiPermission object "implies" the specified permission.
*
* More specifically, this method returns true if:
*
*
* - p is an instanceof JndiPermission,
*
*
- p's actions are a proper subset of this object's actions, and
*
*
- p's pathname is implied by this object's pathname. For
* example, "/tmp/*" implies "/tmp/foo", since "/tmp/*" encompasses all files
* in the "/tmp" directory, including the one named "foo".
*
*
* @param p the permission to check against.
* @return true
if the specified permission is not
* null
and is implied by this object,
* false
otherwise.
*/
public boolean implies(Permission p) {
if (!(p instanceof JndiPermission))
return false;
JndiPermission that = (JndiPermission) p;
// we get the effective mask. i.e., the "and" of this and that.
// They must be equal to that.mask for implies to return true.
return ((this.mask & that.mask) == that.mask) && impliesIgnoreMask(that);
}
/**
* Checks if the Permission's actions are a proper subset of the this
* object's actions. Returns the effective mask iff the this JndiPermission's
* path also implies that JndiPermission's path.
*
* @param that the JndiPermission to check against.
* @return the effective mask
*/
boolean impliesIgnoreMask(JndiPermission that) {
if (this.directory) {
if (this.recursive) {
// make sure that.path is longer then path so
// something like /foo/- does not imply /foo
if (that.directory) {
return (that.cpath.length() >= this.cpath.length())
&& that.cpath.startsWith(this.cpath);
} else {
return ((that.cpath.length() >= this.cpath.length()) && that.cpath
.startsWith(this.cpath));
}
} else {
if (that.directory) {
// if the permission passed in is a directory
// specification, make sure that a non-recursive
// permission (i.e., this object) can't imply a recursive
// permission.
if (that.recursive)
return false;
else
return (this.cpath.equals(that.cpath));
} else {
int last = that.cpath.lastIndexOf('/');
if (last == -1)
return false;
else {
// this.cpath.equals(that.cpath.substring(0, last+1));
// Use regionMatches to avoid creating new string
return (this.cpath.length() == (last + 1))
&& this.cpath.regionMatches(0, that.cpath, 0, last + 1);
}
}
}
} else if (that.directory) {
// if this is NOT recursive/wildcarded,
// do not let it imply a recursive/wildcarded permission
return false;
} else {
return (this.cpath.equals(that.cpath));
}
}
/**
* Checks two JndiPermission objects for equality. Checks that obj is
* a JndiPermission, and has the same pathname and actions as this object.
*
*
* @param obj the object we are testing for equality with this object.
* @return true
if obj is a JndiPermission, and has the same
* pathname and actions as this JndiPermission object,
* false
otherwise.
*/
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof JndiPermission))
return false;
JndiPermission that = (JndiPermission) obj;
return (this.mask == that.mask) && this.cpath.equals(that.cpath)
&& (this.directory == that.directory)
&& (this.recursive == that.recursive);
}
/**
* Returns the hash code value for this object.
*
* @return a hash code value for this object.
*/
public int hashCode() {
return this.cpath.hashCode();
}
/**
* Converts an actions array to an actions mask.
*
* @param actions - an array of actions
* @return the actions mask.
*/
private static int getMask(final Action[] actions) {
int mask = Action.NONE.mask;
// Null action valid?
if (actions == null || actions.length == 0) {
return mask;
}
if(actions.length == 1) {
return actions[0].mask;
}
for (Action action : actions) {
mask |= action.mask;
}
return mask;
}
/**
* Converts an actions String to an actions mask.
*
* @param actions - the comma separated list of actions.
* @return the actions mask.
*/
private static int getMask(String actions) {
int mask = Action.NONE.mask;
// Null action valid?
if (actions == null || actions.length() == 0) {
return mask;
}
// Check against use of constants
Action action = Action.forName(actions);
if(action != null) {
return action.mask;
}
String[] sa = actions.split(",");
for (String s : sa) {
String key = s.trim().toLowerCase(Locale.ENGLISH);
action = Action.forName(key);
if (action == null) {
throw MESSAGES.invalidPermissionAction(s);
}
int i = action.mask;
mask |= i;
}
return mask;
}
/**
* Return the current action mask. Used by the JndiPermissionCollection.
*
* @return the actions mask.
*/
int getMask() {
return mask;
}
/**
* Return the canonical string representation of the actions. Always returns
* present actions in the following order: bind, rebind, unbind, lookup,
* list, listBindings, createSubcontext
*
* @return the canonical string representation of the actions.
*/
private static String getActions(int mask) {
StringBuilder sb = new StringBuilder();
boolean insertComma = false;
final Action[] allActions = Action.values();
//none is an illegal value, so we're skipping it
for (int n = 1; n < allActions.length; n++) {
int action = 1 << (n - 1);
if ((mask & action) == action) {
if (insertComma)
sb.append(',');
sb.append(allActions[n].actionName);
insertComma = true;
}
}
return sb.toString();
}
/**
* Returns the "canonical string representation" of the actions. That is,
* this method always returns present actions in the following order: bind,
* rebind, unbind, lookup, list, listBindings, createSubcontext.
* For example, if this JndiPermission object allows
* both unbind and bind actions, a call to getActions
will
* return the string "bind,unbind".
*
* @return the canonical string representation of the actions.
*/
public String getActions() {
if (actions == null)
actions = getActions(this.mask);
return actions;
}
/**
* Returns a new PermissionCollection object for storing JndiPermission
* objects.
*
* JndiPermission objects must be stored in a manner that allows them to be
* inserted into the collection in any order, but that also enables the
* PermissionCollection implies
method to be implemented in an
* efficient (and consistent) manner.
*
*
* For example, if you have two JndiPermissions:
*
* -
"/tmp/-", "bind"
* -
"/tmp/scratch/foo", "unbind"
*
*
*
* and you are calling the implies
method with the
* JndiPermission:
*
*
* "/tmp/scratch/foo", "bind,unbind",
*
*
* then the implies
function must take into account both the
* "/tmp/-" and "/tmp/scratch/foo" permissions, so the effective permission
* is "bind,unbind", and implies
returns true. The "implies"
* semantics for JndiPermissions are handled properly by the
* PermissionCollection object returned by this
* newPermissionCollection
method.
*
* @return a new PermissionCollection object suitable for storing
* JndiPermissions.
*/
public PermissionCollection newPermissionCollection() {
return new JndiPermissionCollection();
}
/**
* WriteObject is called to save the state of the JndiPermission to a stream.
* The actions are serialized, and the superclass takes care of the name.
*/
private void writeObject(ObjectOutputStream s) throws IOException {
// Write out the actions. The superclass takes care of the name
// call getActions to make sure actions field is initialized
if (actions == null)
getActions();
s.defaultWriteObject();
}
/**
* readObject is called to restore the state of the JndiPermission from a
* stream.
*/
private void readObject(ObjectInputStream s) throws IOException,
ClassNotFoundException {
// Read in the actions, then restore everything else by calling init.
s.defaultReadObject();
init(getMask(actions));
}
}
/**
* A JndiPermissionCollection stores a set of JndiPermission permissions.
* JndiPermission objects must be stored in a manner that allows them to be
* inserted in any order, but enable the implies function to evaluate the
* implies method. For example, if you have two JndiPermissions:
*
* - "/tmp/-", "bind"
*
- "/tmp/scratch/foo", "unbind"
*
* And you are calling the implies function with the JndiPermission:
* "/tmp/scratch/foo", "bind,unbind", then the implies function must take into
* account both the /tmp/- and /tmp/scratch/foo permissions, so the effective
* permission is "bind,unbind".
*
* @author Marianne Mueller
* @author Roland Schemers
* @author [email protected]
* @version $Revision: 81310 $
* @serial include
* @see java.security.Permission
* @see java.security.Permissions
* @see java.security.PermissionCollection
*/
final class JndiPermissionCollection extends PermissionCollection implements
Serializable {
private static final long serialVersionUID = 1;
private List perms;
/**
* Create an empty JndiPermissions object.
*/
public JndiPermissionCollection() {
perms = new ArrayList();
}
/**
* Adds a permission to the JndiPermissions. The key for the hash is
* permission.path.
*
* @param permission the Permission object to add.
* @throws IllegalArgumentException -
* if the permission is not a JndiPermission
* @throws SecurityException -
* if this JndiPermissionCollection object has been marked
* readonly
*/
public void add(Permission permission) {
if (!(permission instanceof JndiPermission))
throw MESSAGES.invalidPermission(permission);
if (isReadOnly())
throw MESSAGES.cannotAddToReadOnlyPermissionCollection();
synchronized (this) {
perms.add((JndiPermission) permission);
}
}
/**
* Check and see if this set of permissions implies the permissions expressed
* in "permission".
*
* @param permission the Permission object to compare
* @return true if "permission" is a proper subset of a permission in the
* set, false if not.
*/
public boolean implies(Permission permission) {
if (!(permission instanceof JndiPermission))
return false;
JndiPermission fp = (JndiPermission) permission;
int desired = fp.getMask();
int effective = 0;
int needed = desired;
synchronized (this) {
for (JndiPermission x : perms) {
if (((needed & x.getMask()) != 0) && x.impliesIgnoreMask(fp)) {
effective |= x.getMask();
if ((effective & desired) == desired)
return true;
needed = (desired ^ effective);
}
}
}
return false;
}
/**
* Returns an enumeration of all the JndiPermission objects in the container.
*
* @return an enumeration of all the JndiPermission objects.
*/
public Enumeration elements() {
// Convert Iterator into Enumeration
synchronized (this) {
return Collections.enumeration(perms);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy