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

Valery Kharseko
23 hours ago 3b360568a7063c65fde7d905debe5519577b10d1
[#1158] Split a check-references-filter-criteria value at its first colon, so the filter can contain one (#1167)

Fixes #1158

## Problem

The referential integrity plugin split each
`check-references-filter-criteria` value (`attribute:filter`) at the
**last** colon, both when it validated the configuration and when it
applied it. An attribute description cannot contain `:`, but a filter
often does — a URN or URL value (`(o=urn:example)`), or an extensible
match (`(cn:dn:=x)`) — so such a value was refused: `member:(cn:dn` was
taken for the attribute and `=x)` for the filter.

There was a second, quieter difference between the two paths: only
validation trimmed the parts. `manager :(objectClass=person)` passed
validation, but the apply path looked up the attribute `"manager "`, got
a placeholder type, and the filter was never enforced.

## Fix

Both paths now call one helper, `splitFilterCriteria`, that splits at
the **first** colon and trims both parts. The property syntax
(`^[^:]+:\(.+\)$`) already guarantees a colon after a non-empty,
colon-free attribute, so the first colon is always the separator.

## Tests

`ReferentialIntegrityPluginTestCase`:
- `validConfigs` gains a configuration with `member:(o=urn:example)` and
`member:(cn:dn:=x)`.
- New `testEnforceIntegrityWithColonInFilter`, for
`manager:(description=urn:example:manager)`,
`manager:(description:caseExactMatch:=urn:example:manager)` and `manager
:(description=*manager)`: the configuration change is accepted, adding
an employee whose manager has no `description` is refused with
`CONSTRAINT_VIOLATION`, and it succeeds once the manager's `description`
is `urn:example:manager`.

Without the fix all four new cases fail: the colon cases because the
configuration is refused (`Unwilling to Perform`), and the
trailing-space case because the configuration is accepted but the filter
is not enforced (`expected [Constraint Violation] but found [Success]`).
With the fix the class passes 65/65.


## Follow-up

Two `check-references-filter-criteria` values for the same attribute
type are both accepted, but only one of them is enforced. This was
already true before this change and is tracked in #1172.
2 files modified
104 ■■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/plugins/ReferentialIntegrityPlugin.java 27 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/test/java/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java 77 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/plugins/ReferentialIntegrityPlugin.java
@@ -221,9 +221,9 @@
    for (String attrFilt : newConfiguration.getCheckReferencesFilterCriteria())
    {
      int sepInd = attrFilt.lastIndexOf(":");
      String attr = attrFilt.substring(0, sepInd);
      String filtStr = attrFilt.substring(sepInd + 1);
      String[] attrAndFilter = splitFilterCriteria(attrFilt);
      String attr = attrAndFilter[0];
      String filtStr = attrAndFilter[1];
      AttributeType attrType = DirectoryServer.getInstance().getServerContext().getSchema().getAttributeType(attr);
      try
@@ -365,9 +365,9 @@
    for (String attrFilt : pluginCfg.getCheckReferencesFilterCriteria())
    {
      int sepInd = attrFilt.lastIndexOf(":");
      String attr = attrFilt.substring(0, sepInd).trim();
      String filtStr = attrFilt.substring(sepInd + 1).trim();
      String[] attrAndFilter = splitFilterCriteria(attrFilt);
      String attr = attrAndFilter[0];
      String filtStr = attrAndFilter[1];
      /* TODO: strip the ;options part? */
@@ -398,6 +398,21 @@
    return isAcceptable;
  }
  /**
   * Splits a check-references-filter-criteria value ({@code attribute:filter}) at its first colon: an attribute
   * description cannot contain one, but the filter that follows it can, as in {@code (o=urn:example)} or
   * {@code (cn:dn:=x)}. The property syntax guarantees that the value has a colon after a non-empty attribute.
   *
   * @param attrFilt
   *          The check-references-filter-criteria value.
   * @return The attribute and the filter, both trimmed.
   */
  private static String[] splitFilterCriteria(String attrFilt)
  {
    int sepInd = attrFilt.indexOf(':');
    return new String[] { attrFilt.substring(0, sepInd).trim(), attrFilt.substring(sepInd + 1).trim() };
  }
  @Override
  public boolean isConfigurationChangeAcceptable(
          ReferentialIntegrityPluginCfg configuration,
opendj-server-legacy/src/test/java/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java
@@ -508,7 +508,26 @@
            "ds-cfg-plugin-type: preOperationModify",
            "ds-cfg-attribute-type: member",
            "ds-cfg-check-references: false",
            "ds-cfg-check-references-filter-criteria: member:(objectclass=person)"
            "ds-cfg-check-references-filter-criteria: member:(objectclass=person)",
            "",
            // check-references enabled, filters that contain a colon
            "dn: cn=Referential Integrity,cn=Plugins,cn=config",
            "objectClass: top",
            "objectClass: ds-cfg-plugin",
            "objectClass: ds-cfg-referential-integrity-plugin",
            "cn: Referential Integrity",
            "ds-cfg-java-class: org.opends.server.plugins.ReferentialIntegrityPlugin",
            "ds-cfg-enabled: true",
            "ds-cfg-plugin-type: postOperationDelete",
            "ds-cfg-plugin-type: postOperationModifyDN",
            "ds-cfg-plugin-type: subordinateModifyDN",
            "ds-cfg-plugin-type: preOperationAdd",
            "ds-cfg-plugin-type: preOperationModify",
            "ds-cfg-attribute-type: member",
            "ds-cfg-base-dn: o=test",
            "ds-cfg-check-references: true",
            "ds-cfg-check-references-filter-criteria: member:(o=urn:example)",
            "ds-cfg-check-references-filter-criteria: member:(cn:dn:=x)"
    );
    Object[][] array = new Object[entries.size()][1];
    for (int i=0; i < array.length; i++)
@@ -1639,6 +1658,62 @@
  }
  /**
   * Filter criteria whose filter contains a colon, or whose attribute is followed by a space. Each filter matches the
   * manager entry only once its description is {@code urn:example:manager}.
   */
  @DataProvider
  public Object[][] filterCriteriaWithColonInFilter()
  {
    return new Object[][] {
      { "manager:(description=urn:example:manager)" },
      { "manager:(description:caseExactMatch:=urn:example:manager)" },
      { "manager :(description=*manager)" },
    };
  }
  /**
   * A check-references-filter-criteria value is split at its first colon, and both of its parts are trimmed: the
   * filter that follows the attribute can contain colons of its own, and is enforced as written.
   */
  @Test(dataProvider = "filterCriteriaWithColonInFilter")
  public void testEnforceIntegrityWithColonInFilter(String filterCriteria) throws Exception
  {
    replaceAttrEntry(configDN, "ds-cfg-enabled", "false");
    replaceAttrEntry(configDN, dsConfigPluginType,
                               "postoperationdelete",
                               "postoperationmodifydn",
                               "subordinatemodifydn",
                               "subordinatedelete",
                               "preoperationadd",
                               "preoperationmodify");
    addAttrEntry(configDN, dsConfigBaseDN, "dc=example,dc=com");
    replaceAttrEntry(configDN, dsConfigEnforceIntegrity, "true");
    replaceAttrEntry(configDN, dsConfigAttrType, "manager");
    assertEquals(addAttrEntry(configDN, dsConfigAttrFiltMapping, filterCriteria).getResultCode(), ResultCode.SUCCESS);
    assertEquals(replaceAttrEntry(configDN, "ds-cfg-enabled", "true").getResultCode(), ResultCode.SUCCESS);
    String manager = "uid=manager,ou=people,ou=dept,dc=example,dc=com";
    addEntry(manager);
    Entry employee = TestCaseUtils.makeEntry(
      "dn: uid=employee,ou=people,ou=dept,dc=example,dc=com",
      "objectclass: top",
      "objectclass: person",
      "objectclass: organizationalperson",
      "objectclass: inetorgperson",
      "uid: employee",
      "cn: employee",
      "sn: employee",
      "givenname: employee",
      "manager: " + manager);
    assertEquals(getRootConnection().processAdd(employee).getResultCode(), ResultCode.CONSTRAINT_VIOLATION);
    assertEquals(addAttrEntry(DN.valueOf(manager), "description", "urn:example:manager").getResultCode(),
        ResultCode.SUCCESS);
    assertEquals(getRootConnection().processAdd(employee).getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Test case:
   * - integrity is enforced on the attribute 'manager'
   * - value of the 'manager' attribute should match the filter: