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