/**
   * Process all ACIs under the "cn=config" naming context and adds them to the ACI list cache. It
   * also logs messages about the number of ACIs added to the cache. This method is called once at
   * startup. It will put the server in lockdown mode if needed.
   *
   * @throws InitializationException If there is an error searching for the ACIs in the naming
   *     context.
   */
  private void processConfigAcis() throws InitializationException {
    LinkedHashSet<String> requestAttrs = new LinkedHashSet<String>(1);
    requestAttrs.add("aci");
    LinkedList<Message> failedACIMsgs = new LinkedList<Message>();
    InternalClientConnection conn = InternalClientConnection.getRootConnection();

    ConfigHandler configBackend = DirectoryServer.getConfigHandler();
    for (DN baseDN : configBackend.getBaseDNs()) {
      try {
        if (!configBackend.entryExists(baseDN)) {
          continue;
        }
      } catch (Exception e) {
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }

        // FIXME -- Is there anything that we need to do here?
        continue;
      }

      try {
        InternalSearchOperation internalSearch =
            new InternalSearchOperation(
                conn,
                InternalClientConnection.nextOperationID(),
                InternalClientConnection.nextMessageID(),
                null,
                baseDN,
                SearchScope.WHOLE_SUBTREE,
                DereferencePolicy.NEVER_DEREF_ALIASES,
                0,
                0,
                false,
                SearchFilter.createFilterFromString("aci=*"),
                requestAttrs,
                null);
        LocalBackendSearchOperation localSearch = new LocalBackendSearchOperation(internalSearch);

        configBackend.search(localSearch);

        if (!internalSearch.getSearchEntries().isEmpty()) {
          int validAcis = aciList.addAci(internalSearch.getSearchEntries(), failedACIMsgs);
          if (!failedACIMsgs.isEmpty()) {
            aciListenerMgr.logMsgsSetLockDownMode(failedACIMsgs);
          }
          Message message =
              INFO_ACI_ADD_LIST_ACIS.get(Integer.toString(validAcis), String.valueOf(baseDN));
          logError(message);
        }
      } catch (Exception e) {
        Message message = INFO_ACI_HANDLER_FAIL_PROCESS_ACI.get();
        throw new InitializationException(message, e);
      }
    }
  }
  /**
   * Tests performing an internal search using the VLV control to retrieve a subset of the entries
   * using an assertion value that is after all values in the list.
   *
   * @throws Exception If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByValueAfterAll() throws Exception {
    populateDB();

    InternalClientConnection conn = InternalClientConnection.getRootConnection();

    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("sn"));
    requestControls.add(new VLVRequestControl(0, 3, ByteString.valueOf("zz")));

    InternalSearchOperation internalSearch =
        new InternalSearchOperation(
            conn,
            InternalClientConnection.nextOperationID(),
            InternalClientConnection.nextMessageID(),
            requestControls,
            DN.decode("dc=example,dc=com"),
            SearchScope.WHOLE_SUBTREE,
            DereferencePolicy.NEVER_DEREF_ALIASES,
            0,
            0,
            false,
            SearchFilter.createFilterFromString("(objectClass=person)"),
            null,
            null);

    internalSearch.run();

    // It will be successful because the control isn't critical.
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);

    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);

    VLVResponseControl vlvResponse = null;
    for (Control c : responseControls) {
      if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL)) {
        if (c instanceof LDAPControl) {
          vlvResponse =
              VLVResponseControl.DECODER.decode(c.isCritical(), ((LDAPControl) c).getValue());
        } else {
          vlvResponse = (VLVResponseControl) c;
        }
      }
    }

    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), LDAPResultCode.SUCCESS);
    assertEquals(vlvResponse.getTargetPosition(), 10);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
  /**
   * Tests performing an internal search using the VLV control to retrieve a subset of the entries
   * using an assertion value before any actual value in the list.
   *
   * @throws Exception If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByValueBeforeAll() throws Exception {
    populateDB();

    InternalClientConnection conn = InternalClientConnection.getRootConnection();

    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(0, 3, ByteString.valueOf("a")));

    InternalSearchOperation internalSearch =
        new InternalSearchOperation(
            conn,
            InternalClientConnection.nextOperationID(),
            InternalClientConnection.nextMessageID(),
            requestControls,
            DN.decode("dc=example,dc=com"),
            SearchScope.WHOLE_SUBTREE,
            DereferencePolicy.NEVER_DEREF_ALIASES,
            0,
            0,
            false,
            SearchFilter.createFilterFromString("(objectClass=person)"),
            null,
            null);

    internalSearch.run();
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);

    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(aaccfJohnsonDN); // Aaccf
    expectedDNOrder.add(aaronZimmermanDN); // Aaron
    expectedDNOrder.add(albertZimmermanDN); // Albert, lower entry ID
    expectedDNOrder.add(albertSmithDN); // Albert, higher entry ID

    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries()) {
      returnedDNOrder.add(e.getDN());
    }

    assertEquals(returnedDNOrder, expectedDNOrder);

    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);
    assertEquals(responseControls.size(), 2);

    ServerSideSortResponseControl sortResponse = null;
    VLVResponseControl vlvResponse = null;
    for (Control c : responseControls) {
      if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL)) {
        if (c instanceof LDAPControl) {
          sortResponse =
              ServerSideSortResponseControl.DECODER.decode(
                  c.isCritical(), ((LDAPControl) c).getValue());
        } else {
          sortResponse = (ServerSideSortResponseControl) c;
        }
      } else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL)) {
        if (c instanceof LDAPControl) {
          vlvResponse =
              VLVResponseControl.DECODER.decode(c.isCritical(), ((LDAPControl) c).getValue());
        } else {
          vlvResponse = (VLVResponseControl) c;
        }
      } else {
        fail("Response control with unexpected OID " + c.getOID());
      }
    }

    assertNotNull(sortResponse);
    assertEquals(sortResponse.getResultCode(), 0);

    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), 0);
    assertEquals(vlvResponse.getTargetPosition(), 1);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
  /**
   * Tests performing an internal search using the VLV control with a start start position beyond
   * the end of the result set.
   *
   * @throws Exception If an unexpected problem occurred.
   */
  @Test()
  public void testInternalSearchByOffsetStartPositionTooHigh() throws Exception {
    populateDB();

    InternalClientConnection conn = InternalClientConnection.getRootConnection();

    ArrayList<Control> requestControls = new ArrayList<Control>();
    requestControls.add(new ServerSideSortRequestControl("givenName"));
    requestControls.add(new VLVRequestControl(3, 3, 30, 0));

    InternalSearchOperation internalSearch =
        new InternalSearchOperation(
            conn,
            InternalClientConnection.nextOperationID(),
            InternalClientConnection.nextMessageID(),
            requestControls,
            DN.decode("dc=example,dc=com"),
            SearchScope.WHOLE_SUBTREE,
            DereferencePolicy.NEVER_DEREF_ALIASES,
            0,
            0,
            false,
            SearchFilter.createFilterFromString("(objectClass=person)"),
            null,
            null);

    internalSearch.run();

    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);

    ArrayList<DN> expectedDNOrder = new ArrayList<DN>();
    expectedDNOrder.add(maryJonesDN); // Mary
    expectedDNOrder.add(samZweckDN); // Sam
    expectedDNOrder.add(zorroDN); // No first name

    ArrayList<DN> returnedDNOrder = new ArrayList<DN>();
    for (Entry e : internalSearch.getSearchEntries()) {
      returnedDNOrder.add(e.getDN());
    }

    assertEquals(returnedDNOrder, expectedDNOrder);

    List<Control> responseControls = internalSearch.getResponseControls();
    assertNotNull(responseControls);

    VLVResponseControl vlvResponse = null;
    for (Control c : responseControls) {
      if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL)) {
        if (c instanceof LDAPControl) {
          vlvResponse =
              VLVResponseControl.DECODER.decode(c.isCritical(), ((LDAPControl) c).getValue());
        } else {
          vlvResponse = (VLVResponseControl) c;
        }
      }
    }

    assertNotNull(vlvResponse);
    assertEquals(vlvResponse.getVLVResultCode(), LDAPResultCode.SUCCESS);
    assertEquals(vlvResponse.getTargetPosition(), 10);
    assertEquals(vlvResponse.getContentCount(), 9);
  }
  /**
   * Retrieves a set of virtual attribute rules that may be used for testing purposes. The return
   * data will also include a Boolean value indicating whether the rule would apply to a minimal
   * "o=test" entry.
   *
   * @return A set of virtual attribute rules that may be used for testing purposes.
   * @throws Exception If an unexpected problem occurs.
   */
  @DataProvider(name = "testRules")
  public Object[][] getVirtualAttributeRules() throws Exception {
    EntryDNVirtualAttributeProvider provider = new EntryDNVirtualAttributeProvider();

    LinkedHashSet<DN> dnSet1 = new LinkedHashSet<DN>(1);
    dnSet1.add(DN.decode("o=test"));

    LinkedHashSet<DN> dnSet2 = new LinkedHashSet<DN>(1);
    dnSet2.add(DN.decode("dc=example,dc=com"));

    LinkedHashSet<DN> dnSet3 = new LinkedHashSet<DN>(2);
    dnSet3.add(DN.decode("o=test"));
    dnSet3.add(DN.decode("dc=example,dc=com"));

    LinkedHashSet<DN> groupSet1 = new LinkedHashSet<DN>(1);
    groupSet1.add(DN.decode("cn=Test Group,o=test"));

    LinkedHashSet<DN> groupSet2 = new LinkedHashSet<DN>(1);
    groupSet2.add(DN.decode("cn=Example Group,o=test"));

    LinkedHashSet<DN> groupSet3 = new LinkedHashSet<DN>(2);
    groupSet3.add(DN.decode("cn=Test Group,o=test"));
    groupSet3.add(DN.decode("cn=Example Group,o=test"));

    LinkedHashSet<SearchFilter> filterSet1 = new LinkedHashSet<SearchFilter>(1);
    filterSet1.add(SearchFilter.createFilterFromString("(objectClass=*)"));

    LinkedHashSet<SearchFilter> filterSet2 = new LinkedHashSet<SearchFilter>(1);
    filterSet2.add(SearchFilter.createFilterFromString("(o=test)"));

    LinkedHashSet<SearchFilter> filterSet3 = new LinkedHashSet<SearchFilter>(1);
    filterSet3.add(SearchFilter.createFilterFromString("(foo=bar)"));

    LinkedHashSet<SearchFilter> filterSet4 = new LinkedHashSet<SearchFilter>(2);
    filterSet4.add(SearchFilter.createFilterFromString("(o=test)"));
    filterSet4.add(SearchFilter.createFilterFromString("(foo=bar)"));

    return new Object[][] {
      new Object[] {
        new VirtualAttributeRule(
            entryDNType,
            provider,
            Collections.<DN>emptySet(),
            SearchScope.WHOLE_SUBTREE,
            Collections.<DN>emptySet(),
            Collections.<SearchFilter>emptySet(),
            ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[] {
        new VirtualAttributeRule(
            entryDNType,
            provider,
            dnSet1,
            SearchScope.WHOLE_SUBTREE,
            Collections.<DN>emptySet(),
            Collections.<SearchFilter>emptySet(),
            ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[] {
        new VirtualAttributeRule(
            entryDNType,
            provider,
            dnSet2,
            SearchScope.WHOLE_SUBTREE,
            Collections.<DN>emptySet(),
            Collections.<SearchFilter>emptySet(),
            ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        false
      },
      new Object[] {
        new VirtualAttributeRule(
            entryDNType,
            provider,
            dnSet3,
            SearchScope.WHOLE_SUBTREE,
            Collections.<DN>emptySet(),
            Collections.<SearchFilter>emptySet(),
            ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[] {
        new VirtualAttributeRule(
            entryDNType,
            provider,
            Collections.<DN>emptySet(),
            SearchScope.WHOLE_SUBTREE,
            groupSet1,
            Collections.<SearchFilter>emptySet(),
            ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[] {
        new VirtualAttributeRule(
            entryDNType,
            provider,
            Collections.<DN>emptySet(),
            SearchScope.WHOLE_SUBTREE,
            groupSet2,
            Collections.<SearchFilter>emptySet(),
            ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        false
      },
      new Object[] {
        new VirtualAttributeRule(
            entryDNType,
            provider,
            Collections.<DN>emptySet(),
            SearchScope.WHOLE_SUBTREE,
            groupSet3,
            Collections.<SearchFilter>emptySet(),
            ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[] {
        new VirtualAttributeRule(
            entryDNType,
            provider,
            Collections.<DN>emptySet(),
            SearchScope.WHOLE_SUBTREE,
            Collections.<DN>emptySet(),
            filterSet1,
            ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[] {
        new VirtualAttributeRule(
            entryDNType,
            provider,
            Collections.<DN>emptySet(),
            SearchScope.WHOLE_SUBTREE,
            Collections.<DN>emptySet(),
            filterSet2,
            ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
      new Object[] {
        new VirtualAttributeRule(
            entryDNType,
            provider,
            Collections.<DN>emptySet(),
            SearchScope.WHOLE_SUBTREE,
            Collections.<DN>emptySet(),
            filterSet3,
            ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        false
      },
      new Object[] {
        new VirtualAttributeRule(
            entryDNType,
            provider,
            Collections.<DN>emptySet(),
            SearchScope.WHOLE_SUBTREE,
            Collections.<DN>emptySet(),
            filterSet4,
            ConflictBehavior.VIRTUAL_OVERRIDES_REAL),
        true
      },
    };
  }
  @Test(enabled = true)
  public void testValidRequest() throws Exception {
    final CryptoManagerImpl cm = DirectoryServer.getCryptoManager();
    final String secretMessage = "zyxwvutsrqponmlkjihgfedcba";
    final String cipherTransformationName = "AES/CBC/PKCS5Padding";
    final int cipherKeyLength = 128;

    CryptoManagerImpl.publishInstanceKeyEntryInADS();

    // Initial encryption ensures a cipher key entry is in ADS.
    cm.encrypt(cipherTransformationName, cipherKeyLength, secretMessage.getBytes());

    // Retrieve all uncompromised cipher key entries corresponding to the
    // specified transformation and key length.
    final String baseDNStr // TODO: is this DN defined elsewhere as a constant?
        = "cn=secret keys," + ADSContext.getAdministrationSuffixDN();
    final DN baseDN = DN.decode(baseDNStr);
    final String FILTER_OC_INSTANCE_KEY =
        new StringBuilder("(objectclass=")
            .append(ConfigConstants.OC_CRYPTO_CIPHER_KEY)
            .append(")")
            .toString();
    final String FILTER_NOT_COMPROMISED =
        new StringBuilder("(!(")
            .append(ConfigConstants.ATTR_CRYPTO_KEY_COMPROMISED_TIME)
            .append("=*))")
            .toString();
    final String FILTER_CIPHER_TRANSFORMATION_NAME =
        new StringBuilder("(")
            .append(ConfigConstants.ATTR_CRYPTO_CIPHER_TRANSFORMATION_NAME)
            .append("=")
            .append(cipherTransformationName)
            .append(")")
            .toString();
    final String FILTER_CIPHER_KEY_LENGTH =
        new StringBuilder("(")
            .append(ConfigConstants.ATTR_CRYPTO_KEY_LENGTH_BITS)
            .append("=")
            .append(String.valueOf(cipherKeyLength))
            .append(")")
            .toString();
    final String searchFilter =
        new StringBuilder("(&")
            .append(FILTER_OC_INSTANCE_KEY)
            .append(FILTER_NOT_COMPROMISED)
            .append(FILTER_CIPHER_TRANSFORMATION_NAME)
            .append(FILTER_CIPHER_KEY_LENGTH)
            .append(")")
            .toString();
    final LinkedHashSet<String> requestedAttributes = new LinkedHashSet<String>();
    requestedAttributes.add(ConfigConstants.ATTR_CRYPTO_SYMMETRIC_KEY);
    final InternalClientConnection icc = InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOp =
        icc.processSearch(
            baseDN,
            SearchScope.SINGLE_LEVEL,
            DereferencePolicy.NEVER_DEREF_ALIASES,
            /* size limit */ 0, /* time limit */
            0,
            /* types only */ false,
            SearchFilter.createFilterFromString(searchFilter),
            requestedAttributes);
    assertTrue(0 < searchOp.getSearchEntries().size());

    final InternalClientConnection internalConnection =
        InternalClientConnection.getRootConnection();
    final String instanceKeyID = cm.getInstanceKeyID();
    final AttributeType attrSymmetricKey =
        DirectoryServer.getAttributeType(ConfigConstants.ATTR_CRYPTO_SYMMETRIC_KEY);
    for (Entry e : searchOp.getSearchEntries()) {
      final String symmetricKeyAttributeValue =
          e.getAttributeValue(attrSymmetricKey, DirectoryStringSyntax.DECODER);
      final ByteString requestValue =
          GetSymmetricKeyExtendedOperation.encodeRequestValue(
              symmetricKeyAttributeValue, instanceKeyID);
      final ExtendedOperation extendedOperation =
          internalConnection.processExtendedOperation(
              ServerConstants.OID_GET_SYMMETRIC_KEY_EXTENDED_OP, requestValue);
      assertEquals(extendedOperation.getResultCode(), ResultCode.SUCCESS);
      // The key should be re-wrapped, and hence have a different binary
      // representation....
      final String responseValue = extendedOperation.getResponseValue().toString();
      assertFalse(symmetricKeyAttributeValue.equals(responseValue));
      // ... but the keyIDs should be equal (ideally, the validity of
      // the returned value would be checked by decoding the
      // returned ds-cfg-symmetric-key attribute value; however, there
      // is no non-private method to call.
      assertEquals(responseValue.split(":")[0], symmetricKeyAttributeValue.split(":")[0]);
    }
  }