/* * JBoss, Home of Professional Open Source. * Copyright 2016, 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.wildfly.naming.java.permission; import java.security.Permission; import java.security.PermissionCollection; import java.util.Iterator; import org.jboss.as.naming.logging.NamingLogger; import org.wildfly.common.Assert; /** * Permission to access an object within the "java:" JNDI namespace. * <p> * This permission does not span into bound nested contexts; such contexts may be governed by their own permission scheme. * <p> * The {@code name} segment of the permission is a JNDI path whose segments are separated by {@code /} characters. The * name may be preceded with the string {@code java:} for compatibility with previous schemes. A name of * {@code <<ALL BINDINGS>>} is translated to {@code -} for compatibility reasons. */ public final class JndiPermission extends Permission { private static final long serialVersionUID = 1272655825146515997L; private final int actionBits; private String actionString; /** * The bitwise encoding of the {@code bind} action. */ public static final int ACTION_BIND = 0b000000001; /** * The bitwise encoding of the {@code rebind} action. */ public static final int ACTION_REBIND = 0b000000010; /** * The bitwise encoding of the {@code unbind} action. */ public static final int ACTION_UNBIND = 0b000000100; /** * The bitwise encoding of the {@code lookup} action. */ public static final int ACTION_LOOKUP = 0b000001000; /** * The bitwise encoding of the {@code list} action. */ public static final int ACTION_LIST = 0b000010000; /** * The bitwise encoding of the {@code listBindings} action. */ public static final int ACTION_LIST_BINDINGS = 0b000100000; /** * The bitwise encoding of the {@code createSubcontext} action. */ public static final int ACTION_CREATE_SUBCONTEXT = 0b001000000; /** * The bitwise encoding of the {@code destroySubcontext} action. */ public static final int ACTION_DESTROY_SUBCONTEXT = 0b010000000; /** * The bitwise encoding of the {@code addNamingListener} action. */ public static final int ACTION_ADD_NAMING_LISTENER = 0b100000000; /** * The bitwise encoding of the {@code *} action. This value is the bitwise-OR of all of the other action bits. */ public static final int ACTION_ALL = 0b111111111; /** * Construct a new instance. * * @param name the path name (must not be {@code null}) * @param actions the actions (must not be {@code null}) */ public JndiPermission(final String name, final String actions) { this(name, parseActions(Assert.checkNotNullParam("actions", actions))); } /** * Construct a new instance using an action bitmask. If a bit in the mask falls outside of {@link #ACTION_ALL}, it * is stripped. * * @param name the path name (must not be {@code null}) * @param actionBits the action bits */ public JndiPermission(final String name, final int actionBits) { super(canonicalize1(Assert.checkNotNullParam("name", name))); this.actionBits = actionBits & ACTION_ALL; } /** * Determine if this permission implies the other permission. * * @param permission the other permission * @return {@code true} if this permission implies the other, {@code false} if it does not or {@code permission} is {@code null} */ public boolean implies(final Permission permission) { return permission instanceof JndiPermission && implies((JndiPermission) permission); } /** * Determine if this permission implies the other permission. * * @param permission the other permission * @return {@code true} if this permission implies the other, {@code false} if it does not or {@code permission} is {@code null} */ public boolean implies(final JndiPermission permission) { return permission != null && ((actionBits & permission.actionBits) == permission.actionBits) && impliesPath(permission.getName()); } /** * Determine if this permission implies the given {@code actions} on the given {@code name}. * * @param name the name (must not be {@code null}) * @param actions the actions (must not be {@code null}) * @return {@code true} if this permission implies the {@code actions} on the {@code name}, {@code false} otherwise */ public boolean implies(final String name, final String actions) { return implies(name, parseActions(actions)); } /** * Determine if this permission implies the given {@code actionsBits} on the given {@code name}. * * @param name the name (must not be {@code null}) * @param actionBits the action bits * @return {@code true} if this permission implies the {@code actionBits} on the {@code name}, {@code false} otherwise */ public boolean implies(final String name, final int actionBits) { Assert.checkNotNullParam("name", name); final int maskedBits = actionBits & ACTION_ALL; return (this.actionBits & maskedBits) == maskedBits && impliesPath(name); } /** * Determine whether this object is equal to another. * * @param other the other object * @return {@code true} if they are equal, {@code false} otherwise */ public boolean equals(Object other) { return other instanceof JndiPermission && equals((JndiPermission)other); } /** * Determine whether this object is equal to another. * * @param other the other object * @return {@code true} if they are equal, {@code false} otherwise */ public boolean equals(JndiPermission other) { return this == other || other != null && getName().equals(other.getName()) && actionBits == other.actionBits; } /** * Get the hash code of this permission. * * @return the hash code of this permission */ public int hashCode() { return actionBits * 23 + getName().hashCode(); } /** * Get the actions string. The actions string will be a canonical version of the one passed in at construction. * * @return the actions string (not {@code null}) */ public String getActions() { final String actionString = this.actionString; if (actionString != null) { return actionString; } int actionBits = this.actionBits; if (actionBits == ACTION_ALL) { return this.actionString = "*"; } int m = Integer.lowestOneBit(actionBits); if (m != 0) { StringBuilder b = new StringBuilder(); b.append(getAction(m)); actionBits &= ~m; while (actionBits != 0) { m = Integer.lowestOneBit(actionBits); b.append(',').append(getAction(m)); actionBits &= ~m; } return this.actionString = b.toString(); } else { return this.actionString = ""; } } /** * Get the action bits. * * @return the action bits */ public int getActionBits() { return actionBits; } /** * Return a permission which is equal to this one except with its actions reset to {@code actionBits}. If the given * {@code actionBits} equals the current bits of this permission, then this permission instance is returned; otherwise * a new permission is constructed. Any action bits which fall outside of {@link #ACTION_ALL} are silently ignored. * * @param actionBits the action bits to use * @return a permission with only the given action bits (not {@code null}) */ public JndiPermission withNewActions(int actionBits) { actionBits &= ACTION_ALL; if (actionBits == this.actionBits) { return this; } else { return new JndiPermission(getName(), actionBits); } } /** * Return a permission which is equal to this one except with its actions reset to {@code actions}. If the given * {@code actions} equals the current actions of this permission, then this permission instance is returned; otherwise * a new permission is constructed. * * @param actions the actions to use (must not be {@code null}) * @return a permission with only the given action bits (not {@code null}) */ public JndiPermission withNewActions(String actions) { return withNewActions(parseActions(Assert.checkNotNullParam("actions", actions))); } /** * Return a permission which is equal to this one except with additional action bits. If the given {@code actionBits} * do not add any new actions, then this permission instance is returned; otherwise a new permission is constructed. * Any action bits which fall outside of {@link #ACTION_ALL} are silently ignored. * * @param actionBits the action bits to add * @return a permission with the union of permissions from this instance and the given bits (not {@code null}) */ public JndiPermission withActions(int actionBits) { return withNewActions(actionBits & ACTION_ALL | this.actionBits); } /** * Return a permission which is equal to this one except with additional actions. If the given {@code actionBits} * do not add any new actions, then this permission instance is returned; otherwise a new permission is constructed. * * @param actions the actions to add (must not be {@code null}) * @return a permission with the union of permissions from this instance and the given bits (not {@code null}) */ public JndiPermission withActions(String actions) { return withActions(parseActions(Assert.checkNotNullParam("actions", actions))); } /** * Return a permission which is equal to this one except without some action bits. If the given {@code actionBits} * do not remove any actions, then this permission instance is returned; otherwise a new permission is constructed. * Any action bits which fall outside of {@link #ACTION_ALL} are silently ignored. * * @param actionBits the action bits to remove * @return a permission with the given bits subtracted from this instance (not {@code null}) */ public JndiPermission withoutActions(int actionBits) { return withNewActions(this.actionBits & ~(actionBits & ACTION_ALL)); } /** * Return a permission which is equal to this one except without some actions. If the given {@code actions} * do not remove any actions, then this permission instance is returned; otherwise a new permission is constructed. * * @param actions the actions to remove (must not be {@code null}) * @return a permission with the given bits subtracted from this instance (not {@code null}) */ public JndiPermission withoutActions(String actions) { return withoutActions(parseActions(Assert.checkNotNullParam("actions", actions))); } /** * Construct a new type-specific permission collection. * * @return the new permission collection instance (not {@code null}) */ public PermissionCollection newPermissionCollection() { return new JndiPermissionCollection(); } // semi-private Object writeReplace() { return new SerializedJndiPermission(getName(), getActions()); } boolean impliesPath(final String yourName) { return yourName.startsWith("java:") ? impliesPath0(yourName.substring(5)) : impliesPath0(yourName); } // private private boolean impliesPath0(final String yourName) { // segment-by-segment comparison final String myName = getName(); final Iterator<String> myIter = JndiPermissionNameParser.nameIterator(myName); final Iterator<String> yourIter = JndiPermissionNameParser.nameIterator(yourName); // even if it's just "", there is always a first element assert myIter.hasNext() && yourIter.hasNext(); String myNext; String yourNext; for (;;) { myNext = myIter.next(); yourNext = yourIter.next(); if (myNext.equals("-")) { // "-" implies everything including "" return true; } if (! myNext.equals("*") && ! myNext.equals(yourNext)) { // "foo/bar" does not imply "foo/baz" return false; } if (myIter.hasNext()) { if (! yourIter.hasNext()) { // "foo/bar" does not imply "foo" return false; } } else { // if neither has next, "foo/bar" implies "foo/bar", else "foo" does not imply "foo/bar" return ! yourIter.hasNext(); } } } private static String canonicalize1(String name) { Assert.checkNotNullParam("name", name); return name.equalsIgnoreCase("<<ALL BINDINGS>>") ? "-" : canonicalize2(name); } private static String canonicalize2(String name) { return name.startsWith("java:") ? name.substring(5) : name; } private static int parseActions(final String actionsString) { // TODO: switch to Elytron utility methods to do this 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 |= parseAction(str); return actions; } else { str = actionsString.substring(pos, idx).trim(); pos = idx + 1; if (! str.isEmpty()) actions |= parseAction(str); idx = actionsString.indexOf(',', pos); } } } private static int parseAction(final String str) { switch (str) { case "*": case "all": return ACTION_ALL; case "bind": return ACTION_BIND; case "rebind": return ACTION_REBIND; case "unbind": return ACTION_UNBIND; case "lookup": return ACTION_LOOKUP; case "list": return ACTION_LIST; case "listBindings": return ACTION_LIST_BINDINGS; case "createSubcontext": return ACTION_CREATE_SUBCONTEXT; case "destroySubcontext": return ACTION_DESTROY_SUBCONTEXT; case "addNamingListener": return ACTION_ADD_NAMING_LISTENER; default: { throw NamingLogger.ROOT_LOGGER.invalidPermissionAction(str); } } } private String getAction(final int bit) { switch (bit) { case ACTION_BIND: return "bind"; case ACTION_REBIND: return "rebind"; case ACTION_UNBIND: return "unbind"; case ACTION_LOOKUP: return "lookup"; case ACTION_LIST: return "list"; case ACTION_LIST_BINDINGS: return "listBindings"; case ACTION_CREATE_SUBCONTEXT: return "createSubcontext"; case ACTION_DESTROY_SUBCONTEXT: return "destroySubcontext"; case ACTION_ADD_NAMING_LISTENER: return "addNamingListener"; default: throw Assert.impossibleSwitchCase(bit); } } }