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"); } }