You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@river.apache.org by pe...@apache.org on 2012/10/07 00:55:37 UTC

svn commit: r1395185 - /river/jtsk/trunk/src/org/apache/river/api/security/DelegatePermission.java

Author: peter_firmstone
Date: Sat Oct  6 22:55:36 2012
New Revision: 1395185

URL: http://svn.apache.org/viewvc?rev=1395185&view=rev
Log:
refine DelegatePermission implementation

Modified:
    river/jtsk/trunk/src/org/apache/river/api/security/DelegatePermission.java

Modified: river/jtsk/trunk/src/org/apache/river/api/security/DelegatePermission.java
URL: http://svn.apache.org/viewvc/river/jtsk/trunk/src/org/apache/river/api/security/DelegatePermission.java?rev=1395185&r1=1395184&r2=1395185&view=diff
==============================================================================
--- river/jtsk/trunk/src/org/apache/river/api/security/DelegatePermission.java (original)
+++ river/jtsk/trunk/src/org/apache/river/api/security/DelegatePermission.java Sat Oct  6 22:55:36 2012
@@ -27,9 +27,8 @@ import java.security.Permission;
 import java.security.PermissionCollection;
 import java.security.Permissions;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.Enumeration;
-import java.util.HashSet;
+import java.util.TreeSet;
 import java.util.Set;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ConcurrentSkipListMap;
@@ -38,26 +37,32 @@ import au.net.zeus.collection.Ref;
 import au.net.zeus.collection.Referrer;
 import java.io.StreamTokenizer;
 import java.io.StringReader;
-import java.util.ArrayList;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.security.UnresolvedPermission;
+import java.util.concurrent.ConcurrentHashMap;
 import org.apache.river.api.security.DefaultPolicyScanner.PermissionEntry;
