From f232c8c260cb67b483d688909e239bf7715cc1d3 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 06 Aug 2024 15:00:24 +0000
Subject: [PATCH] [#204] ADD LDAP Relax Rules Control (#362)

---
 opendj-doc-generated-ref/src/main/docbkx/admin-guide/index.xml                                                     |    8 ++
 opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/RelaxRulesControl.java                                |   43 ++++++++++
 opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java |   15 +++
 opendj-doc-generated-ref/src/main/docbkx/admin-guide/appendix-controls.xml                                         |   15 +++
 opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java                                       |   17 +---
 opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java    |   15 +++
 opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/RelaxRulesTestCase.java                         |   83 ++++++++++++++++++++
 7 files changed, 180 insertions(+), 16 deletions(-)

diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/RelaxRulesControl.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/RelaxRulesControl.java
new file mode 100644
index 0000000..ecbdcd0
--- /dev/null
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/RelaxRulesControl.java
@@ -0,0 +1,43 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2024 3A Systems,LLC.
+ */
+package org.forgerock.opendj.ldap.controls;
+
+import org.forgerock.opendj.ldap.ByteString;
+
+public class RelaxRulesControl implements Control{
+
+    public final static String OID="1.3.6.1.4.1.4203.666.5.12";
+
+    @Override
+    public String getOID() {
+        return OID;
+    }
+
+    @Override
+    public ByteString getValue() {
+        return null;
+    }
+
+    @Override
+    public boolean hasValue() {
+        return false;
+    }
+
+    @Override
+    public boolean isCritical() {
+        return true;
+    }
+}
diff --git a/opendj-doc-generated-ref/src/main/docbkx/admin-guide/appendix-controls.xml b/opendj-doc-generated-ref/src/main/docbkx/admin-guide/appendix-controls.xml
index 1f16425..5e88d56 100644
--- a/opendj-doc-generated-ref/src/main/docbkx/admin-guide/appendix-controls.xml
+++ b/opendj-doc-generated-ref/src/main/docbkx/admin-guide/appendix-controls.xml
@@ -21,6 +21,7 @@
   ! CCPL HEADER END
   !
   !      Copyright 2011 ForgeRock AS
+  !      Portions copyright 2024 3A Systems,LLC.
   !    
 -->
 <appendix xml:id='appendix-controls'
@@ -445,5 +446,19 @@
     Browsing of Search Results</link></para>
    </listitem>
   </varlistentry>
+
+  <varlistentry xml:id="relax-rules-control">
+   <term>The LDAP Relax Rules Control</term>
+   <listitem>
+    <indexterm>
+     <primary>LDAP controls</primary>
+     <secondary>Relax Rules Control</secondary>
+    </indexterm>
+    <para>Object Identifier: 1.3.6.1.4.1.4203.666.5.12</para>
+    <para>Internet-Draft: <link
+            xlink:href='https://tools.ietf.org/html/draft-zeilenga-ldap-relax-03'
+    >ddraft-zeilenga-ldap-relax-03 - The LDAP Relax Rules Control</link></para>
+   </listitem>
+  </varlistentry>
  </variablelist>
 </appendix>
diff --git a/opendj-doc-generated-ref/src/main/docbkx/admin-guide/index.xml b/opendj-doc-generated-ref/src/main/docbkx/admin-guide/index.xml
index cc280db..e2647ee 100644
--- a/opendj-doc-generated-ref/src/main/docbkx/admin-guide/index.xml
+++ b/opendj-doc-generated-ref/src/main/docbkx/admin-guide/index.xml
@@ -21,6 +21,7 @@
   ! CCPL HEADER END
   !
   !      Copyright 2011-2014 ForgeRock AS
+  !      Portions copyright 2024 3A Systems,LLC.
   !
 -->
 <book xml:id='admin-guide'
@@ -40,8 +41,15 @@
    <year>2011-2014</year>
    <holder>ForgeRock AS</holder>
   </copyright>
+  <copyright>
+   <year>2024- </year>
+   <holder>Open Identity Platform Community</holder>
+  </copyright>
   <authorgroup>
    <author>
+    <personname><firstname>Valery </firstname><surname>Kharseko</surname></personname>
+   </author>
+   <author>
     <personname><firstname>Mark </firstname><surname>Craig</surname></personname>
    </author>
    <author>
diff --git a/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java b/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java
index 54651f3..b56505a 100644
--- a/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java
+++ b/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2006-2010 Sun Microsystems, Inc.
  * Portions copyright 2014-2016 ForgeRock AS.
+ * Portions copyright 2022-2024 3A Systems,LLC.
  */
 package com.forgerock.opendj.ldap.tools;
 
@@ -58,19 +59,7 @@
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.ResultCode;
-import org.forgerock.opendj.ldap.controls.AssertionRequestControl;
-import org.forgerock.opendj.ldap.controls.AuthorizationIdentityRequestControl;
-import org.forgerock.opendj.ldap.controls.AuthorizationIdentityResponseControl;
-import org.forgerock.opendj.ldap.controls.Control;
-import org.forgerock.opendj.ldap.controls.GenericControl;
-import org.forgerock.opendj.ldap.controls.GetEffectiveRightsRequestControl;
-import org.forgerock.opendj.ldap.controls.PasswordExpiredResponseControl;
-import org.forgerock.opendj.ldap.controls.PasswordExpiringResponseControl;
-import org.forgerock.opendj.ldap.controls.PasswordPolicyErrorType;
-import org.forgerock.opendj.ldap.controls.PasswordPolicyRequestControl;
-import org.forgerock.opendj.ldap.controls.PasswordPolicyResponseControl;
-import org.forgerock.opendj.ldap.controls.PasswordPolicyWarningType;
-import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl;
+import org.forgerock.opendj.ldap.controls.*;
 import org.forgerock.opendj.ldap.requests.BindRequest;
 import org.forgerock.opendj.ldap.requests.Request;
 import org.forgerock.opendj.ldap.responses.BindResult;
@@ -379,6 +368,8 @@
         case "effectiverights":
         case "geteffectiverights":
             return GetEffectiveRightsRequestControl.OID;
+        case "relaxrules":
+            return RelaxRulesControl.OID;
         case "noop":
         case "no-op":
         case "subentries":
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
index 6ae0629..3aa4daa 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2008-2010 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions copyright 2024 3A Systems,LLC.
  */
 package org.opends.server.workflowelement.localbackend;
 
@@ -37,6 +38,7 @@
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.controls.RelaxRulesControl;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.forgerock.opendj.ldap.schema.ObjectClass;
 import org.forgerock.opendj.ldap.schema.Syntax;
@@ -108,7 +110,8 @@
   private Map<AttributeType, List<Attribute>> operationalAttributes;
   /** The set of user attributes for the entry to add. */
   private Map<AttributeType, List<Attribute>> userAttributes;
-
+  /** Indicates whether the request included the RelaxRules request control. */
+  private boolean RelaxRulesControlRequested=false;
   /**
    * Creates a new operation that may be used to add a new entry in a
    * local backend of the Directory Server.
@@ -122,6 +125,10 @@
     LocalBackendWorkflowElement.attachLocalOperation (add, this);
   }
 
+  @Override
+  public boolean isSynchronizationOperation() {
+    return super.isSynchronizationOperation()||RelaxRulesControlRequested;
+  }
 
 
   /**
@@ -406,7 +413,7 @@
       // sensitive information to the client.
       try
       {
-        if (!getAccessControlHandler().isAllowed(this))
+        if (!getAccessControlHandler().isAllowed(this) || (RelaxRulesControlRequested && !clientConnection.hasPrivilege(Privilege.BYPASS_ACL, this)))
         {
           setResultCodeAndMessageNoInfoDisclosure(entryDN,
               ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
@@ -957,6 +964,10 @@
         // We don't need to do anything here because it's already handled
         // in LocalBackendAddOperation.handlePasswordPolicy().
       }
+      else if (RelaxRulesControl.OID.equals(oid))
+      {
+        RelaxRulesControlRequested = true;
+      }
       else if (c.isCritical() && !backend.supportsControl(oid))
       {
         throw newDirectoryException(entryDN, ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
index 963ea88..070c528 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2008-2011 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions copyright 2024 3A Systems,LLC.
  */
 package org.opends.server.workflowelement.localbackend;
 
@@ -33,6 +34,7 @@
 import org.forgerock.opendj.ldap.ModificationType;
 import org.forgerock.opendj.ldap.RDN;
 import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.controls.RelaxRulesControl;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.forgerock.opendj.ldap.schema.MatchingRule;
 import org.forgerock.opendj.ldap.schema.ObjectClass;
@@ -123,6 +125,8 @@
   private boolean permissiveModify;
   /** Indicates whether the request included the password policy request control. */
   private boolean pwPolicyControlRequested;
+  /** Indicates whether the request included the RelaxRules request control. */
+  private boolean RelaxRulesControlRequested=false;
   /** The post-read request control, if present. */
   private LDAPPostReadRequestControl postReadRequest;
   /** The pre-read request control, if present. */
@@ -163,6 +167,11 @@
     LocalBackendWorkflowElement.attachLocalOperation (modify, this);
   }
 
