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