From d77fbdbc376a2760ea1c4a064e1544d19798202a Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Fri, 13 Apr 2007 15:05:04 +0000
Subject: [PATCH] Add support for user-defined virtual attributes, in which the administrator may explicitly specify the values that should be used for a virtual attribute. This provides functionality similar to the Class of Service (CoS) facility in the Sun Java System Directory Server.

---
 opends/resource/schema/02-config.ldif                                                                                   |   12 
 opends/src/admin/defn/org/opends/server/admin/std/UserDefinedVirtualAttributeConfiguration.xml                          |   38 ++
 opends/src/server/org/opends/server/extensions/UserDefinedVirtualAttributeProvider.java                                 |  195 +++++++++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UserDefinedVirtualAttributeProviderTestCase.java |  743 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 985 insertions(+), 3 deletions(-)

diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index 1bced74..9138149 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -1153,6 +1153,9 @@
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.343 NAME 'ds-target-group-dn'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE
   X-ORIGIN 'OpenDS Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.26027.1.1.344
+  NAME 'ds-cfg-virtual-attribute-value' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+  X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
   NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
   MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
@@ -1605,8 +1608,11 @@
   MUST ( ds-task-rebuild-base-dn $ ds-task-rebuild-index )
   MAY ( ds-task-rebuild-max-threads )
   X-ORIGIN 'OpenDS Directory Server' )
-objectClasses: ( 1.3.6.1.4.1.26027.1.2.99
-  NAME 'ds-virtual-static-group' SUP top AUXILIARY MUST ds-target-group-dn
+objectClasses: ( 1.3.6.1.4.1.26027.1.2.99 NAME 'ds-virtual-static-group'
+  SUP top AUXILIARY MUST ds-target-group-dn
   X-ORIGIN 'OpenDS Directory Server' )
-
+objectClasses: ( 1.3.6.1.4.1.26027.1.2.100
+  NAME 'ds-cfg-user-defined-virtual-attribute' SUP ds-cfg-virtual-attribute
+  STRUCTURAL MUST ds-cfg-virtual-attribute-value
+  X-ORIGIN 'OpenDS Directory Server' )
 
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/UserDefinedVirtualAttributeConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/UserDefinedVirtualAttributeConfiguration.xml
new file mode 100644
index 0000000..91d6e32
--- /dev/null
+++ b/opends/src/admin/defn/org/opends/server/admin/std/UserDefinedVirtualAttributeConfiguration.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<adm:managed-object name="user-defined-virtual-attribute"
+plural-name="user-defined-virtual-attributes"
+package="org.opends.server.admin.std" extends="virtual-attribute"
+xmlns:adm="http://www.opends.org/admin"
+xmlns:ldap="http://www.opends.org/admin-ldap">
+  <adm:synopsis>
+    The
+    <adm:user-friendly-name />
+    is used to create virtual attributes with user-defined values in entries
+    that match the criteria set in the associated virtual attribute rule.  This
+    provides functionality that is similar to Class of Service (CoS) in the Sun
+    Java System Directory Server.
+  </adm:synopsis>
+  <adm:profile name="ldap">
+    <ldap:object-class>
+      <ldap:oid>1.3.6.1.4.1.26027.1.2.100</ldap:oid>
+      <ldap:name>ds-cfg-user-defined-virtual-attribute</ldap:name>
+      <ldap:superior>ds-cfg-virtual-attribute</ldap:superior>
+    </ldap:object-class>
+  </adm:profile>
+  <adm:property name="value" mandatory="true" multi-valued="true">
+    <adm:synopsis>
+      Specifies the value (or set of values) that should be included in entries
+      matching the critieria in the associated virtual attribute rule.
+    </adm:synopsis>
+    <adm:syntax>
+      <adm:string />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:oid>1.3.6.1.4.1.26027.1.1.344</ldap:oid>
+        <ldap:name>ds-cfg-virtual-attribute-value</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+</adm:managed-object>
+
diff --git a/opends/src/server/org/opends/server/extensions/UserDefinedVirtualAttributeProvider.java b/opends/src/server/org/opends/server/extensions/UserDefinedVirtualAttributeProvider.java
new file mode 100644
index 0000000..7ea6862
--- /dev/null
+++ b/opends/src/server/org/opends/server/extensions/UserDefinedVirtualAttributeProvider.java
@@ -0,0 +1,195 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.extensions;
+
+
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.std.server.UserDefinedVirtualAttributeCfg;
+import org.opends.server.api.VirtualAttributeProvider;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.SearchOperation;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.ConfigChangeResult;
+import org.opends.server.types.Entry;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.VirtualAttributeRule;
+
+
+
+/**
+ * This class implements a virtual attribute provider that allows administrators
+ * to define their own values that will be inserted into any entry that matches
+ * the criteria defined in the virtual attribute rule.  This can be used to
+ * provide functionality like Class of Service (CoS) in the Sun Java System
+ * Directory Server.
+ */
+public class UserDefinedVirtualAttributeProvider
+       extends VirtualAttributeProvider<UserDefinedVirtualAttributeCfg>
+       implements ConfigurationChangeListener<UserDefinedVirtualAttributeCfg>
+{
+  // The current configuration for this virtual attribute provider.
+  private UserDefinedVirtualAttributeCfg currentConfig;
+
+
+
+  /**
+   * Creates a new instance of this member virtual attribute provider.
+   */
+  public UserDefinedVirtualAttributeProvider()
+  {
+    super();
+
+    // All initialization should be performed in the
+    // initializeVirtualAttributeProvider method.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void initializeVirtualAttributeProvider(
+                            UserDefinedVirtualAttributeCfg configuration)
+         throws ConfigException, InitializationException
+  {
+    this.currentConfig = configuration;
+    configuration.addUserDefinedChangeListener(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void finalizeVirtualAttributeProvider()
+  {
+    currentConfig.removeUserDefinedChangeListener(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean isMultiValued()
+  {
+    if (currentConfig == null)
+    {
+      return true;
+    }
+    else
+    {
+      return (currentConfig.getValue().size() > 1);
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public LinkedHashSet<AttributeValue> getValues(Entry entry,
+                                                 VirtualAttributeRule rule)
+  {
+    AttributeType attributeType = rule.getAttributeType();
+    Set<String> userDefinedValues = currentConfig.getValue();
+
+    LinkedHashSet<AttributeValue> values =
+         new LinkedHashSet<AttributeValue>(userDefinedValues.size());
+    for (String valueString : userDefinedValues)
+    {
+      values.add(new AttributeValue(attributeType, valueString));
+    }
+
+    return values;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public boolean isSearchable(VirtualAttributeRule rule,
+                              SearchOperation searchOperation)
+  {
+    // We will not allow searches based only on user-defined virtual attributes.
+    return false;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override()
+  public void processSearch(VirtualAttributeRule rule,
+                            SearchOperation searchOperation)
+  {
+    searchOperation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+    return;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isConfigurationChangeAcceptable(
+                      UserDefinedVirtualAttributeCfg configuration,
+                      List<String> unacceptableReasons)
+  {
+    // The new configuration should always be acceptable.
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ConfigChangeResult applyConfigurationChange(
+                                 UserDefinedVirtualAttributeCfg configuration)
+  {
+    // Just accept the new configuration as-is.
+    currentConfig = configuration;
+
+    return new ConfigChangeResult(ResultCode.SUCCESS, false);
+  }
+}
+
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UserDefinedVirtualAttributeProviderTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UserDefinedVirtualAttributeProviderTestCase.java
new file mode 100644
index 0000000..8145c69
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UserDefinedVirtualAttributeProviderTestCase.java
@@ -0,0 +1,743 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.extensions;
+
+
+
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.tools.LDAPModify;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchScope;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.VirtualAttributeRule;
+
+import static org.testng.Assert.*;
+
+
+
+/**
+ * A set of test cases for the user-defined virtual attribute provider.
+ */
+public class UserDefinedVirtualAttributeProviderTestCase
+       extends ExtensionsTestCase
+{
+  // The attribute type for the description attribute.
+  private AttributeType descriptionType;
+
+  // The attribute type for the ds-privilege-name attribute.
+  private AttributeType privNameType;
+
+  // The attribute type for the ds-pwp-password-policy-dn attribute.
+  private AttributeType pwPolicyDNType;
+
+
+
+  /**
+   * Ensures that the Directory Server is running.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @BeforeClass()
+  public void startServer()
+         throws Exception
+  {
+    TestCaseUtils.startServer();
+
+    descriptionType = DirectoryServer.getAttributeType("description", false);
+    assertNotNull(descriptionType);
+
+    pwPolicyDNType =
+         DirectoryServer.getAttributeType("ds-pwp-password-policy-dn", false);
+    assertNotNull(pwPolicyDNType);
+
+    privNameType = DirectoryServer.getAttributeType("ds-privilege-name", false);
+    assertNotNull(privNameType);
+  }
+
+
+
+  /**
+   * Tests the methods which are part of the virtual group API using a
+   * single-valued virtual attribute.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRuleAPISingleValued()
+         throws Exception
+  {
+   String ruleDN = "cn=User-Defined Test,cn=Virtual Attributes,cn=config";
+   TestCaseUtils.addEntry(
+      "dn: " + ruleDN,
+      "objectClass: top",
+      "objectClass: ds-cfg-virtual-attribute",
+      "objectClass: ds-cfg-user-defined-virtual-attribute",
+      "cn: User-Defined Test",
+      "ds-cfg-virtual-attribute-class: org.opends.server.extensions." +
+           "UserDefinedVirtualAttributeProvider",
+      "ds-cfg-virtual-attribute-enabled: true",
+      "ds-cfg-virtual-attribute-type: description",
+      "ds-cfg-virtual-attribute-conflict-behavior: real-overrides-virtual",
+      "ds-cfg-virtual-attribute-value: single value");
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+    InternalSearchOperation searchOperation =
+         new InternalSearchOperation(conn, conn.nextOperationID(),
+                  conn.nextMessageID(), null, DN.decode(ruleDN),
+                  SearchScope.BASE_OBJECT,
+                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                  SearchFilter.createFilterFromString("(objectClass=*)"), null,
+                  null);
+
+    for (VirtualAttributeRule rule : DirectoryServer.getVirtualAttributes())
+    {
+      if (rule.getAttributeType().equals(descriptionType))
+      {
+        UserDefinedVirtualAttributeProvider provider =
+             (UserDefinedVirtualAttributeProvider) rule.getProvider();
+        assertFalse(provider.isMultiValued());
+        assertFalse(provider.isSearchable(rule, searchOperation));
+
+        provider.processSearch(rule, searchOperation);
+        assertEquals(searchOperation.getResultCode(),
+                     ResultCode.UNWILLING_TO_PERFORM);
+      }
+    }
+
+    DeleteOperation deleteOperation = conn.processDelete(DN.decode(ruleDN));
+    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+  }
+
+
+
+  /**
+   * Tests the methods which are part of the virtual group API using a
+   * multi-valued virtual attribute.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRuleAPIMultiValued()
+         throws Exception
+  {
+   String ruleDN = "cn=User-Defined Test,cn=Virtual Attributes,cn=config";
+   TestCaseUtils.addEntry(
+      "dn: " + ruleDN,
+      "objectClass: top",
+      "objectClass: ds-cfg-virtual-attribute",
+      "objectClass: ds-cfg-user-defined-virtual-attribute",
+      "cn: User-Defined Test",
+      "ds-cfg-virtual-attribute-class: org.opends.server.extensions." +
+           "UserDefinedVirtualAttributeProvider",
+      "ds-cfg-virtual-attribute-enabled: true",
+      "ds-cfg-virtual-attribute-type: description",
+      "ds-cfg-virtual-attribute-conflict-behavior: real-overrides-virtual",
+      "ds-cfg-virtual-attribute-value: first value",
+      "ds-cfg-virtual-attribute-value: second value");
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+    InternalSearchOperation searchOperation =
+         new InternalSearchOperation(conn, conn.nextOperationID(),
+                  conn.nextMessageID(), null, DN.decode(ruleDN),
+                  SearchScope.BASE_OBJECT,
+                  DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
+                  SearchFilter.createFilterFromString("(objectClass=*)"), null,
+                  null);
+
+    for (VirtualAttributeRule rule : DirectoryServer.getVirtualAttributes())
+    {
+      if (rule.getAttributeType().equals(descriptionType))
+      {
+        UserDefinedVirtualAttributeProvider provider =
+             (UserDefinedVirtualAttributeProvider) rule.getProvider();
+        assertTrue(provider.isMultiValued());
+        assertFalse(provider.isSearchable(rule, searchOperation));
+
+        provider.processSearch(rule, searchOperation);
+        assertEquals(searchOperation.getResultCode(),
+                     ResultCode.UNWILLING_TO_PERFORM);
+      }
+    }
+
+    DeleteOperation deleteOperation = conn.processDelete(DN.decode(ruleDN));
+    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+  }
+
+
+
+  /**
+   * Tests the creation of a description virtual attribute when there is only a
+   * single virtual value and no real value.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testSingleDescriptionOnlyVirtual()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    String ruleDN = "cn=User-Defined Test,cn=Virtual Attributes,cn=config";
+    String userDN = "uid=test.user,o=test";
+    String value  = "This is the virtual value";
+
+    TestCaseUtils.addEntries(
+      "dn: " + ruleDN,
+      "objectClass: top",
+      "objectClass: ds-cfg-virtual-attribute",
+      "objectClass: ds-cfg-user-defined-virtual-attribute",
+      "cn: User-Defined Test",
+      "ds-cfg-virtual-attribute-class: org.opends.server.extensions." +
+           "UserDefinedVirtualAttributeProvider",
+      "ds-cfg-virtual-attribute-enabled: true",
+      "ds-cfg-virtual-attribute-type: description",
+      "ds-cfg-virtual-attribute-conflict-behavior: real-overrides-virtual",
+      "ds-cfg-virtual-attribute-value: " + value,
+      "",
+      "dn: " + userDN,
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: test");
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+    InternalSearchOperation searchOperation =
+         conn.processSearch(DN.decode(userDN), SearchScope.BASE_OBJECT,
+              SearchFilter.createFilterFromString("(objectClass=*)"));
+
+    List<Attribute> attrList =
+         searchOperation.getSearchEntries().get(0).getAttribute(
+              descriptionType);
+    assertNotNull(attrList);
+    assertEquals(attrList.size(), 1);
+
+    Attribute attr = attrList.get(0);
+    assertEquals(attr.getValues().size(), 1);
+    assertTrue(attr.hasValue(new AttributeValue(descriptionType, value)));
+
+    DeleteOperation deleteOperation = conn.processDelete(DN.decode(ruleDN));
+    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+  }
+
+
+
+  /**
+   * Tests the creation of a description virtual attribute when there are
+   * multiple virtual values and no real value.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testMultipleDescriptionsOnlyVirtual()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    String ruleDN = "cn=User-Defined Test,cn=Virtual Attributes,cn=config";
+    String userDN = "uid=test.user,o=test";
+    String value1 = "This is the first virtual value";
+    String value2 = "This is the second virtual value";
+
+    TestCaseUtils.addEntries(
+      "dn: " + ruleDN,
+      "objectClass: top",
+      "objectClass: ds-cfg-virtual-attribute",
+      "objectClass: ds-cfg-user-defined-virtual-attribute",
+      "cn: User-Defined Test",
+      "ds-cfg-virtual-attribute-class: org.opends.server.extensions." +
+           "UserDefinedVirtualAttributeProvider",
+      "ds-cfg-virtual-attribute-enabled: true",
+      "ds-cfg-virtual-attribute-type: description",
+      "ds-cfg-virtual-attribute-conflict-behavior: real-overrides-virtual",
+      "ds-cfg-virtual-attribute-value: " + value1,
+      "ds-cfg-virtual-attribute-value: " + value2,
+      "",
+      "dn: " + userDN,
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: test");
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+    InternalSearchOperation searchOperation =
+         conn.processSearch(DN.decode(userDN), SearchScope.BASE_OBJECT,
+              SearchFilter.createFilterFromString("(objectClass=*)"));
+
+    List<Attribute> attrList =
+         searchOperation.getSearchEntries().get(0).getAttribute(
+              descriptionType);
+    assertNotNull(attrList);
+    assertEquals(attrList.size(), 1);
+
+    Attribute attr = attrList.get(0);
+    assertEquals(attr.getValues().size(), 2);
+    assertTrue(attr.hasValue(new AttributeValue(descriptionType, value1)));
+    assertTrue(attr.hasValue(new AttributeValue(descriptionType, value2)));
+
+    DeleteOperation deleteOperation = conn.processDelete(DN.decode(ruleDN));
+    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+  }
+
+
+
+  /**
+   * Tests the creation of a description virtual attribute when real values
+   * should override virtual values and the entry has a real value.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testSingleDescriptionRealOverridesVirtual()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    String ruleDN = "cn=User-Defined Test,cn=Virtual Attributes,cn=config";
+    String userDN = "uid=test.user,o=test";
+    String virtualValue = "This is the virtual value";
+    String realValue    = "This is the real value";
+
+    TestCaseUtils.addEntries(
+      "dn: " + ruleDN,
+      "objectClass: top",
+      "objectClass: ds-cfg-virtual-attribute",
+      "objectClass: ds-cfg-user-defined-virtual-attribute",
+      "cn: User-Defined Test",
+      "ds-cfg-virtual-attribute-class: org.opends.server.extensions." +
+           "UserDefinedVirtualAttributeProvider",
+      "ds-cfg-virtual-attribute-enabled: true",
+      "ds-cfg-virtual-attribute-type: description",
+      "ds-cfg-virtual-attribute-conflict-behavior: real-overrides-virtual",
+      "ds-cfg-virtual-attribute-value: " + virtualValue,
+      "",
+      "dn: " + userDN,
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: test",
+      "description: " + realValue);
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+    InternalSearchOperation searchOperation =
+         conn.processSearch(DN.decode(userDN), SearchScope.BASE_OBJECT,
+              SearchFilter.createFilterFromString("(objectClass=*)"));
+
+    List<Attribute> attrList =
+         searchOperation.getSearchEntries().get(0).getAttribute(
+              descriptionType);
+    assertNotNull(attrList);
+    assertEquals(attrList.size(), 1);
+
+    Attribute attr = attrList.get(0);
+    assertEquals(attr.getValues().size(), 1);
+    assertTrue(attr.hasValue(new AttributeValue(descriptionType, realValue)));
+
+    DeleteOperation deleteOperation = conn.processDelete(DN.decode(ruleDN));
+    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+  }
+
+
+
+  /**
+   * Tests the creation of a description virtual attribute when virtual values
+   * should override real values and the entry has a real value.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testSingleDescriptionVirtualOverridesReal()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    String ruleDN = "cn=User-Defined Test,cn=Virtual Attributes,cn=config";
+    String userDN = "uid=test.user,o=test";
+    String virtualValue = "This is the virtual value";
+    String realValue    = "This is the real value";
+
+    TestCaseUtils.addEntries(
+      "dn: " + ruleDN,
+      "objectClass: top",
+      "objectClass: ds-cfg-virtual-attribute",
+      "objectClass: ds-cfg-user-defined-virtual-attribute",
+      "cn: User-Defined Test",
+      "ds-cfg-virtual-attribute-class: org.opends.server.extensions." +
+           "UserDefinedVirtualAttributeProvider",
+      "ds-cfg-virtual-attribute-enabled: true",
+      "ds-cfg-virtual-attribute-type: description",
+      "ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real",
+      "ds-cfg-virtual-attribute-value: " + virtualValue,
+      "",
+      "dn: " + userDN,
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: test",
+      "description: " + realValue);
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+    InternalSearchOperation searchOperation =
+         conn.processSearch(DN.decode(userDN), SearchScope.BASE_OBJECT,
+              SearchFilter.createFilterFromString("(objectClass=*)"));
+
+    List<Attribute> attrList =
+         searchOperation.getSearchEntries().get(0).getAttribute(
+              descriptionType);
+    assertNotNull(attrList);
+    assertEquals(attrList.size(), 1);
+
+    Attribute attr = attrList.get(0);
+    assertEquals(attr.getValues().size(), 1);
+    assertTrue(attr.hasValue(new AttributeValue(descriptionType,
+                                                virtualValue)));
+
+    DeleteOperation deleteOperation = conn.processDelete(DN.decode(ruleDN));
+    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+  }
+
+
+
+  /**
+   * Tests the creation of a description virtual attribute when real and virtual
+   * values should be merged and the entry has a real value.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testSingleDescriptionMergeRealAndVirtual()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    String ruleDN = "cn=User-Defined Test,cn=Virtual Attributes,cn=config";
+    String userDN = "uid=test.user,o=test";
+    String virtualValue = "This is the virtual value";
+    String realValue    = "This is the real value";
+
+    TestCaseUtils.addEntries(
+      "dn: " + ruleDN,
+      "objectClass: top",
+      "objectClass: ds-cfg-virtual-attribute",
+      "objectClass: ds-cfg-user-defined-virtual-attribute",
+      "cn: User-Defined Test",
+      "ds-cfg-virtual-attribute-class: org.opends.server.extensions." +
+           "UserDefinedVirtualAttributeProvider",
+      "ds-cfg-virtual-attribute-enabled: true",
+      "ds-cfg-virtual-attribute-type: description",
+      "ds-cfg-virtual-attribute-conflict-behavior: merge-real-and-virtual",
+      "ds-cfg-virtual-attribute-value: " + virtualValue,
+      "",
+      "dn: " + userDN,
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: test",
+      "description: " + realValue);
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+    InternalSearchOperation searchOperation =
+         conn.processSearch(DN.decode(userDN), SearchScope.BASE_OBJECT,
+              SearchFilter.createFilterFromString("(objectClass=*)"));
+
+    List<Attribute> attrList =
+         searchOperation.getSearchEntries().get(0).getAttribute(
+              descriptionType);
+    assertNotNull(attrList);
+    assertEquals(attrList.size(), 2);
+
+    LinkedHashSet<AttributeValue> allValues =
+         new LinkedHashSet<AttributeValue>();
+    for (Attribute a : attrList)
+    {
+      allValues.addAll(a.getValues());
+    }
+
+    assertTrue(allValues.contains(new AttributeValue(descriptionType,
+                                                     realValue)));
+    assertTrue(allValues.contains(new AttributeValue(descriptionType,
+                                                     virtualValue)));
+
+    DeleteOperation deleteOperation = conn.processDelete(DN.decode(ruleDN));
+    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+  }
+
+
+
+  /**
+   * Tests to ensure that the user-defined virtual attribute provider can be
+   * used to grant a privilege to a user.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testVirtualPrivilege()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    String policyDN = "cn=Test Policy,cn=Password Policies,cn=config";
+    String ruleDN   = "cn=User-Defined Test,cn=Virtual Attributes,cn=config";
+    String userDN   = "uid=test.user,o=test";
+
+    TestCaseUtils.addEntries(
+      "dn: cn=Test Policy,cn=Password Policies,cn=config",
+      "objectClass: top",
+      "objectClass: ds-cfg-password-policy",
+      "cn: Test Policy",
+      "ds-cfg-password-attribute: userPassword",
+      "ds-cfg-default-password-storage-scheme: SSHA",
+      "ds-cfg-allow-expired-password-changes: false",
+      "ds-cfg-allow-multiple-password-values: false",
+      "ds-cfg-allow-pre-encoded-passwords: false",
+      "ds-cfg-allow-user-password-changes: true",
+      "ds-cfg-expire-passwords-without-warning: false",
+      "ds-cfg-force-change-on-add: false",
+      "ds-cfg-force-change-on-reset: false",
+      "ds-cfg-grace-login-count: 0",
+      "ds-cfg-idle-lockout-interval: 0 seconds",
+      "ds-cfg-lockout-failure-count: 0",
+      "ds-cfg-lockout-duration: 0 seconds",
+      "ds-cfg-lockout-failure-expiration-interval: 0 seconds",
+      "ds-cfg-minimum-password-age: 0 seconds",
+      "ds-cfg-maximum-password-age: 0 seconds",
+      "ds-cfg-maximum-password-reset-age: 0 seconds",
+      "ds-cfg-password-expiration-warning-interval: 5 days",
+      "ds-cfg-password-change-requires-current-password: true",
+      "ds-cfg-password-validator-dn: cn=Length-Based Password Validator," +
+           "cn=Password Validators,cn=config",
+      "ds-cfg-require-secure-authentication: false",
+      "ds-cfg-require-secure-password-changes: false",
+      "ds-cfg-skip-validation-for-administrators: false",
+      "",
+      "dn: " + ruleDN,
+      "objectClass: top",
+      "objectClass: ds-cfg-virtual-attribute",
+      "objectClass: ds-cfg-user-defined-virtual-attribute",
+      "cn: User-Defined Test",
+      "ds-cfg-virtual-attribute-class: org.opends.server.extensions." +
+           "UserDefinedVirtualAttributeProvider",
+      "ds-cfg-virtual-attribute-enabled: true",
+      "ds-cfg-virtual-attribute-type: ds-pwp-password-policy-dn",
+      "ds-cfg-virtual-attribute-conflict-behavior: merge-real-and-virtual",
+      "ds-cfg-virtual-attribute-value: " + policyDN,
+      "",
+      "dn: " + userDN,
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: test");
+
+
+    String path1 = TestCaseUtils.createTempFile(
+      "dn: " + userDN,
+      "changetype: modify",
+      "replace: userPassword",
+      "userPassword: short");
+
+    String[] args1 =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path1
+    };
+
+    assertFalse(LDAPModify.mainModify(args1, false, null, null) == 0);
+
+
+    String path2 = TestCaseUtils.createTempFile(
+      "dn: " + ruleDN,
+      "changetype: modify",
+      "replace: ds-cfg-virtual-attribute-enabled",
+      "ds-cfg-virtual-attribute-enabled: false");
+
+    String[] args2 = new String[]
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path2
+    };
+
+    assertEquals(LDAPModify.mainModify(args2, false, null, null), 0);
+    assertEquals(LDAPModify.mainModify(args1, false, null, null), 0);
+
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+
+    DeleteOperation deleteOperation = conn.processDelete(DN.decode(ruleDN));
+    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+
+    deleteOperation = conn.processDelete(DN.decode(policyDN));
+    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+  }
+
+
+
+  /**
+   * Tests to ensure that the user-defined virtual attribute provider can be
+   * used to apply a custom password policy for a user.  The custom password
+   * policy will reject passwords shorter than six characters, whereas the
+   * default policy will not.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testVirtualPasswordPolicyDN()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+
+    String ruleDN   = "cn=User-Defined Test,cn=Virtual Attributes,cn=config";
+    String userDN   = "uid=test.user,o=test";
+
+    TestCaseUtils.addEntries(
+      "dn: " + ruleDN,
+      "objectClass: top",
+      "objectClass: ds-cfg-virtual-attribute",
+      "objectClass: ds-cfg-user-defined-virtual-attribute",
+      "cn: User-Defined Test",
+      "ds-cfg-virtual-attribute-class: org.opends.server.extensions." +
+           "UserDefinedVirtualAttributeProvider",
+      "ds-cfg-virtual-attribute-enabled: false",
+      "ds-cfg-virtual-attribute-type: ds-privilege-name",
+      "ds-cfg-virtual-attribute-conflict-behavior: merge-real-and-virtual",
+      "ds-cfg-virtual-attribute-value: bypass-acl",
+      "",
+      "dn: " + userDN,
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password");
+
+
+    String path1 = TestCaseUtils.createTempFile(
+      "dn: o=test",
+      "changetype: modify",
+      "replace: description",
+      "description: foo");
+
+    String[] args1 =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", userDN,
+      "-w", "password",
+      "-f", path1
+    };
+
+    assertFalse(LDAPModify.mainModify(args1, false, null, null) == 0);
+
+
+    String path2 = TestCaseUtils.createTempFile(
+      "dn: " + ruleDN,
+      "changetype: modify",
+      "replace: ds-cfg-virtual-attribute-enabled",
+      "ds-cfg-virtual-attribute-enabled: true");
+
+    String[] args2 = new String[]
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path2
+    };
+
+    assertEquals(LDAPModify.mainModify(args2, false, null, null), 0);
+    assertEquals(LDAPModify.mainModify(args1, false, null, null), 0);
+
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+
+    DeleteOperation deleteOperation = conn.processDelete(DN.decode(ruleDN));
+    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
+  }
+}
+

--
Gitblit v1.10.0