+  @Override
+  public boolean isSynchronizationOperation() {
+    return super.isSynchronizationOperation()||RelaxRulesControlRequested;
+  }
+
   /**
    * Returns whether authentication for this user is managed locally
    * or via Pass-Through Authentication.
@@ -527,7 +536,7 @@
   {
     try
     {
-      if (!getAccessControlHandler().isAllowed(this))
+      if (!getAccessControlHandler().isAllowed(this) || (RelaxRulesControlRequested && !clientConnection.hasPrivilege(Privilege.BYPASS_ACL, this)))
       {
         setResultCodeAndMessageNoInfoDisclosure(modifiedEntry,
             ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
@@ -684,6 +693,10 @@
       {
         pwPolicyControlRequested = true;
       }
+      else if (RelaxRulesControl.OID.equals(oid))
+      {
+        RelaxRulesControlRequested = true;
+      }
       else if (c.isCritical() && !backend.supportsControl(oid))
       {
         throw newDirectoryException(currentEntry, ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
diff --git a/opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/RelaxRulesTestCase.java b/opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/RelaxRulesTestCase.java
new file mode 100644
index 0000000..703b85d
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/RelaxRulesTestCase.java
@@ -0,0 +1,83 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2024 3A Systems, LLC.
+ */
+package org.openidentityplatform.opendj;
+
+
+import org.forgerock.opendj.adapter.server3x.Adapters;
+import org.forgerock.opendj.ldap.*;
+import org.forgerock.opendj.ldap.controls.RelaxRulesControl;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import org.opends.server.DirectoryServerTestCase;
+import org.opends.server.TestCaseUtils;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Test(sequential = true)
+public class RelaxRulesTestCase extends DirectoryServerTestCase {
+    Connection connection;
+
+    @BeforeClass
+    public void startServer() throws Exception {
+        TestCaseUtils.startServer();
+        TestCaseUtils.initializeTestBackend(true);
+
+        TestCaseUtils.addEntries(
+           "dn: uid=user.2, o=test",
+            "objectClass: top",
+            "objectClass: person",
+            "objectClass: inetOrgPerson",
+            "objectClass: organizationalPerson",
+            "cn: Aarika Atpco",
+            "sn: user.2",
+            "uid:user.2",
+            "description: This is the description for Aarika Atpco.",
+            "userPassword:: cGFzc3dvcmQ=",
+            "postalAddress: Aarika Atpco$00900 Maple Street$New Orleans, KS  10857",
+            "postalCode: 10857",
+            ""
+        );
+
+        final LDAPConnectionFactory factory =new LDAPConnectionFactory("localhost", TestCaseUtils.getServerLdapPort());
+        connection = factory.getConnection();
+        connection.bind("cn=Directory Manager", "password".toCharArray());
+        assertThat(connection.isValid()).isTrue();
+    }
+
+    @Test
+    public void test() throws LdapException {
+        final ModifyRequest changeRequest =
+                Requests.newModifyRequest("uid=user.2, o=test")
+                        .addControl(new RelaxRulesControl())
+                        .addModification(ModificationType.REPLACE, "pwdChangedTime", "20211203224637.000Z");
+
+        final Result result = connection.modify(changeRequest);
+        assertThat(result.getDiagnosticMessage()).isEmpty();
+        assertThat(result.getMatchedDN()).isEmpty();
+
+        //Verifies that entry has been correctly modified.
+        final SearchResultEntry srEntry =
+                connection.searchSingleEntry(Requests.newSearchRequest(
+                        "uid=user.2, o=test", SearchScope.BASE_OBJECT, "(uid=user.2)").addAttribute("+"));
+        assertThat(srEntry.getAttribute("pwdChangedTime").firstValueAsString()).isEqualTo(
+                "20211203224637.000Z");
+    }
+}

--
Gitblit v1.10.0