From 2b2f34cd5875a0f76a7a20862c1b59bc3ccfa50e Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 22 Sep 2026 09:21:58 +0000
Subject: [PATCH] [#1023] Delete every objectClass value a modification asks for (#1024)
---
opendj-server-legacy/src/test/java/org/opends/server/types/TestEntry.java | 63 +++++++++++++++++++++
opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java | 16 +++-
opendj-server-legacy/src/test/java/org/opends/server/core/ModifyOperationTestCase.java | 85 ++++++++++++++++++++++++++++
3 files changed, 159 insertions(+), 5 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java b/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java
index f826fca..cbb62d1 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java
@@ -1323,27 +1323,33 @@
return true;
}
- boolean allSuccessful = true;
+ // The objectClass attribute is present as soon as the entry has an object class: a value of
+ // the modification which the entry does not have is a missing value, not an absent attribute.
+ boolean attributeTypePresent = !objectClasses.isEmpty();
MatchingRule rule = attrType.getEqualityMatchingRule();
for (ByteString v : attribute)
{
String ocName = toLowerName(rule, v);
+ boolean matchFound = false;
for (ObjectClass oc : objectClasses.keySet())
{
if (oc.hasNameOrOID(ocName))
{
objectClasses.remove(oc);
- return true;
+ matchFound = true;
+ break;
}
}
- allSuccessful = false;
- missingValues.add(v);
+ if (!matchFound)
+ {
+ missingValues.add(v);
+ }
}
- return allSuccessful;
+ return attributeTypePresent;
}
private boolean removeNonObjectClassAttribute(Attribute attribute, Collection<? super ByteString> missingValues)
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/core/ModifyOperationTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/core/ModifyOperationTestCase.java
index 31ed0c3..23a0ad2 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/core/ModifyOperationTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/core/ModifyOperationTestCase.java
@@ -2136,6 +2136,91 @@
/**
+ * Adds the entry the object class deletes below work on: two auxiliary classes which the entry
+ * can lose without breaking the schema.
+ */
+ private void addUserWithTwoAuxiliaryObjectClasses(String baseDN) throws Exception
+ {
+ TestCaseUtils.addEntry(
+ "dn: uid=test.user," + baseDN,
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "objectClass: uidObject",
+ "objectClass: userSecurityInformation",
+ "uid: test.user",
+ "givenName: Test",
+ "sn: User",
+ "cn: Test User");
+ }
+
+ private void assertObjectClassesAre(String baseDN, String... objectClasses) throws Exception
+ {
+ Entry e = DirectoryServer.getEntry(DN.valueOf("uid=test.user," + baseDN));
+ assertThat(e.getObjectClasses().values()).containsOnly(objectClasses);
+ }
+
+ /**
+ * A delete of several object class values must remove every one of them, not just the first
+ * (see issue #1023).
+ */
+ @Test(dataProvider = "baseDNs")
+ public void testSuccessRemoveSeveralObjectClassValues(String baseDN) throws Exception
+ {
+ addUserWithTwoAuxiliaryObjectClasses(baseDN);
+
+ RawModification mod =
+ newRawModification(DELETE, "objectClass", "uidObject", "userSecurityInformation");
+ ModifyOperation modifyOperation = processModify("uid=test.user," + baseDN, mod);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+ retrieveSuccessfulOperationElements(modifyOperation);
+
+ assertObjectClassesAre(baseDN, "top", "person", "organizationalPerson", "inetOrgPerson");
+ }
+
+ /**
+ * A delete of several object class values one of which the entry does not have must name that
+ * value: the objectClass attribute itself is there, so this is a missing value and not an
+ * absent attribute.
+ */
+ @Test(dataProvider = "baseDNs")
+ public void testFailRemoveSeveralObjectClassValuesOneOfWhichIsMissing(String baseDN)
+ throws Exception
+ {
+ addUserWithTwoAuxiliaryObjectClasses(baseDN);
+
+ RawModification mod = newRawModification(DELETE, "objectClass", "uidObject", "domain");
+ ModifyOperation modifyOperation = processModify("uid=test.user," + baseDN, mod);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.NO_SUCH_ATTRIBUTE);
+ assertThat(modifyOperation.getErrorMessage().toString()).contains("domain");
+ retrieveFailedOperationElements(modifyOperation);
+
+ assertObjectClassesAre(baseDN, "top", "person", "organizationalPerson", "inetOrgPerson",
+ "uidObject", "userSecurityInformation");
+ }
+
+ /**
+ * The permissive modify control drops the object class value which the entry does not have, and
+ * the values it does have are all removed.
+ */
+ @Test(dataProvider = "baseDNs")
+ public void testSuccessPermissiveModifyControlRemoveSeveralObjectClassValuesOneMissing(
+ String baseDN) throws Exception
+ {
+ addUserWithTwoAuxiliaryObjectClasses(baseDN);
+
+ ModifyRequest modifyRequest = Requests.newModifyRequest("uid=test.user," + baseDN)
+ .addModification(DELETE, "objectClass", "uidObject", "domain", "userSecurityInformation")
+ .addControl(newControl(OID_PERMISSIVE_MODIFY_CONTROL));
+ ModifyOperation modifyOperation = getRootConnection().processModify(modifyRequest);
+ assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
+ retrieveSuccessfulOperationElements(modifyOperation);
+
+ assertObjectClassesAre(baseDN, "top", "person", "organizationalPerson", "inetOrgPerson");
+ }
+
+ /**
* Tests the ability to perform a modification that adds an auxiliary
* objectclass.
*
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/types/TestEntry.java b/opendj-server-legacy/src/test/java/org/opends/server/types/TestEntry.java
index d3a99a5..b025a9b 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/types/TestEntry.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/types/TestEntry.java
@@ -13,6 +13,7 @@
*
* Copyright 2006-2008 Sun Microsystems, Inc.
* Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
*/
package org.opends.server.types;
@@ -26,6 +27,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -108,6 +110,67 @@
TestCaseUtils.startServer();
}
+ /** Returns an entry to delete object class values from. */
+ private Entry newTestUserEntry() throws Exception
+ {
+ return TestCaseUtils.makeEntry(
+ "dn: cn=Test User,ou=People,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "objectClass: inetOrgPerson",
+ "cn: Test User",
+ "sn: User");
+ }
+
+ /**
+ * A delete of several object class values must remove every one of them, the way a delete of
+ * several values of any other attribute does.
+ */
+ @Test
+ public void testRemoveSeveralObjectClassValues() throws Exception
+ {
+ Entry e = newTestUserEntry();
+
+ List<ByteString> missingValues = new LinkedList<>();
+ assertTrue(e.removeAttribute(
+ Attributes.create("objectClass", "organizationalPerson", "inetOrgPerson"), missingValues));
+
+ assertThat(missingValues).isEmpty();
+ assertThat(e.getObjectClasses().values()).containsOnly("top", "person");
+ }
+
+ /**
+ * A delete of an object class value which the entry does not have must be reported as a missing
+ * value, whatever the other values of the same modification are.
+ */
+ @Test
+ public void testRemoveObjectClassValuesOneOfWhichIsMissing() throws Exception
+ {
+ Entry e = newTestUserEntry();
+
+ List<ByteString> missingValues = new LinkedList<>();
+ assertTrue(e.removeAttribute(
+ Attributes.create("objectClass", "inetOrgPerson", "domain"), missingValues));
+
+ assertThat(missingValues).containsOnly(ByteString.valueOfUtf8("domain"));
+ assertThat(e.getObjectClasses().values()).containsOnly("top", "person", "organizationalPerson");
+ }
+
+ /** The same, with the missing value first: the walk must not stop at it. */
+ @Test
+ public void testRemoveObjectClassValuesTheFirstOfWhichIsMissing() throws Exception
+ {
+ Entry e = newTestUserEntry();
+
+ List<ByteString> missingValues = new LinkedList<>();
+ assertTrue(e.removeAttribute(
+ Attributes.create("objectClass", "domain", "inetOrgPerson"), missingValues));
+
+ assertThat(missingValues).containsOnly(ByteString.valueOfUtf8("domain"));
+ assertThat(e.getObjectClasses().values()).containsOnly("top", "person", "organizationalPerson");
+ }
+
/**
* Test the {@link Entry#parseAttribute(String)} method.
*/
--
Gitblit v1.10.0