opends/src/server/org/opends/server/authorization/dseecompat/AciHandler.java
@@ -29,21 +29,22 @@ import org.opends.server.admin.std.server.DseeCompatAccessControlHandlerCfg; import org.opends.server.api.AccessControlHandler; import static org.opends.server.messages.AciMessages.*; import static org.opends.server.authorization.dseecompat.Aci.*; import static org.opends.server.config.ConfigConstants.ATTR_AUTHZ_GLOBAL_ACI; import org.opends.server.core.*; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.debug.DebugLogger.getTracer; import org.opends.server.loggers.debug.DebugTracer; import static org.opends.server.messages.AciMessages.*; import static org.opends.server.messages.MessageHandler.getMessage; import org.opends.server.types.*; import static org.opends.server.util.StaticUtils.toLowerCase; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import static org.opends.server.config.ConfigConstants.*; import org.opends.server.protocols.internal.InternalSearchOperation; import org.opends.server.protocols.internal.InternalClientConnection; import static org.opends.server.schema.SchemaConstants.*; import org.opends.server.protocols.internal.InternalSearchOperation; import static org.opends.server.schema.SchemaConstants.SYNTAX_DN_OID; import org.opends.server.types.*; import static org.opends.server.util.ServerConstants.OID_GET_EFFECTIVE_RIGHTS; import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; import static org.opends.server.util.StaticUtils.toLowerCase; import java.util.*; import java.util.concurrent.locks.Lock; @@ -52,19 +53,26 @@ * The AciHandler class performs the main processing for the * dseecompat package. */ public class AciHandler extends AccessControlHandler { public class AciHandler extends AccessControlHandler { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /* * The list that holds that ACIs keyed by the DN of the entry * holding the ACI. * holding the ACI. */ private AciList aciList; /* * The listener that handles ACI changes caused by LDAP operations, ACI * decode failure alert logging and backend initialization ACI list * adjustment. */ private AciListenerManager aciListenerMgr; /** * Attribute type corresponding to "aci" attribute. */ @@ -95,21 +103,15 @@ */ public static String ALL_ATTRS_MATCHED = "allAttrsMatched"; /** * This constructor instantiates the ACI handler class that performs the * main processing for the dseecompat ACI package. It does the following * initializations: * * * - Instantiates the ACI list cache. * * - Instantiates and registers the change notification listener that is * used to manage the ACI list cache after ACI modifications have been * performed. * * - Instantiates and registers the backend initialization listener that is * used to manage the ACI list cache when backends are * initialized/finalized. * - Instantiates thr AciListenerManager. * * - Processes all global attribute types found in the configuration entry * and adds them to the ACI list cache. @@ -124,11 +126,9 @@ */ public AciHandler(DseeCompatAccessControlHandlerCfg configuration) throws InitializationException { aciList = new AciList(configuration.dn()); AciListenerManager aciListenerMgr = new AciListenerManager(aciList); DirectoryServer.registerChangeNotificationListener(aciListenerMgr); DirectoryServer.registerBackendInitializationListener(aciListenerMgr); DN configurationDN=configuration.dn(); aciList = new AciList(configurationDN); aciListenerMgr = new AciListenerManager(aciList, configurationDN); if((aciType = DirectoryServer.getAttributeType("aci")) == null) aciType = DirectoryServer.getDefaultAttributeType("aci"); if((globalAciType = @@ -143,7 +143,9 @@ * Process all global ACI attribute types found in the configuration * entry and adds them to that ACI list cache. It also logs messages about * the number of ACI attribute types added to the cache. This method is * called once at startup. * called once at startup. It also will put the server into lockdown * mode if needed. * * @param configuration The config handler containing the ACI * configuration information. * @throws InitializationException If there is an error reading @@ -153,6 +155,7 @@ DseeCompatAccessControlHandlerCfg configuration) throws InitializationException { int msgID; LinkedList<String>failedACIMsgs=new LinkedList<String>(); SortedSet<String> globalAci = configuration.getGlobalACI(); try { if (globalAci != null) { @@ -167,7 +170,9 @@ attVals); Entry e = new Entry(configuration.dn(), null, null, null); e.addAttribute(attr, new ArrayList<AttributeValue>()); int aciCount = aciList.addAci(e, false, true); int aciCount = aciList.addAci(e, false, true, failedACIMsgs); if(!failedACIMsgs.isEmpty()) aciListenerMgr.logMsgsSetLockDownMode(failedACIMsgs); msgID = MSGID_ACI_ADD_LIST_GLOBAL_ACIS; String message = getMessage(msgID, Integer.toString(aciCount)); logError(ErrorLogCategory.ACCESS_CONTROL, @@ -194,7 +199,9 @@ /** * Process all ACIs under the "cn=config" naming context and adds them to * the ACI list cache. It also logs messages about the number of ACIs added * to the cache. This method is called once at startup. * to the cache. This method is called once at startup. It will put the * server in lockdown mode if needed. * * @throws InitializationException If there is an error searching for * the ACIs in the naming context. */ @@ -204,6 +211,7 @@ DN configDN=DN.decode("cn=config"); LinkedHashSet<String> attrs = new LinkedHashSet<String>(1); attrs.add("aci"); LinkedList<String>failedACIMsgs=new LinkedList<String>(); InternalClientConnection conn = InternalClientConnection.getRootConnection(); InternalSearchOperation op = conn.processSearch(configDN, @@ -216,7 +224,10 @@ logError(ErrorLogCategory.ACCESS_CONTROL, ErrorLogSeverity.INFORMATIONAL, message, msgID); } else { int validAcis = aciList.addAci(op.getSearchEntries()); int validAcis = aciList.addAci(op.getSearchEntries(), failedACIMsgs); if(!failedACIMsgs.isEmpty()) aciListenerMgr.logMsgsSetLockDownMode(failedACIMsgs); int msgID = MSGID_ACI_ADD_LIST_ACIS; String message = getMessage(msgID, Integer.toString(validAcis), String.valueOf(configDN)); @@ -231,6 +242,7 @@ } } /** * Checks to see if a LDAP modification is allowed access. * @@ -372,7 +384,7 @@ * @return True if access is allowed. */ private boolean testApplicableLists(AciEvalContext evalCtx) { EnumEvalResult res=EnumEvalResult.FALSE; EnumEvalResult res; evalCtx.setEvalReason(EnumEvalReason.NO_REASON); LinkedList<Aci>denys=evalCtx.getDenyList(); LinkedList<Aci>allows=evalCtx.getAllowList(); opends/src/server/org/opends/server/authorization/dseecompat/AciList.java
@@ -27,16 +27,16 @@ package org.opends.server.authorization.dseecompat; import static org.opends.server.messages.AciMessages.*; import org.opends.server.api.Backend; import static org.opends.server.authorization.dseecompat.AciHandler.aciType; import static org.opends.server.authorization.dseecompat.AciHandler.globalAciType; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.messages.AciMessages.MSGID_ACI_ADD_LIST_FAILED_DECODE; import static org.opends.server.messages.MessageHandler.getMessage; import org.opends.server.types.*; import java.util.*; import static org.opends.server.authorization.dseecompat.AciHandler.*; import org.opends.server.types.*; import org.opends.server.api.Backend; /** * The AciList class performs caching of the ACI attribute values * using the entry DN as the key. @@ -135,9 +135,12 @@ * to check for global ACIs since they are processe by the AciHandler at * startup using the addACi single entry method. * @param entries The set of entries containing the "aci" attribute values. * @param failedACIMsgs List that will hold error messages from ACI decode * exceptions. * @return The number of valid ACI attribute values added to the ACI list. */ public synchronized int addAci(List<? extends Entry> entries) public synchronized int addAci(List<? extends Entry> entries, LinkedList<String> failedACIMsgs) { // Copy the ACI list. LinkedHashMap<DN,List<Aci>> aciCopy = copyList(); @@ -147,7 +150,8 @@ DN dn=entry.getDN(); List<Attribute> attributeList = entry.getOperationalAttribute(AciHandler.aciType); validAcis += addAciAttributeList(aciCopy, dn, attributeList); validAcis += addAciAttributeList(aciCopy, dn, configDN, attributeList, failedACIMsgs); } // Replace the ACI list with the copy. @@ -162,10 +166,13 @@ * @param hasAci True if the "aci" attribute type was seen in the entry. * @param hasGlobalAci True if the "ds-cfg-global-aci" attribute type was * seen in the entry. * @param failedACIMsgs List that will hold error messages from ACI decode * exceptions. * @return The number of valid ACI attribute values added to the ACI list. */ public synchronized int addAci(Entry entry, boolean hasAci, boolean hasGlobalAci) { boolean hasGlobalAci, LinkedList<String> failedACIMsgs) { int validAcis=0; // Copy the ACI list. @@ -175,12 +182,14 @@ //attributes are skipped. if(hasGlobalAci && entry.getDN().equals(configDN)) { List<Attribute> attributeList = entry.getAttribute(globalAciType); validAcis = addAciAttributeList(aciCopy, DN.nullDN(), attributeList); validAcis = addAciAttributeList(aciCopy, DN.nullDN(), configDN, attributeList, failedACIMsgs); } if(hasAci) { List<Attribute> attributeList = entry.getAttribute(aciType); validAcis += addAciAttributeList(aciCopy, entry.getDN(), attributeList); validAcis += addAciAttributeList(aciCopy, entry.getDN(), configDN, attributeList, failedACIMsgs); } // Replace the ACI list with the copy. aciList = aciCopy; @@ -194,13 +203,18 @@ * returned of the number of valid ACIs added. * @param aciList The ACI list to which the ACI is to be added. * @param dn The DN to use as the key in the ACI list. * @param configDN The DN of the configuration entry used to configure the * ACI handler. Used if a global ACI has an decode exception. * @param attributeList List of attributes containing the ACI attribute * values. * @param failedACIMsgs List that will hold error messages from ACI decode * exceptions. * @return The number of valid attribute values added to the ACI list. */ private static int addAciAttributeList( LinkedHashMap<DN,List<Aci>> aciList, DN dn, List<Attribute> attributeList) { private static int addAciAttributeList(LinkedHashMap<DN,List<Aci>> aciList, DN dn, DN configDN, List<Attribute> attributeList, LinkedList<String> failedACIMsgs) { if (attributeList == null) { return 0; @@ -215,19 +229,16 @@ acis.add(aci); validAcis++; } catch (AciException ex) { /* An illegal ACI might have been loaded * during import and is failing at ACI handler * initialization time. Log a message and continue * processing. ACIs added via LDAP add have their * syntax checked before adding and should never * hit this code. */ int msgID = MSGID_ACI_ADD_LIST_FAILED_DECODE; String message = getMessage(msgID, ex.getMessage()); logError(ErrorLogCategory.ACCESS_CONTROL, ErrorLogSeverity.INFORMATIONAL, message, msgID); DN msgDN=dn; if(dn == DN.nullDN()) { msgDN=configDN; } String t=value.getValue().toString(); String message = getMessage(msgID, value.getValue().toString(), String.valueOf(msgDN), ex.getMessage()); failedACIMsgs.add(message); } } } @@ -255,12 +266,14 @@ // Copy the ACI list. LinkedHashMap<DN,List<Aci>> aciCopy = copyList(); LinkedList<String>failedACIMsgs=new LinkedList<String>(); //Process "aci" attribute types. if(hasAci) { aciCopy.remove(oldEntry.getDN()); List<Attribute> attributeList = newEntry.getOperationalAttribute(aciType); addAciAttributeList(aciCopy,newEntry.getDN(),attributeList); addAciAttributeList(aciCopy,newEntry.getDN(), configDN, attributeList, failedACIMsgs); } //Process global "ds-cfg-global-aci" attribute type. The oldentry //DN is checked to verify it is equal to the config DN. If not those @@ -269,7 +282,8 @@ aciCopy.remove(DN.nullDN()); List<Attribute> attributeList = newEntry.getAttribute(globalAciType); addAciAttributeList(aciCopy, DN.nullDN(), attributeList); addAciAttributeList(aciCopy, DN.nullDN(), configDN, attributeList, failedACIMsgs); } // Replace the ACI list with the copy. aciList = aciCopy; @@ -376,8 +390,9 @@ //ACI with a new DN is being made. Log a message if it does and //keep going. int msgID = MSGID_ACI_ADD_LIST_FAILED_DECODE; String message = getMessage(msgID, ex.getMessage()); String message = getMessage(msgID, aci.toString(), String.valueOf(relocateDN), ex.getMessage()); logError(ErrorLogCategory.ACCESS_CONTROL, ErrorLogSeverity.INFORMATIONAL, message, msgID); opends/src/server/org/opends/server/authorization/dseecompat/AciListenerManager.java
@@ -30,6 +30,7 @@ import org.opends.server.api.ChangeNotificationListener; import org.opends.server.api.BackendInitializationListener; import org.opends.server.api.Backend; import org.opends.server.api.AlertGenerator; import org.opends.server.types.operation.PostResponseAddOperation; import org.opends.server.types.operation.PostResponseDeleteOperation; import org.opends.server.types.operation.PostResponseModifyOperation; @@ -42,8 +43,12 @@ import org.opends.server.types.*; import static org.opends.server.messages.AciMessages.*; import static org.opends.server.messages.MessageHandler.getMessage; import org.opends.server.core.DirectoryServer; import static org.opends.server.util.ServerConstants.*; import java.util.LinkedHashSet; import java.util.List; import java.util.LinkedList; import java.util.LinkedHashMap; /** * The AciListenerManager updates an ACI list after each @@ -51,13 +56,31 @@ * and finalized. */ public class AciListenerManager implements ChangeNotificationListener, BackendInitializationListener { implements ChangeNotificationListener, BackendInitializationListener, AlertGenerator { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /** * The fully-qualified name of this class. */ private static final String CLASS_NAME = "org.opends.server.authorization.dseecompat.AciListenerManager"; /* * The configuration DN. */ private DN configurationDN; /* * True if the server is in lockdown mode. */ private boolean inLockDownMode=false; /* * The AciList caches the ACIs. */ @@ -87,11 +110,23 @@ } /** * Save the list created by the AciHandler routine. * Save the list created by the AciHandler routine. Registers as an * Alert Generator that can send alerts when the server is being put * in lockdown mode. Registers as backend initialization listener that is * used to manage the ACI list cache when backends are * initialized/finalized. Registers as a change notification listener that * is used to manage the ACI list cache after ACI modifications have been * performed. * * @param aciList The list object created and loaded by the handler. * @param cfgDN The DN of the access control configuration entry. */ public AciListenerManager(AciList aciList) { public AciListenerManager(AciList aciList, DN cfgDN) { this.aciList=aciList; this.configurationDN=cfgDN; DirectoryServer.registerChangeNotificationListener(this); DirectoryServer.registerBackendInitializationListener(this); DirectoryServer.registerAlertGenerator(this); } /** @@ -118,10 +153,13 @@ public void handleAddOperation(PostResponseAddOperation addOperation, Entry entry) { boolean hasAci, hasGlobalAci=false; //Ignore this list, the ACI syntax has already passed and it should be //empty. LinkedList<String>failedACIMsgs=new LinkedList<String>(); //This entry might have both global and aci attribute types. if((hasAci=entry.hasOperationalAttribute(AciHandler.aciType)) || (hasGlobalAci=entry.hasAttribute(AciHandler.globalAciType))) aciList.addAci(entry, hasAci, hasGlobalAci); aciList.addAci(entry, hasAci, hasGlobalAci, failedACIMsgs); } /** @@ -176,18 +214,18 @@ public void performBackendInitializationProcessing(Backend backend) { InternalClientConnection conn = InternalClientConnection.getRootConnection(); LinkedList<String>failedACIMsgs=new LinkedList<String>(); for (DN baseDN : backend.getBaseDNs()) { try { if (! backend.entryExists(baseDN)) { continue; } } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } //TODO log message continue; if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } continue; } InternalSearchOperation internalSearch = new InternalSearchOperation( @@ -200,12 +238,11 @@ try { backend.search(internalSearch); } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } //TODO log message continue; if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } continue; } if(internalSearch.getSearchEntries().isEmpty()) { int msgID = MSGID_ACI_ADD_LIST_NO_ACIS; @@ -214,7 +251,9 @@ ErrorLogSeverity.INFORMATIONAL, message, msgID); } else { int validAcis = aciList.addAci( internalSearch.getSearchEntries()); internalSearch.getSearchEntries(), failedACIMsgs); if(!failedACIMsgs.isEmpty()) logMsgsSetLockDownMode(failedACIMsgs); int msgID = MSGID_ACI_ADD_LIST_ACIS; String message = getMessage(msgID, Integer.toString(validAcis), String.valueOf(baseDN)); @@ -232,4 +271,92 @@ public void performBackendFinalizationProcessing(Backend backend) { aciList.removeAci(backend); } /** * Retrieves the fully-qualified name of the Java class for this alert * generator implementation. * * @return The fully-qualified name of the Java class for this alert * generator implementation. */ public String getClassName() { return CLASS_NAME; } /** * Retrieves the DN of the configuration entry used to configure the * handler. * * @return The DN of the configuration entry containing the Access Control * configuration information. */ public DN getComponentEntryDN() { return this.configurationDN; } /** * Retrieves information about the set of alerts that this generator may * produce. The map returned should be between the notification type for a * particular notification and the human-readable description for that * notification. This alert generator must not generate any alerts with * types that are not contained in this list. * * @return Information about the set of alerts that this generator may * produce. */ public LinkedHashMap<String,String> getAlerts() { LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>(); alerts.put(ALERT_TYPE_ACCESS_CONTROL_PARSE_FAILED, ALERT_DESCRIPTION_ACCESS_CONTROL_PARSE_FAILED); return alerts; } /** * Log the exception messages from the failed ACI decode and then put the * server in lockdown mode -- if needed. * * @param failedACIMsgs List of exception messages from failed ACI decodes. */ public void logMsgsSetLockDownMode(LinkedList<String> failedACIMsgs) { int msgID=MSGID_ACI_SERVER_DECODE_FAILED; for(String msg : failedACIMsgs) { String message=getMessage(msgID, msg); logError(ErrorLogCategory.ACCESS_CONTROL, ErrorLogSeverity.SEVERE_ERROR, message, msgID); } if(!inLockDownMode) setLockDownMode(); } /** * Send an MSGID_ACI_ENTER_LOCKDOWN_MODE alert notification and put the * server in lockdown mode. * */ private void setLockDownMode() { if(!inLockDownMode) { inLockDownMode=true; //Send ALERT_TYPE_ACCESS_CONTROL_PARSE_FAILED alert that //lockdown is about to be entered. int lockDownID=MSGID_ACI_ENTER_LOCKDOWN_MODE; String lockDownMsg=getMessage(lockDownID); DirectoryServer.sendAlertNotification(this, ALERT_TYPE_ACCESS_CONTROL_PARSE_FAILED, lockDownID, lockDownMsg ); //Enter lockdown mode. DirectoryServer.setLockdownMode(true); } } } opends/src/server/org/opends/server/messages/AciMessages.java
@@ -496,8 +496,9 @@ * The message ID for the ACI message that will be generated when * an ACI decode failed because of an syntax error. This message is usually * generated by an invalid ACI that was added during import which * fails the decode at server startup. This takes one * argument, which is the message string thrown by the AciException. * fails the decode at server startup. This takes three * argument, which are the ACI string, the DN of the entry containing the * ACI, and the message string thrown by the AciException. */ public static final int MSGID_ACI_ADD_LIST_FAILED_DECODE = CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 48; @@ -764,6 +765,25 @@ public static final int MSGID_ACI_SYNTAX_ROLEDN_NOT_SUPPORTED = CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 76; /** * The message ID for the message that will be used if there are ACI decode * failures at server initialization time or during a backend initialization. * This takes one argument, which is the error message from the ACI decode * exception. */ public static final int MSGID_ACI_SERVER_DECODE_FAILED = CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 77; /** * The message ID for the message that will be used if there are ACI decode * failures at server initialization time or during a backend initialization * causing the server is being put in lockdown mode. The takes no arguments. */ public static final int MSGID_ACI_ENTER_LOCKDOWN_MODE = CATEGORY_MASK_ACCESS_CONTROL | SEVERITY_MASK_SEVERE_WARNING | 78; /** * Associates a set of generic messages with the message IDs defined in * this class. @@ -1066,8 +1086,8 @@ "because of the following reason: %s"); registerMessage(MSGID_ACI_ADD_LIST_FAILED_DECODE, "An attempt to decode an Access Control Instruction (ACI)" + " failed because of the following reason: %s"); " \"%s\", located in the entry \"%s\", " + "because of the following reason: %s"); registerMessage(MSGID_ACI_ADD_LIST_NO_ACIS, "No Access Control Instruction (ACI) attribute types were" + @@ -1203,5 +1223,15 @@ "value \"%s\" is invalid because it contains" + " the roledn keyword, which is not supported, replace it with " + "the groupdn keyword"); registerMessage(MSGID_ACI_SERVER_DECODE_FAILED, "Failed to decode the Access Control " + "Instruction (ACI)%s"); registerMessage(MSGID_ACI_ENTER_LOCKDOWN_MODE, "The server is being put into lockdown mode because " + "invalid ACIs rules were detected either when the server " + "was started or during a backend initialization"); } } opends/src/server/org/opends/server/util/ServerConstants.java
@@ -1557,6 +1557,30 @@ /** * The description for the alert type that will be used for the alert * notification generated if the dseecompat access control subsystem failed * to parse one or more ACI rules when the server is first started or a * backend is being initialized. */ public static final String ALERT_DESCRIPTION_ACCESS_CONTROL_PARSE_FAILED = "This alert type will be used to notify administrators if the " + "dseecompat access control subsystem failed to correctly parse " + "one or more ACI rules when the server is first started."; /** * The alert type string that will be used for the alert notification * generated if the dseecompat access control subsystem failed to parse * one or more ACI rules when the server is first started or a backend * is being initialized. */ public static final String ALERT_TYPE_ACCESS_CONTROL_PARSE_FAILED = "org.opends.server.authentiation.dseecompat.ACIParseFailed"; /** * The name of the default password storage scheme that will be used for new * passwords. */