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