Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
information: "Portions copyright [year] [name of copyright owner]".

Copyright 2017 ForgeRock AS.
Portions Copyright 2024 3A Systems LLC.
Portions Copyright 2024-2026 3A Systems LLC.
////

:figure-caption!:
Expand Down Expand Up @@ -264,7 +264,10 @@ The LDAP Relax Rules Control::
Object Identifier: 1.3.6.1.4.1.4203.666.5.12

+
Internet-Draft: link:https://tools.ietf.org/html/draft-zeilenga-ldap-relax-03[ddraft-zeilenga-ldap-relax-03 - The LDAP Relax Rules Control, window=\_top]
Internet-Draft: link:https://tools.ietf.org/html/draft-zeilenga-ldap-relax-03[draft-zeilenga-ldap-relax-03 - The LDAP Relax Rules Control, window=\_top]

+
Lets a client with the `bypass-acl` privilege add or modify attributes marked `NO-USER-MODIFICATION` or `OBSOLETE`, and skips the schema check of the resulting entry. The change is an ordinary one otherwise: it is logged, and it is replicated like any other.

--

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
! CCPL HEADER END
!
! Copyright 2011 ForgeRock AS
! Portions copyright 2024 3A Systems,LLC.
! Portions copyright 2024-2026 3A Systems,LLC.
!
-->
<appendix xml:id='appendix-controls'
Expand Down Expand Up @@ -457,7 +457,12 @@
<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>
>draft-zeilenga-ldap-relax-03 - The LDAP Relax Rules Control</link></para>
<para>Lets a client with the <literal>bypass-acl</literal> privilege add
or modify attributes marked <literal>NO-USER-MODIFICATION</literal> or
<literal>OBSOLETE</literal>, and skips the schema check of the resulting
entry. The change is an ordinary one otherwise: it is logged, and it is
replicated like any other.</para>
</listitem>
</varlistentry>
</variablelist>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*
* Copyright 2007-2010 Sun Microsystems, Inc.
* Portions Copyright 2013-2016 ForgeRock AS.
* Portions Copyright 2026 3A Systems, LLC.
*/
package org.opends.server.core;

Expand Down Expand Up @@ -288,11 +289,13 @@ private final void computeObjectClassesAndAttributes()
AttributeType attrType = attrDesc.getAttributeType();

