/************************************************************************** * Copyright (c) 2001 by Punch Telematix. All rights reserved. * * * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * 1. Redistributions of source code must retain the above copyright * * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the following disclaimer in the * * documentation and/or other materials provided with the distribution. * * 3. Neither the name of Punch Telematix nor the names of * * other contributors may be used to endorse or promote products * * derived from this software without specific prior written permission.* * * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * * IN NO EVENT SHALL PUNCH TELEMATIX OR OTHER CONTRIBUTORS BE LIABLE * * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **************************************************************************/ package wonka.security; import java.util.PropertyPermission; import java.security.Permission; import java.security.PermissionCollection; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; /** * The class PropertyPermissionCollection is designed to optimize performance * of the `implies' operation, which must determine whether a candidate * PropertyPermission is implied by any permission in the collection. * This we do using a `trie' data structure for each action: * e.g. the four PropertyPremissions {"foo.bar","read,write"}, * {"foo.baz","read"}, {"foo.qu*","read"}, and {"something.else","read"} * would result in the following tree for "read": * * +---> r * | * r+---> foo. +---> ba +---> z * e| | * a| +---> qu ---> * * d| * +---> something.else * * Each level of the tree is a hashtable in which they keys are property * names. The data associated with each key is either a PropertyPermission * (leaf node) or another hashtable which holds the possible sequels, and * so on recursively. * * Note: this is essentially the same algorithm as that used in * BasicPermissionsCollection, except that we have two tries * (one for each action) instead of just one. */ public class PropertyPermissionCollection extends PermissionCollection { private static int INITIAL_TABSIZE = 11; private Hashtable read_prefixes; private Hashtable write_prefixes; /** ** Constructor PropertyPermissionCollection() builds two empty Hashtables. */ public PropertyPermissionCollection() { read_prefixes = new Hashtable(INITIAL_TABSIZE); write_prefixes = new Hashtable(INITIAL_TABSIZE); } /** Method add_to(PropertyPermission,Hashtable) rebuilds a Hashtable * to take account of a new PropertyPermission. * * If the name of the Permission ends in `*' then we note this * and then try first to add the name without the trailing `*'. * We will "remember" the trailing `*' later when the rest of * the name is exhausted. * * We then try to match the name of the new permission against each key * of the root hashtable ('prefixes') in turn, looking for the longest * prefix common to both the name and the key; the longest such prefix * then determines what should happen next: * - if its length is zero, then in fact we have a new prefix. We add * an entry to the (root) hashtable with the new name as key and * the Permission object as data. * Example in the illustration: something/else * - if the longest prefix is shorter than the key in which it is found, * or the key is a leaf rather than a branch node (associated data is * a Permission not a Hashtable), then we need to split a key. The * current key is removed from the hashtable and replaced by an entry * with the shorter prefix as key and as data a new hashtable containing * one entry, namely a mapping from the remainder of the existing key to * the data that was referenced by the old key. We then resume the * algorithm with the new hashtable and the remainder of the name. * Example: hashtable contains * +---> foo.bar.baz * and we wish to add 'foo.bar.quux'. We replace the existing entry * 'foo.bar.baz' by a link to a new hashtable: * +---> foo.bar. ---> baz * and then add 'quux' to the new hashtable. * - else the longest prefix exactly matches some key: we follow the * link from this key to its associated Hashtable and resume the * algorithm with the new hashtable and the remainder of the name. */ private void add_to (PropertyPermission newperm, Hashtable prefixes) throws SecurityException { // System.out.println("Prefix table before adding permission '"+newperm+"': "+prefixes); Hashtable table = prefixes; String name = newperm.getName(); String longest_prefix = ""; String target_matched = ""; int namelen = name.length(); int longest_prefix_length = 0; boolean wild = false; if (name.endsWith(".*")) { wild = true; namelen -= 1; name = name.substring(0,namelen); // System.out.println("Name ends in '*', first insert '"+name+"' ..."); } while (namelen > 0) { // System.out.println("Looking for prefix '"+name+"' in "+table); Enumeration e = table.keys(); while (e.hasMoreElements()) { String prefix = name; int prefixlen = namelen; String target = (String)e.nextElement(); // System.out.println(" Trying '"+target+"'"); while (prefixlen>longest_prefix_length) { if (target.startsWith(prefix)) { longest_prefix = prefix; target_matched = target; longest_prefix_length = prefixlen; } prefix = prefix.substring(0,--prefixlen); } } if (longest_prefix_length == 0) { // System.out.println(" No match found, adding new entry: '"+name+"'->"+newperm); if (wild) { Hashtable new_table = new Hashtable(INITIAL_TABSIZE); table.put(name,new_table); table = new_table; } else { table.put(name,newperm); } name = ""; namelen = 0; } else { Object old_data = table.get(target_matched); if (longest_prefix_length < target_matched.length() || old_data instanceof Permission) { // System.out.println(" Partial match found, splitting entry for '"+target_matched+"' after '"+longest_prefix+"' ("+longest_prefix_length+" chars)"); Hashtable new_table = new Hashtable(INITIAL_TABSIZE); new_table.put(target_matched.substring(longest_prefix_length),old_data); // System.out.println(" New hashtable: "+new_table); table.remove(target_matched); table.put(longest_prefix,new_table); // System.out.println(" Old hashtable: "+table); table = new_table; } else { // System.out.println(" Total match found for '"+longest_prefix+"'"); table = (Hashtable)old_data; // System.out.println(" --> switch to hashtable: "+table); } name = name.substring(longest_prefix_length); namelen = name.length(); longest_prefix = ""; longest_prefix_length = 0; // System.out.println(" Remainder of name is '"+name+"'"); } if (wild && namelen==0) { // System.out.println("Now deal with the trailing '*' ..."); name = "*"; namelen = 1; wild = false; } } // System.out.println("Prefix table after adding permission '"+newperm+"': "+prefixes); } /** * Method add(Permission) rebuilds the Hashtables to take account * of the new Permission (which must be a PropertyPermission). */ public synchronized void add (Permission permission) throws SecurityException { if (super.isReadOnly()) throw new SecurityException("read-only"); PropertyPermission newperm; try { newperm = (PropertyPermission) permission; } catch (ClassCastException e) { throw new IllegalArgumentException("not a PropertyPermission"); } if (newperm.getActions().indexOf("read") >= 0) { add_to(newperm,read_prefixes); } if (newperm.getActions().indexOf("write") >= 0) { add_to(newperm,write_prefixes); } } /** * Method implies_action tests whether a given action is implied by any * of the PropertyPermissions in this collection. * * We first test to see whether the whole pathname of the permission is a * key of the action's hashtable ('prefixes'), with a Permission as data. * If so then the algorithm terminates successfully. It also terminates * successfully if the hashtable contains a key "-", or if the hashtable * contains a key "*" and the pathname does not contain `/' (the associated * data will always be a Permission in this case). Otherwise, we try to * match the pathname of the new permission against each key in the hashtable: * if any key is a prefix of the pathname then the associated data must be a * Hashtable, and we resume the algorithm with the remainder of the name * in this hashtable. */ public boolean implies_action (String path, Hashtable prefixes) { if (prefixes.get("*") != null) { return true; } Hashtable table = prefixes; String name = path; int namelen = name.length(); while (namelen >= 0) { // System.out.println("Looking for prefix '"+name+"' in "+table); Object data = table.get(name); if (data instanceof Permission) { // System.out.println(" Found an exact match for '"+name+"'->"+data+", success!"); return true; } data = table.get(new String("*")); if (data != null) { // System.out.println(" Found a * ->"+data+", success!"); return namelen > 0; } Enumeration e = table.keys(); boolean found = false; while (e.hasMoreElements()) { String target = (String)e.nextElement(); // System.out.println(" Trying '"+target+"'"); if (name.startsWith(target)) { data = table.get(target); if (data instanceof Hashtable) { table = (Hashtable)data; // System.out.println(" Matched prefix '"+target+"'->hashtable: "+table); name = name.substring(target.length()); namelen = name.length(); // System.out.println(" Remainder of name is '"+name+"'"); found = true; break; } else System.err.println(" Matched prefix '"+target+"', but ->Permission (?!)"); } } if (!found) { break; } } // System.out.println(" No match found, failed."); return false; } /** * Method 'implies' tests whether a given permission is implied by any * of the PropertyPermissions in this collection. The permission must of * course be a PropertyPermission. A PropertyPermission is implied by this * collection if none of its actions are not implied by this collection * for the given path. (If you like double negatives, do not fail to * raise your hand.) */ public boolean implies (Permission permission) { PropertyPermission tryperm; String trypath; try { tryperm = (PropertyPermission) permission; } catch (ClassCastException e) { // System.out.println(permission+" is not a PropertyPermission!"); return false; } trypath = tryperm.getName(); return (tryperm.getActions().indexOf("read") < 0 || this.implies_action(trypath,read_prefixes)) && (tryperm.getActions().indexOf("write") < 0 || this.implies_action(trypath,write_prefixes)) ; } /** * Method to enumerate one subtree of a prefix tree. Recursively, of course. * Uses a temporary Vector, which is ugly but so be it. */ private Enumeration enumerate_subtree(Hashtable table) { Vector temp = new Vector(); Enumeration e = table.elements(); // System.out.println("Start outer enumeration"); while (e.hasMoreElements()) { Object data = e.nextElement(); // System.out.println(" next element: "+data); if (data instanceof Hashtable) { // System.out.println(" Element is Hashtable, start inner enumeration"); Hashtable next_table = (Hashtable)data; Enumeration ee = enumerate_subtree(next_table); while (ee.hasMoreElements()) { // System.out.println(" next element: "+data); temp.addElement(ee.nextElement()); } } else { // System.out.println(" Element is Permission, add "+data+" to Vector"); temp.addElement(data); // System.out.println(" -> Vector: "+temp); } } // System.out.println("End outer enumeration"); // System.out.println("Final Vector: "+temp); return temp.elements(); } /** * The elements() method returns an instance of PropertyPermissionEnumeration * (see above). */ public synchronized Enumeration elements() { Hashtable accumulator = new Hashtable(); Enumeration e; e = enumerate_subtree(read_prefixes); while (e.hasMoreElements()) { Object p = e.nextElement(); // System.out.println("read: put "+p); accumulator.put(p,p); } e = enumerate_subtree(write_prefixes); while (e.hasMoreElements()) { Object p = e.nextElement(); // System.out.println("write: put "+p); accumulator.put(p,p); } return accumulator.keys(); } }