001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package javax.management;
023:
024: // $Id: MBeanPermission.java 57200 2006-09-26 12:10:47Z dimitris@jboss.org $
025:
026: import java.io.IOException;
027: import java.io.ObjectInputStream;
028: import java.security.Permission;
029: import java.util.StringTokenizer;
030: import java.util.TreeSet;
031: import java.util.Iterator;
032:
033: /** Permission controlling access to MBeanServer operations. If a security
034: manager has been set using System.setSecurityManager(java.lang.SecurityManager),
035: most operations on the MBean Server require that the caller's permissions imply
036: an MBeanPermission appropriate for the operation. This is described in detail
037: in the documentation for the MBeanServer interface.
038:
039: As with other Permission objects, an MBeanPermission can represent either a
040: permission that you have or a permission that you need. When a sensitive
041: operation is being checked for permission, an MBeanPermission is constructed
042: representing the permission you need. The operation is only allowed if the
043: permissions you have imply the permission you need.
044:
045: An MBeanPermission contains four items of information:
046:
047: - The action. For a permission you need, this is one of the actions in the
048: list below. For a permission you have, this is a comma-separated list of those
049: actions, or *, representing all actions.
050:
051: The action is returned by getActions().
052:
053: - The class name.
054:
055: For a permission you need, this is the class name of an MBean you are
056: accessing, as returned by MBeanServer.getMBeanInfo(name).getClassName().
057: Certain operations do not reference a class name, in which case the class
058: name is null.
059:
060: For a permission you have, this is either empty or a class name pattern.
061: A class name pattern is a string following the Java conventions for
062: dot-separated class names. It may end with ".*" meaning that the permission
063: grants access to any class that begins with the string preceding ".*". For
064: instance, "javax.management.*" grants access to
065: javax.management.MBeanServerDelegate and javax.management.timer.Timer,
066: among other classes.
067:
068: A class name pattern can also be empty or the single character "*", both
069: of which grant access to any class.
070:
071: - The member.
072:
073: For a permission you need, this is the name of the attribute or operation
074: you are accessing. For operations that do not reference an attribute or
075: operation, the member is null.
076:
077: For a permission you have, this is either the name of an attribute or
078: operation you can access, or it is empty or the single character "*", both of
079: which grant access to any member.
080:
081: - The object name.
082:
083: For a permission you need, this is the ObjectName of the MBean you are
084: accessing. For operations that do not reference a single MBean, it is null.
085: It is never an object name pattern.
086:
087: For a permission you have, this is the ObjectName of the MBean or MBeans
088: you can access. It may be an object name pattern to grant access to all MBeans
089: whose names match the pattern. It may also be empty, which grants access to all
090: MBeans whatever their name.
091:
092: If you have an MBeanPermission, it allows operations only if all four of the
093: items match.
094:
095: The class name, member, and object name can be written together as a single
096: string, which is the name of this permission. The name of the permission is
097: the string returned by getName(). The format of the string is:
098:
099: className#member[objectName]
100:
101: The object name is written using the usual syntax for ObjectName. It may
102: contain any legal characters, including ]. It is terminated by a ] character
103: that is the last character in the string.
104:
105: One or more of the className, member, or objectName may be omitted. If the
106: member is omitted, the # may be too (but does not have to be). If the
107: objectName is omitted, the [] may be too (but does not have to be). It is not
108: legal to omit all three items, that is to have a name that is the empty string.
109:
110: One or more of the className, member, or objectName may be the character "-",
111: which is equivalent to a null value. A null value is implied by any value
112: (including another null value) but does not imply any other value.
113:
114: The possible actions are these:
115:
116: * addNotificationListener
117: * getAttribute
118: * getClassLoader
119: * getClassLoaderFor
120: * getClassLoaderRepository
121: * getDomains
122: * getMBeanInfo
123: * getObjectInstance
124: * instantiate
125: * invoke
126: * isInstanceOf
127: * queryMBeans
128: * queryNames
129: * registerMBean
130: * removeNotificationListener
131: * setAttribute
132: * unregisterMBean
133:
134: In a comma-separated list of actions, spaces are allowed before and after each
135: action.
136:
137: * @author <a href="mailto:thomas.diesler@jboss.org">Thomas Diesler</a>.
138: * @author Scott.Stark@jboss.org
139: * @version $Revision: 57200 $
140: */
141: public class MBeanPermission extends Permission {
142: private static final long serialVersionUID = -2416928705275160661L;
143: private static ObjectName ANY_NAME;
144: private static TreeSet VALID_ACTIONS = new TreeSet();
145: static {
146: VALID_ACTIONS.add("addNotificationListener");
147: VALID_ACTIONS.add("getAttribute");
148: VALID_ACTIONS.add("getClassLoader");
149: VALID_ACTIONS.add("getClassLoaderFor");
150: VALID_ACTIONS.add("getClassLoaderRepository");
151: VALID_ACTIONS.add("getDomains");
152: VALID_ACTIONS.add("getMBeanInfo");
153: VALID_ACTIONS.add("getObjectInstance");
154: VALID_ACTIONS.add("instantiate");
155: VALID_ACTIONS.add("invoke");
156: VALID_ACTIONS.add("isInstanceOf");
157: VALID_ACTIONS.add("queryMBeans");
158: VALID_ACTIONS.add("queryNames");
159: VALID_ACTIONS.add("registerMBean");
160: VALID_ACTIONS.add("removeNotificationListener");
161: VALID_ACTIONS.add("setAttribute");
162: VALID_ACTIONS.add("unregisterMBean");
163: }
164:
165: /** The class name this applies to */
166: private transient String className;
167: private transient boolean prefixMatch;
168: private transient String member;
169: private transient ObjectName objectName;
170: private transient TreeSet actionSet;
171: private String actions;
172:
173: /**
174: * Create a new MBeanPermission object with the specified target name and actions.
175: *
176: * The target name is of the form "className#member[objectName]" where each
177: * part is optional. It must not be empty or null.
178: *
179: * The actions parameter contains a comma-separated list of the desired
180: * actions granted on the target name. It must not be empty or null.
181: *
182: * @param name the triplet "className#member[objectName]".
183: * @param actions the action string.
184: * @throws IllegalArgumentException if the name or actions is invalid.
185: */
186: public MBeanPermission(String name, String actions) {
187: super (name);
188: parseName(name);
189: parseActions(actions);
190: }
191:
192: /** Create a new MBeanPermission object with the specified target name
193: * (class name, member, object name) and actions.
194: *
195: * The class name, member and object name parameters define a target name of
196: * the form "className#member[objectName]" where each part is optional. This
197: * will be the result of Permission.getName() on the resultant
198: * MBeanPermission.
199: *
200: * The actions parameter contains a comma-separated list of the desired
201: * actions granted on the target name. It must not be empty or null.
202: *
203: * @param className the class name to which this permission applies. May be
204: * null or "-", which represents a class name that is implied by any class
205: * name but does not imply any other class name.
206: * @param member the member to which this permission applies. May be null or
207: * "-", which represents a member that is implied by any member but does not
208: * imply any other member.
209: * @param objectName the object name to which this permission applies. May
210: * be null, which represents an object name that is implied by any object
211: * name but does not imply any other object name.
212: * @param actions the action string.
213: */
214: public MBeanPermission(String className, String member,
215: ObjectName objectName, String actions) {
216: super ((className == null ? "-" : className) + "#"
217: + (member == null ? "-" : member) + "["
218: + (objectName == null ? "-" : objectName.toString())
219: + "]");
220: this .className = className;
221: this .member = member;
222: this .objectName = objectName;
223: parseActions(actions);
224: }
225:
226: /**
227: * Returns the "canonical string representation" of the actions.
228: * That is, this method always returns present actions in alphabetical order.
229: * @return the canonical string representation of the actions.
230: */
231: public String getActions() {
232: return actions;
233: }
234:
235: /**
236: * Returns the hash code value for this object.
237: * @return a hash code value for this object.
238: */
239: public int hashCode() {
240: int hashCode = getName().hashCode();
241: if (actionSet != null)
242: hashCode += actionSet.hashCode();
243: return hashCode;
244: }
245:
246: /**
247: * Checks if this MBeanPermission object "implies" the specified permission.
248: *
249: * More specifically, this method returns true if:
250: * <ul>
251: * <li>p is an instance of MBeanPermission; and
252: * <li>p has a null className or p's className matches this object's
253: * className; and
254: * <li>p has a null member or p's member matches this object's member; and
255: * <li>p has a null object name name or or p's object name matches this
256: * object's object name; and
257: * <li>p's actions are a subset of this object's actions
258: * </ul>
259: *
260: * If this object's className is "*", p's className always matches it. If it
261: * is "a.*", p's className matches it if it begins with "a.".
262: *
263: * If this object's member is "*", p's member always matches it.
264: *
265: * If this object's objectName n1 is an object name pattern, p's objectName
266: * n2 matches it if n1.equals(n2) or if n1.apply(n2).
267: *
268: * A permission that includes the queryMBeans action is considered to include
269: * queryNames as well.
270: *
271: * @param p the permission to check against.
272: * @return true if the specified permission is implied by this object, false
273: * if not.
274: */
275: public boolean implies(Permission p) {
276: if (p == null || (p instanceof MBeanPermission) == false)
277: return false;
278:
279: MBeanPermission perm = (MBeanPermission) p;
280: boolean implies = false;
281: // Check the className
282: if (perm.className == null)
283: implies = true;
284: else if (className == null)
285: implies = false;
286: else if (className.length() == 0)
287: implies = true;
288: else if (prefixMatch == true && perm.className != null)
289: implies = perm.className.startsWith(className);
290: else
291: implies = className.equals(perm.className);
292:
293: // Check the member
294: if (implies == true) {
295: if (perm.member == null)
296: implies = true;
297: else if (member == null)
298: implies = false;
299: else if (member.length() == 0)
300: implies = true;
301: else
302: implies = member.equals(perm.member);
303: }
304:
305: // Check the object name
306: if (implies == true) {
307: if (perm.objectName == null)
308: implies = true;
309: else if (objectName == null)
310: implies = false;
311: else {
312: implies = objectName == perm.objectName
313: || objectName.apply(perm.objectName);
314: if (implies == false && perm.objectName.isPattern())
315: implies = objectName.equals(perm.objectName);
316: }
317: }
318:
319: // Check the actions
320: if (actionSet != null && implies == true) {
321: implies = perm.actionSet != null
322: && actionSet.containsAll(perm.actionSet);
323: }
324:
325: return implies;
326: }
327:
328: /**
329: * Checks two MBeanPermission objects for equality. Checks that obj is an
330: * MBeanPermission, and has the same name and actions as this object.
331: * @param p the object we are testing for equality with this object.
332: * @return true if obj is an MBeanPermission, and has the same name and
333: * actions as this MBeanPermission object.
334: */
335: public boolean equals(Object p) {
336: if (p == null || (p instanceof MBeanPermission) == false)
337: return false;
338:
339: MBeanPermission perm = (MBeanPermission) p;
340: boolean equals = getName().equals(perm.getName());
341: if (equals) {
342: equals = actionSet == perm.actionSet;
343: if (equals == false && actionSet != null)
344: equals = actionSet.equals(perm.actionSet);
345: }
346:
347: return equals;
348: }
349:
350: /** Parse the className#member[objectName] name.
351: * @param name - className#member[objectName]
352: * @throws IllegalArgumentException
353: */
354: private void parseName(String name) throws IllegalArgumentException {
355: if (name == null || name.length() == 0)
356: throw new IllegalArgumentException(
357: "name must not be empty or null");
358:
359: StringTokenizer tokenizer = new StringTokenizer(name, "#[]",
360: true);
361: boolean inMember = false;
362: boolean inObjectName = false;
363: // Parse the className
364: className = tokenizer.nextToken();
365: if (className.equals("#")) {
366: className = "";
367: inMember = true;
368: } else if (className.equals("[")) {
369: className = "";
370: inObjectName = true;
371: } else if (className.equals("*"))
372: className = "";
373: else if (className.equals("-"))
374: className = null;
375: else if (className.endsWith(".*")) {
376: className = className.substring(0, className.length() - 2);
377: prefixMatch = true;
378: }
379:
380: // Parse the member
381: member = "";
382: if (inObjectName == false) {
383: if (inMember == true) {
384: // Parse after the #
385: if (tokenizer.hasMoreTokens()) {
386: member = tokenizer.nextToken();
387: if (member.equals("[")) {
388: inObjectName = true;
389: member = "";
390: } else if (member.equals("*")) {
391: member = "";
392: } else if (member.equals("-")) {
393: member = null;
394: }
395: }
396: }
397: // See if there is a #
398: else if (tokenizer.hasMoreTokens()) {
399: // Can only be a # or [
400: member = tokenizer.nextToken();
401: if (member.equals("#")) {
402: if (tokenizer.hasMoreTokens()) {
403: member = tokenizer.nextToken();
404: if (member.equals("[")) {
405: inObjectName = true;
406: member = "";
407: } else if (member.equals("*")) {
408: member = "";
409: } else if (member.equals("-")) {
410: member = null;
411: }
412: } else {
413: member = "";
414: }
415: } else {
416: inObjectName = true;
417: }
418: }
419: }
420:
421: if (ANY_NAME == null) {
422: try {
423: ANY_NAME = new ObjectName("*:*");
424: } catch (Exception e) {
425: throw new IllegalStateException(
426: "Could not create ObjectName(*:*)");
427: }
428: }
429:
430: // Parse the objectName
431: objectName = ANY_NAME;
432: if (inObjectName == false && tokenizer.hasMoreTokens()) {
433: inObjectName = true;
434: // Throw away the [
435: tokenizer.nextToken();
436: }
437:
438: if (inObjectName) {
439: // Get the token upto the trailing ]
440: String token = tokenizer.nextToken("]");
441: try {
442: if (token.equals("-"))
443: objectName = null;
444: else if (token.equals("]"))
445: objectName = ANY_NAME;
446: else
447: objectName = new ObjectName(token);
448: } catch (Exception e) {
449: IllegalArgumentException ex = new IllegalArgumentException(
450: "Invalid objectName");
451: ex.initCause(e);
452: throw ex;
453: }
454: }
455: }
456:
457: /** Parse the actions list
458: * @param actions - a comma-separated list of the desired actions granted on
459: * the target name. It must not be empty or null
460: * @throws IllegalArgumentException
461: */
462: private void parseActions(String actions)
463: throws IllegalArgumentException {
464: if (actions == null || actions.length() == 0)
465: throw new IllegalArgumentException(
466: "actions must not be empty or null");
467:
468: if (actions.equals("*")) {
469: this .actionSet = null;
470: this .actions = "*";
471: } else {
472: this .actionSet = new TreeSet();
473: StringTokenizer tokenizer = new StringTokenizer(actions,
474: ", ");
475: while (tokenizer.hasMoreTokens()) {
476: String action = tokenizer.nextToken();
477: if (VALID_ACTIONS.contains(action) == false)
478: throw new IllegalArgumentException(action
479: + " is not one of: " + VALID_ACTIONS);
480: this .actionSet.add(action);
481: }
482: // queryMBeans -> queryNames;
483: if (this .actionSet.contains("queryMBeans"))
484: this .actionSet.add("queryNames");
485:
486: StringBuffer tmp = new StringBuffer();
487: Iterator iter = this .actionSet.iterator();
488: while (iter.hasNext()) {
489: tmp.append(iter.next());
490: tmp.append(',');
491: }
492: tmp.setLength(tmp.length() - 1);
493: this .actions = tmp.toString();
494: }
495: }
496:
497: private void readObject(ObjectInputStream ois) throws IOException,
498: ClassNotFoundException {
499: ois.defaultReadObject();
500: parseName(getName());
501: parseActions(getActions());
502: }
503: }
|