From dd94891ad6946da5646fffdf11631144725b5636 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Thu, 24 Sep 2026 08:03:16 +0000
Subject: [PATCH] [#1051] Replicate a change made under the Relax Rules control like any other (#1053)

---
 opendj-server-legacy/src/main/java/org/opends/server/plugins/LastModPlugin.java |  143 +++++++++++++++++++++++++++++++----------------
 1 files changed, 94 insertions(+), 49 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/plugins/LastModPlugin.java b/opendj-server-legacy/src/main/java/org/opends/server/plugins/LastModPlugin.java
index a7970cd..b42d677 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/plugins/LastModPlugin.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/plugins/LastModPlugin.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2006-2008 Sun Microsystems, Inc.
  * Portions Copyright 2014-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.plugins;
 
@@ -32,6 +33,7 @@
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.ModificationType;
+import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.forgerock.opendj.server.config.meta.PluginCfgDefn;
 import org.forgerock.opendj.server.config.server.LastModPluginCfg;
 import org.forgerock.opendj.server.config.server.PluginCfg;
@@ -47,6 +49,7 @@
 import org.opends.server.types.operation.PreOperationAddOperation;
 import org.opends.server.types.operation.PreOperationModifyDNOperation;
 import org.opends.server.types.operation.PreOperationModifyOperation;
+import org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement;
 
 /**
  * This class implements a Directory Server plugin that will add the
@@ -117,25 +120,31 @@
                doPreOperation(PreOperationAddOperation addOperation)
   {
     // Create the attribute list for the creatorsName attribute, if appropriate.
-    AttributeBuilder builder = new AttributeBuilder(getCreatorsNameAttributeType());
-    DN creatorDN = addOperation.getAuthorizationDN();
-    if (creatorDN == null)
+    if (!isSuppliedUnderRelaxRules(addOperation, getCreatorsNameAttributeType()))
     {
-      // This must mean that the operation was performed anonymously.
-      // Even so, we still need to update the creatorsName attribute.
-      builder.add(ByteString.empty());
+      AttributeBuilder builder = new AttributeBuilder(getCreatorsNameAttributeType());
+      DN creatorDN = addOperation.getAuthorizationDN();
+      if (creatorDN == null)
+      {
+        // This must mean that the operation was performed anonymously.
+        // Even so, we still need to update the creatorsName attribute.
+        builder.add(ByteString.empty());
+      }
+      else
+      {
+        builder.add(creatorDN.toString());
+      }
+      addOperation.setAttribute(getCreatorsNameAttributeType(), builder.toAttributeList());
     }
-    else
-    {
-      builder.add(creatorDN.toString());
-    }
-    addOperation.setAttribute(getCreatorsNameAttributeType(), builder.toAttributeList());
 
 
     //  Create the attribute list for the createTimestamp attribute.
-    List<Attribute> timeList = Attributes.createAsList(
-        getCreateTimestampAttributeType(), OP_ATTR_CREATE_TIMESTAMP, getGMTTime());
-    addOperation.setAttribute(getCreateTimestampAttributeType(), timeList);
+    if (!isSuppliedUnderRelaxRules(addOperation, getCreateTimestampAttributeType()))
+    {
+      List<Attribute> timeList = Attributes.createAsList(
+          getCreateTimestampAttributeType(), OP_ATTR_CREATE_TIMESTAMP, getGMTTime());
+      addOperation.setAttribute(getCreateTimestampAttributeType(), timeList);
+    }
 
     // We shouldn't ever need to return a non-success result.
     return PluginResult.PreOperation.continueOperationProcessing();
@@ -148,49 +157,55 @@
        doPreOperation(PreOperationModifyOperation modifyOperation)
   {
     // Create the modifiersName attribute.
-    AttributeBuilder builder = new AttributeBuilder(getModifiersNameAttributeType());
-    DN modifierDN = modifyOperation.getAuthorizationDN();
-    if (modifierDN == null)
+    if (!isSuppliedUnderRelaxRules(modifyOperation, getModifiersNameAttributeType()))
     {
-      // This must mean that the operation was performed anonymously.
-      // Even so, we still need to update the modifiersName attribute.
-      builder.add(ByteString.empty());
-    }
-    else
-    {
-      builder.add(modifierDN.toString());
-    }
-    Attribute nameAttr = builder.toAttribute();
-    try
-    {
-      modifyOperation.addModification(new Modification(ModificationType.REPLACE,
-                                                       nameAttr, true));
-    }
-    catch (DirectoryException de)
-    {
-      logger.traceException(de);
+      AttributeBuilder builder = new AttributeBuilder(getModifiersNameAttributeType());
+      DN modifierDN = modifyOperation.getAuthorizationDN();
+      if (modifierDN == null)
+      {
+        // This must mean that the operation was performed anonymously.
+        // Even so, we still need to update the modifiersName attribute.
+        builder.add(ByteString.empty());
+      }
+      else
+      {
+        builder.add(modifierDN.toString());
+      }
+      Attribute nameAttr = builder.toAttribute();
+      try
+      {
+        modifyOperation.addModification(new Modification(ModificationType.REPLACE,
+                                                         nameAttr, true));
+      }
+      catch (DirectoryException de)
+      {
+        logger.traceException(de);
 
-      // This should never happen.
-      return PluginResult.PreOperation.stopProcessing(
-          DirectoryConfig.getServerErrorResultCode(), de.getMessageObject());
+        // This should never happen.
+        return PluginResult.PreOperation.stopProcessing(
+            DirectoryConfig.getServerErrorResultCode(), de.getMessageObject());
+      }
     }
 
 
     //  Create the modifyTimestamp attribute.
-    Attribute timeAttr = Attributes.create(getModifyTimestampAttributeType(),
-        OP_ATTR_MODIFY_TIMESTAMP, getGMTTime());
-    try
+    if (!isSuppliedUnderRelaxRules(modifyOperation, getModifyTimestampAttributeType()))
     {
-      modifyOperation.addModification(new Modification(ModificationType.REPLACE,
-                                                       timeAttr, true));
-    }
-    catch (DirectoryException de)
-    {
-      logger.traceException(de);
+      Attribute timeAttr = Attributes.create(getModifyTimestampAttributeType(),
+          OP_ATTR_MODIFY_TIMESTAMP, getGMTTime());
+      try
+      {
+        modifyOperation.addModification(new Modification(ModificationType.REPLACE,
+                                                         timeAttr, true));
+      }
+      catch (DirectoryException de)
+      {
+        logger.traceException(de);
 
-      // This should never happen.
-      return PluginResult.PreOperation.stopProcessing(
-          DirectoryConfig.getServerErrorResultCode(), de.getMessageObject());
+        // This should never happen.
+        return PluginResult.PreOperation.stopProcessing(
+            DirectoryConfig.getServerErrorResultCode(), de.getMessageObject());
+      }
     }
 
 
@@ -198,6 +213,36 @@
     return PluginResult.PreOperation.continueOperationProcessing();
   }
 
+  /**
+   * Indicates whether a client relaxing the rules supplies the provided attribute in the entry it
+   * adds: such a value is kept, the way it is kept when the change is replayed on another replica.
+   */
+  private static boolean isSuppliedUnderRelaxRules(PreOperationAddOperation addOperation, AttributeType type)
+  {
+    return LocalBackendWorkflowElement.isRelaxRulesRequested(addOperation)
+        && addOperation.getOperationalAttributes().containsKey(type);
+  }
+
+  /**
+   * Indicates whether a client relaxing the rules modifies the provided attribute: such a value is
+   * kept, the way it is kept when the change is replayed on another replica.
+   */
+  private static boolean isSuppliedUnderRelaxRules(PreOperationModifyOperation modifyOperation, AttributeType type)
+  {
+    if (!LocalBackendWorkflowElement.isRelaxRulesRequested(modifyOperation))
+    {
+      return false;
+    }
+    for (Modification m : modifyOperation.getModifications())
+    {
+      if (m.getAttribute().getAttributeDescription().getAttributeType().equals(type))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
 
 
   @Override

--
Gitblit v1.10.0