/* * The contents of this file are subject to the Mozilla Public License Version 1.1 * (the "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at . * * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT * WARRANTY OF ANY KIND, either express or implied. See the License for the specific * language governing rights and limitations under the License. * * The Original Code is the Venice Web Communities System. * * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ package com.silverwrist.venice.security; import java.util.*; import org.apache.log4j.*; import org.w3c.dom.*; import com.silverwrist.util.DOMElementHelper; import com.silverwrist.venice.except.AccessError; import com.silverwrist.venice.except.ConfigException; import com.silverwrist.venice.svc.SecurityMonitorEnvironment; import com.silverwrist.venice.util.XMLLoader; /** * A SecurityMonitor which is configured by means of XML data, supplied by means of a Venice * configuration file. The configuration file defines roles, role lists, defaults, and permissions within * the security monitor's scope. * * @author Eric J. Bowersox <erbo@silcom.com> * @version X */ public class StaticSecurityMonitor implements SecurityMonitor { /*-------------------------------------------------------------------------------- * Internal class for evaluating static permissions *-------------------------------------------------------------------------------- */ final class StaticPermission { private Role role; // the role for the test private String message; // the failure message StaticPermission(Role role, String message) { this.role = role; this.message = message; } // end constructor final void test(int level, String errormessage) throws AccessError { if (!(role.isSatisfiedBy(level))) { // the static permission test failed! logger.warn("Static permission test (level " + level + " vs. role " + role + ") failed"); if (errormessage==null) errormessage = message; if (errormessage==null) errormessage = "Operation not permitted."; throw new AccessError(errormessage); } // end if } // end test final boolean test(int level) { return role.isSatisfiedBy(level); } // end test } // end class StaticPermission /*-------------------------------------------------------------------------------- * Static data members *-------------------------------------------------------------------------------- */ private static Category logger = Category.getInstance(StaticSecurityMonitor.class); private static volatile SecurityMonitor root_monitor = null; private static final int DEFAULT_SCOPE_OFFSET = 3; /*-------------------------------------------------------------------------------- * Attributes *-------------------------------------------------------------------------------- */ private String id; // the identity of this security monitor private ScopeInfo scope; // the scope of this security monitor private SecurityMonitor parent; // the parent of this security monitor private Map sym_to_role; // mapping of role symbols to roles private Map level_to_role; // mapping of role levels to roles private Map lists; // mapping of list symbols to lists private Map default_roles; // mapping of symbols to default values private Map static_permissions; // mapping of symbols to static permissions private Set dynamic_permissions; // set of defined dynamic permission names /*-------------------------------------------------------------------------------- * Constructor *-------------------------------------------------------------------------------- */ /** * Creates a new StaticSecurityMonitor from information stored as XML in a configuration file. * * @param cfg The root element of the security monitor configuration, which must be a * <security-definition/> element. * @param env A mapping of all previously-known security monitors. This one will be added to * it on success. * @exception com.silverwrist.venice.except.ConfigException The XML configuration data was incorrect in * some fashion. */ public StaticSecurityMonitor(Element cfg, SecurityMonitorEnvironment env) throws ConfigException { XMLLoader loader = XMLLoader.get(); boolean set_root_monitor = false; loader.configVerifyTagName(cfg,"security-definition"); // verify the tag name // Get the new security monitor's ID. id = loader.configGetAttribute(cfg,"id"); if (logger.isDebugEnabled()) logger.debug("defining new StaticSecurityMonitor with id=" + id); // Make sure this security monitor isn't already defined. if (env.isMonitorDefined(id)) { // the monitor with this ID has already been defined! logger.fatal("security monitor with id=" + id + " is already defined!"); throw new ConfigException("security monitor id=" + id + " is already defined!"); } // end if // See if the security monitor has a parent attribute. DOMElementHelper root_h = new DOMElementHelper(cfg); if (root_h.hasAttribute("parent")) { // get the parent and determine if it exists or not String parent_id = cfg.getAttribute("parent"); parent = env.getMonitor(parent_id); if (parent==null) { // no parent! that's bogus! logger.fatal("parent security monitor with id=" + parent_id + " does not exist!"); throw new ConfigException("parent security monitor with id=" + parent_id + " does not exist!"); } // end if // Determine the new scope for this security monitor. int my_scope = parent.getScopeInfo().getScope(); int my_offset = DEFAULT_SCOPE_OFFSET; if (root_h.hasAttribute("offset")) { // load the offset attribute and make sure it's in range my_offset = loader.configGetAttributeInt(cfg,"offset"); if (my_offset<1) { // the offset must be greater than or equal to 1! logger.fatal("offset= value (" + my_offset + ") was out of range"); throw new ConfigException("offset= attribute of must be >= 1"); } // end if } // end if // Determine the final scope and check its validity. my_scope += my_offset; if (!(ScopeInfo.isValidScope(my_scope))) { // resulting scope is out of range! logger.fatal("scope for id=" + id + " comes out to " + my_scope + ", and that's not in range"); throw new ConfigException("scope for security monitor id=" + id + " is out of range!"); } // end if // allocate a scope info object with the new scope scope = new ScopeInfo(my_scope); } // end if (security monitor has parent) else { // this must be the root security monitor! if (env.isRootMonitorDefined()) { // but we already have a root - can't be two roots! logger.fatal("trying to define root security monitor but we already have one"); throw new ConfigException("root security monitor is already defined!"); } // end if // we are the root security monitor...we live at scope 0, our parent is the primordial monitor set_root_monitor = true; scope = new ScopeInfo(0); parent = PrimordialSecurityMonitor.get(); } // end else (security monitor is root) // get the defined roles Element sect = root_h.getSubElement("defined-roles"); NodeList nl; int i; if (sect!=null) { // we need to define some roles here... HashMap tmp_sym_to_role = new HashMap(); HashMap tmp_level_to_role = new HashMap(); nl = sect.getChildNodes(); for (i=0; i Node n = nl.item(i); if ((n.getNodeType()==Node.ELEMENT_NODE) && (n.getNodeName().equals("role"))) { // create the role and add it to the temporary Role r = createRole((Element)n); tmp_sym_to_role.put(r.getSymbol(),r); tmp_level_to_role.put(new Integer(r.getLevel()),r); } // end if } // end for if (tmp_sym_to_role.size()>0) { // save these off as unmodifiable maps sym_to_role = Collections.unmodifiableMap(tmp_sym_to_role); level_to_role = Collections.unmodifiableMap(tmp_level_to_role); } // end if else { // nothing defined here! sym_to_role = Collections.EMPTY_MAP; level_to_role = Collections.EMPTY_MAP; } // end else } // end if else { // I guess we don't define any roles! sym_to_role = Collections.EMPTY_MAP; level_to_role = Collections.EMPTY_MAP; } // end else // since lists may indirectly define default roles and permissions, create storage space for them HashMap tmp_default_roles = new HashMap(); HashMap tmp_static_permissions = new HashMap(); HashSet tmp_dynamic_permissions = new HashSet(); // get the defined role lists sect = root_h.getSubElement("defined-lists"); if (sect!=null) { // we need to define some role lists here! HashMap tmp_lists = new HashMap(); nl = sect.getChildNodes(); for (i=0; i Node n = nl.item(i); if ((n.getNodeType()==Node.ELEMENT_NODE) && (n.getNodeName().equals("list"))) { // create the role list and add it to the temporary map // but first, get the ID String list_id = id + "." + loader.configGetAttribute((Element)n,"id"); // now actually build the list and insert it List rlist = buildList((Element)n,list_id,tmp_default_roles,tmp_static_permissions, tmp_dynamic_permissions); tmp_lists.put(list_id,rlist); } // end if } // end for if (tmp_lists.size()>0) lists = Collections.unmodifiableMap(tmp_lists); else lists = Collections.EMPTY_MAP; } // end if else // no lists defined here! lists = Collections.EMPTY_MAP; // Get the additional defined default roles. sect = root_h.getSubElement("defaults"); if (sect!=null) { // get the nodes in the defaults section nl = sect.getChildNodes(); for (i=0; i Node n = nl.item(i); if ((n.getNodeType()==Node.ELEMENT_NODE) && (n.getNodeName().equals("default"))) processDefault((Element)n,tmp_default_roles); } // end for } // end if // else no more defined defaults // Since that's it for the defaults, freeze the defaults list. if (tmp_default_roles.size()>0) default_roles = Collections.unmodifiableMap(tmp_default_roles); else default_roles = Collections.EMPTY_MAP; // Get the defined permissions. sect = root_h.getSubElement("permissions"); if (sect!=null) { // get the nodes in the permissions section nl = sect.getChildNodes(); for (i=0; i Node n = nl.item(i); if ((n.getNodeType()==Node.ELEMENT_NODE) && (n.getNodeName().equals("permission"))) processPermission((Element)n,tmp_static_permissions,tmp_dynamic_permissions); } // end for } // end if // else no more defined permissions // That's now it for the permissions, so freeze those elements. if (tmp_static_permissions.size()>0) static_permissions = Collections.unmodifiableMap(tmp_static_permissions); else static_permissions = Collections.EMPTY_MAP; if (tmp_dynamic_permissions.size()>0) dynamic_permissions = Collections.unmodifiableSet(tmp_dynamic_permissions); else dynamic_permissions = Collections.EMPTY_SET; // Finish up by adding ourselves to the known monitors list. env.storeMonitor(this,set_root_monitor); } // end constructor /*-------------------------------------------------------------------------------- * Internal operations *-------------------------------------------------------------------------------- */ private Role createRole(Element e) throws ConfigException { XMLLoader loader = XMLLoader.get(); // Get the role symbol and automagically scope it. String symbol = id + "." + loader.configGetAttribute(e,"id"); // Look for the value. String value_str = loader.configGetAttribute(e,"value").trim().toUpperCase(); int level; if (value_str.equals("LMIN")) level = scope.getLowBandLow(); else if (value_str.equals("LMAX")) level = scope.getLowBandHigh(); else if (value_str.equals("HMIN")) level = scope.getHighBandLow(); else if (value_str.equals("HMAX")) level = scope.getHighBandHigh(); else if ( value_str.startsWith("L+") || value_str.startsWith("L-") || value_str.startsWith("H+") || value_str.startsWith("H-")) { // take the characters following the 2-character prefix and convert them to an integer int offset; try { // convert the value and make sure it's not less than 0 offset = Integer.parseInt(value_str.substring(2)); if (offset<0) { // don't want it less than zero here! logger.fatal("offset value " + offset + " was out of range"); throw new ConfigException("offset value= attribute for was out of range",e); } // end if } // end try catch (NumberFormatException nfe) { // not a numeric offset value logger.fatal("offset value \"" + value_str + "\" was not numeric"); throw new ConfigException("offset value= attribute for was not properly numeric",e); } // end catch if (value_str.charAt(1)=='-') offset = -offset; // compute as negative offset try { // now use the scope to compute the level! level = scope.getLevel((value_str.charAt(0)=='H'),offset); } // end try catch (IllegalArgumentException iae) { // we landed with a value outside the scope! logger.fatal("offset value \"" + value_str + "\" was not in the scope"); throw new ConfigException("offset value= attribute for was not within the scope",e); } // end catch } // end else if else { // just a straight numeric level try { // parse it out and give it a scope check level = Integer.parseInt(value_str); if (!(scope.isInScope(level))) { // not in the right scope - can't help you, pal! logger.fatal("level value \"" + level + "\" was not in the scope"); throw new ConfigException("level value= attribute for was not within the scope",e); } // end if } // end try catch (NumberFormatException nfe) { // the level was not numeric logger.fatal("level value \"" + value_str + "\" was not numeric"); throw new ConfigException("level value= attribute for was not properly numeric",e); } // end catch } // end else // Get the text; default to the symbol name if it doesn't exist. DOMElementHelper h = new DOMElementHelper(e); String text = h.getElementText(); if (text==null) text = symbol; // create the resulting role! return Role.create(level,text,symbol); } // end createRole private List buildList(Element elem, String listid, Map defaultrole, Map static_perm, Set dynamic_perm) throws ConfigException { XMLLoader loader = XMLLoader.get(); // If there's a permission tag under this list, take it. DOMElementHelper h = new DOMElementHelper(elem); Element perm = h.getSubElement("permission"); if (perm!=null) { // Find out what the permission is. DOMElementHelper ph = new DOMElementHelper(perm); if (ph.hasAttribute("role")) { // look up the role and make sure it corresponds to one we know Role role = this.getRole(perm.getAttribute("role")); if (role==null) { // role not present! logger.fatal("list role (" + perm.getAttribute("role") + ") not defined"); throw new ConfigException(" inside of did not use defined role!",perm); } // end if // create a new StaticPermission and add it to the mapping StaticPermission sp = new StaticPermission(role,ph.getElementText()); static_perm.put(listid,sp); } // end if else // this is a dynamic permission, add it to the set dynamic_perm.add(listid); } // end if // else just skip this check // Begin loading the list elements. NodeList nl = elem.getChildNodes(); ArrayList rc = new ArrayList(nl.getLength()); boolean have_default = false; for (int i=0; i role (" + rname + ") not defined"); throw new ConfigException(" inside of did not use defined role!",(Element)n); } // end if rc.add(r); // add element to defining list // Check and see if this item is a default. DOMElementHelper nh = new DOMElementHelper((Element)n); if (nh.hasAttribute("default")) { // this is a default item... if (have_default) { // but there can't be two defaults! logger.fatal("duplicate default= attributes in list nodes!"); throw new ConfigException("duplicate default= attribute in list ",nh.getElement()); } // end if else { // we have a default for the list now! defaultrole.put(listid,r); have_default = true; } // end else } // end if } // end if } // end for // Final prep on the list prior to returning it. Collections.sort(rc); rc.trimToSize(); return Collections.unmodifiableList(rc); } // end buildlist private void processDefault(Element elem, Map defaultrole) throws ConfigException { XMLLoader loader = XMLLoader.get(); // Start by getting the default ID. String def_id = id + "." + loader.configGetAttribute(elem,"id"); // Now get the associated default role. Role r = this.getRole(loader.configGetAttribute(elem,"role")); if (r==null) { // no role found - this is an error! logger.fatal(" role (" + elem.getAttribute("role") + ") not defined"); throw new ConfigException(" did not use defined role!",elem); } // end if // and save the default defaultrole.put(def_id,r); } // end processDefault private void processPermission(Element elem, Map static_perm, Set dynamic_perm) throws ConfigException { XMLLoader loader = XMLLoader.get(); // Start by getting the permission ID. String perm_id = id + "." + loader.configGetAttribute(elem,"id"); // Now get the associated role, if any. DOMElementHelper h = new DOMElementHelper(elem); if (h.hasAttribute("role")) { // this is a static permission; try and get the associated role Role r = this.getRole(elem.getAttribute("role")); if (r==null) { // no role found - this is an error! logger.fatal(" role (" + elem.getAttribute("role") + ") not defined"); throw new ConfigException(" did not use defined role!",elem); } // end if // create static permission and add it StaticPermission sp = new StaticPermission(r,h.getElementText()); static_perm.put(perm_id,sp); } // end if else // this is a dynamic permission; just add to our set dynamic_perm.add(perm_id); } // end processPermission /*-------------------------------------------------------------------------------- * Implementations from interface SecurityMonitor *-------------------------------------------------------------------------------- */ /** * Tests a specified permission within this security monitor. If the supplied security level is less * than the defined security level for this permission, an exception is thrown. * * @param symbol The programmatic symbol of the permission to test. * @param level The security level to supply for the test. * @param errormsg The error message to be thrown if the test fails. May be null, in * which case the security monitor will throw an appropriate default, if applicable. * @return true if the specified permission was defined and satisfied; false * if the specified permission was not defined within this security monitor. * @exception com.silverwrist.venice.except.AccessError If the specified permission was defined but * not satisfied. * @see #testPermission(java.lang.String,int) */ public boolean testPermission(String symbol, int level, String errormsg) throws AccessError { if (symbol==null) throw new NullPointerException("testPermission() got null symbol"); StaticPermission sp = (StaticPermission)(static_permissions.get(symbol)); if (sp==null) { // permission not found here - NOTE! Do not call to parent unless we are at the root level, as // permission tests always follow the DYNAMIC chain, not the static one! if (scope.getScope()==0) return parent.testPermission(symbol,level,errormsg); else return false; } // end if sp.test(level,errormsg); // will throw AccessError on failure return true; } // end testPermission /** * Tests a specified permission within this security monitor. If the supplied security level is greater * than or equal to the defined security level for this permission, true is returned. * * @param symbol The programmatic symbol of the permission to test. * @param level The security level to supply for the test. * @return true if the specified permission was defined and satisfied; false * if the specified permission was not defined within this security monitor, or was defined but * not satisfied. * @see #testPermission(java.lang.String,int,java.lang.String) */ public boolean testPermission(String symbol, int level) { if (symbol==null) throw new NullPointerException("testPermission() got null symbol"); StaticPermission sp = (StaticPermission)(static_permissions.get(symbol)); if (sp==null) { // permission not found here - NOTE! Do not call to parent unless we are at the root level, as // permission tests always follow the DYNAMIC chain, not the static one! if (scope.getScope()==0) return parent.testPermission(symbol,level); else return false; } // end if return sp.test(level); } // end testPermission /** * Determines whether this SecurityMonitor defines the specified permission symbol. * * @param symbol The programmatic symbol of the permission to look up. * @param no_follow If this is true, we look only in this SecurityMonitor * for the specified permission, and not in any "parent" SecurityMonitor * objects. * @return true if this is a defined permission symbol, false if not. */ public boolean permissionDefined(String symbol, boolean no_follow) { if (symbol==null) throw new NullPointerException("permissionDefined() got null symbol"); if (static_permissions.containsKey(symbol) || dynamic_permissions.contains(symbol)) return true; if (no_follow) return false; return parent.permissionDefined(symbol,false); } // end permissionDefined /** * Returns a defined list of roles for this SecurityMonitor. * * @param symbol The programmatic symbol of the role list to look up. * @return The associated role list, or null if the role list name was not defined. * @see Role */ public List getRoleList(String symbol) { if (symbol==null) throw new NullPointerException("getRoleList() got null symbol"); List rc = (List)(lists.get(symbol)); if (rc==null) rc = parent.getRoleList(symbol); return rc; } // end getRoleList /** * Returns a defined role for this SecurityMonitor. * * @param symbol The programmatic symbol of the role to look up. * @return The associated role, or null if the role name was not defined. * @see Role */ public Role getRole(String symbol) { if (symbol==null) throw new NullPointerException("getRole() got null symbol"); Role rc = (Role)(sym_to_role.get(symbol)); if (rc==null) rc = parent.getRole(symbol); return rc; } // end getRole /** * Returns the defined role that has the given level. * * @param level The level value to look up. * @return The associated role, or null if no such role could be found. * @see Role */ public Role getRoleForLevel(int level) { Role rc = (Role)(level_to_role.get(new Integer(level))); if (rc==null) rc = parent.getRoleForLevel(level); return rc; } // end getRoleForLevel /** * Returns a defined default role for this SecurityMonitor. * * @param symbol The programmatic symbol of the default role to look up. * @return The associated role, or null if the default role name was not defined. * @see Role */ public Role getDefaultRole(String symbol) { if (symbol==null) throw new NullPointerException("getRole() got null symbol"); Role rc = (Role)(default_roles.get(symbol)); if (rc==null) rc = parent.getDefaultRole(symbol); return rc; } // end getDefaultRole /** * Returns the ScopeInfo associated with this SecurityMonitor. * * @return The ScopeInfo associated with this SecurityMonitor. * @see ScopeInfo */ public ScopeInfo getScopeInfo() { return scope; } // end getScopeInfo /** * Returns the identifier for this SecurityMonitor. * * @return The identifier for this SecurityMonitor. */ public String getID() { return id; } // end getID } // end class StaticSecurityMonitor