+import org.cliffc.high_scale_lib.NonBlockingHashMap;
 
 /**
- * A DelegatePermission represents any other Permission, called a candidate
+ * A DelegatePermission represents another Permission, called a candidate
  * Permission.  A user granted a DelegatePermission does not have the privilege
  * of the candidate Permission, although a user with a candidate Permission
  * has the privilege of the DelegatePermission that represents the candidate, 
  * while the @ref DelegateSecurityManager is in force.
  * 
  * A DelegatePermission requires a security delegate to be of any
- * use or value.  A security delegates ProtectionDomain is granted the 
+ * use or value. A security delegate has the responsibility to
+ * prevent security sensitive objects guarded by the candidate permission from
+ * escaping. Typically the candidate Permission is only checked during 
+ * construction of a security sensitive object, who's reference may later escape 
+ * into untrusted scope. Security delegates utilise Li Gong's 
+ * method guard pattern. A security delegates ProtectionDomain is granted the 
  * candidate permission, the security delegate allows any user granted the
  * DelegatePermission to utilise the functions that the candidate Permission
  * guards, when the user no longer has the DelegatePermission, the security
  * delegate no longer allows the user to access the functions guarded by the
- * candidate permission.  A security delegate has the responsibility to
- * prevent security sensitive objects guarded by the candidate permission from
- * escaping.  In order to do so, a security delegate utilises Li Gong's 
- * method guard pattern.
+ * candidate permission.  
  *
  * Security Delegates enable sensitive objects to be used by code that isn't
  * fully trusted you may want to monitor, such as a 
@@ -87,6 +92,85 @@ import org.apache.river.api.security.Def
  * Serialization has been implemented so the implementation is not
  * tied to the serialized form, instead serialization proxy's are used.
  * 
+ * The candidate permission name (also referred to as the "target name") of each
+ * <code>DelegatePermission</code> instance carries a string representation of the
+ * permission represented by the <code>DelegatePermission</code>, while the actions
+ * string of each <code>DelegatePermission</code> is always the empty string.  If
+ * a <code>DelegatePermission</code> is serialized, only its name string is sent
+ * (i.e., the candidate permission is not serialized).  Upon
+ * deserialization, the candidate permission is reconstituted based on
+ * information in the name string.  After deserialization a DelegatePermission
+ * may be safely used in a policy, however it should not be used as a Guard to check
+ * Permission as the candidate Permission may be unresolved.
+ * <p>
+ * The syntax of the target name approximates that used for specifying
+ * permissions in the default security policy file, almost identical to 
+ * <code>GrantPermission</code>, with the exception that only
+ * one candidate permission can be specified; it is listed below using
+ * the same grammar notation employed by <i>The Java(TM) Language
+ * Specification</i>:
+ * <pre>
+ * <i>Target</i>:
+ *   <i>DelimiterDeclaration</i><sub>opt</sub> <i>Permissions</i> ;<sub>opt</sub>
+ *   
+ * <i>DelimiterDeclaration</i>:
+ *   delim = <i>DelimiterCharacter</i>
+ *   
+ * <i>Permission</i>:
+ *   <i>PermissionClassName</i>
+ *   <i>PermissionClassName Name</i>
+ *   <i>PermissionClassName Name</i> , <i>Actions</i>
+ *   
+ * <i>PermissionClassName</i>:
+ *   <i>ClassName</i>
+ *   
+ * <i>Name</i>:
+ *   <i>DelimitedString</i>
+ *   
+ * <i>Actions</i>:
+ *   <i>DelimitedString</i>
+ * </pre>
+ * The production for <i>ClassName</i> is the same as that used in <i>The
+ * Java Language Specification</i>.  <i>DelimiterCharacter</i> can be any
+ * unquoted non-whitespace character other than ';' (single and
+ * double-quote characters themselves are allowed).  If
+ * <i>DelimiterCharacter</i> is not specified, then the double-quote
+ * character is the default delimiter.  <i>DelimitedString</i> is the same
+ * as the <i>StringLiteral</i> production in <i>The Java Language
+ * Specification</i>, except that it is delimited by the
+ * <i>DelimiterDeclaration</i>-specified (or default) delimiter character
+ * instead of the double-quote character exclusively.
+ * <p>
+ * Note that if the double-quote character is used as the delimiter and the
+ * name or actions strings of specified permissions themselves contain nested
+ * double-quote characters, then those characters must be escaped (or in some
+ * cases doubly-escaped) appropriately.  For example, the following policy file
+ * entry would yield a <code>DelegatePermission</code> containing a
+ * <code>FooPermission</code> in which the target name would include the word
+ * "quoted" surrounded by double-quote characters:
+ * <pre>
+ * permission org.apache.river.api.security.DelegatePermission
+ *     "FooPermission \"a \\\"quoted\\\" string\"";
+ * </pre>
+ * For comparison, the following policy file entry which uses a custom
+ * delimiter would yield an equivalent <code>DelegatePermission</code>:
+ * <pre>
+ * permission org.apache.river.api.security.DelegatePermission
+ *     "delim=| FooPermission |a \"quoted\" string|";
+ * </pre>
+ * Some additional example policy file permissions:
+ * <pre>
+ * // allow permission to listen for and accept connections
+ * permission org.apache.river.api.security.DelegatePermission
+ *     "java.net.SocketPermission \"localhost:1024-\", \"accept,listen\"";
+ *
+ * // allow permission to read files under /foo, /bar directories
+ * permission org.apache.river.api.security.DelegatePermission
+ *     "delim=' java.io.FilePermission '/foo/-', 'read'; java.io.FilePermission '/bar/-', 'read'";
+ *
+ * </pre>
+ * 
+ * @see DelegateSecurityManager
  * @author Peter Firmstone
  */
 public final class DelegatePermission extends Permission{
@@ -95,11 +179,12 @@ public final class DelegatePermission ex
      * optimum AccessControlContext result caching and minimises memory 
      * consumption.
      */
-    @SuppressWarnings("unchecked")
-    private static final ConcurrentMap instances 
-        = RC.concurrentMap( new ConcurrentSkipListMap( 
-            RC.comparator( new PermissionComparator()))
-            , Ref.WEAK, Ref.WEAK, 1000L, 1000L ); // Value weak too, because it references key.
+    private static final ConcurrentMap<String,DelegatePermission> instances 
+        = RC.concurrentMap( new NonBlockingHashMap<Referrer<String>,Referrer<DelegatePermission>>()
+            , Ref.STRONG, Ref.WEAK, 1000L, 1000L );
+    private static final Class[] PARAMS0 = {};
+    private static final Class[] PARAMS1 = { String.class };
+    private static final Class[] PARAMS2 = { String.class, String.class };
         
     /**
      * Factory method to obtain a DelegatePermission, this is essential to 
@@ -109,12 +194,13 @@ public final class DelegatePermission ex
      * @param p Permission to be represented.
      * @return DelegatePermission
      */
-    public static Permission get(Permission p){
-	Permission del = (Permission) instances.get(p);
+    public static DelegatePermission get(Permission p){
+        String name = constructName(p);
+	DelegatePermission del = instances.get(name);
 	if ( del == null ){
 	    del = new DelegatePermission(p);
             @SuppressWarnings("unchecked")
-	    Permission existed = (Permission) instances.putIfAbsent(p, del);
+	    DelegatePermission existed = instances.putIfAbsent(name, del);
 	    if ( existed != null ){
 		del = existed;
 	    }
@@ -123,23 +209,196 @@ public final class DelegatePermission ex
     }
     
     private final Permission permission;
-//    private final transient int hashCode;
+    private final int hashCode;
     
     private DelegatePermission(Permission p){
-	super(p.getName());
+	super(constructName(p));
 	permission = p;
-//	int hash = 5;
-//	hash = 41 * hash + (this.permission != null ? this.permission.hashCode() : 0);
-//	hashCode = hash;
+	int hash = 5;
+	hash = 41 * hash + (this.permission != null ? this.permission.hashCode() : 0);
+	hashCode = hash;
+    }
+    
+    /**
+     * 
+     * This constructor is provided for java policy instantiation, 
+     * and is usually called reflectively.  
+     * <p>
+     * Do not use this constructor, use the static factory method
+     * instead.
+     * <p>
+     * Objects created by this constructor will not be cached.
+     * <p>
+     * Candidate Permission may be unresolved, this is acceptable for policy
+     * use, where the permission class can be resolved later, it is not
+     * suitable for Guard or Permission checks.
+     * 
+     * @param name 
+     */
+    public DelegatePermission(String name){
+        this(initFromName(name)); //Ensures getName() is always identical.
+    }
+    
+    /**
+     * Constructs GrantPermission name/target string appropriate for given list
+     * of permissions.
+     */
+    private static String constructName(Permission p) {
+	StringBuilder sb = new StringBuilder(60);
+	    if (p instanceof UnresolvedPermission) {
+                UnresolvedPermission u = (UnresolvedPermission)p;
+                String t = u.getUnresolvedType(), n = u.getUnresolvedName(), a = u.getUnresolvedActions();
+		sb.append(t);
+                if (n != null) {
+                    sb.append(" ").append(quote(n));
+                    if (a != null){
+                        sb.append(", ").append(quote(a));
+                    }
+                } 
+                sb.append("; ");
+	    } else {
+		Class cl = p.getClass();
+		int nargs = maxConsArgs(cl);
+		String t = cl.getName(), n = p.getName(), a = p.getActions();
+		if (nargs == 2 && a != null) {
+		    // REMIND: handle null name?
+		    sb.append(t).append(" ").append(quote(n)).append(", ").append(quote(a)).append("; ");
+		} else if (nargs >= 1 && n != null) {
+		    sb.append(t).append(" ").append(quote(n)).append("; ");
+		} else {
+		    sb.append(t).append("; ");
+		}
+	    }
+	return sb.toString().trim();
     }
     
     /**
-     * Parses permission information from given GrantPermission name string.
+     * Returns the maximum number of String parameters (up to 2) accepted by a
+     * constructor of the given class.  Returns -1 if no matching constructor
+     * (including no-arg constructor) is defined by given class.
+     */
+    @SuppressWarnings("unchecked")
+    private static int maxConsArgs(Class cl) {
+	try {
+	    cl.getConstructor(PARAMS2);
+	    return 2;
+	} catch (Exception ex) {
+	}
+	try {
+	    cl.getConstructor(PARAMS1);
+	    return 1;
+	} catch (Exception ex) {
+	}
+	try {
+	    cl.getConstructor(PARAMS0);
+	    return 0;
+	} catch (Exception ex) {
+	}
+	return -1;
+    }
+    
+    /**
+     * Returns quoted string literal that, if parsed by
+     * java.io.StreamTokenizer, would yield the given string.  This method is
+     * essentially a copy of com.sun.jini.config.ConfigUtil.stringLiteral; the
+     * two methods are kept separate since ConfigUtil.stringLiteral could
+     * conceivably escape unicode characters, while such escaping would be
+     * incorrect for DelegatePermission.
+     */
+    private static String quote(String s) {
+	StringBuilder sb = new StringBuilder(s.length() + 2);
+	sb.append('"');
+	char[] ca = s.toCharArray();
+	for (int i = 0; i < ca.length; i++) {
+	    char c = ca[i];
+	    if (c == '\\' || c == '"') {
+		sb.append("\\").append(c);
+	    } else if (c == '\n') {
+		sb.append("\\n");
+	    } else if (c == '\r') {
+		sb.append("\\r");
+	    } else if (c == '\t') {
+		sb.append("\\t");
+	    } else if (c == '\f') {
+		sb.append("\\f");
+	    } else if (c == '\b') {
+		sb.append("\\b");
+	    } else if (c < 0x20) {
+		sb.append("\\").append(Integer.toOctalString(c));
+	    } else {
+		sb.append(c);
+	    }
+	}
+	return sb.append('"').toString();
+    }
+    
+    /**
+     * Initializes DelegatePermission to contain permission described in the
+     * given name.  Throws an IllegalArgumentException if the name is
+     * misformatted, or specifies an invalid permission class.  Throws a
+     * SecurityException if access to the class is not permitted.
+     */
+    private static Permission initFromName(String name) {
+	PermissionEntry pi = parsePermission(name);
+
+	    SecurityManager sm = System.getSecurityManager();
+	    if (sm != null) {
+		int d = pi.getKlass().lastIndexOf('.');
+		if (d != -1) {
+		    sm.checkPackageAccess(pi.getKlass().substring(0, d));
+		}
+	    }
+	    Class cl;
+	    try {
+		cl = Class.forName(pi.getKlass());
+	    } catch (ClassNotFoundException ex) {
+		return new UnresolvedPermission(
+		    pi.getKlass(), pi.getName(), pi.getActions(), null);
+	    }
+	    if (!Permission.class.isAssignableFrom(cl)) {
+		throw new IllegalArgumentException(
+		    "not a permission class: " + cl);
+	    }
+	    if (!Modifier.isPublic(cl.getModifiers())) {
+		throw new IllegalArgumentException(
+		    "non-public permission class: " + cl);
+	    }
+	    
+	    if (pi.getName() == null) {
+		try {
+                @SuppressWarnings("unchecked")
+		    Constructor c = cl.getConstructor(PARAMS0);
+		    return (Permission) c.newInstance(new Object[0]);
+		} catch (Exception ex) {
+                    if (ex instanceof RuntimeException) throw (RuntimeException) ex;
+		}
+	    } 
+	    if (pi.getActions() == null) {
+		try {
+                @SuppressWarnings("unchecked")
+		    Constructor c = cl.getConstructor(PARAMS1);
+		    return (Permission) c.newInstance(new Object[]{ pi.getName() });
+		} catch (Exception ex) {
+                    if (ex instanceof RuntimeException) throw (RuntimeException) ex;
+		}
+	    } 
+	    try {
+            @SuppressWarnings("unchecked")
+		Constructor c = cl.getConstructor(PARAMS2);
+		return (Permission) c.newInstance(new Object[]{ pi.getName(), pi.getActions() });
+	    } catch (Exception ex) {
+                if (ex instanceof RuntimeException) throw (RuntimeException) ex;
+	    }
+	    throw new IllegalArgumentException(
+		"uninstantiable permission class: " + cl);
+    }
+    
+    /**
+     * Parses permission information from given DelegatePermission name string.
      * Throws an IllegalArgumentException if the name string is misformatted.
      */
-    private static PermissionEntry[] parsePermissions(String s) {
+    private static PermissionEntry parsePermission(String s) {
 	try {
-	    ArrayList l = new ArrayList();
 	    StreamTokenizer st = createTokenizer(s);
 	    char delim = '"';
 
@@ -167,52 +426,45 @@ public final class DelegatePermission ex
 	    }
 	    st.quoteChar(delim);
 
-	    do {
-		String type, name = null, actions = null;
+            
+            String type, name = null, actions = null;
 
-		if (st.ttype != StreamTokenizer.TT_WORD) {
-		    throw new IllegalArgumentException(
-			"expected permission type");
-		}
-		type = st.sval;
-		
-		// REMIND: allow unquoted name/actions?
-		st.nextToken();
-		if (st.ttype == StreamTokenizer.TT_EOF || st.ttype == ';') {
-		    l.add(new PermissionEntry(type, null, null, null));
-		    continue;
-		} else if (st.ttype == delim) {
-		    name = st.sval;
-		} else {
-		    throw new IllegalArgumentException(
-			"expected permission name or ';'");
-		}
-		
-		st.nextToken();
-		if (st.ttype == StreamTokenizer.TT_EOF || st.ttype == ';') {
-		    l.add(new PermissionEntry(type, name, null, null));
-		    continue;
-		} else if (st.ttype != ',') {
-		    throw new IllegalArgumentException("expected ',' or ';'");
-		}
+            if (st.ttype != StreamTokenizer.TT_WORD) {
+                throw new IllegalArgumentException(
+                    "expected permission type");
+            }
+            type = st.sval;
 
-		if (st.nextToken() != delim) {
-		    throw new IllegalArgumentException(
-			"expected permission actions");
-		}
-		actions = st.sval;
-		
-		st.nextToken();
-		if (st.ttype == StreamTokenizer.TT_EOF || st.ttype == ';') {
-		    l.add(new PermissionEntry(type, name, actions, null));
-		    continue;
-		} else {
-		    throw new IllegalArgumentException("expected ';'");
-		}
+            // REMIND: allow unquoted name/actions?
+            st.nextToken();
+            if (st.ttype == StreamTokenizer.TT_EOF || st.ttype == ';') {
+                return new PermissionEntry(type, null, null, null);
+            } else if (st.ttype == delim) {
+                name = st.sval;
+            } else {
+                throw new IllegalArgumentException(
+                    "expected permission name or ';'");
+            }
 
-	    } while (st.nextToken() != StreamTokenizer.TT_EOF);
+            st.nextToken();
+            if (st.ttype == StreamTokenizer.TT_EOF || st.ttype == ';') {
+                return new PermissionEntry(type, name, null, null);
+            } else if (st.ttype != ',') {
+                throw new IllegalArgumentException("expected ',' or ';'");
+            }
+
+            if (st.nextToken() != delim) {
+                throw new IllegalArgumentException(
+                    "expected permission actions");
+            }
+            actions = st.sval;
 
-	    return (PermissionEntry[]) l.toArray(new PermissionEntry[l.size()]);
+            st.nextToken();
+            if (st.ttype == StreamTokenizer.TT_EOF || st.ttype == ';') {
+                return new PermissionEntry(type, name, actions, null);
+            } else {
+                throw new IllegalArgumentException("expected ';'");
+            }
 	} catch (IOException ex) {
 	    throw (Error) new InternalError().initCause(ex);
 	}
@@ -259,24 +511,18 @@ public final class DelegatePermission ex
 	return permission;
     }
 
-    // Don't override equals so all Delegates can be used in Collections
-    // including those containing SocketPermission.
     @Override
     public boolean equals(Object obj) {
-        return obj == this;
-//	if (obj == this) return true;
-//	if (obj == null) return false;
-//	if ( obj.hashCode() != hashCode ) return false;
-//	if (!(obj instanceof DelegatePermission)) return false;
-//	if ( obj.getClass() != this.getClass() ) return false;
-//	return permission.equals(((DelegatePermission) permission).getPermission());
+	if (obj == this) return true;
+	if (obj == null) return false;
+	if ( obj.hashCode() != hashCode ) return false;
+	if ( obj.getClass() != this.getClass() ) return false;
+	return getName().equals(((Permission)obj).getName());
     }
 
     @Override
     public int hashCode() {
-//	return hashCode;
-        // Not in constructor so we don't let this escape.
-        return System.identityHashCode(this);
+	return hashCode;
     }
 
     @Override
@@ -292,10 +538,10 @@ public final class DelegatePermission ex
     /* Serialization Proxy */
     private static class SerializationProxy implements Serializable {
 	private static final long serialVersionUID = 1L;
-	private Permission perm;
+	private String perm;
 	
 	SerializationProxy(Permission p){
-	    perm = p;
+	    perm = constructName(p);
 	}
         
         private void writeObject(ObjectOutputStream out) throws IOException{
@@ -308,7 +554,9 @@ public final class DelegatePermission ex
         
         private Object readResolve() {
             // perm is the field from the Serialization proxy.
-            return get(perm);
+            Permission p = instances.get(perm);
+            if (p != null) return p;
+            return new DelegatePermission(perm); // May contain Unresolved candidate
         }
     }
     
@@ -331,7 +579,7 @@ public final class DelegatePermission ex
 	
 	DelegatePermissionCollection(){
 	    candidates = new Permissions();
-	    delegates = new HashSet<Permission>(32);
+	    delegates = new TreeSet<Permission>(new PermissionComparator());
 	}
 
 	@Override