// --- 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.Enumeration;
import java.util.Hashtable;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.Vector;
import com.netscape.certsrv.acls.ACL;
import com.netscape.certsrv.acls.ACLEntry;
import com.netscape.certsrv.acls.EACLsException;
import com.netscape.certsrv.acls.IACL;
import com.netscape.certsrv.apps.CMS;
import com.netscape.certsrv.authentication.IAuthToken;
import com.netscape.certsrv.authorization.AuthzToken;
import com.netscape.certsrv.base.EBaseException;
import com.netscape.certsrv.base.IConfigStore;
import com.netscape.certsrv.evaluators.IAccessEvaluator;
import com.netscape.certsrv.logging.ILogger;
import com.netscape.cmsutil.util.Utils;
/**
* 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
*
*
*
* 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$, $Date$
* @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 = 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) {
throw new EACLsException(CMS.getUserMessage("CMS_ACL_CLASS_LOAD_FAIL",
evalClassPath));
}
if (evaluator != null) {
evaluator.init();
// store evaluator
registerEvaluator(type, evaluator);
} else {
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:
* :right-1[,right-n]:[allow,deny](right(s))=:
* Example: resTurnKnob:left,right:allow(left) group="lefties":door knobs for lefties
*
* @param resACLs same format as the resourceACLs attribute
* @throws EBaseException parsing error from 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 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.
*
* 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;
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 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
*
* 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 = 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 = 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