| /* | |
| * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package java.security; | |
| import java.io.InvalidObjectException; | |
| import java.io.IOException; | |
| import java.io.ObjectInputStream; | |
| import java.io.ObjectOutputStream; | |
| import java.io.ObjectStreamField; | |
| import java.io.Serializable; | |
| import java.util.Enumeration; | |
| import java.util.Hashtable; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.NoSuchElementException; | |
| import java.util.concurrent.ConcurrentHashMap; | |
| /** | |
| * This class represents a heterogeneous collection of permissions. | |
| * That is, it contains different types of {@code Permission} objects, | |
| * organized into {@code PermissionCollection} objects. For example, if any | |
| * {@code java.io.FilePermission} objects are added to an instance of | |
| * this class, they are all stored in a single {@code PermissionCollection}. | |
| * It is the {@code PermissionCollection} returned by a call to | |
| * the {@code newPermissionCollection} method in the {@code FilePermission} | |
| * class. Similarly, any {@code java.lang.RuntimePermission} objects are | |
| * stored in the {@code PermissionCollection} returned by a call to the | |
| * {@code newPermissionCollection} method in the {@code RuntimePermission} | |
| * class. Thus, this class represents a collection of | |
| * {@code PermissionCollection} objects. | |
| * | |
| * <p>When the {@code add} method is called to add a {@code Permission}, the | |
| * {@code Permission} is stored in the appropriate {@code PermissionCollection}. | |
| * If no such collection exists yet, the {@code Permission} object's class is | |
| * determined and the {@code newPermissionCollection} method is called on that | |
| * class to create the {@code PermissionCollection} and add it to the | |
| * {@code Permissions} object. If {@code newPermissionCollection} returns | |
| * {@code null}, then a default {@code PermissionCollection} that uses a | |
| * hashtable will be created and used. Each hashtable entry stores a | |
| * {@code Permission} object as both the key and the value. | |
| * | |
| * <p> Enumerations returned via the {@code elements} method are | |
| * not <em>fail-fast</em>. Modifications to a collection should not be | |
| * performed while enumerating over that collection. | |
| * | |
| * @see Permission | |
| * @see PermissionCollection | |
| * @see AllPermission | |
| * | |
| * | |
| * @author Marianne Mueller | |
| * @author Roland Schemers | |
| * @since 1.2 | |
| * | |
| * @serial exclude | |
| */ | |
| public final class Permissions extends PermissionCollection | |
| implements Serializable | |
| { | |
| /** | |
| * Key is permissions Class, value is {@code PermissionCollection} for | |
| * that class. Not serialized; see serialization section at end of class. | |
| */ | |
| private transient ConcurrentHashMap<Class<?>, PermissionCollection> permsMap; | |
| // optimization. keep track of whether unresolved permissions need to be | |
| // checked | |
| private transient boolean hasUnresolved = false; | |
| /** @serial */ | |
| // optimization. keep track of the AllPermission collection | |
| // - package private for ProtectionDomain optimization | |
| PermissionCollection allPermission; | |
| /** | |
| * Creates a new {@code Permissions} object containing no | |
| * {@code PermissionCollection} objects. | |
| */ | |
| public Permissions() { | |
| permsMap = new ConcurrentHashMap<>(11); | |
| allPermission = null; | |
| } | |
| /** | |
| * Adds a {@code Permission} object to the {@code PermissionCollection} | |
| * for the class the permission belongs to. For example, | |
| * if <i>permission</i> is a {@code FilePermission}, it is added to | |
| * the {@code FilePermissionCollection} stored in this | |
| * {@code Permissions} object. | |
| * | |
| * This method creates a new {@code PermissionCollection} object | |
| * (and adds the permission to it) if an appropriate collection does | |
| * not yet exist. | |
| * | |
| * @param permission the {@code Permission} object to add. | |
| * | |
| * @throws SecurityException if this {@code Permissions} object is | |
| * marked as readonly. | |
| * | |
| * @see PermissionCollection#isReadOnly() | |
| */ | |
| @Override | |
| @SuppressWarnings("removal") | |
| public void add(Permission permission) { | |
| if (isReadOnly()) | |
| throw new SecurityException( | |
| "attempt to add a Permission to a readonly Permissions object"); | |
| PermissionCollection pc = getPermissionCollection(permission, true); | |
| pc.add(permission); | |
| // No sync; staleness -> optimizations delayed, which is OK | |
| if (permission instanceof AllPermission) { | |
| allPermission = pc; | |
| } | |
| if (permission instanceof UnresolvedPermission) { | |
| hasUnresolved = true; | |
| } | |
| } | |
| /** | |
| * Checks to see if this object's {@code PermissionCollection} for | |
| * permissions of the specified permission's class implies the permissions | |
| * expressed in the <i>permission</i> object. Returns {@code true} if the | |
| * combination of permissions in the appropriate | |
| * {@code PermissionCollection} (e.g., a {@code FilePermissionCollection} | |
| * for a {@code FilePermission}) together imply the specified permission. | |
| * | |
| * <p>For example, suppose there is a {@code FilePermissionCollection} | |
| * in this {@code Permissions} object, and it contains one | |
| * {@code FilePermission} that specifies "read" access for all files | |
| * in all subdirectories of the "/tmp" directory, and another | |
| * {@code FilePermission} that specifies "write" access for all files | |
| * in the "/tmp/scratch/foo" directory. Then if the {@code implies} method | |
| * is called with a permission specifying both "read" and "write" access | |
| * to files in the "/tmp/scratch/foo" directory, {@code true} is | |
| * returned. | |
| * | |
| * <p>Additionally, if this {@code PermissionCollection} contains the | |
| * {@code AllPermission}, this method will always return {@code true}. | |
| * | |
| * @param permission the {@code Permission} object to check. | |
| * | |
| * @return {@code true} if "permission" is implied by the permissions in the | |
| * {@code PermissionCollection} it belongs to, {@code false} if not. | |
| */ | |
| @Override | |
| public boolean implies(Permission permission) { | |
| // No sync; staleness -> skip optimization, which is OK | |
| if (allPermission != null) { | |
| return true; // AllPermission has already been added | |
| } else { | |
| PermissionCollection pc = getPermissionCollection(permission, | |
| false); | |
| if (pc != null) { | |
| return pc.implies(permission); | |
| } else { | |
| // none found | |
| return false; | |
| } | |
| } | |
| } | |
| /** | |
| * Returns an enumeration of all the {@code Permission} objects in all the | |
| * {@code PermissionCollection} objects in this {@code Permissions} object. | |
| * | |
| * @return an enumeration of all the {@code Permission} objects. | |
| */ | |
| @Override | |
| public Enumeration<Permission> elements() { | |
| // go through each Permissions in the hash table | |
| // and call their elements() function. | |
| return new PermissionsEnumerator(permsMap.values().iterator()); | |
| } | |
| /** | |
| * Gets the {@code PermissionCollection} in this {@code Permissions} | |
| * object for permissions whose type is the same as that of <i>p</i>. | |
| * For example, if <i>p</i> is a {@code FilePermission}, | |
| * the {@code FilePermissionCollection} stored in this {@code Permissions} | |
| * object will be returned. | |
| * | |
| * If {@code createEmpty} is {@code true}, | |
| * this method creates a new {@code PermissionCollection} object for the | |
| * specified type of permission objects if one does not yet exist. | |
| * To do so, it first calls the {@code newPermissionCollection} method | |
| * on <i>p</i>. Subclasses of class {@code Permission} | |
| * override that method if they need to store their permissions in a | |
| * particular {@code PermissionCollection} object in order to provide the | |
| * correct semantics when the {@code PermissionCollection.implies} | |
| * method is called. | |
| * If the call returns a {@code PermissionCollection}, that collection is | |
| * stored in this {@code Permissions} object. If the call returns | |
| * {@code null} and {@code createEmpty} is {@code true}, then this method | |
| * instantiates and stores a default {@code PermissionCollection} | |
| * that uses a hashtable to store its permission objects. | |
| * | |
| * {@code createEmpty} is ignored when creating empty | |
| * {@code PermissionCollection} for unresolved permissions because of the | |
| * overhead of determining the {@code PermissionCollection} to use. | |
| * | |
| * {@code createEmpty} should be set to {@code false} when this method is | |
| * invoked from implies() because it incurs the additional overhead of | |
| * creating and adding an empty {@code PermissionCollection} that will | |
| * just return {@code false}. | |
| * It should be set to {@code true} when invoked from add(). | |
| */ | |
| private PermissionCollection getPermissionCollection(Permission p, | |
| boolean createEmpty) { | |
| PermissionCollection pc = permsMap.get(p.getClass()); | |
| if ((!hasUnresolved && !createEmpty) || pc != null) { | |
| // Collection not to be created, or already created | |
| return pc; | |
| } | |
| return createPermissionCollection(p, createEmpty); | |
| } | |
| private PermissionCollection createPermissionCollection(Permission p, | |
| boolean createEmpty) { | |
| synchronized (permsMap) { | |
| // Re-read under lock | |
| Class<?> c = p.getClass(); | |
| PermissionCollection pc = permsMap.get(c); | |
| // Collection already created | |
| if (pc != null) { | |
| return pc; | |
| } | |
| // Create and add permission collection to map if it is absent. | |
| // Check for unresolved permissions | |
| pc = (hasUnresolved ? getUnresolvedPermissions(p) : null); | |
| // if still null, create a new collection | |
| if (pc == null && createEmpty) { | |
| pc = p.newPermissionCollection(); | |
| // still no PermissionCollection? | |
| // We'll give them a PermissionsHash. | |
| if (pc == null) { | |
| pc = new PermissionsHash(); | |
| } | |
| } | |
| if (pc != null) { | |
| // Add pc, resolving any race | |
| PermissionCollection oldPc = permsMap.putIfAbsent(c, pc); | |
| if (oldPc != null) { | |
| pc = oldPc; | |
| } | |
| } | |
| return pc; | |
| } | |
| } | |
| /** | |
| * Resolves any unresolved permissions of type p. | |
| * | |
| * @param p the type of unresolved permission to resolve | |
| * | |
| * @return PermissionCollection containing the unresolved permissions, | |
| * or {@code null} if there were no unresolved permissions of type p. | |
| * | |
| */ | |
| @SuppressWarnings("removal") | |
| private PermissionCollection getUnresolvedPermissions(Permission p) | |
| { | |
| UnresolvedPermissionCollection uc = | |
| (UnresolvedPermissionCollection) permsMap.get(UnresolvedPermission.class); | |
| // we have no unresolved permissions if uc is null | |
| if (uc == null) | |
| return null; | |
| List<UnresolvedPermission> unresolvedPerms = | |
| uc.getUnresolvedPermissions(p); | |
| // we have no unresolved permissions of this type if unresolvedPerms is null | |
| if (unresolvedPerms == null) | |
| return null; | |
| java.security.cert.Certificate[] certs = null; | |
| Object[] signers = p.getClass().getSigners(); | |
| int n = 0; | |
| if (signers != null) { | |
| for (int j=0; j < signers.length; j++) { | |
| if (signers[j] instanceof java.security.cert.Certificate) { | |
| n++; | |
| } | |
| } | |
| certs = new java.security.cert.Certificate[n]; | |
| n = 0; | |
| for (int j=0; j < signers.length; j++) { | |
| if (signers[j] instanceof java.security.cert.Certificate) { | |
| certs[n++] = (java.security.cert.Certificate)signers[j]; | |
| } | |
| } | |
| } | |
| PermissionCollection pc = null; | |
| synchronized (unresolvedPerms) { | |
| int len = unresolvedPerms.size(); | |
| for (int i = 0; i < len; i++) { | |
| UnresolvedPermission up = unresolvedPerms.get(i); | |
| Permission perm = up.resolve(p, certs); | |
| if (perm != null) { | |
| if (pc == null) { | |
| pc = p.newPermissionCollection(); | |
| if (pc == null) | |
| pc = new PermissionsHash(); | |
| } | |
| pc.add(perm); | |
| } | |
| } | |
| } | |
| return pc; | |
| } | |
| @java.io.Serial | |
| private static final long serialVersionUID = 4858622370623524688L; | |
| // Need to maintain serialization interoperability with earlier releases, | |
| // which had the serializable field: | |
| // private Hashtable perms; | |
| /** | |
| * @serialField perms java.util.Hashtable | |
| * A table of the {@code Permission} classes and | |
| * {@code PermissionCollection} objects. | |
| * @serialField allPermission java.security.PermissionCollection | |
| */ | |
| @java.io.Serial | |
| private static final ObjectStreamField[] serialPersistentFields = { | |
| new ObjectStreamField("perms", Hashtable.class), | |
| new ObjectStreamField("allPermission", PermissionCollection.class), | |
| }; | |
| /** | |
| * @serialData Default fields. | |
| */ | |
| /* | |
| * Writes the contents of the permsMap field out as a Hashtable for | |
| * serialization compatibility with earlier releases. allPermission | |
| * unchanged. | |
| */ | |
| @java.io.Serial | |
| private void writeObject(ObjectOutputStream out) throws IOException { | |
| // Don't call out.defaultWriteObject() | |
| // Copy perms into a Hashtable | |
| Hashtable<Class<?>, PermissionCollection> perms = | |
| new Hashtable<>(permsMap.size()*2); // no sync; estimate | |
| perms.putAll(permsMap); | |
| // Write out serializable fields | |
| ObjectOutputStream.PutField pfields = out.putFields(); | |
| pfields.put("allPermission", allPermission); // no sync; staleness OK | |
| pfields.put("perms", perms); | |
| out.writeFields(); | |
| } | |
| /* | |
| * Reads in a Hashtable of Class/PermissionCollections and saves them in the | |
| * permsMap field. Reads in allPermission. | |
| */ | |
| @java.io.Serial | |
| @SuppressWarnings("removal") | |
| private void readObject(ObjectInputStream in) throws IOException, | |
| ClassNotFoundException { | |
| // Don't call defaultReadObject() | |
| // Read in serialized fields | |
| ObjectInputStream.GetField gfields = in.readFields(); | |
| // Get allPermission | |
| allPermission = (PermissionCollection) gfields.get("allPermission", null); | |
| // Get permissions | |
| // writeObject writes a Hashtable<Class<?>, PermissionCollection> for | |
| // the perms key, so this cast is safe, unless the data is corrupt. | |
| @SuppressWarnings("unchecked") | |
| Hashtable<Class<?>, PermissionCollection> perms = | |
| (Hashtable<Class<?>, PermissionCollection>)gfields.get("perms", null); | |
| if (perms == null) { | |
| throw new InvalidObjectException("perms can't be null"); | |
| } | |
| permsMap = new ConcurrentHashMap<>(perms.size()*2); | |
| permsMap.putAll(perms); | |
| // Check that Class is mapped to PermissionCollection containing | |
| // Permissions of the same class | |
| for (Map.Entry<Class<?>, PermissionCollection> e : perms.entrySet()) { | |
| Class<?> k = e.getKey(); | |
| PermissionCollection v = e.getValue(); | |
| Enumeration<Permission> en = v.elements(); | |
| while (en.hasMoreElements()) { | |
| Permission p = en.nextElement(); | |
| if (!k.equals(p.getClass())) { | |
| throw new InvalidObjectException("Permission with class " + | |
| k + " incorrectly mapped to PermissionCollection " + | |
| "containing Permission with " + p.getClass()); | |
| } | |
| } | |
| } | |
| // Set hasUnresolved | |
| UnresolvedPermissionCollection uc = | |
| (UnresolvedPermissionCollection) permsMap.get(UnresolvedPermission.class); | |
| hasUnresolved = (uc != null && uc.elements().hasMoreElements()); | |
| } | |
| } | |
| final class PermissionsEnumerator implements Enumeration<Permission> { | |
| // all the perms | |
| private final Iterator<PermissionCollection> perms; | |
| // the current set | |
| private Enumeration<Permission> permset; | |
| PermissionsEnumerator(Iterator<PermissionCollection> e) { | |
| perms = e; | |
| permset = getNextEnumWithMore(); | |
| } | |
| // No need to synchronize; caller should sync on object as required | |
| public boolean hasMoreElements() { | |
| // if we enter with permissionimpl null, we know | |
| // there are no more left. | |
| if (permset == null) | |
| return false; | |
| // try to see if there are any left in the current one | |
| if (permset.hasMoreElements()) | |
| return true; | |
| // get the next one that has something in it... | |
| permset = getNextEnumWithMore(); | |
| // if it is null, we are done! | |
| return (permset != null); | |
| } | |
| // No need to synchronize; caller should sync on object as required | |
| public Permission nextElement() { | |
| // hasMoreElements will update permset to the next permset | |
| // with something in it... | |
| if (hasMoreElements()) { | |
| return permset.nextElement(); | |
| } else { | |
| throw new NoSuchElementException("PermissionsEnumerator"); | |
| } | |
| } | |
| private Enumeration<Permission> getNextEnumWithMore() { | |
| while (perms.hasNext()) { | |
| PermissionCollection pc = perms.next(); | |
| Enumeration<Permission> next =pc.elements(); | |
| if (next.hasMoreElements()) | |
| return next; | |
| } | |
| return null; | |
| } | |
| } | |
| /** | |
| * A {@code PermissionsHash} stores a homogeneous set of permissions in a | |
| * hashtable. | |
| * | |
| * @see Permission | |
| * @see Permissions | |
| * | |
| * | |
| * @author Roland Schemers | |
| * | |
| * @serial include | |
| */ | |
| final class PermissionsHash extends PermissionCollection | |
| implements Serializable | |
| { | |
| /** | |
| * Key and value are (same) permissions objects. | |
| * Not serialized; see serialization section at end of class. | |
| */ | |
| private transient ConcurrentHashMap<Permission, Permission> permsMap; | |
| /** | |
| * Create an empty {@code PermissionsHash} object. | |
| */ | |
| PermissionsHash() { | |
| permsMap = new ConcurrentHashMap<>(11); | |
| } | |
| /** | |
| * Adds a permission to the {@code PermissionsHash}. | |
| * | |
| * @param permission the {@code Permission} object to add. | |
| */ | |
| @Override | |
| public void add(Permission permission) { | |
| permsMap.put(permission, permission); | |
| } | |
| /** | |
| * Check and see if this set of permissions implies the permissions | |
| * expressed in "permission". | |
| * | |
| * @param permission the {@code Permission} object to compare | |
| * | |
| * @return {@code true} if "permission" is a proper subset of a permission | |
| * in the set, {@code false} if not. | |
| */ | |
| @Override | |
| public boolean implies(Permission permission) { | |
| // attempt a fast lookup and implies. If that fails | |
| // then enumerate through all the permissions. | |
| Permission p = permsMap.get(permission); | |
| // If permission is found, then p.equals(permission) | |
| if (p == null) { | |
| for (Permission p_ : permsMap.values()) { | |
| if (p_.implies(permission)) | |
| return true; | |
| } | |
| return false; | |
| } else { | |
| return true; | |
| } | |
| } | |
| /** | |
| * Returns an enumeration of all the {@code Permission} objects in the | |
| * container. | |
| * | |
| * @return an enumeration of all the {@code Permission} objects. | |
| */ | |
| @Override | |
| public Enumeration<Permission> elements() { | |
| return permsMap.elements(); | |
| } | |
| @java.io.Serial | |
| private static final long serialVersionUID = -8491988220802933440L; | |
| // Need to maintain serialization interoperability with earlier releases, | |
| // which had the serializable field: | |
| // private Hashtable perms; | |
| /** | |
| * @serialField perms java.util.Hashtable | |
| * A table of the permissions (both key and value are same). | |
| */ | |
| @java.io.Serial | |
| private static final ObjectStreamField[] serialPersistentFields = { | |
| new ObjectStreamField("perms", Hashtable.class), | |
| }; | |
| /** | |
| * Writes the contents of the permsMap field out as a Hashtable for | |
| * serialization compatibility with earlier releases. | |
| * | |
| * @param out the {@code ObjectOutputStream} to which data is written | |
| * @throws IOException if an I/O error occurs | |
| */ | |
| @java.io.Serial | |
| private void writeObject(ObjectOutputStream out) throws IOException { | |
| // Don't call out.defaultWriteObject() | |
| // Copy perms into a Hashtable | |
| Hashtable<Permission, Permission> perms = | |
| new Hashtable<>(permsMap.size()*2); | |
| perms.putAll(permsMap); | |
| // Write out serializable fields | |
| ObjectOutputStream.PutField pfields = out.putFields(); | |
| pfields.put("perms", perms); | |
| out.writeFields(); | |
| } | |
| /** | |
| * Reads in a {@code Hashtable} of Permission/Permission and saves them | |
| * in the permsMap field. | |
| * | |
| * @param in the {@code ObjectInputStream} from which data is read | |
| * @throws IOException if an I/O error occurs | |
| * @throws ClassNotFoundException if a serialized class cannot be loaded | |
| */ | |
| @java.io.Serial | |
| private void readObject(ObjectInputStream in) throws IOException, | |
| ClassNotFoundException { | |
| // Don't call defaultReadObject() | |
| // Read in serialized fields | |
| ObjectInputStream.GetField gfields = in.readFields(); | |
| // Get permissions | |
| // writeObject writes a Hashtable<Class<?>, PermissionCollection> for | |
| // the perms key, so this cast is safe, unless the data is corrupt. | |
| @SuppressWarnings("unchecked") | |
| Hashtable<Permission, Permission> perms = | |
| (Hashtable<Permission, Permission>)gfields.get("perms", null); | |
| permsMap = new ConcurrentHashMap<>(perms.size()*2); | |
| permsMap.putAll(perms); | |
| // check that the Permission key and value are the same object | |
| for (Map.Entry<Permission, Permission> e : perms.entrySet()) { | |
| Permission k = e.getKey(); | |
| Permission v = e.getValue(); | |
| if (k != v) { | |
| throw new InvalidObjectException("Permission (" + k + | |
| ") incorrectly mapped to Permission (" + v + ")"); | |
| } | |
| } | |
| } | |
| } |