From 85ba0a0b7c41216411fe99c1e714415e53fa5629 Mon Sep 17 00:00:00 2001
From: Nemanja Lukic <nemanja.lukic@forgerock.com>
Date: Thu, 24 Nov 2011 14:35:30 +0000
Subject: [PATCH] Patch for OPENDJ-221

---
 opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java |  505 ++++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 396 insertions(+), 109 deletions(-)

diff --git a/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java b/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
index aab71ce..b83976f 100644
--- a/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
+++ b/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
@@ -24,11 +24,15 @@
  *
  *      Copyright 2008-2010 Sun Microsystems, Inc.
  *      Portions copyright 2011 ForgeRock AS.
+ *      Portions copyright 2011 profiq s.r.o.
  */
 package org.opends.server.plugins;
 
 
 
+import java.util.Iterator;
+import org.opends.server.types.operation.PreOperationAddOperation;
+import org.opends.server.types.operation.PreOperationModifyOperation;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.File;
@@ -50,6 +54,8 @@
 import org.opends.server.admin.std.server.PluginCfg;
 import org.opends.server.admin.std.meta.PluginCfgDefn;
 import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.std.meta.ReferentialIntegrityPluginCfgDefn
+  .CheckReferencesScopeCriteria;
 import org.opends.server.api.Backend;
 import org.opends.server.api.DirectoryThread;
 import org.opends.server.api.ServerShutdownListener;
@@ -153,6 +159,12 @@
   //when the plugin is in background processing mode.
   private BufferedWriter writer;
 
+  /* Specifies the mapping between the attribute type (specified in the
+   * attributeTypes list) and the filter which the plugin should use
+   * to verify the integrity of the value of the given attribute.
+   */
+  private LinkedHashMap<AttributeType, SearchFilter> attrFiltMap =
+    new LinkedHashMap<AttributeType, SearchFilter>();
 
 
   /**
@@ -163,64 +175,14 @@
          throws ConfigException
   {
     pluginCfg.addReferentialIntegrityChangeListener(this);
-    currentConfiguration = pluginCfg;
+    LinkedList<Message> unacceptableReasons = new LinkedList<Message>();
 
-    for (PluginType t : pluginTypes)
+    if (!isConfigurationAcceptable(pluginCfg, unacceptableReasons))
     {
-      switch (t)
-      {
-        case POST_OPERATION_DELETE:
-        case POST_OPERATION_MODIFY_DN:
-        case SUBORDINATE_MODIFY_DN:
-        case SUBORDINATE_DELETE:
-          // These are acceptable.
-          break;
-
-        default:
-          throw new
-             ConfigException(ERR_PLUGIN_REFERENT_INVALID_PLUGIN_TYPE.get(
-                                  t.toString()));
-      }
+      throw new ConfigException(unacceptableReasons.getFirst());
     }
 
-    Set<DN> cfgBaseDNs = pluginCfg.getBaseDN();
-    if ((cfgBaseDNs == null) || cfgBaseDNs.isEmpty())
-    {
-      cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet();
-    }
-    else
-    {
-      baseDNs.addAll(cfgBaseDNs);
-    }
-
-    // Iterate through all of the defined attribute types and ensure that they
-    // have acceptable syntaxes and that they are indexed for equality below all
-    // base DNs.
-    for (AttributeType type : pluginCfg.getAttributeType())
-    {
-      if (! isAttributeSyntaxValid(type))
-      {
-        throw new ConfigException(
-                       ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX.get(
-                            type.getNameOrOID(),
-                             type.getSyntax().getSyntaxName()));
-      }
-
-      for (DN baseDN : cfgBaseDNs)
-      {
-        Backend b = DirectoryServer.getBackend(baseDN);
-        if ((b != null) && (! b.isIndexed(type, IndexType.EQUALITY)))
-        {
-          throw new ConfigException(ERR_PLUGIN_REFERENT_ATTR_UNINDEXED.get(
-                                         pluginCfg.dn().toString(),
-                                         type.getNameOrOID(),
-                                         b.getBackendID()));
-        }
-      }
-
-      attributeTypes.add(type);
-    }
-
+    applyConfigurationChange(pluginCfg);
 
     // Set up log file. Note: it is not allowed to change once the plugin is
     // active.
@@ -261,10 +223,42 @@
       newAttributeTypes.add(type);
     }
 
+    // Load the attribute-filter mapping
+
+    LinkedHashMap<AttributeType, SearchFilter> newAttrFiltMap =
+      new LinkedHashMap<AttributeType, SearchFilter>();
+
+    for (String attrFilt : newConfiguration.getCheckReferencesFilterCriteria())
+    {
+      int sepInd = attrFilt.lastIndexOf(":");
+      String attr = attrFilt.substring(0, sepInd);
+      String filtStr = attrFilt.substring(sepInd + 1);
+
+      AttributeType attrType =
+        DirectoryServer.getAttributeType(attr.toLowerCase());
+
+      try
+      {
+        SearchFilter filter =
+          SearchFilter.createFilterFromString(filtStr);
+        newAttrFiltMap.put(attrType, filter);
+      }
+      catch (DirectoryException de)
+      {
+        /* This should never happen because the filter has already
+         * been verified.
+         */
+        logError(de.getMessageObject());
+      }
+    }
+
     //User is not allowed to change the logfile name, append a message that the
     //server needs restarting for change to take effect.
