From 490181201bfd34a4ca940caca019e49ef824f921 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Fri, 25 Sep 2026 06:25:43 +0000
Subject: [PATCH] [#1050] Document the replication repair control, and test that a repaired modify stays on its replica (#1052)
---
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/MultimasterReplication.java | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 55 insertions(+), 0 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/MultimasterReplication.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/MultimasterReplication.java
index 3e1f4a9..49181cf 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/MultimasterReplication.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/MultimasterReplication.java
@@ -52,6 +52,7 @@
import org.opends.server.api.ImportTaskListener;
import org.opends.server.api.RestoreTaskListener;
import org.opends.server.api.SynchronizationProvider;
+import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.BackendConfigManager;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ServerContext;
@@ -64,6 +65,7 @@
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.Modification;
import org.opends.server.types.Operation;
+import org.opends.server.types.OperationType;
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.SynchronizationProviderResult;
import org.opends.server.types.operation.PluginOperation;
@@ -161,6 +163,15 @@
Control c = it.next();
if (OID_REPLICATION_REPAIR_CONTROL.equals(c.getOID()))
{
+ if (!mayUseRepairControl(dn, op, c))
+ {
+ /*
+ Leave the control on the request: the backend drops it when it is not critical,
+ and refuses the request when it is, as it does with any control the client may
+ not use.
+ */
+ break;
+ }
op.setSynchronizationOperation(true);
op.setDontSynchronize(true);
/*
@@ -197,6 +208,50 @@
}
/**
+ * Whether the client may use the repair control on this operation, as the access control of
+ * the controls decides it: a client with the {@code bypass-acl} privilege, or one an ACI allows
+ * to use the control. The question is asked at the entry being repaired, on every operation:
+ * on an add as well, where the backend judges the other controls at the parent entry.
+ * <p>
+ * On a modify and a modify DN the backend has already asked it of every control by the time
+ * the replication plugin runs. An add and a delete reach the plugin before the backend checks
+ * their controls, and the plugin takes the control off the request, so on those operations the
+ * answer given here is the only one. They also reach it before the backend has applied a
+ * proxied authorization control: the answer would be given for the bound client rather than
+ * for the one the operation runs as, so a repair is refused on an add or a delete which
+ * carries one.
+ */
+ private static boolean mayUseRepairControl(DN dn, Operation op, Control control)
+ {
+ final OperationType type = op.getOperationType();
+ if ((type == OperationType.ADD || type == OperationType.DELETE) && carriesProxiedAuthorization(op))
+ {
+ return false;
+ }
+ try
+ {
+ return AccessControlConfigManager.getInstance().getAccessControlHandler().isAllowed(dn, op, control);
+ }
+ catch (DirectoryException e)
+ {
+ logger.traceException(e);
+ return false;
+ }
+ }
+
+ private static boolean carriesProxiedAuthorization(Operation op)
+ {
+ for (Control c : op.getRequestControls())
+ {
+ if (OID_PROXIED_AUTH_V1.equals(c.getOID()) || OID_PROXIED_AUTH_V2.equals(c.getOID()))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Creates a new domain from its configEntry, do the
* necessary initialization and starts it so that it is
* fully operational when this method returns.
--
Gitblit v1.10.0