mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
06.00.2024 f232c8c260cb67b483d688909e239bf7715cc1d3
[#204] ADD LDAP Relax Rules Control (#362)

Co-authored-by: Maxim Thomas <maxim.thomas@gmail.com>
2 files added
5 files modified
196 ■■■■■ changed files
opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/RelaxRulesControl.java 43 ●●●●● patch | view | raw | blame | history
opendj-doc-generated-ref/src/main/docbkx/admin-guide/appendix-controls.xml 15 ●●●●● patch | view | raw | blame | history
opendj-doc-generated-ref/src/main/docbkx/admin-guide/index.xml 8 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java 17 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java 15 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java 15 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/RelaxRulesTestCase.java 83 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/RelaxRulesControl.java
New file
@@ -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;
    }
}
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>
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>
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":
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,
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,
opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/RelaxRulesTestCase.java
New file
@@ -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");
    }
}