From 6a96c259696ed3934de4aa7c139a7e9e3aa839b1 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 03 Apr 2026 10:15:50 +0000
Subject: [PATCH] Fix ReferentialIntegrityPlugin silently bypassing check-references on modify operations (#600)

---
 opendj-server-legacy/src/test/java/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java |   57 ++++++++++++++++++++++++++++
 opendj-server-legacy/src/main/java/org/opends/server/plugins/ReferentialIntegrityPlugin.java         |   33 +++++++++++++---
 2 files changed, 84 insertions(+), 6 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/plugins/ReferentialIntegrityPlugin.java b/opendj-server-legacy/src/main/java/org/opends/server/plugins/ReferentialIntegrityPlugin.java
index c5b74a0..0413aa9 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/plugins/ReferentialIntegrityPlugin.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/plugins/ReferentialIntegrityPlugin.java
@@ -14,6 +14,7 @@
  * Copyright 2008-2010 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
  * Portions copyright 2011 profiq s.r.o.
+ * Portions copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.plugins;
 
@@ -991,10 +992,9 @@
     }
 
     final List<Modification> mods = modifyOperation.getModifications();
-    final Entry entry = modifyOperation.getModifiedEntry();
 
     /* Make sure the entry belongs to one of the configured naming contexts. */
-    DN entryDN = entry.getName();
+    DN entryDN = modifyOperation.getEntryDN();
     DN entryBaseDN = getEntryBaseDN(entryDN);
     if (entryBaseDN == null)
     {
@@ -1009,14 +1009,35 @@
       if (modType != ModificationType.ADD
           && modType != ModificationType.REPLACE)
       {
-        break;
+        continue;
       }
 
-      Attribute modifiedAttribute = entry.getAttribute(mod.getAttribute().getAttributeDescription());
-      if (modifiedAttribute != null)
+      Attribute modifiedAttribute = mod.getAttribute();
+      if (modifiedAttribute != null && !modifiedAttribute.isEmpty())
       {
+        // Only enforce referential integrity on attributes that this plugin is configured to manage.
+        final AttributeType modifiedAttrType = modifiedAttribute.getAttributeDescription().getAttributeType();
+        boolean isManagedAttributeType = false;
+        if (modifiedAttrType != null && attributeTypes != null)
+        {
+          for (AttributeType configuredType : attributeTypes)
+          {
+            if (modifiedAttrType.equals(configuredType)
+                || modifiedAttrType.isSubTypeOf(configuredType))
+            {
+              isManagedAttributeType = true;
+              break;
+            }
+          }
+        }
+
+        if (!isManagedAttributeType)
+        {
+          // Skip integrity checks for attributes not configured for this plugin.
+          continue;
+        }
         PluginResult.PreOperation result =
-        isIntegrityMaintained(modifiedAttribute, entryDN, entryBaseDN);
+            isIntegrityMaintained(modifiedAttribute, entryDN, entryBaseDN);
         if (result.getResultCode() != ResultCode.SUCCESS)
         {
           return result;
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java
index 531e27a..068e308 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java
@@ -14,6 +14,7 @@
  * Copyright 2008-2010 Sun Microsystems, Inc.
  * Portions copyright 2011 profiq s.r.o.
  * Portions Copyright 2014-2016 ForgeRock AS.
+ * Portions copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.plugins;
 
@@ -1829,4 +1830,60 @@
       "member", "uid=user.1,ou=people,ou=dept,o=test");
     assertEquals(modOperation.getResultCode(), ResultCode.SUCCESS);
   }
+
+  @Test
+  public void testEnforceIntegrityModifyGroupAddMissingUniqueMember() throws Exception
+  {
+    replaceAttrEntry(configDN, "ds-cfg-enabled", "false");
+    replaceAttrEntry(configDN, dsConfigPluginType,
+                               "postoperationdelete",
+                               "postoperationmodifydn",
+                               "subordinatemodifydn",
+                               "subordinatedelete",
+                               "preoperationadd",
+                               "preoperationmodify");
+    addAttrEntry(configDN, dsConfigBaseDN, "dc=example,dc=com");
+    replaceAttrEntry(configDN, dsConfigEnforceIntegrity, "true");
+    replaceAttrEntry(configDN, dsConfigAttrType, "uniquemember");
+    addAttrEntry(configDN, dsConfigAttrFiltMapping,
+                           "uniquemember:(objectclass=person)");
+    replaceAttrEntry(configDN, "ds-cfg-enabled", "true");
+
+    ModifyOperation modOperation = addAttrEntry(DN.valueOf(ugroup),
+      "uniquemember", "uid=user.100,ou=people,ou=dept,dc=example,dc=com");
+    assertEquals(modOperation.getResultCode(), ResultCode.CONSTRAINT_VIOLATION);
+  }
+
+  @Test
+  public void testEnforceIntegrityModifyGroupAddMissingUniqueMemberWithPriorDelete() throws Exception
+  {
+    // Configure the plugin in the same way as for the single-ADD test.
+    replaceAttrEntry(configDN, "ds-cfg-enabled", "false");
+    replaceAttrEntry(configDN, dsConfigPluginType,
+                               "postoperationdelete",
+                               "postoperationmodifydn",
+                               "subordinatemodifydn",
+                               "subordinatedelete",
+                               "preoperationadd",
+                               "preoperationmodify");
+    addAttrEntry(configDN, dsConfigBaseDN, "dc=example,dc=com");
+    replaceAttrEntry(configDN, dsConfigEnforceIntegrity, "true");
+    replaceAttrEntry(configDN, dsConfigAttrType, "uniquemember");
+    addAttrEntry(configDN, dsConfigAttrFiltMapping,
+                           "uniquemember:(objectclass=person)");
+    replaceAttrEntry(configDN, "ds-cfg-enabled", "true");
+
+    // Ensure 'description' exists on ugroup so the DELETE modification succeeds.
+    addAttrEntry(DN.valueOf(ugroup), "description", "test description");
+    // Build a modify request with a non-ADD/REPLACE modification first,
+    // followed by an ADD of a uniquemember referencing a missing DN.
+    final ModifyRequest modifyRequest = Requests.newModifyRequest(DN.valueOf(ugroup));
+    modifyRequest.addModification(DELETE, "description");
+    modifyRequest.addModification(ADD, "uniquemember",
+        "uid=user.100,ou=people,ou=dept,dc=example,dc=com");
+
+    final InternalClientConnection connection = getRootConnection();
+    final ModifyOperation multiModOperation = connection.processModify(modifyRequest);
+    assertEquals(multiModOperation.getResultCode(), ResultCode.CONSTRAINT_VIOLATION);
+  }
 }

--
Gitblit v1.10.0