1 /*
   2  * Copyright (c) 2001, 2007, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.management;
  27 
  28 import java.io.IOException;
  29 import java.io.ObjectInputStream;
  30 import java.security.BasicPermission;
  31 import java.security.Permission;
  32 import java.security.PermissionCollection;
  33 import java.util.Collections;
  34 import java.util.Enumeration;
  35 import java.util.Set;
  36 import java.util.StringTokenizer;
  37 
  38 /** A Permission to perform actions related to MBeanServers.
  39     The <em>name</em> of the permission specifies the operation requested
  40     or granted by the permission.  For a granted permission, it can be
  41     <code>*</code> to allow all of the MBeanServer operations specified below.
  42     Otherwise, for a granted or requested permission, it must be one of the
  43     following:
  44     <dl>
  45     <dt>createMBeanServer</dt>
  46     <dd>Create a new MBeanServer object using the method
  47     {@link MBeanServerFactory#createMBeanServer()} or
  48     {@link MBeanServerFactory#createMBeanServer(java.lang.String)}.
  49     <dt>findMBeanServer</dt>
  50     <dd>Find an MBeanServer with a given name, or all MBeanServers in this
  51     JVM, using the method {@link MBeanServerFactory#findMBeanServer}.
  52     <dt>newMBeanServer</dt>
  53     <dd>Create a new MBeanServer object without keeping a reference to it,
  54     using the method {@link MBeanServerFactory#newMBeanServer()} or
  55     {@link MBeanServerFactory#newMBeanServer(java.lang.String)}.
  56     <dt>releaseMBeanServer</dt>
  57     <dd>Remove the MBeanServerFactory's reference to an MBeanServer,
  58     using the method {@link MBeanServerFactory#releaseMBeanServer}.
  59     </dl>
  60     The <em>name</em> of the permission can also denote a list of one or more
  61     comma-separated operations.  Spaces are allowed at the beginning and
  62     end of the <em>name</em> and before and after commas.
  63     <p>
  64     <code>MBeanServerPermission("createMBeanServer")</code> implies
  65     <code>MBeanServerPermission("newMBeanServer")</code>.
  66  *
  67  * @since 1.5
  68  */
  69 public class MBeanServerPermission extends BasicPermission {
  70     private static final long serialVersionUID = -5661980843569388590L;
  71 
  72     private final static int
  73         CREATE = 0,
  74         FIND = 1,
  75         NEW = 2,
  76         RELEASE = 3,
  77         N_NAMES = 4;
  78 
  79     private final static String[] names = {
  80         "createMBeanServer",
  81         "findMBeanServer",
  82         "newMBeanServer",
  83         "releaseMBeanServer",
  84     };
  85 
  86     private final static int
  87         CREATE_MASK = 1<<CREATE,
  88         FIND_MASK = 1<<FIND,
  89         NEW_MASK = 1<<NEW,
  90         RELEASE_MASK = 1<<RELEASE,
  91         ALL_MASK = CREATE_MASK|FIND_MASK|NEW_MASK|RELEASE_MASK;
  92 
  93     /*
  94      * Map from permission masks to canonical names.  This array is
  95      * filled in on demand.
  96      *
  97      * This isn't very scalable.  If we have more than five or six
  98      * permissions, we should consider doing this differently,
  99      * e.g. with a Map.
 100      */
 101     private final static String[] canonicalNames = new String[1 << N_NAMES];
 102 
 103     /*
 104      * The target names mask.  This is not private to avoid having to
 105      * generate accessor methods for accesses from the collection class.
 106      *
 107      * This mask includes implied bits.  So if it has CREATE_MASK then
 108      * it necessarily has NEW_MASK too.
 109      */
 110     transient int mask;
 111 
 112     /** <p>Create a new MBeanServerPermission with the given name.</p>
 113         <p>This constructor is equivalent to
 114         <code>MBeanServerPermission(name,null)</code>.</p>
 115         @param name the name of the granted permission.  It must
 116         respect the constraints spelt out in the description of the
 117         {@link MBeanServerPermission} class.
 118         @exception NullPointerException if the name is null.
 119         @exception IllegalArgumentException if the name is not
 120         <code>*</code> or one of the allowed names or a comma-separated
 121         list of the allowed names.
 122     */
 123     public MBeanServerPermission(String name) {
 124         this(name, null);
 125     }
 126 
 127     /** <p>Create a new MBeanServerPermission with the given name.</p>
 128         @param name the name of the granted permission.  It must
 129         respect the constraints spelt out in the description of the
 130         {@link MBeanServerPermission} class.
 131         @param actions the associated actions.  This parameter is not
 132         currently used and must be null or the empty string.
 133         @exception NullPointerException if the name is null.
 134         @exception IllegalArgumentException if the name is not
 135         <code>*</code> or one of the allowed names or a comma-separated
 136         list of the allowed names, or if <code>actions</code> is a non-null
 137         non-empty string.
 138      *
 139      * @throws NullPointerException if <code>name</code> is <code>null</code>.
 140      * @throws IllegalArgumentException if <code>name</code> is empty or
 141      * if arguments are invalid.
 142      */
 143     public MBeanServerPermission(String name, String actions) {
 144         super(getCanonicalName(parseMask(name)), actions);
 145 
 146         /* It's annoying to have to parse the name twice, but since
 147            Permission.getName() is final and since we can't access "this"
 148            until after the call to the superclass constructor, there
 149            isn't any very clean way to do this.  MBeanServerPermission
 150            objects aren't constructed very often, luckily.  */
 151         mask = parseMask(name);
 152 
 153         /* Check that actions is a null empty string */
 154         if (actions != null && actions.length() > 0)
 155             throw new IllegalArgumentException("MBeanServerPermission " +
 156                                                "actions must be null: " +
 157                                                actions);
 158     }
 159 
 160     MBeanServerPermission(int mask) {
 161         super(getCanonicalName(mask));
 162         this.mask = impliedMask(mask);
 163     }
 164 
 165     private void readObject(ObjectInputStream in)
 166             throws IOException, ClassNotFoundException {
 167         in.defaultReadObject();
 168         mask = parseMask(getName());
 169     }
 170 
 171     static int simplifyMask(int mask) {
 172         if ((mask & CREATE_MASK) != 0)
 173             mask &= ~NEW_MASK;
 174         return mask;
 175     }
 176 
 177     static int impliedMask(int mask) {
 178         if ((mask & CREATE_MASK) != 0)
 179             mask |= NEW_MASK;
 180         return mask;
 181     }
 182 
 183     static String getCanonicalName(int mask) {
 184         if (mask == ALL_MASK)
 185             return "*";
 186 
 187         mask = simplifyMask(mask);
 188 
 189         synchronized (canonicalNames) {
 190             if (canonicalNames[mask] == null)
 191                 canonicalNames[mask] = makeCanonicalName(mask);
 192         }
 193 
 194         return canonicalNames[mask];
 195     }
 196 
 197     private static String makeCanonicalName(int mask) {
 198         final StringBuilder buf = new StringBuilder();
 199         for (int i = 0; i < N_NAMES; i++) {
 200             if ((mask & (1<<i)) != 0) {
 201                 if (buf.length() > 0)
 202                     buf.append(',');
 203                 buf.append(names[i]);
 204             }
 205         }
 206         return buf.toString().intern();
 207         /* intern() avoids duplication when the mask has only
 208            one bit, so is equivalent to the string constants
 209            we have for the names[] array.  */
 210     }
 211 
 212     /* Convert the string into a bitmask, including bits that
 213        are implied by the permissions in the string.  */
 214     private static int parseMask(String name) {
 215         /* Check that target name is a non-null non-empty string */
 216         if (name == null) {
 217             throw new NullPointerException("MBeanServerPermission: " +
 218                                            "target name can't be null");
 219         }
 220 
 221         name = name.trim();
 222         if (name.equals("*"))
 223             return ALL_MASK;
 224 
 225         /* If the name is empty, nameIndex will barf. */
 226         if (name.indexOf(',') < 0)
 227             return impliedMask(1 << nameIndex(name.trim()));
 228 
 229         int mask = 0;
 230 
 231         StringTokenizer tok = new StringTokenizer(name, ",");
 232         while (tok.hasMoreTokens()) {
 233             String action = tok.nextToken();
 234             int i = nameIndex(action.trim());
 235             mask |= (1 << i);
 236         }
 237 
 238         return impliedMask(mask);
 239     }
 240 
 241     private static int nameIndex(String name)
 242             throws IllegalArgumentException {
 243         for (int i = 0; i < N_NAMES; i++) {
 244             if (names[i].equals(name))
 245                 return i;
 246         }
 247         final String msg =
 248             "Invalid MBeanServerPermission name: \"" + name + "\"";
 249         throw new IllegalArgumentException(msg);
 250     }
 251 
 252     public int hashCode() {
 253         return mask;
 254     }
 255 
 256     /**
 257      * <p>Checks if this MBeanServerPermission object "implies" the specified
 258      * permission.</p>
 259      *
 260      * <p>More specifically, this method returns true if:</p>
 261      *
 262      * <ul>
 263      * <li> <i>p</i> is an instance of MBeanServerPermission,</li>
 264      * <li> <i>p</i>'s target names are a subset of this object's target
 265      * names</li>
 266      * </ul>
 267      *
 268      * <p>The <code>createMBeanServer</code> permission implies the
 269      * <code>newMBeanServer</code> permission.</p>
 270      *
 271      * @param p the permission to check against.
 272      * @return true if the specified permission is implied by this object,
 273      * false if not.
 274      */
 275     public boolean implies(Permission p) {
 276         if (!(p instanceof MBeanServerPermission))
 277             return false;
 278 
 279         MBeanServerPermission that = (MBeanServerPermission) p;
 280 
 281         return ((this.mask & that.mask) == that.mask);
 282     }
 283 
 284     /**
 285      * Checks two MBeanServerPermission objects for equality. Checks that
 286      * <i>obj</i> is an MBeanServerPermission, and represents the same
 287      * list of allowable actions as this object.
 288      *
 289      * @param obj the object we are testing for equality with this object.
 290      * @return true if the objects are equal.
 291      */
 292     public boolean equals(Object obj) {
 293         if (obj == this)
 294             return true;
 295 
 296         if (! (obj instanceof MBeanServerPermission))
 297             return false;
 298 
 299         MBeanServerPermission that = (MBeanServerPermission) obj;
 300 
 301         return (this.mask == that.mask);
 302     }
 303 
 304     public PermissionCollection newPermissionCollection() {
 305         return new MBeanServerPermissionCollection();
 306     }
 307 }
 308 
 309 /**
 310  * Class returned by {@link MBeanServerPermission#newPermissionCollection()}.
 311  *
 312  * @serial include
 313  */
 314 
 315 /*
 316  * Since every collection of MBSP can be represented by a single MBSP,
 317  * that is what our PermissionCollection does.  We need to define a
 318  * PermissionCollection because the one inherited from BasicPermission
 319  * doesn't know that createMBeanServer implies newMBeanServer.
 320  *
 321  * Though the serial form is defined, the TCK does not check it.  We do
 322  * not require independent implementations to duplicate it.  Even though
 323  * PermissionCollection is Serializable, instances of this class will
 324  * hardly ever be serialized, and different implementations do not
 325  * typically exchange serialized permission collections.
 326  *
 327  * If we did require that a particular form be respected here, we would
 328  * logically also have to require it for
 329  * MBeanPermission.newPermissionCollection, which would preclude an
 330  * implementation from defining a PermissionCollection there with an
 331  * optimized "implies" method.
 332  */
 333 class MBeanServerPermissionCollection extends PermissionCollection {
 334     /** @serial Null if no permissions in collection, otherwise a
 335         single permission that is the union of all permissions that
 336         have been added.  */
 337     private MBeanServerPermission collectionPermission;
 338 
 339     private static final long serialVersionUID = -5661980843569388590L;
 340 
 341     public synchronized void add(Permission permission) {
 342         if (!(permission instanceof MBeanServerPermission)) {
 343             final String msg =
 344                 "Permission not an MBeanServerPermission: " + permission;
 345             throw new IllegalArgumentException(msg);
 346         }
 347         if (isReadOnly())
 348             throw new SecurityException("Read-only permission collection");
 349         MBeanServerPermission mbsp = (MBeanServerPermission) permission;
 350         if (collectionPermission == null)
 351             collectionPermission = mbsp;
 352         else if (!collectionPermission.implies(permission)) {
 353             int newmask = collectionPermission.mask | mbsp.mask;
 354             collectionPermission = new MBeanServerPermission(newmask);
 355         }
 356     }
 357 
 358     public synchronized boolean implies(Permission permission) {
 359         return (collectionPermission != null &&
 360                 collectionPermission.implies(permission));
 361     }
 362 
 363     public synchronized Enumeration<Permission> elements() {
 364         Set<Permission> set;
 365         if (collectionPermission == null)
 366             set = Collections.emptySet();
 367         else
 368             set = Collections.singleton((Permission) collectionPermission);
 369         return Collections.enumeration(set);
 370     }
 371 }