| PropertyPermission.java |
1 /*
2 * %W% %E%
3 *
4 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
5 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6 */
7
8 package java.util;
9
10 import java.io.Serializable;
11 import java.io.IOException;
12 import java.security.*;
13 import java.util.Map;
14 import java.util.HashMap;
15 import java.util.Enumeration;
16 import java.util.Hashtable;
17 import java.util.Collections;
18 import java.io.ObjectStreamField;
19 import java.io.ObjectOutputStream;
20 import java.io.ObjectInputStream;
21 import java.io.IOException;
22 import sun.security.util.SecurityConstants;
23
24 /**
25 * This class is for property permissions.
26 *
27 * <P>
28 * The name is the name of the property ("java.home",
29 * "os.name", etc). The naming
30 * convention follows the hierarchical property naming convention.
31 * Also, an asterisk
32 * may appear at the end of the name, following a ".", or by itself, to
33 * signify a wildcard match. For example: "java.*" or "*" is valid,
34 * "*java" or "a*b" is not valid.
35 * <P>
36 * <P>
37 * The actions to be granted are passed to the constructor in a string containing
38 * a list of zero or more comma-separated keywords. The possible keywords are
39 * "read" and "write". Their meaning is defined as follows:
40 * <P>
41 * <DL>
42 * <DT> read
43 * <DD> read permission. Allows <code>System.getProperty</code> to
44 * be called.
45 * <DT> write
46 * <DD> write permission. Allows <code>System.setProperty</code> to
47 * be called.
48 * </DL>
49 * <P>
50 * The actions string is converted to lowercase before processing.
51 * <P>
52 * Care should be taken before granting code permission to access
53 * certain system properties. For example, granting permission to
54 * access the "java.home" system property gives potentially malevolent
55 * code sensitive information about the system environment (the Java
56 * installation directory). Also, granting permission to access
57 * the "user.name" and "user.home" system properties gives potentially
58 * malevolent code sensitive information about the user environment
59 * (the user's account name and home directory).
60 *
61 * @see java.security.BasicPermission
62 * @see java.security.Permission
63 * @see java.security.Permissions
64 * @see java.security.PermissionCollection
65 * @see java.lang.SecurityManager
66 *
67 * @version %I% %E%
68 *
69 * @author Roland Schemers
70 * @since 1.2
71 *
72 * @serial exclude
73 */
74
75 public final class PropertyPermission extends BasicPermission {
76
77 /**
78 * Read action.
79 */
80 private final static int READ = 0x1;
81
82 /**
83 * Write action.
84 */
85 private final static int WRITE = 0x2;
86 /**
87 * All actions (read,write);
88 */
89 private final static int ALL = READ|WRITE;
90 /**
91 * No actions.
92 */
93 private final static int NONE = 0x0;
94
95 /**
96 * The actions mask.
97 *
98 */
99 private transient int mask;
100
101 /**
102 * The actions string.
103 *
104 * @serial
105 */
106 private String actions; // Left null as long as possible, then
107 // created and re-used in the getAction function.
108
109 /**
110 * initialize a PropertyPermission object. Common to all constructors.
111 * Also called during de-serialization.
112 *
113 * @param mask the actions mask to use.
114 *
115 */
116
117 private void init(int mask)
118 {
119
120 if ((mask & ALL) != mask)
121 throw new IllegalArgumentException("invalid actions mask");
122
123 if (mask == NONE)
124 throw new IllegalArgumentException("invalid actions mask");
125
126 if (getName() == null)
127 throw new NullPointerException("name can't be null");
128
129 this.mask = mask;
130 }
131
132 /**
133 * Creates a new PropertyPermission object with the specified name.
134 * The name is the name of the system property, and
135 * <i>actions</i> contains a comma-separated list of the
136 * desired actions granted on the property. Possible actions are
137 * "read" and "write".
138 *
139 * @param name the name of the PropertyPermission.
140 * @param actions the actions string.
141 *
142 * @throws NullPointerException if <code>name</code> is <code>null</code>.
143 * @throws IllegalArgumentException if <code>name</code> is empty or if
144 * <code>actions</code> is invalid.
145 */
146
147 public PropertyPermission(String name, String actions)
148 {
149 super(name,actions);
150 init(getMask(actions));
151 }
152
153 /**
154 * Checks if this PropertyPermission object "implies" the specified
155 * permission.
156 * <P>
157 * More specifically, this method returns true if:<p>
158 * <ul>
159 * <li> <i>p</i> is an instanceof PropertyPermission,<p>
160 * <li> <i>p</i>'s actions are a subset of this
161 * object's actions, and <p>
162 * <li> <i>p</i>'s name is implied by this object's
163 * name. For example, "java.*" implies "java.home".
164 * </ul>
165 * @param p the permission to check against.
166 *
167 * @return true if the specified permission is implied by this object,
168 * false if not.
169 */
170 public boolean implies(Permission p) {
171 if (!(p instanceof PropertyPermission))
172 return false;
173
174 PropertyPermission that = (PropertyPermission) p;
175
176 // we get the effective mask. i.e., the "and" of this and that.
177 // They must be equal to that.mask for implies to return true.
178
179 return ((this.mask & that.mask) == that.mask) && super.implies(that);
180 }
181
182
183 /**
184 * Checks two PropertyPermission objects for equality. Checks that <i>obj</i> is
185 * a PropertyPermission, and has the same name and actions as this object.
186 * <P>
187 * @param obj the object we are testing for equality with this object.
188 * @return true if obj is a PropertyPermission, and has the same name and
189 * actions as this PropertyPermission object.
190 */
191 public boolean equals(Object obj) {
192 if (obj == this)
193 return true;
194
195 if (! (obj instanceof PropertyPermission))
196 return false;
197
198 PropertyPermission that = (PropertyPermission) obj;
199
200 return (this.mask == that.mask) &&
201 (this.getName().equals(that.getName()));
202 }
203
204 /**
205 * Returns the hash code value for this object.
206 * The hash code used is the hash code of this permissions name, that is,
207 * <code>getName().hashCode()</code>, where <code>getName</code> is
208 * from the Permission superclass.
209 *
210 * @return a hash code value for this object.
211 */
212
213 public int hashCode() {
214 return this.getName().hashCode();
215 }
216
217
218 /**
219 * Converts an actions String to an actions mask.
220 *
221 * @param action the action string.
222 * @return the actions mask.
223 */
224 private static int getMask(String actions) {
225
226 int mask = NONE;
227
228 if (actions == null) {
229 return mask;
230 }
231
232 // Check against use of constants (used heavily within the JDK)
233 if (actions == SecurityConstants.PROPERTY_READ_ACTION) {
234 return READ;
235 } if (actions == SecurityConstants.PROPERTY_WRITE_ACTION) {
236 return WRITE;
237 } else if (actions == SecurityConstants.PROPERTY_RW_ACTION) {
238 return READ|WRITE;
239 }
240
241 char[] a = actions.toCharArray();
242
243 int i = a.length - 1;
244 if (i < 0)
245 return mask;
246
247 while (i != -1) {
248 char c;
249
250 // skip whitespace
251 while ((i!=-1) && ((c = a[i]) == ' ' ||
252 c == '\r' ||
253 c == '\n' ||
254 c == '\f' ||
255 c == '\t'))
256 i--;
257
258 // check for the known strings
259 int matchlen;
260
261 if (i >= 3 && (a[i-3] == 'r' || a[i-3] == 'R') &&
262 (a[i-2] == 'e' || a[i-2] == 'E') &&
263 (a[i-1] == 'a' || a[i-1] == 'A') &&
264 (a[i] == 'd' || a[i] == 'D'))
265 {
266 matchlen = 4;
267 mask |= READ;
268
269 } else if (i >= 4 && (a[i-4] == 'w' || a[i-4] == 'W') &&
270 (a[i-3] == 'r' || a[i-3] == 'R') &&
271 (a[i-2] == 'i' || a[i-2] == 'I') &&
272 (a[i-1] == 't' || a[i-1] == 'T') &&
273 (a[i] == 'e' || a[i] == 'E'))
274 {
275 matchlen = 5;
276 mask |= WRITE;
277
278 } else {
279 // parse error
280 throw new IllegalArgumentException(
281 "invalid permission: " + actions);
282 }
283
284 // make sure we didn't just match the tail of a word
285 // like "ackbarfaccept". Also, skip to the comma.
286 boolean seencomma = false;
287 while (i >= matchlen && !seencomma) {
288 switch(a[i-matchlen]) {
289 case ',':
290 seencomma = true;
291 /*FALLTHROUGH*/
292 case ' ': case '\r': case '\n':
293 case '\f': case '\t':
294 break;
295 default:
296 throw new IllegalArgumentException(
297 "invalid permission: " + actions);
298 }
299 i--;
300 }
301
302 // point i at the location of the comma minus one (or -1).
303 i -= matchlen;
304 }
305
306 return mask;
307 }
308
309
310 /**
311 * Return the canonical string representation of the actions.
312 * Always returns present actions in the following order:
313 * read, write.
314 *
315 * @return the canonical string representation of the actions.
316 */
317 static String getActions(int mask)
318 {
319 StringBuilder sb = new StringBuilder();
320 boolean comma = false;
321
322 if ((mask & READ) == READ) {
323 comma = true;
324 sb.append("read");
325 }
326
327 if ((mask & WRITE) == WRITE) {
328 if (comma) sb.append(',');
329 else comma = true;
330 sb.append("write");
331 }
332 return sb.toString();
333 }
334
335 /**
336 * Returns the "canonical string representation" of the actions.
337 * That is, this method always returns present actions in the following order:
338 * read, write. For example, if this PropertyPermission object
339 * allows both write and read actions, a call to <code>getActions</code>
340 * will return the string "read,write".
341 *
342 * @return the canonical string representation of the actions.
343 */
344 public String getActions()
345 {
346 if (actions == null)
347 actions = getActions(this.mask);
348
349 return actions;
350 }
351
352 /**
353 * Return the current action mask.
354 * Used by the PropertyPermissionCollection
355 *
356 * @return the actions mask.
357 */
358
359 int getMask() {
360 return mask;
361 }
362
363 /**
364 * Returns a new PermissionCollection object for storing
365 * PropertyPermission objects.
366 * <p>
367 *
368 * @return a new PermissionCollection object suitable for storing
369 * PropertyPermissions.
370 */
371
372 public PermissionCollection newPermissionCollection() {
373 return new PropertyPermissionCollection();
374 }
375
376
377 private static final long serialVersionUID = 885438825399942851L;
378
379 /**
380 * WriteObject is called to save the state of the PropertyPermission
381 * to a stream. The actions are serialized, and the superclass
382 * takes care of the name.
383 */
384 private synchronized void writeObject(java.io.ObjectOutputStream s)
385 throws IOException
386 {
387 // Write out the actions. The superclass takes care of the name
388 // call getActions to make sure actions field is initialized
389 if (actions == null)
390 getActions();
391 s.defaultWriteObject();
392 }
393
394 /**
395 * readObject is called to restore the state of the PropertyPermission from
396 * a stream.
397 */
398 private synchronized void readObject(java.io.ObjectInputStream s)
399 throws IOException, ClassNotFoundException
400 {
401 // Read in the action, then initialize the rest
402 s.defaultReadObject();
403 init(getMask(actions));
404 }
405 }
406
407 /**
408 * A PropertyPermissionCollection stores a set of PropertyPermission
409 * permissions.
410 *
411 * @see java.security.Permission
412 * @see java.security.Permissions
413 * @see java.security.PermissionCollection
414 *
415 * @version %I%, %G%
416 *
417 * @author Roland Schemers
418 *
419 * @serial include
420 */
421 final class PropertyPermissionCollection extends PermissionCollection
422 implements Serializable
423 {
424
425 /**
426 * Key is property name; value is PropertyPermission.
427 * Not serialized; see serialization section at end of class.
428 */
429 private transient Map perms;
430
431 /**
432 * Boolean saying if "*" is in the collection.
433 *
434 * @see #serialPersistentFields
435 */
436 // No sync access; OK for this to be stale.
437 private boolean all_allowed;
438
439 /**
440 * Create an empty PropertyPermissions object.
441 *
442 */
443
444 public PropertyPermissionCollection() {
445 perms = new HashMap(32); // Capacity for default policy
446 all_allowed = false;
447 }
448
449 /**
450 * Adds a permission to the PropertyPermissions. The key for the hash is
451 * the name.
452 *
453 * @param permission the Permission object to add.
454 *
455 * @exception IllegalArgumentException - if the permission is not a
456 * PropertyPermission
457 *
458 * @exception SecurityException - if this PropertyPermissionCollection
459 * object has been marked readonly
460 */
461
462 public void add(Permission permission)
463 {
464 if (! (permission instanceof PropertyPermission))
465 throw new IllegalArgumentException("invalid permission: "+
466 permission);
467 if (isReadOnly())
468 throw new SecurityException(
469 "attempt to add a Permission to a readonly PermissionCollection");
470
471 PropertyPermission pp = (PropertyPermission) permission;
472 String propName = pp.getName();
473
474 synchronized (this) {
475 PropertyPermission existing = (PropertyPermission) perms.get(propName);
476
477 if (existing != null) {
478 int oldMask = existing.getMask();
479 int newMask = pp.getMask();
480 if (oldMask != newMask) {
481 int effective = oldMask | newMask;
482 String actions = PropertyPermission.getActions(effective);
483 perms.put(propName, new PropertyPermission(propName, actions));
484 }
485 } else {
486 perms.put(propName, permission);
487 }
488 }
489
490 if (!all_allowed) {
491 if (propName.equals("*"))
492 all_allowed = true;
493 }
494 }
495
496 /**
497 * Check and see if this set of permissions implies the permissions
498 * expressed in "permission".
499 *
500 * @param p the Permission object to compare
501 *
502 * @return true if "permission" is a proper subset of a permission in
503 * the set, false if not.
504 */
505
506 public boolean implies(Permission permission)
507 {
508 if (! (permission instanceof PropertyPermission))
509 return false;
510
511 PropertyPermission pp = (PropertyPermission) permission;
512 PropertyPermission x;
513
514 int desired = pp.getMask();
515 int effective = 0;
516
517 // short circuit if the "*" Permission was added
518 if (all_allowed) {
519 synchronized (this) {
520 x = (PropertyPermission) perms.get("*");
521 }
522 if (x != null) {
523 effective |= x.getMask();
524 if ((effective & desired) == desired)
525 return true;
526 }
527 }
528
529 // strategy:
530 // Check for full match first. Then work our way up the
531 // name looking for matches on a.b.*
532
533 String name = pp.getName();
534 //System.out.println("check "+name);
535
536 synchronized (this) {
537 x = (PropertyPermission) perms.get(name);
538 }
539
540 if (x != null) {
541 // we have a direct hit!
542 effective |= x.getMask();
543 if ((effective & desired) == desired)
544 return true;
545 }
546
547 // work our way up the tree...
548 int last, offset;
549
550 offset = name.length()-1;
551
552 while ((last = name.lastIndexOf(".", offset)) != -1) {
553
554 name = name.substring(0, last+1) + "*";
555 //System.out.println("check "+name);
556 synchronized (this) {
557 x = (PropertyPermission) perms.get(name);
558 }
559
560 if (x != null) {
561 effective |= x.getMask();
562 if ((effective & desired) == desired)
563 return true;
564 }
565 offset = last -1;
566 }
567
568 // we don't have to check for "*" as it was already checked
569 // at the top (all_allowed), so we just return false
570 return false;
571 }
572
573 /**
574 * Returns an enumeration of all the PropertyPermission objects in the
575 * container.
576 *
577 * @return an enumeration of all the PropertyPermission objects.
578 */
579
580 public Enumeration elements() {
581 // Convert Iterator of Map values into an Enumeration
582 synchronized (this) {
583 return Collections.enumeration(perms.values());
584 }
585 }
586
587 private static final long serialVersionUID = 7015263904581634791L;
588
589 // Need to maintain serialization interoperability with earlier releases,
590 // which had the serializable field:
591 //
592 // Table of permissions.
593 //
594 // @serial
595 //
596 // private Hashtable permissions;
597 /**
598 * @serialField permissions java.util.Hashtable
599 * A table of the PropertyPermissions.
600 * @serialField all_allowed boolean
601 * boolean saying if "*" is in the collection.
602 */
603 private static final ObjectStreamField[] serialPersistentFields = {
604 new ObjectStreamField("permissions", Hashtable.class),
605 new ObjectStreamField("all_allowed", Boolean.TYPE),
606 };
607
608 /**
609 * @serialData Default fields.
610 */
611 /*
612 * Writes the contents of the perms field out as a Hashtable for
613 * serialization compatibility with earlier releases. all_allowed
614 * unchanged.
615 */
616 private void writeObject(ObjectOutputStream out) throws IOException {
617 // Don't call out.defaultWriteObject()
618
619 // Copy perms into a Hashtable
620 Hashtable permissions = new Hashtable(perms.size()*2);
621 synchronized (this) {
622 permissions.putAll(perms);
623 }
624
625 // Write out serializable fields
626 ObjectOutputStream.PutField pfields = out.putFields();
627 pfields.put("all_allowed", all_allowed);
628 pfields.put("permissions", permissions);
629 out.writeFields();
630 }
631
632 /*
633 * Reads in a Hashtable of PropertyPermissions and saves them in the
634 * perms field. Reads in all_allowed.
635 */
636 private void readObject(ObjectInputStream in) throws IOException,
637 ClassNotFoundException {
638 // Don't call defaultReadObject()
639
640 // Read in serialized fields
641 ObjectInputStream.GetField gfields = in.readFields();
642
643 // Get all_allowed
644 all_allowed = gfields.get("all_allowed", false);
645
646 // Get permissions
647 Hashtable permissions = (Hashtable)gfields.get("permissions", null);
648 perms = new HashMap(permissions.size()*2);
649 perms.putAll(permissions);
650 }
651 }
652