/*
* 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