1/*
2 * Copyright (c) 2001, 2014, 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
26package javax.management;
27
28import java.io.IOException;
29import java.io.ObjectInputStream;
30import java.security.BasicPermission;
31import java.security.Permission;
32import java.security.PermissionCollection;
33import java.util.Collections;
34import java.util.Enumeration;
35import java.util.Set;
36import 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 */
69public 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 */
333class 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}
372