All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.owasp.esapi.reference.accesscontrol.FileBasedACRs Maven / Gradle / Ivy

/**
 * OWASP Enterprise Security API (ESAPI)
 * 
 * This file is part of the Open Web Application Security Project (OWASP)
 * Enterprise Security API (ESAPI) project. For details, please see
 * http://www.owasp.org/index.php/ESAPI.
 *
 * Copyright (c) 2007 - The OWASP Foundation
 * 
 * The ESAPI is published by OWASP under the BSD license. You should read and accept the
 * LICENSE before you use, modify, and/or redistribute this software.
 * 
 * @author Mike Fauzy Aspect Security
 * @author Jeff Williams Aspect Security
 * @created 2007
 */
package org.owasp.esapi.reference.accesscontrol;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.owasp.esapi.ESAPI;
import org.owasp.esapi.Logger;
import org.owasp.esapi.User;
import org.owasp.esapi.errors.AccessControlException;
import org.owasp.esapi.errors.EncodingException;
import org.owasp.esapi.errors.IntrusionException;

// CHECKME: If this exists for backward compatibility, should this
//          class be deprecated??? If so, mark it using annotation.
/**
 * This class exists for backwards compatibility with the AccessController 1.0 
 * reference implementation.
 *
 * This reference implementation uses a simple model for specifying a set of
 * access control rules. Many organizations will want to create their own
 * implementation of the methods provided in the AccessController interface.
 * 

* This reference implementation uses a simple scheme for specifying the rules. * The first step is to create a namespace for the resources being accessed. For * files and URL's, this is easy as they already have a namespace. Be extremely * careful about canonicalizing when relying on information from the user in an * access control decision. *

* For functions, data, and services, you will have to come up with your own * namespace for the resources being accessed. You might simply define a flat * namespace with a list of category names. For example, you might specify * 'FunctionA', 'FunctionB', and 'FunctionC'. Or you can create a richer * namespace with a hierarchical structure, such as: *

* /functions *

    *
  • purchasing
  • *
  • shipping
  • *
  • inventory
  • *
* /admin *
    *
  • createUser
  • *
  • deleteUser
  • *
* Once you've defined your namespace, you have to work out the rules that * govern access to the different parts of the namespace. This implementation * allows you to attach a simple access control list (ACL) to any part of the * namespace tree. The ACL lists a set of roles that are either allowed or * denied access to a part of the tree. You specify these rules in a textfile * with a simple format. *

* There is a single configuration file supporting each of the five methods in * the AccessController interface. These files are located in the ESAPI * resources directory as specified when the JVM was started. The use of a * default deny rule is STRONGLY recommended. The file format is as follows: * *

 * path          | role,role   | allow/deny | comment
 * ------------------------------------------------------------------------------------
 * /banking/*    | user,admin  | allow      | authenticated users can access /banking
 * /admin        | admin       | allow      | only admin role can access /admin
 * /             | any         | deny       | default deny rule
 * 
* * To find the matching rules, this implementation follows the general approach * used in Java EE when matching HTTP requests to servlets in web.xml. The four * mapping rules are used in the following order: *
    *
  • exact match, e.g. /access/login
  • *
  • longest path prefix match, beginning / and ending /*, e.g. /access/* or * /*
  • *
  • extension match, beginning *., e.g. *.css
  • *
  • default rule, specified by the single character pattern /
  • *
* * @author Mike Fauzy ([email protected]) * @author Jeff Williams ([email protected]) * @since June 1, 2007 */ public class FileBasedACRs { /** The url map. */ private Map urlMap = new HashMap(); /** The function map. */ private Map functionMap = new HashMap(); /** The data map. */ private Map dataMap = new HashMap(); /** The file map. */ private Map fileMap = new HashMap(); /** The service map. */ private Map serviceMap = new HashMap(); /** A rule containing "deny". */ private Rule deny = new Rule(); /** The logger. */ private Logger logger = ESAPI.getLogger("FileBasedACRs"); /** * Check if URL is authorized. * @param url The URL tested for authorization * @return {@code true} if access is allowed, {@code false} otherwise. */ public boolean isAuthorizedForURL(String url) { if (urlMap==null || urlMap.isEmpty()) { urlMap = loadRules("URLAccessRules.txt"); } return matchRule(urlMap, url); } /** * TODO Javadoc */ public boolean isAuthorizedForFunction(String functionName) throws AccessControlException { if (functionMap==null || functionMap.isEmpty()) { functionMap = loadRules("FunctionAccessRules.txt"); } return matchRule(functionMap, functionName); } /** * TODO Javadoc */ public boolean isAuthorizedForData(String action, Object data) throws AccessControlException{ if (dataMap==null || dataMap.isEmpty()) { dataMap = loadDataRules("DataAccessRules.txt"); } return matchRule(dataMap, (Class)data, action); } /** * TODO Javadoc */ public boolean isAuthorizedForFile(String filepath) throws AccessControlException { if (fileMap==null || fileMap.isEmpty()) { fileMap = loadRules("FileAccessRules.txt"); } return matchRule(fileMap, filepath.replaceAll("\\\\","/")); } /** * TODO Javadoc */ public boolean isAuthorizedForService(String serviceName) throws AccessControlException { if (serviceMap==null || serviceMap.isEmpty()) { serviceMap = loadRules("ServiceAccessRules.txt"); } return matchRule(serviceMap, serviceName); } /** * Checks to see if the current user has access to the specified data, File, Object, etc. * If the User has access, as specified by the map parameter, this method returns true. If the * User does not have access or an exception is thrown, false is returned. * * @param map * the map containing access rules * @param path * the path of the requested File, URL, Object, etc. * * @return * true, if the user has access, false otherwise * */ private boolean matchRule(Map map, String path) { // get users roles User user = ESAPI.authenticator().getCurrentUser(); Set roles = user.getRoles(); // search for the first rule that matches the path and rules Rule rule = searchForRule(map, roles, path); return rule.allow; } /** * Checks to see if the current user has access to the specified Class and action. * If the User has access, as specified by the map parameter, this method returns true. * If the User does not have access or an exception is thrown, false is returned. * * @param map * the map containing access rules * @param clazz * the Class being requested for access * @param action * the action the User has asked to perform * @return * true, if the user has access, false otherwise * */ private boolean matchRule(Map map, Class clazz, String action) { // get users roles User user = ESAPI.authenticator().getCurrentUser(); Set roles = user.getRoles(); // search for the first rule that matches the path and rules Rule rule = searchForRule(map, roles, clazz, action); return rule != null; } /** * Search for rule. Four mapping rules are used in order: - exact match, * e.g. /access/login - longest path prefix match, beginning / and ending * /*, e.g. /access/* or /* - extension match, beginning *., e.g. *.css - * default servlet, specified by the single character pattern / * * @param map * the map containing access rules * @param roles * the roles of the User being checked for access * @param path * the File, URL, Object, etc. being checked for access * * @return * the rule stating whether to allow or deny access * */ private Rule searchForRule(Map map, Set roles, String path) { String canonical = ESAPI.encoder().canonicalize(path); String part = canonical; if ( part == null ) { part = ""; } while (part.endsWith("/")) { part = part.substring(0, part.length() - 1); } if (part.indexOf("..") != -1) { throw new IntrusionException("Attempt to manipulate access control path", "Attempt to manipulate access control path: " + path ); } // extract extension if any String extension = ""; int extIndex = part.lastIndexOf("."); if (extIndex != -1) { extension = part.substring(extIndex + 1); } // Check for exact match - ignore any ending slash Rule rule = (Rule) map.get(part); // Check for ending with /* if (rule == null) rule = (Rule) map.get(part + "/*"); // Check for matching extension rule *.ext if (rule == null) rule = (Rule) map.get("*." + extension); // if rule found and user's roles match rules' roles, return the rule if (rule != null && overlap(rule.roles, roles)) return rule; // rule hasn't been found - if there are no more parts, return a deny int slash = part.lastIndexOf('/'); if ( slash == -1 ) { return deny; } // if there are more parts, strip off the last part and recurse part = part.substring(0, part.lastIndexOf('/')); // return default deny if (part.length() <= 1) { return deny; } return searchForRule(map, roles, part); } /** * Search for rule. Searches the specified access map to see if any of the roles specified have * access to perform the specified action on the specified Class. * * @param map * the map containing access rules * @param roles * the roles used to determine access level * @param clazz * the Class being requested for access * @param action * the action the User has asked to perform * * @return * the rule that allows the specified roles access to perform the requested action on the specified Class, or null if access is not granted * */ private Rule searchForRule(Map map, Set roles, Class clazz, String action) { // Check for exact match - ignore any ending slash Rule rule = (Rule) map.get(clazz); if( ( rule != null ) && ( overlap(rule.actions, action) ) && ( overlap(rule.roles, roles) )){ return rule; } return null; } /** * Return true if there is overlap between the two sets. This method merely checks to see if * ruleRoles contains any of the roles listed in userRoles. * * @param ruleRoles * the rule roles * @param userRoles * the user roles * * @return * true, if any roles exist in both Sets. False otherwise. */ private boolean overlap(Set ruleRoles, Set userRoles) { if (ruleRoles.contains("any")) { return true; } Iterator i = userRoles.iterator(); while (i.hasNext()) { String role = (String) i.next(); if (ruleRoles.contains(role)) { return true; } } return false; } /** * This method merely checks to see if ruleActions contains the action requested. * * @param ruleActions * actions listed for a rule * @param action * the action requested that will be searched for in ruleActions * * @return * true, if any action exists in ruleActions. False otherwise. */ private boolean overlap( List ruleActions, String action){ if( ruleActions.contains(action) ) return true; return false; } /** * Checks that the roles passed in contain only letters, numbers, and underscores. Also checks that * roles are no more than 10 characters long. If a role does not pass validation, it is not included in the * list of roles returned by this method. A log warning is also generated for any invalid roles. * * @param roles * roles to validate according to criteria started above * @return * a List of roles that are valid according to the criteria stated above. * */ private List validateRoles(List roles){ List ret = new ArrayList(); for(int x = 0; x < roles.size(); x++){ String canonical = ESAPI.encoder().canonicalize(((String)roles.get(x)).trim()); if(!ESAPI.validator().isValidInput("Validating user roles in FileBasedAccessController", canonical, "RoleName", 20, false)) { logger.warning( Logger.SECURITY_FAILURE, "Role: " + ((String)roles.get(x)).trim() + " is invalid, so was not added to the list of roles for this Rule."); } else { ret.add(canonical.trim()); } } return ret; } /** * Loads access rules by storing them in a hashmap. This method begins reading the File specified by * the ruleset parameter, ignoring any lines that begin with '#' characters as comments. Sections of the access rules file * are split by the pipe character ('|'). The method loads all paths, replacing '\' characters with '/' for uniformity then loads * the list of comma separated roles. The roles are validated to be sure they are within a * length and character set, specified in the validateRoles(String) method. Then the permissions are stored for each item in the rules list. * If the word "allow" appears on the line, the specified roles are granted access to the data - otherwise, they will be denied access. * * Each path may only appear once in the access rules file. Any entry, after the first, containing the same path will be logged and ignored. * * @param ruleset * the name of the data that contains access rules * * @return * a hash map containing the ruleset */ private Map loadRules(String ruleset) { ruleset = "fbac-policies/" + ruleset; Map map = new HashMap(); InputStream is = null; try { is = ESAPI.securityConfiguration().getResourceStream(ruleset); String line = ""; while ((line = ESAPI.validator().safeReadLine(is, 500)) != null) { if (line.length() > 0 && line.charAt(0) != '#') { Rule rule = new Rule(); String[] parts = line.split("\\|"); // fix Windows paths rule.path = parts[0].trim().replaceAll("\\\\", "/"); List roles = commaSplit(parts[1].trim().toLowerCase()); roles = validateRoles(roles); for(int x = 0; x < roles.size(); x++) rule.roles.add(((String)roles.get(x)).trim()); String action = parts[2].trim(); rule.allow = action.equalsIgnoreCase("allow"); if (map.containsKey(rule.path)) { logger.warning( Logger.SECURITY_FAILURE, "Problem in access control file. Duplicate rule ignored: " + rule); } else { map.put(rule.path, rule); } } } } catch (Exception e) { logger.warning( Logger.SECURITY_FAILURE, "Problem in access control file: " + ruleset, e ); } finally { try { if (is != null) { is.close(); } } catch (IOException e) { logger.warning(Logger.SECURITY_FAILURE, "Failure closing access control file: " + ruleset, e); } } return map; } /** * Loads access rules by storing them in a hashmap. This method begins reading the File specified by * the ruleset parameter, ignoring any lines that begin with '#' characters as comments. Sections of the access rules file * are split by the pipe character ('|'). The method then loads all Classes, loads the list of comma separated roles, then the list of comma separated actions. * The roles are validated to be sure they are within a length and character set, specified in the validateRoles(String) method. * * Each path may only appear once in the access rules file. Any entry, after the first, containing the same path will be logged and ignored. * * @param ruleset * the name of the data that contains access rules * * @return * a hash map containing the ruleset */ private Map loadDataRules(String ruleset) { Map map = new HashMap(); InputStream is = null; try { ruleset = "fbac-policies/" + ruleset; is = ESAPI.securityConfiguration().getResourceStream(ruleset); String line = ""; while ((line = ESAPI.validator().safeReadLine(is, 500)) != null) { if (line.length() > 0 && line.charAt(0) != '#') { Rule rule = new Rule(); String[] parts = line.split("\\|"); rule.clazz = Class.forName(parts[0].trim()); List roles = commaSplit(parts[1].trim().toLowerCase()); roles = validateRoles(roles); for(int x = 0; x < roles.size(); x++) rule.roles.add(((String)roles.get(x)).trim()); List action = commaSplit(parts[2].trim().toLowerCase()); for(int x = 0; x < action.size(); x++) rule.actions.add(((String) action.get(x)).trim()); if (map.containsKey(rule.path)) { logger.warning( Logger.SECURITY_FAILURE, "Problem in access control file. Duplicate rule ignored: " + rule); } else { map.put(rule.clazz, rule); } } } } catch (Exception e) { logger.warning( Logger.SECURITY_FAILURE, "Problem in access control file : " + ruleset, e ); } finally { try { if (is != null) { is.close(); } } catch (IOException e) { logger.warning(Logger.SECURITY_FAILURE, "Failure closing access control file : " + ruleset, e); } } return map; } /** * This method splits a String by the ',' and returns the result as a List. * * @param input * the String to split by ',' * @return * a List where each entry was on either side of a ',' in the original String */ private List commaSplit(String input){ String[] array = input.split(","); return Arrays.asList(array); } /** * The Class Rule. */ private class Rule { protected String path = ""; protected Set roles = new HashSet(); protected boolean allow = false; protected Class clazz = null; protected List actions = new ArrayList(); /** * * Creates a new Rule object. */ protected Rule() { // to replace synthetic accessor method } /** * {@inheritDoc} */ public String toString() { return "URL:" + path + " | " + roles + " | " + (allow ? "allow" : "deny"); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy