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