/************************************************************************** * 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.security.BasicPermission; import java.security.Permission; import java.security.PermissionCollection; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; /** * The class BasicPermissionCollection is designed to optimize performance * of the 'implies' operation, which must determine whether a candidate * BasicPermission is implied by any permission in the collection. * This we do using a data structure based on a 'trie': e.g. the * three BasicPremissions foo.bar, foo.baz, foo.qu*, and something.else * would be represented as * * p +---> r * r | * e+---> foo. +---> ba +---> z * f| | * i| +---> qu ---> * * x| * e+---> something.else * s * * 'prefixes' is the root hashtable in which the keys are the longest * possible prefixes of all the Permission names. The data associated with * each key is either a Permission (leaf node) or another hashtable which * holds the possible sequels to the prefix, and so on recursively. * * As can be seen in the illustration above, a terminating "*" is * always treated separately. This simplifies the search algorithm * for method 'implies'. */ public class BasicPermissionCollection extends PermissionCollection { static final int INITIAL_TABSIZE = 11; private static final String ASTERISK = "*"; private Hashtable prefixes; /** ** Constructor BasicPermissionCollection() builds an empty Hashtable. */ public BasicPermissionCollection() { prefixes = new Hashtable(INITIAL_TABSIZE); } /** * Method add(Permission) rebuilds the Hashtable to take account * of the new Permission (which must be a BasicPermission). * * 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.qu'. We replace the existing entry * 'foo.bar.baz' by a link to a new hashtable: * +---> foo.bar. ---> baz * and then add 'qu' 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. */ public synchronized void add (Permission permission) throws SecurityException { if (super.isReadOnly()) throw new SecurityException("read-only"); BasicPermission newperm; try { newperm = (BasicPermission) permission; } catch (ClassCastException e) { throw new IllegalArgumentException("not a BasicPermission"); } //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; 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+"'"); 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 'implies' tests whether a given permission is implied by any * of the BasicPermissions in this collection. The permission must of * course be a BasicPermission. * * We first test to see whether the whole name of the permission is a key * of the root hashtable ('prefixes'), with a Permission as data. If so * then the algorithm terminates successfully. It also terminates * successfully if the hashtable contains a key "*" (the associated data * will always be a Permission in this case). Otherwise, we try to match * the name of the new permission against each key in the hashtable: if * any key is a prefix of the name 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 (Permission permission) { BasicPermission tryperm; try { tryperm = (BasicPermission) permission; } catch (ClassCastException e) { //System.out.println(permission+" is not a BasicPermission!"); return false; } //System.out.println("Testing '"+tryperm+"'in: "+prefixes); Hashtable table = prefixes; String name = tryperm.getName(); int namelen = name.length(); String lastTarget = null; 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(ASTERISK); if (data != null) { //System.out.println(" Found a * ->"+data+", success!");: return namelen > 0 && (lastTarget == null || lastTarget.endsWith(".")); } Enumeration e = table.keys(); boolean notImplied = true; 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+"'"); notImplied = false; lastTarget = target; break; } //else //System.out.println(" Matched prefix '"+target+"', but ->Permission (?!)"); } } if (notImplied) { break; } } //System.out.println(" No match found, failed."); return false; } private Enumeration enumerate_subtree(Hashtable h) { Vector temp = new Vector(); Enumeration e = h.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 hh = (Hashtable)data; Enumeration ee = enumerate_subtree(hh); 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(); } public synchronized Enumeration elements() { return enumerate_subtree(prefixes); } }