// --- BEGIN COPYRIGHT BLOCK ---
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (C) 2007 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package com.netscape.cms.authorization;
import java.util.*;
import java.lang.Class;
import com.netscape.certsrv.base.*;
import com.netscape.certsrv.base.SessionContext;
import com.netscape.certsrv.authentication.*;
import com.netscape.certsrv.authorization.AuthzToken;
import com.netscape.certsrv.acls.*;
import com.netscape.certsrv.evaluators.*;
import com.netscape.certsrv.logging.*;
import com.netscape.certsrv.apps.*;
import com.netscape.cmsutil.util.*;
/**
* An abstract class represents an authorization manager that governs the
* access of internal resources such as servlets.
* It parses in the ACLs associated with each protected
* resources, and provides protected method checkPermission
* for code that needs to verify access before performing
* actions.
*
* Here is a sample resourceACLS for a resource *
* certServer.UsrGrpAdminServlet: * execute: * deny (execute) user="tempAdmin"; * allow (execute) group="Administrators"; ** To perform permission checking, code call authz mgr authorize() * method to verify access. See AuthzMgr for calling example. *
* default "evaluators" are used to evaluate the "group=.." or "user=.."
* rules. See evaluator for more info
*
* @version $Revision: 14561 $, $Date: 2007-05-01 10:28:56 -0700 (Tue, 01 May 2007) $
* @see ACL Files
*/
public abstract class AAclAuthz {
protected static final String PROP_CLASS = "class";
protected static final String PROP_IMPL = "impl";
protected static final String PROP_EVAL = "accessEvaluator";
protected static final String ACLS_ATTR = "aclResources";
private IConfigStore mConfig = null;
private Hashtable mACLs = new Hashtable();
private Hashtable mEvaluators = new Hashtable();
private ILogger mLogger = null;
/* Vector of extendedPluginInfo strings */
protected static Vector mExtendedPluginInfo = null;
protected static String[] mConfigParams = null;
static {
mExtendedPluginInfo = new Vector();
}
/**
* Constructor
*/
public AAclAuthz() {
}
/**
* Initializes
*/
protected void init(IConfigStore config)
throws EBaseException {
mLogger = CMS.getLogger();
CMS.debug("AAclAuthz: init begins");
mConfig = config;
// load access evaluators specified in the config file
IConfigStore mainConfig = CMS.getConfigStore();
IConfigStore evalConfig = mainConfig.getSubStore(PROP_EVAL);
IConfigStore i = evalConfig.getSubStore(PROP_IMPL);
IAccessEvaluator evaluator = null;
Enumeration mImpls = i.getSubStoreNames();
while (mImpls.hasMoreElements()) {
String type = (String) mImpls.nextElement();
String evalClassPath = null;
try {
evalClassPath = i.getString(type + "." + PROP_CLASS);
} catch (Exception e) {
log(ILogger.LL_MISCONF, "failed to get config class info");
throw new EBaseException(CMS.getUserMessage("CMS_BASE_GET_PROPERTY_FAILED",
type + "." + PROP_CLASS));
}
// instantiate evaluator
try {
evaluator =
(IAccessEvaluator) Class.forName(evalClassPath).newInstance();
} catch (Exception e) {
String errMsg = "init(): failed to load class: " +
evalClassPath + ":" + e.toString();
throw new
EACLsException(CMS.getUserMessage("CMS_ACL_CLASS_LOAD_FAIL",
evalClassPath));
}
if (evaluator != null) {
evaluator.init();
// store evaluator
registerEvaluator(type, evaluator);
} else {
String errMsg = "access evaluator " + type + " is null";
log(ILogger.LL_FAILURE, CMS.getLogMessage("AUTHZ_EVALUATOR_NULL", type));
}
}
log(ILogger.LL_INFO, "initialization done");
}
/**
* Parse ACL resource attributes, then update the ACLs memory store
* This is intended to be used if storing ACLs on ldap is not desired,
* and the caller is expected to call this method to add resource
* and acl info into acls memory store. The resACLs format should conform
* to the following:
*
* note that if a resource does not exist in the aclResources
* entry, but a higher level node exist, it will still be
* evaluated. The highest level node's acl determines the
* permission. If the higher level node doesn't contain any acl
* information, then it's passed down to the lower node. If
* a node has no aci in its resourceACLs, then it's considered
* passed.
*
* example: certServer.common.users, if failed permission check for
* "certServer", then it's considered failed, and there is no need to
* continue the check. If passed permission check for "certServer",
* then it's considered passed, and no need to continue the
* check. If certServer contains no aci then "certServer.common" will be
* checked for permission instead. If down to the leaf level,
* the node still contains no aci, then it's considered passed.
* If at the leaf level, no such resource exist, or no acis, it's
* considered passed.
*
* If there are multiple aci's for a resource, ALL aci's will be
* checked, and only if all passed permission checks, will the
* eventual access be granted.
* @param name resource name
* @param perm permission requested
* @exception EACLsException access permission denied
*/
protected synchronized void checkPermission(String name, String perm)
throws EACLsException {
String resource = "";
StringTokenizer st = new StringTokenizer(name, ".");
while (st.hasMoreTokens()) {
String node = st.nextToken();
if (! "".equals(resource)) {
resource = resource + "." + node;
} else {
resource = node;
}
boolean passed = false;
try {
passed = checkACLs(resource, perm);
} catch (EACLsException e) {
Object[] params = new Object[2];
params[0] = name;
params[1] = perm;
String errMsg = "checkPermission(): permission denied for the resource " +
name + " on operation " + perm;
log(ILogger.LL_SECURITY, CMS.getLogMessage("AUTHZ_EVALUATOR_ACCESS_DENIED", name, perm));
throw new
EACLsException(CMS.getUserMessage("CMS_ACL_NO_PERMISSION",
(String[]) params));
}
if (passed) {
String infoMsg = "checkPermission(): permission granted for the resource " +
name + " on operation " + perm;
log(ILogger.LL_INFO, infoMsg);
return;
} // else, continue
}
}
/**
* Checks if the permission is granted or denied in
* the current execution context.
*
* An
* negative ("deny") aclEntries are treated differently than
* positive ("allow") statements. If a negative aclEntries
* fails the acl check, the permission check will return "false"
* right away; while in the case of a positive aclEntry, if the
* the aclEntry fails the acl check, the next aclEntry will be
* evaluated.
* @param name resource name
* @param perm permission requested
* @return true if access allowed
* false if should be passed down to the next node
* @exception EACLsException if access disallowed
*/
private boolean checkACLs(String name, String perm)
throws EACLsException {
ACL acl = (ACL) mACLs.get(name);
// no such resource, pass it down
if (acl == null) {
String infoMsg = "checkACLs(): no acl for" +
name + "...pass down to next node";
log(ILogger.LL_INFO, infoMsg);
return false;
}
Enumeration e = acl.entries();
if ((e == null) || (e.hasMoreElements() == false)) {
// no acis for node, pass down to next node
String infoMsg = " AAclAuthz.checkACLs(): no acis for " +
name + " acl entry...pass down to next node";
log(ILogger.LL_INFO, infoMsg);
return false;
}
/**
* must pass all ACLEntry
*/
for (; e.hasMoreElements();) {
ACLEntry entry = (ACLEntry) e.nextElement();
// if permission not pertinent, move on to next ACLEntry
if (entry.containPermission(perm) == true) {
if (evaluateExpressions(entry.getAttributeExpressions())) {
if (entry.checkPermission(perm) == false) {
log(ILogger.LL_SECURITY, " checkACLs(): permission denied");
throw new EACLsException(CMS.getUserMessage("CMS_ACL_PERMISSION_DENIED"));
}
} else if (!entry.isNegative()) {
// didn't meet the access expression for "allow", failed
log(ILogger.LL_SECURITY, "checkACLs(): permission denied");
throw new EACLsException(CMS.getUserMessage("CMS_ACL_PERMISSION_DENIED"));
}
}
}
return true;
}
/**
* Resolves the given expressions.
* expression || expression || ...
* example:
* group="Administrators" || group="Operators"
*/
private boolean evaluateExpressions(String s) {
// XXX - just handle "||" (or) among multiple expressions for now
// XXX - could use some optimization ... later
CMS.debug("evaluating expressions: " + s);
Vector v = new Vector();
while (s.length() > 0) {
int orIndex = s.indexOf("||");
int andIndex = s.indexOf("&&");
// this is the last expression
if (orIndex == -1 && andIndex == -1) {
boolean passed = evaluateExpression(s.trim());
v.addElement(Boolean.valueOf(passed));
break;
// || first
} else if (andIndex == -1 || (orIndex != -1 && orIndex < andIndex)) {
String s1 = s.substring(0, orIndex);
boolean passed = evaluateExpression(s1.trim());
v.addElement(new Boolean(passed));
v.addElement("||");
s = s.substring(orIndex + 2);
// && first
} else {
String s1 = s.substring(0, andIndex);
boolean passed = evaluateExpression(s1.trim());
v.addElement(new Boolean(passed));
v.addElement("&&");
s = s.substring(andIndex + 2);
}
}
if (v.size() == 1) {
Boolean bool = (Boolean) v.remove(0);
return bool.booleanValue();
}
boolean left = false;
String op = "";
boolean right = false;
while (v.size() > 0) {
if (op.equals(""))
left = ((Boolean) v.remove(0)).booleanValue();
op = (String) v.remove(0);
right = ((Boolean) v.remove(0)).booleanValue();
left = evaluateExp(left, op, right);
}
return left;
}
/**
* Resolves the given expression.
*/
private boolean evaluateExpression(String expression) {
// XXX - just recognize "=" for now!!
int i = expression.indexOf("=");
String type = expression.substring(0, i);
String value = expression.substring(i + 1);
IAccessEvaluator evaluator = (IAccessEvaluator) mEvaluators.get(type);
if (evaluator == null) {
String errMsg = "evaluator for type " + type + "not found";
log(ILogger.LL_FAILURE, CMS.getLogMessage("AUTHZ_EVALUATOR_NOT_FOUND", type));
return false;
}
return evaluator.evaluate(type, "=", value);
}
/*******************************************************
* with authToken
*******************************************************/
/**
* Checks if the permission is granted or denied with id from authtoken
* gotten from authentication that precedes authorization. If the code is
* marked as privileged, this methods will simply
* return.
*
* note that if a resource does not exist in the aclResources
* entry, but a higher level node exist, it will still be
* evaluated. The highest level node's acl determines the
* permission. If the higher level node doesn't contain any acl
* information, then it's passed down to the lower node. If
* a node has no aci in its resourceACLs, then it's considered
* passed.
*
* example: certServer.common.users, if failed permission check for
* "certServer", then it's considered failed, and there is no need to
* continue the check. If passed permission check for "certServer",
* then it's considered passed, and no need to continue the
* check. If certServer contains no aci then "certServer.common" will be
* checked for permission instead. If down to the leaf level,
* the node still contains no aci, then it's considered passed.
* If at the leaf level, no such resource exist, or no acis, it's
* considered passed.
*
* If there are multiple aci's for a resource, ALL aci's will be
* checked, and only if all passed permission checks, will the
* eventual access be granted.
* @param authToken authentication token gotten from authentication
* @param name resource name
* @param perm permission requested
* @exception EACLsException access permission denied
*/
public synchronized void checkPermission(IAuthToken authToken, String name,
String perm)
throws EACLsException {
Vector nodev = getNodes(name);
Enumeration nodes = nodev.elements();
String order = getOrder();
Enumeration entries = null;
if (order.equals("deny"))
entries = getDenyEntries(nodes, perm);
else
entries = getAllowEntries(nodes, perm);
boolean permitted = false;
while (entries.hasMoreElements()) {
ACLEntry entry = (ACLEntry) entries.nextElement();
CMS.debug("checkACLS(): ACLEntry expressions= " +
entry.getAttributeExpressions());
if (evaluateExpressions(authToken, entry.getAttributeExpressions())) {
log(ILogger.LL_SECURITY,
" checkACLs(): permission denied");
throw new EACLsException(CMS.getUserMessage("CMS_ACL_PERMISSION_DENIED"));
}
}
nodes = nodev.elements();
if (order.equals("deny"))
entries = getAllowEntries(nodes, perm);
else
entries = getDenyEntries(nodes, perm);
while (entries.hasMoreElements()) {
ACLEntry entry = (ACLEntry) entries.nextElement();
CMS.debug("checkACLS(): ACLEntry expressions= " +
entry.getAttributeExpressions());
if (evaluateExpressions(authToken, entry.getAttributeExpressions())) {
permitted = true;
}
}
nodev = null;
if (permitted) {
String infoMsg = "checkPermission(): permission granted for the resource " +
name + " on operation " + perm;
log(ILogger.LL_INFO, infoMsg);
return;
} else {
Object[] params = new Object[2];
params[0] = name;
params[1] = perm;
String errMsg = "checkPermission(): permission denied for the resource " +
name + " on operation " + perm;
log(ILogger.LL_SECURITY,
CMS.getLogMessage("AUTHZ_EVALUATOR_ACCESS_DENIED", name, perm));
throw new EACLsException(CMS.getUserMessage("CMS_ACL_NO_PERMISSION",
(String[]) params));
}
}
protected Enumeration getAllowEntries(Enumeration nodes, String operation) {
String name = "";
ACL acl = null;
Enumeration e = null;
Vector v = new Vector();
while (nodes.hasMoreElements()) {
name = (String) nodes.nextElement();
acl = (ACL) mACLs.get(name);
if (acl == null)
continue;
e = acl.entries();
while (e.hasMoreElements()) {
ACLEntry entry = (ACLEntry) e.nextElement();
if (!entry.isNegative() &&
entry.containPermission(operation)) {
v.addElement(entry);
}
}
}
return v.elements();
}
protected Enumeration getDenyEntries(Enumeration nodes, String operation) {
String name = "";
ACL acl = null;
Enumeration e = null;
Vector v = new Vector();
while (nodes.hasMoreElements()) {
name = (String) nodes.nextElement();
acl = (ACL) mACLs.get(name);
if (acl == null)
continue;
e = acl.entries();
while (e.hasMoreElements()) {
ACLEntry entry = (ACLEntry) e.nextElement();
if (entry.isNegative() &&
entry.containPermission(operation)) {
v.addElement(entry);
}
}
}
return v.elements();
}
/**
* Resolves the given expressions.
* expression || expression || ...
* example:
* group="Administrators" || group="Operators"
*/
private boolean evaluateExpressions(IAuthToken authToken, String s) {
// XXX - just handle "||" (or) among multiple expressions for now
// XXX - could use some optimization ... later
CMS.debug("evaluating expressions: " + s);
Vector v = new Vector();
while (s.length() > 0) {
int orIndex = s.indexOf("||");
int andIndex = s.indexOf("&&");
// this is the last expression
if (orIndex == -1 && andIndex == -1) {
boolean passed = evaluateExpression(authToken, s.trim());
CMS.debug("evaluated expression: " + s.trim() + " to be " + passed);
v.addElement(Boolean.valueOf(passed));
break;
// || first
} else if (andIndex == -1 || (orIndex != -1 && orIndex < andIndex)) {
String s1 = s.substring(0, orIndex);
boolean passed = evaluateExpression(authToken, s1.trim());
CMS.debug("evaluated expression: " + s1.trim() + " to be " + passed);
v.addElement(new Boolean(passed));
v.addElement("||");
s = s.substring(orIndex + 2);
// && first
} else {
String s1 = s.substring(0, andIndex);
boolean passed = evaluateExpression(authToken, s1.trim());
CMS.debug("evaluated expression: " + s1.trim() + " to be " + passed);
v.addElement(new Boolean(passed));
v.addElement("&&");
s = s.substring(andIndex + 2);
}
}
if (v.size() == 0) {
return false;
}
if (v.size() == 1) {
Boolean bool = (Boolean) v.remove(0);
return bool.booleanValue();
}
boolean left = false;
String op = "";
boolean right = false;
while (v.size() > 0) {
if (op.equals(""))
left = ((Boolean) v.remove(0)).booleanValue();
op = (String) v.remove(0);
right = ((Boolean) v.remove(0)).booleanValue();
left = evaluateExp(left, op, right);
}
return left;
}
public Vector getNodes(String resourceID) {
Enumeration parents = getTargetNames();
Vector v = new Vector();
if (resourceID != null && !resourceID.equals("")) {
v.addElement(resourceID);
} else {
return v;
}
int index = resourceID.lastIndexOf(".");
String name = resourceID;
while (index != -1) {
name = name.substring(0, index);
v.addElement(name);
index = name.lastIndexOf(".");
}
return v;
}
/**
* Resolves the given expression.
*/
private boolean evaluateExpression(IAuthToken authToken, String expression) {
String op = getOp(expression);
String type = "";
String value = "";
if (!op.equals("")) {
int len = op.length();
int i = expression.indexOf(op);
type = expression.substring(0, i).trim();
value = expression.substring(i + len).trim();
}
IAccessEvaluator evaluator = (IAccessEvaluator) mEvaluators.get(type);
if (evaluator == null) {
String errMsg = "evaluator for type " + type + "not found";
log(ILogger.LL_FAILURE, CMS.getLogMessage("AUTHZ_EVALUATOR_NOT_FOUND", type));
return false;
}
return evaluator.evaluate(authToken, type, op, value);
}
private String getOp(String exp) {
int i = exp.indexOf("!=");
if (i == -1) {
i = exp.indexOf("=");
if (i == -1) {
i = exp.indexOf(">");
if (i == -1) {
i = exp.indexOf("<");
if (i == -1) {
log(ILogger.LL_FAILURE, CMS.getLogMessage("AUTHZ_OP_NOT_SUPPORTED", exp));
} else {
return "<";
}
} else {
return ">";
}
} else {
return "=";
}
} else {
return "!=";
}
return "";
}
private boolean evaluateExp(boolean left, String op, boolean right) {
if (op.equals("||")) {
if (left == false && right == false)
return false;
return true;
} else if (op.equals("&&")) {
if (left == true && right == true)
return true;
return false;
}
return false;
}
/*******************************************************
* end identification differentiation
*******************************************************/
/**
* This one only updates the memory. Classes extend this class should
* also update to a permanent storage
*/
public void updateACLs(String id, String rights, String strACLs,
String desc) throws EACLsException {
ACL acl = (ACL) getACL(id);
String resourceACLs = id;
if (rights != null)
resourceACLs = id + ":" + rights + ":" + strACLs + ":" + desc;
// memory update
ACL ac = null;
try {
ac = (ACL) CMS.parseACL(resourceACLs);
} catch (EBaseException ex) {
throw new EACLsException(CMS.getUserMessage("CMS_ACL_PARSING_ERROR_0"));
}
mACLs.put(ac.getName(), ac);
}
/**
* gets an enumeration of resources
* @return an enumeration of resources contained in the ACL table
*/
public Enumeration aclResElements() {
return (mACLs.elements());
}
/**
* gets an enumeration of access evaluators
* @return an enumeraton of access evaluators
*/
public Enumeration aclEvaluatorElements() {
return (mEvaluators.elements());
}
/**
* gets the access evaluators
* @return handle to the access evaluators table
*/
public Hashtable getAccessEvaluators() {
return mEvaluators;
}
/**
* is this resource name unique
* @return true if unique; false otherwise
*/
public boolean isTypeUnique(String type) {
if (mACLs.containsKey((Object) type)) {
return false;
} else {
return true;
}
}
private void log(int level, String msg) {
if (mLogger == null)
return;
mLogger.log(ILogger.EV_SYSTEM, null, ILogger.S_AUTHORIZATION,
level, msg);
}
/*********************************
* abstract methods
**********************************/
/**
* update acls. called after memory upate is done to flush to permanent
* storage.
*
*/
protected abstract void flushResourceACLs() throws EACLsException;
/**
* an abstract class that enforces implementation of the
* authorize() method that will authorize an operation on a
* particular resource
*
* @param authToken the authToken associated with a user
* @param resource - the protected resource name
* @param operation - the protected resource operation name
* @exception EBaseException If an internal error occurred.
* @return authzToken
*/
public abstract AuthzToken authorize(IAuthToken authToken, String resource, String operation) throws EBaseException;
public String getOrder() {
IConfigStore mainConfig = CMS.getConfigStore();
String order = "";
try {
order = mainConfig.getString("authz.evaluateOrder", "");
if (order.startsWith("allow"))
return "allow";
else
return "deny";
} catch (Exception e) {
}
return "deny";
}
public boolean evaluateACLs(IAuthToken authToken, String exp) {
return evaluateExpressions(authToken, exp);
}
}
parseACL
*/
public void addACLs(String resACLs) throws EBaseException {
ACL acl = (ACL) CMS.parseACL(resACLs);
if (acl != null) {
mACLs.put(acl.getName(), acl);
} else {
log(ILogger.LL_FAILURE, "parseACL failed");
}
}
public void accessInit(String accessInfo) throws EBaseException {
addACLs(accessInfo);
}
public IACL getACL(String target) {
return (ACL) mACLs.get(target);
}
protected Enumeration getTargetNames() {
return mACLs.keys();
}
public Enumeration getACLs() {
return mACLs.elements();
}
/**
* Returns the configuration store used by this Authz mgr
*/
public IConfigStore getConfigStore() {
return mConfig;
}
public String[] getExtendedPluginInfo(Locale locale) {
String[] s = Utils.getStringArrayFromVector(mExtendedPluginInfo);
return s;
}
/**
* Returns a list of configuration parameter names.
* The list is passed to the configuration console so instances of
* this implementation can be configured through the console.
*
* @return String array of configuration parameter names.
*/
public String[] getConfigParams() {
return mConfigParams;
}
/**
* graceful shutdown
*/
public abstract void shutdown();
/**
* Registers new handler for the given attribute type
* in the expressions.
*/
public void registerEvaluator(String type, IAccessEvaluator evaluator) {
mEvaluators.put(type, evaluator);
log(ILogger.LL_INFO, type + " evaluator registered");
}
/*******************************************************
* with session context
*******************************************************/
/**
* Checks if the permission is granted or denied in
* the current execution context. If the code is
* marked as privileged, this methods will simply
* return.
* ACL
may contain one or more ACLEntry
.
* However, in case of multiple ACLEntry
, a subject must
* pass ALL of the ACLEntry
evaluation for permission
* to be granted
*