// If the attribute type is marked "NO-USER-MODIFICATION" then fail
// unless this is an internal operation or is related to
// synchronization in some way.
// unless this is an internal operation, is related to
// synchronization in some way, or the client asked for the rules
// to be relaxed.
if (attrType.isNoUserModification()
&& !isInternalOperation()
&& !isSynchronizationOperation())
&& !isSynchronizationOperation()
&& !isRelaxRulesRequested(this))
{
throw new LDAPException(LDAPResultCode.UNWILLING_TO_PERFORM,
ERR_ADD_ATTR_IS_NO_USER_MOD.get(entryDN, attrDesc));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,14 @@ public class LocalBackendAddOperation
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;
/**
* Indicates whether the request included the Relax Rules request control, which relaxes the
* constraints of the schema on this change and nothing else.
*
* @see LocalBackendWorkflowElement#isRelaxRulesRequested(org.opends.server.types.Operation)
*/
private final boolean relaxRules;

/**
* Creates a new operation that may be used to add a new entry in a
* local backend of the Directory Server.
Expand All @@ -123,11 +129,7 @@ public LocalBackendAddOperation(AddOperation add)
super(add);

LocalBackendWorkflowElement.attachLocalOperation (add, this);
}

@Override
public boolean isSynchronizationOperation() {
return super.isSynchronizationOperation()||RelaxRulesControlRequested;
relaxRules = LocalBackendWorkflowElement.isRelaxRulesRequested(add);
}


Expand Down Expand Up @@ -382,10 +384,11 @@ private void processAdd(ClientConnection clientConnection,
}

// If the server is configured to check schema and the
// operation is not a synchronization operation,
// check to see if the entry is valid according to the server schema,
// and also whether its attributes are valid according to their syntax.
if (DirectoryServer.getCoreConfigManager().isCheckSchema() && !isSynchronizationOperation())
// operation is not a synchronization operation nor one whose rules the
// client asked to relax, check to see if the entry is valid according to
// the server schema, and also whether its attributes are valid according
// to their syntax.
if (DirectoryServer.getCoreConfigManager().isCheckSchema() && !isSynchronizationOperation() && !relaxRules)
{
checkSchema(parentEntry);
}
Expand Down Expand Up @@ -413,7 +416,7 @@ private void processAdd(ClientConnection clientConnection,
// sensitive information to the client.
try
{
if (!getAccessControlHandler().isAllowed(this) || (RelaxRulesControlRequested && !clientConnection.hasPrivilege(Privilege.BYPASS_ACL, this)))
if (!getAccessControlHandler().isAllowed(this) || (relaxRules && !clientConnection.hasPrivilege(Privilege.BYPASS_ACL, this)))
{
setResultCodeAndMessageNoInfoDisclosure(entryDN,
ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
Expand Down Expand Up @@ -530,7 +533,8 @@ private boolean checkHasReadOnlyAttributes(
{
if (at.isNoUserModification()
&& !isInternalOperation()
&& !isSynchronizationOperation())
&& !isSynchronizationOperation()
&& !relaxRules)
{
setResultCodeAndMessageNoInfoDisclosure(entryDN,
ResultCode.CONSTRAINT_VIOLATION,
Expand Down Expand Up @@ -976,7 +980,7 @@ else if (OID_PASSWORD_POLICY_CONTROL.equals(oid))
}
else if (RelaxRulesControl.OID.equals(oid))
{
RelaxRulesControlRequested = true;
// Already taken into account: see relaxRules.
}
else if (TransactionSpecificationRequestControl.OID.equals(oid))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,13 @@ public class LocalBackendModifyOperation
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;
/**
* Indicates whether the request included the Relax Rules request control, which relaxes the
* constraints of the schema on this change and nothing else.
*
* @see LocalBackendWorkflowElement#isRelaxRulesRequested(org.opends.server.types.Operation)
*/
private final boolean relaxRules;
/** The post-read request control, if present. */
private LDAPPostReadRequestControl postReadRequest;
/** The pre-read request control, if present. */
Expand Down Expand Up @@ -166,11 +171,7 @@ public LocalBackendModifyOperation(ModifyOperation modify)
{
super(modify);
LocalBackendWorkflowElement.attachLocalOperation (modify, this);
}

@Override
public boolean isSynchronizationOperation() {
return super.isSynchronizationOperation()||RelaxRulesControlRequested;
relaxRules = LocalBackendWorkflowElement.isRelaxRulesRequested(modify);
}

/**
Expand Down Expand Up @@ -540,7 +541,7 @@ private boolean operationIsAllowed()
{
try
{
if (!getAccessControlHandler().isAllowed(this) || (RelaxRulesControlRequested && !clientConnection.hasPrivilege(Privilege.BYPASS_ACL, this)))
if (!getAccessControlHandler().isAllowed(this) || (relaxRules && !clientConnection.hasPrivilege(Privilege.BYPASS_ACL, this)))
{
setResultCodeAndMessageNoInfoDisclosure(modifiedEntry,
ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
Expand Down Expand Up @@ -699,7 +700,7 @@ else if (OID_PASSWORD_POLICY_CONTROL.equals(oid))
}
else if (RelaxRulesControl.OID.equals(oid))
{
RelaxRulesControlRequested = true;
// Already taken into account: see relaxRules.
}
else if (TransactionSpecificationRequestControl.OID.equals(oid))
{
Expand All @@ -724,22 +725,23 @@ private void processNonPasswordModifications() throws DirectoryException


// If the attribute type is marked "NO-USER-MODIFICATION" then fail unless
// this is an internal operation or is related to synchronization in some way.
final boolean isInternalOrSynchro = isInternalOrSynchro(m);
if (t.isNoUserModification() && !isInternalOrSynchro)
// this is an internal operation, is related to synchronization in some way,
// or the client asked for the rules to be relaxed.
final boolean constraintsRelaxed = isInternalOrSynchro(m) || relaxRules;
if (t.isNoUserModification() && !constraintsRelaxed)
{
throw newDirectoryException(currentEntry,
ResultCode.CONSTRAINT_VIOLATION,
ERR_MODIFY_ATTR_IS_NO_USER_MOD.get(entryDN, attrDesc));
}

// If the attribute type is marked "OBSOLETE" and the modification is
// setting new values, then fail unless this is an internal operation or
// is related to synchronization in some way.
// setting new values, then fail unless this is an internal operation,
// is related to synchronization in some way, or the rules are relaxed.
if (t.isObsolete()
&& !a.isEmpty()
&& m.getModificationType() != ModificationType.DELETE
&& !isInternalOrSynchro)
&& !constraintsRelaxed)
{
throw newDirectoryException(currentEntry,
ResultCode.CONSTRAINT_VIOLATION,
Expand Down Expand Up @@ -1201,7 +1203,7 @@ private void processAddModification(Attribute attr) throws DirectoryException

private boolean mustCheckSchema()
{
return !isSynchronizationOperation() && DirectoryServer.getCoreConfigManager().isCheckSchema();
return !isSynchronizationOperation() && !relaxRules && DirectoryServer.getCoreConfigManager().isCheckSchema();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.controls.RelaxRulesControl;
import org.opends.server.api.AccessControlHandler;
import org.opends.server.api.LocalBackend;
import org.opends.server.controls.LDAPPostReadRequestControl;
Expand Down Expand Up @@ -219,6 +220,30 @@ static boolean isProxyAuthzControl(String oid)
return OID_PROXIED_AUTH_V1.equals(oid) || OID_PROXIED_AUTH_V2.equals(oid);
}

/**
* Indicates whether the provided operation carries the Relax Rules request control.
* <p>
* The control relaxes the constraints of the schema on the request - the attributes marked
* NO-USER-MODIFICATION or OBSOLETE, and the schema check of the resulting entry - for a
* client which has the {@code bypass-acl} privilege. It does not make the change a
* synchronization one: the change is logged, run through the plugins and replicated as any
* other.
*
* @param operation The operation to look at.
* @return {@code true} if the request carries the Relax Rules control.
*/
public static boolean isRelaxRulesRequested(Operation operation)
{
for (Control c : operation.getRequestControls())
{
if (RelaxRulesControl.OID.equals(c.getOID()))
{
return true;
}
}
return false;
}

/**
* Removes all the disallowed request controls from the provided operation.
* <p>
Expand Down
Loading
Loading