+    // The first time the plugin is initialised the 'logFileName' is
+    // not initialised, so in order to verify if it is equal to the new
+    // log file name, we have to make sure the variable is not null.
     String newLogFileName=newConfiguration.getLogFile();
-    if(!logFileName.equals(newLogFileName))
+    if(logFileName != null && !logFileName.equals(newLogFileName))
     {
       adminActionRequired=true;
       messages.add(
@@ -275,6 +269,7 @@
     //Switch to the new lists.
     baseDNs = newConfiguredBaseDNs;
     attributeTypes = newAttributeTypes;
+    attrFiltMap = newAttrFiltMap;
 
     //If the plugin is enabled and the interval has changed, process that
     //change. The change might start or stop the background processing thread.
@@ -294,9 +289,109 @@
   public boolean isConfigurationAcceptable(PluginCfg configuration,
                                            List<Message> unacceptableReasons)
   {
-    ReferentialIntegrityPluginCfg cfg =
+    boolean isAcceptable = true;
+    ReferentialIntegrityPluginCfg pluginCfg =
          (ReferentialIntegrityPluginCfg) configuration;
-    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
+
+    for (PluginCfgDefn.PluginType t : pluginCfg.getPluginType())
+    {
+      switch (t)
+      {
+        case POSTOPERATIONDELETE:
+        case POSTOPERATIONMODIFYDN:
+        case SUBORDINATEMODIFYDN:
+        case SUBORDINATEDELETE:
+        case PREOPERATIONMODIFY:
+        case PREOPERATIONADD:
+          // These are acceptable.
+          break;
+
+        default:
+          isAcceptable = false;
+          unacceptableReasons.add(ERR_PLUGIN_REFERENT_INVALID_PLUGIN_TYPE.get(
+                                  t.toString()));
+      }
+    }
+
+    Set<DN> cfgBaseDNs = pluginCfg.getBaseDN();
+    if ((cfgBaseDNs == null) || cfgBaseDNs.isEmpty())
+    {
+      cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet();
+    }
+
+    // Iterate through all of the defined attribute types and ensure that they
+    // have acceptable syntaxes and that they are indexed for equality below all
+    // base DNs.
+    Set<AttributeType> attributeTypes = pluginCfg.getAttributeType();
+    for (AttributeType type : attributeTypes)
+    {
+      if (! isAttributeSyntaxValid(type))
+      {
+        isAcceptable = false;
+        unacceptableReasons.add(
+                       ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX.get(
+                            type.getNameOrOID(),
+                             type.getSyntax().getSyntaxName()));
+      }
+
+      for (DN baseDN : cfgBaseDNs)
+      {
+        Backend b = DirectoryServer.getBackend(baseDN);
+        if ((b != null) && (!b.isIndexed(type, IndexType.EQUALITY)))
+        {
+          isAcceptable = false;
+          unacceptableReasons.add(ERR_PLUGIN_REFERENT_ATTR_UNINDEXED.get(
+                                         pluginCfg.dn().toString(),
+                                         type.getNameOrOID(),
+                                         b.getBackendID()));
+        }
+      }
+    }
+
+    /* Iterate through the attribute-filter mapping and verify that the
+     * map contains attributes listed in the attribute-type parameter
+     * and that the filter is valid.
+     */
+
+    for (String attrFilt : pluginCfg.getCheckReferencesFilterCriteria())
+    {
+      int sepInd = attrFilt.lastIndexOf(":");
+      String attr = attrFilt.substring(0, sepInd).trim();
+      String filtStr = attrFilt.substring(sepInd + 1).trim();
+
+      /* TODO: strip the ;options part? */
+
+      /* Get the attribute type for the given attribute. The attribute
+       * type has to be present in the attributeType list.
+       */
+
+      AttributeType attrType =
+        DirectoryServer.getAttributeType(attr.toLowerCase());
+
+      if (attrType == null || !attributeTypes.contains(attrType))
+      {
+        isAcceptable = false;
+        unacceptableReasons.add(
+          ERR_PLUGIN_REFERENT_ATTR_NOT_LISTED.get(attr));
+      }
+
+      /* Verify the filter.
+       */
+
+      try
+      {
+        SearchFilter.createFilterFromString(filtStr);
+      }
+      catch (DirectoryException de)
+      {
+        isAcceptable = false;
+        unacceptableReasons.add(
+          ERR_PLUGIN_REFERENT_BAD_FILTER.get(filtStr, de.getMessage()));
+      }
+
+    }
+
+    return isAcceptable;
   }
 
 
@@ -307,61 +402,10 @@
           ReferentialIntegrityPluginCfg configuration,
           List<Message> unacceptableReasons)
   {
-    boolean configAcceptable = true;
-    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
-    {
-      switch (pluginType)
-      {
-        case POSTOPERATIONDELETE:
-        case POSTOPERATIONMODIFYDN:
-        case SUBORDINATEMODIFYDN:
-        case SUBORDINATEDELETE:
-          // These are acceptable.
-          break;
-        default:
-          unacceptableReasons.add(ERR_PLUGIN_REFERENT_INVALID_PLUGIN_TYPE.
-                                  get(pluginType.toString()));
-          configAcceptable = false;
-      }
-    }
-
-    // Iterate through the set of base DNs that we will check and ensure that
-    // the corresponding backend is indexed appropriately.
-    Set<DN> cfgBaseDNs = configuration.getBaseDN();
-    if ((cfgBaseDNs == null) || cfgBaseDNs.isEmpty())
-    {
-      cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet();
-    }
-
-    //Iterate through attributes and check that each has a valid syntax
-    for (AttributeType type : configuration.getAttributeType())
-    {
-      if (!isAttributeSyntaxValid(type))
-      {
-        unacceptableReasons.add(
-             ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX.get(
-                  type.getNameOrOID(), type.getSyntax().getSyntaxName()));
-        configAcceptable = false;
-      }
-
-      for (DN baseDN : cfgBaseDNs)
-      {
-        Backend b = DirectoryServer.getBackend(baseDN);
-        if ((b != null) && (! b.isIndexed(type, IndexType.EQUALITY)))
-        {
-          unacceptableReasons.add(ERR_PLUGIN_REFERENT_ATTR_UNINDEXED.get(
-                                       configuration.dn().toString(),
-                                       type.getNameOrOID(), b.getBackendID()));
-          configAcceptable = false;
-        }
-      }
-    }
-
-    return configAcceptable;
+    return isConfigurationAcceptable(configuration, unacceptableReasons);
   }
 
 
-
   /**
    * {@inheritDoc}
    */
@@ -1057,4 +1101,247 @@
       }
     }
   }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+    public PluginResult.PreOperation doPreOperation(
+    PreOperationModifyOperation modifyOperation)
+  {
+    /* Skip the integrity checks if the enforcing is not enabled
+     */
+
+    if (!currentConfiguration.isCheckReferences())
+    {
+      return PluginResult.PreOperation.continueOperationProcessing();
+    }
+
+    final List<Modification> mods = modifyOperation.getModifications();
+    final Entry entry = modifyOperation.getModifiedEntry();
+
+    for (Modification mod : mods)
+    {
+      final ModificationType modType = mod.getModificationType();
+
+      /* Process only ADD and REPLACE modification types.
+       */
+      if ((modType != ModificationType.ADD)
+          && (modType != ModificationType.REPLACE))
+      {
+        break;
+      }
+
+      AttributeType attrType      = mod.getAttribute().getAttributeType();
+      Set<String> attrOptions     = mod.getAttribute().getOptions();
+      Attribute modifiedAttribute = entry.getExactAttribute(attrType,
+                                                            attrOptions);
+      if (modifiedAttribute != null)
+      {
+        PluginResult.PreOperation result =
+        isIntegrityMaintained(modifiedAttribute, entry.getDN());
+        if (result.getResultCode() != ResultCode.SUCCESS)
+        {
+          return result;
+        }
+      }
+    }
+
+    /* At this point, everything is fine.
+     */
+    return PluginResult.PreOperation.continueOperationProcessing();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public PluginResult.PreOperation doPreOperation(
+    PreOperationAddOperation addOperation)
+  {
+    /* Skip the integrity checks if the enforcing is not enabled.
+     */
+
+    if (!currentConfiguration.isCheckReferences())
+    {
+      return PluginResult.PreOperation.continueOperationProcessing();
+    }
+
+    final Entry entry = addOperation.getEntryToAdd();
+
+    for (AttributeType attrType : attributeTypes)
+    {
+      final List<Attribute> attrs = entry.getAttribute(attrType, false);
+
+      if (attrs != null)
+      {
+        PluginResult.PreOperation result =
+        isIntegrityMaintained(attrs, entry.getDN());
+        if (result.getResultCode() != ResultCode.SUCCESS)
+        {
+          return result;
+        }
+      }
+    }
+
+    /* If we reahed this point, everything is fine.
+     */
+    return PluginResult.PreOperation.continueOperationProcessing();
+  }
+
+  /**
+   * Verifies that the integrity of values is maintained.
+   * @param attrs   Attribute list which refers to another entry in the
+   *                directory.
+   * @param entryDN DN of the entry which contains the <CODE>attr</CODE>
+   *                attribute.
+   * @return        The SUCCESS if the integrity is maintained or
+   *                CONSTRAINT_VIOLATION oherwise
+   */
+  private PluginResult.PreOperation
+    isIntegrityMaintained(List<Attribute> attrs, DN entryDN)
+  {
+    PluginResult.PreOperation result = null;
+    for(Attribute attr : attrs)
+    {
+      result = isIntegrityMaintained(attr, entryDN);
+      if (result != PluginResult.PreOperation.continueOperationProcessing())
+      {
+        return result;
+      }
+    }
+
+    return PluginResult.PreOperation.continueOperationProcessing();
+  }
+
+  /**
+   * Verifies that the integrity of values is maintained.
+   * @param attr    Attribute which refers to another entry in the
+   *                directory.
+   * @param entryDN DN of the entry which contains the <CODE>attr</CODE>
+   *                attribute.
+   * @return        The SUCCESS if the integrity is maintained or
+   *                CONSTRAINT_VIOLATION oherwise
+   */
+  private PluginResult.PreOperation isIntegrityMaintained(Attribute attr,
+                                                          DN entryDN)
+  {
+    /* Verify that the entry belongs to one of the configured naming
+     * contexts.
+     */
+
+    boolean isLocal = false;
+
+    if (baseDNs.isEmpty())
+    {
+      baseDNs = DirectoryServer.getPublicNamingContexts().keySet();
+    }
+
+    for (DN baseDN : baseDNs)
+    {
+      if (entryDN.matchesBaseAndScope(baseDN, SearchScope.SUBORDINATE_SUBTREE))
+      {
+        isLocal = true;
+        break;
+      }
+    }
+
+    /* If the entry does not belong to any of the configured naming
+     * contexts continue further operation processing without checking
+     * the integrity.
+     */
+
+    if (!isLocal)
+    {
+      return PluginResult.PreOperation.continueOperationProcessing();
+    }
+
+    /* Iterate over the list of attributes */
+
+    Iterator<AttributeValue> attrValIt = attr.iterator();
+
+    try
+    {
+      while (attrValIt.hasNext())
+      {
+        AttributeValue attrVal = attrValIt.next();
+        DN valueEntryDN = null;
+        Entry valueEntry = null;
+
+        valueEntryDN = DN.decode(attrVal.getNormalizedValue());
+
+        if (currentConfiguration.getCheckReferencesScopeCriteria()
+          == CheckReferencesScopeCriteria.NAMING_CONTEXT)
+        {
+          boolean matches = false;
+
+          for (DN baseDN : baseDNs)
+          {
+            if (entryDN.matchesBaseAndScope(baseDN,
+              SearchScope.SUBORDINATE_SUBTREE))
+            {
+              matches = true;
+              break;
+            }
+          }
+
+          if (!matches)
+          {
+            return PluginResult.PreOperation.stopProcessing(
+                  ResultCode.CONSTRAINT_VIOLATION,
+                  ERR_PLUGIN_REFERENT_NAMINGCONTEXT_MISMATCH.get(
+                    valueEntryDN.toString(),
+                    attr.getName(),
+                    entryDN.toString()
+                  )
+                );
+          }
+
+          valueEntry = DirectoryServer.getEntry(valueEntryDN);
+        }
+        else
+        {
+          valueEntry = DirectoryServer.getEntry(valueEntryDN);
+        }
+
+        /* Verify that the value entry exists in the backend.
+         */
+
+        if (valueEntry == null)
+        {
+          return PluginResult.PreOperation.stopProcessing(
+            ResultCode.CONSTRAINT_VIOLATION,
+            ERR_PLUGIN_REFERENT_ENTRY_MISSING.get(
+            valueEntryDN.toString(),
+            attr.getName(),
+            entryDN.toString()
+            ));
+        }
+
+        /* Verify that the value entry conforms to the filter.
+         */
+
+        SearchFilter filter = attrFiltMap.get(attr.getAttributeType());
+        if (filter != null && !filter.matchesEntry(valueEntry))
+        {
+          return PluginResult.PreOperation.stopProcessing(
+            ResultCode.CONSTRAINT_VIOLATION,
+            ERR_PLUGIN_REFERENT_FILTER_MISMATCH.get(
+              valueEntry.getDN().toString(),
+              attr.getName(),
+              entryDN.toString(),
+              filter.toString())
+            );
+        }
+      }
+    }
+    catch (DirectoryException de)
+    {
+      return PluginResult.PreOperation.stopProcessing(
+        ResultCode.OTHER,
+        ERR_PLUGIN_REFERENT_EXCEPTION.get(de.getLocalizedMessage()));
+    }
+
+    return PluginResult.PreOperation.continueOperationProcessing();
+  }
 }

--
Gitblit v1.10.0