1 /*
2 * Copyright 1997-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package java.security;
27
28 import java.security;
29 import java.util.Enumeration;
30 import java.util.Map;
31 import java.util.HashMap;
32 import java.util.Hashtable;
33 import java.util.Collections;
34 import java.io.ObjectStreamField;
35 import java.io.ObjectOutputStream;
36 import java.io.ObjectInputStream;
37 import java.io.IOException;
38
39 /**
40 * The BasicPermission class extends the Permission class, and
41 * can be used as the base class for permissions that want to
42 * follow the same naming convention as BasicPermission.
43 * <P>
44 * The name for a BasicPermission is the name of the given permission
45 * (for example, "exit",
46 * "setFactory", "print.queueJob", etc). The naming
47 * convention follows the hierarchical property naming convention.
48 * An asterisk may appear by itself, or if immediately preceded by a "."
49 * may appear at the end of the name, to signify a wildcard match.
50 * For example, "*" and "java.*" are valid, while "*java", "a*b",
51 * and "java*" are not valid.
52 * <P>
53 * The action string (inherited from Permission) is unused.
54 * Thus, BasicPermission is commonly used as the base class for
55 * "named" permissions
56 * (ones that contain a name but no actions list; you either have the
57 * named permission or you don't.)
58 * Subclasses may implement actions on top of BasicPermission,
59 * if desired.
60 * <p>
61 * <P>
62 * @see java.security.Permission
63 * @see java.security.Permissions
64 * @see java.security.PermissionCollection
65 * @see java.lang.SecurityManager
66 *
67 * @author Marianne Mueller
68 * @author Roland Schemers
69 */
70
71 public abstract class BasicPermission extends Permission
72 implements java.io.Serializable
73 {
74
75 private static final long serialVersionUID = 6279438298436773498L;
76
77 // does this permission have a wildcard at the end?
78 private transient boolean wildcard;
79
80 // the name without the wildcard on the end
81 private transient String path;
82
83 // is this permission the old-style exitVM permission (pre JDK 1.6)?
84 private transient boolean exitVM;
85
86 /**
87 * initialize a BasicPermission object. Common to all constructors.
88 *
89 */
90 private void init(String name)
91 {
92 if (name == null)
93 throw new NullPointerException("name can't be null");
94
95 int len = name.length();
96
97 if (len == 0) {
98 throw new IllegalArgumentException("name can't be empty");
99 }
100
101 char last = name.charAt(len - 1);
102
103 // Is wildcard or ends with ".*"?
104 if (last == '*' && (len == 1 || name.charAt(len - 2) == '.')) {
105 wildcard = true;
106 if (len == 1) {
107 path = "";
108 } else {
109 path = name.substring(0, len - 1);
110 }
111 } else {
112 if (name.equals("exitVM")) {
113 wildcard = true;
114 path = "exitVM.";
115 exitVM = true;
116 } else {
117 path = name;
118 }
119 }
120 }
121
122 /**
123 * Creates a new BasicPermission with the specified name.
124 * Name is the symbolic name of the permission, such as
125 * "setFactory",
126 * "print.queueJob", or "topLevelWindow", etc.
127 *
128 * @param name the name of the BasicPermission.
129 *
130 * @throws NullPointerException if <code>name</code> is <code>null</code>.
131 * @throws IllegalArgumentException if <code>name</code> is empty.
132 */
133
134 public BasicPermission(String name)
135 {
136 super(name);
137 init(name);
138 }
139
140
141 /**
142 * Creates a new BasicPermission object with the specified name.
143 * The name is the symbolic name of the BasicPermission, and the
144 * actions String is currently unused.
145 *
146 * @param name the name of the BasicPermission.
147 * @param actions ignored.
148 *
149 * @throws NullPointerException if <code>name</code> is <code>null</code>.
150 * @throws IllegalArgumentException if <code>name</code> is empty.
151 */
152 public BasicPermission(String name, String actions)
153 {
154 super(name);
155 init(name);
156 }
157
158 /**
159 * Checks if the specified permission is "implied" by
160 * this object.
161 * <P>
162 * More specifically, this method returns true if:<p>
163 * <ul>
164 * <li> <i>p</i>'s class is the same as this object's class, and<p>
165 * <li> <i>p</i>'s name equals or (in the case of wildcards)
166 * is implied by this object's
167 * name. For example, "a.b.*" implies "a.b.c".
168 * </ul>
169 *
170 * @param p the permission to check against.
171 *
172 * @return true if the passed permission is equal to or
173 * implied by this permission, false otherwise.
174 */
175 public boolean implies(Permission p) {
176 if ((p == null) || (p.getClass() != getClass()))
177 return false;
178
179 BasicPermission that = (BasicPermission) p;
180
181 if (this.wildcard) {
182 if (that.wildcard) {
183 // one wildcard can imply another
184 return that.path.startsWith(path);
185 } else {
186 // make sure ap.path is longer so a.b.* doesn't imply a.b
187 return (that.path.length() > this.path.length()) &&
188 that.path.startsWith(this.path);
189 }
190 } else {
191 if (that.wildcard) {
192 // a non-wildcard can't imply a wildcard
193 return false;
194 }
195 else {
196 return this.path.equals(that.path);
197 }
198 }
199 }
200
201 /**
202 * Checks two BasicPermission objects for equality.
203 * Checks that <i>obj</i>'s class is the same as this object's class
204 * and has the same name as this object.
205 * <P>
206 * @param obj the object we are testing for equality with this object.
207 * @return true if <i>obj</i>'s class is the same as this object's class
208 * and has the same name as this BasicPermission object, false otherwise.
209 */
210 public boolean equals(Object obj) {
211 if (obj == this)
212 return true;
213
214 if ((obj == null) || (obj.getClass() != getClass()))
215 return false;
216
217 BasicPermission bp = (BasicPermission) obj;
218
219 return getName().equals(bp.getName());
220 }
221
222
223 /**
224 * Returns the hash code value for this object.
225 * The hash code used is the hash code of the name, that is,
226 * <code>getName().hashCode()</code>, where <code>getName</code> is
227 * from the Permission superclass.
228 *
229 * @return a hash code value for this object.
230 */
231 public int hashCode() {
232 return this.getName().hashCode();
233 }
234
235 /**
236 * Returns the canonical string representation of the actions,
237 * which currently is the empty string "", since there are no actions for
238 * a BasicPermission.
239 *
240 * @return the empty string "".
241 */
242 public String getActions()
243 {
244 return "";
245 }
246
247 /**
248 * Returns a new PermissionCollection object for storing BasicPermission
249 * objects.
250 *
251 * <p>BasicPermission objects must be stored in a manner that allows them
252 * to be inserted in any order, but that also enables the
253 * PermissionCollection <code>implies</code> method
254 * to be implemented in an efficient (and consistent) manner.
255 *
256 * @return a new PermissionCollection object suitable for
257 * storing BasicPermissions.
258 */
259 public PermissionCollection newPermissionCollection() {
260 return new BasicPermissionCollection(this.getClass());
261 }
262
263 /**
264 * readObject is called to restore the state of the BasicPermission from
265 * a stream.
266 */
267 private void readObject(ObjectInputStream s)
268 throws IOException, ClassNotFoundException
269 {
270 s.defaultReadObject();
271 // init is called to initialize the rest of the values.
272 init(getName());
273 }
274
275 /**
276 * Returns the canonical name of this BasicPermission.
277 * All internal invocations of getName should invoke this method, so
278 * that the pre-JDK 1.6 "exitVM" and current "exitVM.*" permission are
279 * equivalent in equals/hashCode methods.
280 *
281 * @return the canonical name of this BasicPermission.
282 */
283 final String getCanonicalName() {
284 return exitVM ? "exitVM.*" : getName();
285 }
286 }
287
288 /**
289 * A BasicPermissionCollection stores a collection
290 * of BasicPermission permissions. BasicPermission objects
291 * must be stored in a manner that allows them to be inserted in any
292 * order, but enable the implies function to evaluate the implies
293 * method in an efficient (and consistent) manner.
294 *
295 * A BasicPermissionCollection handles comparing a permission like "a.b.c.d.e"
296 * with a Permission such as "a.b.*", or "*".
297 *
298 * @see java.security.Permission
299 * @see java.security.Permissions
300 * @see java.security.PermissionsImpl
301 *
302 *
303 * @author Roland Schemers
304 *
305 * @serial include
306 */
307
308 final class BasicPermissionCollection
309 extends PermissionCollection
310 implements java.io.Serializable
311 {
312
313 private static final long serialVersionUID = 739301742472979399L;
314
315 /**
316 * Key is name, value is permission. All permission objects in
317 * collection must be of the same type.
318 * Not serialized; see serialization section at end of class.
319 */
320 private transient Map<String, Permission> perms;
321
322 /**
323 * This is set to <code>true</code> if this BasicPermissionCollection
324 * contains a BasicPermission with '*' as its permission name.
325 *
326 * @see #serialPersistentFields
327 */
328 private boolean all_allowed;
329
330 /**
331 * The class to which all BasicPermissions in this
332 * BasicPermissionCollection belongs.
333 *
334 * @see #serialPersistentFields
335 */
336 private Class permClass;
337
338 /**
339 * Create an empty BasicPermissionCollection object.
340 *
341 */
342
343 public BasicPermissionCollection(Class clazz) {
344 perms = new HashMap<String, Permission>(11);
345 all_allowed = false;
346 permClass = clazz;
347 }
348
349 /**
350 * Adds a permission to the BasicPermissions. The key for the hash is
351 * permission.path.
352 *
353 * @param permission the Permission object to add.
354 *
355 * @exception IllegalArgumentException - if the permission is not a
356 * BasicPermission, or if
357 * the permission is not of the
358 * same Class as the other
359 * permissions in this collection.
360 *
361 * @exception SecurityException - if this BasicPermissionCollection object
362 * has been marked readonly
363 */
364
365 public void add(Permission permission)
366 {
367 if (! (permission instanceof BasicPermission))
368 throw new IllegalArgumentException("invalid permission: "+
369 permission);
370 if (isReadOnly())
371 throw new SecurityException("attempt to add a Permission to a readonly PermissionCollection");
372
373 BasicPermission bp = (BasicPermission) permission;
374
375 // make sure we only add new BasicPermissions of the same class
376 // Also check null for compatibility with deserialized form from
377 // previous versions.
378 if (permClass == null) {
379 // adding first permission
380 permClass = bp.getClass();
381 } else {
382 if (bp.getClass() != permClass)
383 throw new IllegalArgumentException("invalid permission: " +
384 permission);
385 }
386
387 synchronized (this) {
388 perms.put(bp.getCanonicalName(), permission);
389 }
390
391 // No sync on all_allowed; staleness OK
392 if (!all_allowed) {
393 if (bp.getCanonicalName().equals("*"))
394 all_allowed = true;
395 }
396 }
397
398 /**
399 * Check and see if this set of permissions implies the permissions
400 * expressed in "permission".
401 *
402 * @param p the Permission object to compare
403 *
404 * @return true if "permission" is a proper subset of a permission in
405 * the set, false if not.
406 */
407
408 public boolean implies(Permission permission)
409 {
410 if (! (permission instanceof BasicPermission))
411 return false;
412
413 BasicPermission bp = (BasicPermission) permission;
414
415 // random subclasses of BasicPermission do not imply each other
416 if (bp.getClass() != permClass)
417 return false;
418
419 // short circuit if the "*" Permission was added
420 if (all_allowed)
421 return true;
422
423 // strategy:
424 // Check for full match first. Then work our way up the
425 // path looking for matches on a.b..*
426
427 String path = bp.getCanonicalName();
428 //System.out.println("check "+path);
429
430 Permission x;
431
432 synchronized (this) {
433 x = perms.get(path);
434 }
435
436 if (x != null) {
437 // we have a direct hit!
438 return x.implies(permission);
439 }
440
441 // work our way up the tree...
442 int last, offset;
443
444 offset = path.length()-1;
445
446 while ((last = path.lastIndexOf(".", offset)) != -1) {
447
448 path = path.substring(0, last+1) + "*";
449 //System.out.println("check "+path);
450
451 synchronized (this) {
452 x = perms.get(path);
453 }
454
455 if (x != null) {
456 return x.implies(permission);
457 }
458 offset = last -1;
459 }
460
461 // we don't have to check for "*" as it was already checked
462 // at the top (all_allowed), so we just return false
463 return false;
464 }
465
466 /**
467 * Returns an enumeration of all the BasicPermission objects in the
468 * container.
469 *
470 * @return an enumeration of all the BasicPermission objects.
471 */
472
473 public Enumeration<Permission> elements() {
474 // Convert Iterator of Map values into an Enumeration
475 synchronized (this) {
476 return Collections.enumeration(perms.values());
477 }
478 }
479
480 // Need to maintain serialization interoperability with earlier releases,
481 // which had the serializable field:
482 //
483 // @serial the Hashtable is indexed by the BasicPermission name
484 //
485 // private Hashtable permissions;
486 /**
487 * @serialField permissions java.util.Hashtable
488 * The BasicPermissions in this BasicPermissionCollection.
489 * All BasicPermissions in the collection must belong to the same class.
490 * The Hashtable is indexed by the BasicPermission name; the value
491 * of the Hashtable entry is the permission.
492 * @serialField all_allowed boolean
493 * This is set to <code>true</code> if this BasicPermissionCollection
494 * contains a BasicPermission with '*' as its permission name.
495 * @serialField permClass java.lang.Class
496 * The class to which all BasicPermissions in this
497 * BasicPermissionCollection belongs.
498 */
499 private static final ObjectStreamField[] serialPersistentFields = {
500 new ObjectStreamField("permissions", Hashtable.class),
501 new ObjectStreamField("all_allowed", Boolean.TYPE),
502 new ObjectStreamField("permClass", Class.class),
503 };
504
505 /**
506 * @serialData Default fields.
507 */
508 /*
509 * Writes the contents of the perms field out as a Hashtable for
510 * serialization compatibility with earlier releases. all_allowed
511 * and permClass unchanged.
512 */
513 private void writeObject(ObjectOutputStream out) throws IOException {
514 // Don't call out.defaultWriteObject()
515
516 // Copy perms into a Hashtable
517 Hashtable<String, Permission> permissions =
518 new Hashtable<String, Permission>(perms.size()*2);
519
520 synchronized (this) {
521 permissions.putAll(perms);
522 }
523
524 // Write out serializable fields
525 ObjectOutputStream.PutField pfields = out.putFields();
526 pfields.put("all_allowed", all_allowed);
527 pfields.put("permissions", permissions);
528 pfields.put("permClass", permClass);
529 out.writeFields();
530 }
531
532 /**
533 * readObject is called to restore the state of the
534 * BasicPermissionCollection from a stream.
535 */
536 private void readObject(java.io.ObjectInputStream in)
537 throws IOException, ClassNotFoundException
538 {
539 // Don't call defaultReadObject()
540
541 // Read in serialized fields
542 ObjectInputStream.GetField gfields = in.readFields();
543
544 // Get permissions
545 Hashtable<String, Permission> permissions =
546 (Hashtable<String, Permission>)gfields.get("permissions", null);
547 perms = new HashMap<String, Permission>(permissions.size()*2);
548 perms.putAll(permissions);
549
550 // Get all_allowed
551 all_allowed = gfields.get("all_allowed", false);
552
553 // Get permClass
554 permClass = (Class) gfields.get("permClass", null);
555
556 if (permClass == null) {
557 // set permClass
558 Enumeration<Permission> e = permissions.elements();
559 if (e.hasMoreElements()) {
560 Permission p = e.nextElement();
561 permClass = p.getClass();
562 }
563 }
564 }
565 }