From f2711b53bdd5f48eaf312981541b61c6e89bdfa1 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 29 Mar 2013 18:06:19 +0000
Subject: [PATCH] Additional change for OPENDJ-354: Implement a RequestHandler which provides an in-memory backend
---
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/MemoryBackendTestCase.java | 99 ++++++++-
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java | 33 +-
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attributes.java | 8
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeFilter.java | 405 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 508 insertions(+), 37 deletions(-)
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeFilter.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeFilter.java
new file mode 100644
index 0000000..f7ee890
--- /dev/null
+++ b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeFilter.java
@@ -0,0 +1,405 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * 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
+ *
+ *
+ * Copyright 2013 ForgeRock AS.
+ */
+
+package org.forgerock.opendj.ldap;
+
+import static org.forgerock.opendj.ldap.Attributes.renameAttribute;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import org.forgerock.opendj.ldap.schema.AttributeType;
+import org.forgerock.opendj.ldap.schema.ObjectClass;
+import org.forgerock.opendj.ldap.schema.Schema;
+
+/**
+ * A configurable factory for filtering the attributes exposed by an entry. An
+ * {@code AttributeFilter} is useful for performing fine-grained access control,
+ * selecting attributes based on search request criteria, and selecting
+ * attributes based on post- and pre- read request control criteria.
+ * <p>
+ * In cases where methods accept a string based list of attribute descriptions,
+ * the following special attribute descriptions are permitted:
+ * <ul>
+ * <li><b>*</b> - include all user attributes
+ * <li><b>+</b> - include all operational attributes
+ * <li><b>1.1</b> - exclude all attributes
+ * <li><b>@<i>objectclass</i></b> - include all attributes identified by the
+ * named object class.
+ * </ul>
+ */
+public final class AttributeFilter {
+ // TODO: exclude specific attributes, matched values, types only, custom predicates, etc.
+ private boolean includeAllOperationalAttributes;
+ private boolean includeAllUserAttributes;
+
+ /**
+ * Use a map so that we can perform membership checks as well as recover the
+ * user requested attribute description.
+ */
+ private Map<AttributeDescription, AttributeDescription> requestedAttributes = Collections
+ .emptyMap();
+
+ /**
+ * Creates a new attribute filter which will include all user attributes but
+ * no operational attributes.
+ */
+ public AttributeFilter() {
+ includeAllUserAttributes = true;
+ includeAllOperationalAttributes = false;
+ }
+
+ /**
+ * Creates a new attribute filter which will include the attributes
+ * identified by the provided search request attribute list. Attributes will
+ * be decoded using the default schema. See the class description for
+ * details regarding the types of supported attribute description.
+ *
+ * @param attributeDescriptions
+ * The names of the attributes to be included with each entry.
+ */
+ public AttributeFilter(final Collection<String> attributeDescriptions) {
+ this(attributeDescriptions, Schema.getDefaultSchema());
+ }
+
+ /**
+ * Creates a new attribute filter which will include the attributes
+ * identified by the provided search request attribute list. Attributes will
+ * be decoded using the provided schema. See the class description for
+ * details regarding the types of supported attribute description.
+ *
+ * @param attributeDescriptions
+ * The names of the attributes to be included with each entry.
+ * @param schema
+ * The schema The schema to use when parsing attribute
+ * descriptions and object class names.
+ */
+ public AttributeFilter(final Collection<String> attributeDescriptions, final Schema schema) {
+ if (attributeDescriptions == null || attributeDescriptions.isEmpty()) {
+ // Fast-path for common case.
+ includeAllUserAttributes = true;
+ includeAllOperationalAttributes = false;
+ } else {
+ for (final String attribute : attributeDescriptions) {
+ includeAttribute(attribute, schema);
+ }
+ }
+ }
+
+ /**
+ * Creates a new attribute filter which will include the attributes
+ * identified by the provided search request attribute list. Attributes will
+ * be decoded using the default schema. See the class description for
+ * details regarding the types of supported attribute description.
+ *
+ * @param attributeDescriptions
+ * The names of the attributes to be included with each entry.
+ */
+ public AttributeFilter(final String... attributeDescriptions) {
+ this(Arrays.asList(attributeDescriptions));
+ }
+
+ /**
+ * Returns a modifiable filtered copy of the provided entry.
+ *
+ * @param entry
+ * The entry to be filtered and copied.
+ * @return The modifiable filtered copy of the provided entry.
+ */
+ public Entry filteredCopyOf(final Entry entry) {
+ return new LinkedHashMapEntry(filteredViewOf(entry));
+ }
+
+ /**
+ * Returns an unmodifiable filtered view of the provided entry. The returned
+ * entry supports all operations except those which modify the contents of
+ * the entry.
+ *
+ * @param entry
+ * The entry to be filtered.
+ * @return The unmodifiable filtered view of the provided entry.
+ */
+ public Entry filteredViewOf(final Entry entry) {
+ return new AbstractEntry() {
+
+ @Override
+ public boolean addAttribute(final Attribute attribute,
+ final Collection<? super ByteString> duplicateValues) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Entry clearAttributes() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Iterable<Attribute> getAllAttributes() {
+ /*
+ * Unfortunately we cannot efficiently re-use the iterators in
+ * {@code Iterators} because we need to transform and filter in
+ * a single step. Transformation is required in order to ensure
+ * that we return an attribute whose name is the same as the one
+ * requested by the user.
+ */
+ return new Iterable<Attribute>() {
+ private boolean hasNextMustIterate = true;
+ private final Iterator<Attribute> iterator = entry.getAllAttributes()
+ .iterator();
+ private Attribute next = null;
+
+ @Override
+ public Iterator<Attribute> iterator() {
+ return new Iterator<Attribute>() {
+ @Override
+ public boolean hasNext() {
+ if (hasNextMustIterate) {
+ hasNextMustIterate = false;
+ while (iterator.hasNext()) {
+ final Attribute attribute = iterator.next();
+ final AttributeDescription ad =
+ attribute.getAttributeDescription();
+ final AttributeType at = ad.getAttributeType();
+ final AttributeDescription requestedAd =
+ requestedAttributes.get(ad);
+ if (requestedAd != null) {
+ next = renameAttribute(attribute, requestedAd);
+ return true;
+ } else if ((at.isOperational() && includeAllOperationalAttributes)
+ || (!at.isOperational() && includeAllUserAttributes)) {
+ next = attribute;
+ return true;
+ }
+ }
+ next = null;
+ return false;
+ } else {
+ return next != null;
+ }
+ }
+
+ @Override
+ public Attribute next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ hasNextMustIterate = true;
+ return next;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ @Override
+ public Attribute getAttribute(final AttributeDescription attributeDescription) {
+ /*
+ * It is tempting to filter based on the passed in attribute
+ * description, but we may get inaccurate results due to
+ * placeholder attribute names.
+ */
+ final Attribute attribute = entry.getAttribute(attributeDescription);
+ if (attribute != null) {
+ final AttributeDescription ad = attribute.getAttributeDescription();
+ final AttributeType at = ad.getAttributeType();
+ final AttributeDescription requestedAd = requestedAttributes.get(ad);
+ if (requestedAd != null) {
+ return renameAttribute(attribute, requestedAd);
+ } else if ((at.isOperational() && includeAllOperationalAttributes)
+ || (!at.isOperational() && includeAllUserAttributes)) {
+ return attribute;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ @SuppressWarnings("unused")
+ public int getAttributeCount() {
+ int i = 0;
+ for (final Attribute attribute : getAllAttributes()) {
+ i++;
+ }
+ return i;
+ }
+
+ @Override
+ public DN getName() {
+ return entry.getName();
+ }
+
+ @Override
+ public Entry setName(final DN dn) {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ /**
+ * Specifies whether or not all operational attributes should be included in
+ * filtered entries. By default operational attributes are not included.
+ *
+ * @param include
+ * {@code true} if operational attributes should be included in
+ * filtered entries.
+ * @return A reference to this attribute filter.
+ */
+ public AttributeFilter includeAllOperationalAttributes(final boolean include) {
+ this.includeAllOperationalAttributes = include;
+ return this;
+ }
+
+ /**
+ * Specifies whether or not all user attributes should be included in
+ * filtered entries. By default user attributes are included.
+ *
+ * @param include
+ * {@code true} if user attributes should be included in filtered
+ * entries.
+ * @return A reference to this attribute filter.
+ */
+ public AttributeFilter includeAllUserAttributes(final boolean include) {
+ this.includeAllUserAttributes = include;
+ return this;
+ }
+
+ /**
+ * Specifies that the named attribute should be included in filtered
+ * entries.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be included in filtered entries.
+ * @return A reference to this attribute filter.
+ */
+ public AttributeFilter includeAttribute(final AttributeDescription attributeDescription) {
+ allocatedRequestedAttributes();
+ requestedAttributes.put(attributeDescription, attributeDescription);
+ return this;
+ }
+
+ /**
+ * Specifies that the named attribute should be included in filtered
+ * entries. The attribute will be decoded using the default schema. See the
+ * class description for details regarding the types of supported attribute
+ * description.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be included in filtered entries.
+ * @return A reference to this attribute filter.
+ */
+ public AttributeFilter includeAttribute(final String attributeDescription) {
+ return includeAttribute(attributeDescription, Schema.getDefaultSchema());
+ }
+
+ /**
+ * Specifies that the named attribute should be included in filtered
+ * entries. The attribute will be decoded using the provided schema. See the
+ * class description for details regarding the types of supported attribute
+ * description.
+ *
+ * @param attributeDescription
+ * The name of the attribute to be included in filtered entries.
+ * @param schema
+ * The schema The schema to use when parsing attribute
+ * descriptions and object class names.
+ * @return A reference to this attribute filter.
+ */
+ public AttributeFilter includeAttribute(final String attributeDescription, final Schema schema) {
+ if (attributeDescription.equals("*")) {
+ includeAllUserAttributes = true;
+ } else if (attributeDescription.equals("+")) {
+ includeAllOperationalAttributes = true;
+ } else if (attributeDescription.equals("1.1")) {
+ // Ignore - by default no attributes are included.
+ } else if (attributeDescription.startsWith("@") && attributeDescription.length() > 1) {
+ final String objectClassName = attributeDescription.substring(1);
+ final ObjectClass objectClass = schema.getObjectClass(objectClassName);
+ if (objectClass != null) {
+ allocatedRequestedAttributes();
+ for (final AttributeType at : objectClass.getRequiredAttributes()) {
+ final AttributeDescription ad = AttributeDescription.create(at);
+ requestedAttributes.put(ad, ad);
+ }
+ for (final AttributeType at : objectClass.getOptionalAttributes()) {
+ final AttributeDescription ad = AttributeDescription.create(at);
+ requestedAttributes.put(ad, ad);
+ }
+ }
+ } else {
+ allocatedRequestedAttributes();
+ final AttributeDescription ad =
+ AttributeDescription.valueOf(attributeDescription, schema);
+ requestedAttributes.put(ad, ad);
+ }
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ if (!includeAllOperationalAttributes && !includeAllUserAttributes
+ && requestedAttributes.isEmpty()) {
+ return "1.1";
+ } else {
+ boolean isFirst = true;
+ final StringBuilder builder = new StringBuilder();
+ if (includeAllUserAttributes) {
+ builder.append('*');
+ isFirst = false;
+ }
+ if (includeAllOperationalAttributes) {
+ if (!isFirst) {
+ builder.append(", ");
+ }
+ builder.append('+');
+ isFirst = false;
+ }
+ for (final AttributeDescription requestedAttribute : requestedAttributes.keySet()) {
+ if (!isFirst) {
+ builder.append(", ");
+ }
+ builder.append(requestedAttribute.toString());
+ isFirst = false;
+ }
+ return builder.toString();
+ }
+ }
+
+ private void allocatedRequestedAttributes() {
+ if (requestedAttributes.isEmpty()) {
+ requestedAttributes = new HashMap<AttributeDescription, AttributeDescription>();
+ }
+ }
+}
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attributes.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attributes.java
index 9d3d416..4514340 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attributes.java
+++ b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attributes.java
@@ -496,7 +496,13 @@
public static final Attribute renameAttribute(final Attribute attribute,
final AttributeDescription attributeDescription) {
Validator.ensureNotNull(attribute, attributeDescription);
- return new RenamedAttribute(attribute, attributeDescription);
+
+ // Optimize for the case where no renaming is required.
+ if (attribute.getAttributeDescription() == attributeDescription) {
+ return attribute;
+ } else {
+ return new RenamedAttribute(attribute, attributeDescription);
+ }
}
/**
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java
index 92020ff..794ee02 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java
+++ b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java
@@ -33,7 +33,6 @@
import static org.forgerock.opendj.ldap.responses.Responses.newSearchResultEntry;
import java.io.IOException;
-import java.util.Collection;
import java.util.NavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -324,10 +323,12 @@
final SearchScope scope = request.getScope();
final Filter filter = request.getFilter();
final Matcher matcher = filter.matcher(schema);
+ final AttributeFilter attributeFilter =
+ new AttributeFilter(request.getAttributes(), schema);
if (scope.equals(SearchScope.BASE_OBJECT)) {
if (matcher.matches(baseEntry).toBoolean()) {
- sendEntry(request, resultHandler, baseEntry);
+ sendEntry(attributeFilter, resultHandler, baseEntry);
}
} else if (scope.equals(SearchScope.SINGLE_LEVEL)) {
final NavigableMap<DN, Entry> subtree =
@@ -338,7 +339,7 @@
final DN childDN = entry.getName();
if (childDN.isChildOf(dn)) {
if (matcher.matches(entry).toBoolean()
- && !sendEntry(request, resultHandler, entry)) {
+ && !sendEntry(attributeFilter, resultHandler, entry)) {
// Caller has asked to stop sending results.
break;
}
@@ -351,7 +352,7 @@
// Check for cancellation.
requestContext.checkIfCancelled(false);
if (matcher.matches(entry).toBoolean()
- && !sendEntry(request, resultHandler, entry)) {
+ && !sendEntry(attributeFilter, resultHandler, entry)) {
// Caller has asked to stop sending results.
break;
}
@@ -378,8 +379,10 @@
if (preRead.isCritical() && before == null) {
throw newErrorResult(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
} else {
- result.addControl(PreReadResponseControl.newControl(filter(before, preRead
- .getAttributes())));
+ final AttributeFilter filter =
+ new AttributeFilter(preRead.getAttributes(), schema);
+ result.addControl(PreReadResponseControl.newControl(filter
+ .filteredViewOf(before)));
}
}
@@ -390,8 +393,10 @@
if (postRead.isCritical() && after == null) {
throw newErrorResult(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
} else {
- result.addControl(PostReadResponseControl.newControl(filter(after, postRead
- .getAttributes())));
+ final AttributeFilter filter =
+ new AttributeFilter(postRead.getAttributes(), schema);
+ result.addControl(PostReadResponseControl.newControl(filter
+ .filteredViewOf(after)));
}
}
return result;
@@ -400,11 +405,6 @@
}
}
- private Entry filter(final Entry entry, final Collection<String> attributes) {
- // FIXME: attribute filtering not supported yet.
- return entry;
- }
-
private BindResult getBindResult(final BindRequest request, final Entry before,
final Entry after) throws ErrorResultException {
return addResultControls(request, before, after, newBindResult(ResultCode.SUCCESS));
@@ -453,9 +453,8 @@
+ "' does not exist");
}
- private boolean sendEntry(final SearchRequest request, final SearchResultHandler resultHandler,
- final Entry entry) {
- return resultHandler
- .handleEntry(newSearchResultEntry(filter(entry, request.getAttributes())));
+ private boolean sendEntry(final AttributeFilter filter,
+ final SearchResultHandler resultHandler, final Entry entry) {
+ return resultHandler.handleEntry(newSearchResultEntry(filter.filteredViewOf(entry)));
}
}
diff --git a/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/MemoryBackendTestCase.java b/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/MemoryBackendTestCase.java
index c695030..cdb7204 100644
--- a/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/MemoryBackendTestCase.java
+++ b/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/MemoryBackendTestCase.java
@@ -221,20 +221,6 @@
}
@Test
- public void testModifyBindPostRead() throws Exception {
- final Connection connection = getConnection();
- assertThat(
- connection.modify(
- newModifyRequest("dn: dc=example,dc=com", "changetype: modify",
- "add: description", "description: test description").addControl(
- PostReadRequestControl.newControl(true))).getControl(
- PostReadResponseControl.DECODER, new DecodeOptions()).getEntry())
- .isEqualTo(
- valueOfLDIFEntry("dn: dc=example,dc=com", "objectClass: domain",
- "objectClass: top", "dc: example", "description: test description"));
- }
-
- @Test
public void testModifyIncrement() throws Exception {
final Connection connection = getConnection();
connection.modify("dn: dc=example,dc=com", "changetype: modify", "add: integer",
@@ -288,16 +274,45 @@
}
@Test
- public void testModifyPreRead() throws Exception {
+ public void testModifyPostRead() throws Exception {
final Connection connection = getConnection();
assertThat(
connection.modify(
newModifyRequest("dn: dc=example,dc=com", "changetype: modify",
"add: description", "description: test description").addControl(
- PreReadRequestControl.newControl(true))).getControl(
- PreReadResponseControl.DECODER, new DecodeOptions()).getEntry()).isEqualTo(
- valueOfLDIFEntry("dn: dc=example,dc=com", "objectClass: domain",
- "objectClass: top", "dc: example"));
+ PostReadRequestControl.newControl(true))).getControl(
+ PostReadResponseControl.DECODER, new DecodeOptions()).getEntry())
+ .isEqualTo(
+ valueOfLDIFEntry("dn: dc=example,dc=com", "objectClass: domain",
+ "objectClass: top", "dc: example", "description: test description"));
+ }
+
+ @Test
+ public void testModifyPostReadAttributesSelected() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(
+ connection.modify(
+ newModifyRequest("dn: dc=example,dc=com", "changetype: modify",
+ "add: description", "description: test description").addControl(
+ PostReadRequestControl.newControl(true, "dc", "entryDN")))
+ .getControl(PostReadResponseControl.DECODER, new DecodeOptions())
+ .getEntry()).isEqualTo(
+ valueOfLDIFEntry("dn: dc=example,dc=com", "dc: example",
+ "entryDN: dc=example,dc=com"));
+ }
+
+ @Test
+ public void testModifyPreReadAttributesSelected() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(
+ connection.modify(
+ newModifyRequest("dn: dc=example,dc=com", "changetype: modify",
+ "add: description", "description: test description").addControl(
+ PreReadRequestControl.newControl(true, "dc", "entryDN")))
+ .getControl(PreReadResponseControl.DECODER, new DecodeOptions()).getEntry())
+ .isEqualTo(
+ valueOfLDIFEntry("dn: dc=example,dc=com", "dc: example",
+ "entryDN: dc=example,dc=com"));
}
@Test(expectedExceptions = ConstraintViolationException.class)
@@ -313,6 +328,48 @@
}
@Test
+ public void testSearchAttributesOperational() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(connection.readEntry("uid=test1,ou=People,dc=example,dc=com", "+")).isEqualTo(
+ valueOfLDIFEntry("dn: uid=test1,ou=People,dc=example,dc=com",
+ "entryDN: uid=test1,ou=people,dc=example,dc=com",
+ "entryUUID: fc252fd9-b982-3ed6-b42a-c76d2546312c"));
+ }
+
+ @Test
+ public void testSearchAttributesSelected() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(connection.readEntry("uid=test1,ou=People,dc=example,dc=com", "uid", "entryDN"))
+ .isEqualTo(
+ valueOfLDIFEntry("dn: uid=test1,ou=People,dc=example,dc=com", "uid: test1",
+ "entryDN: uid=test1,ou=People,dc=example,dc=com"));
+ }
+
+ @Test
+ public void testSearchAttributesRenamed() throws Exception {
+ final Connection connection = getConnection();
+ final Entry entry =
+ connection.readEntry("uid=test1,ou=People,dc=example,dc=com", "commonName",
+ "ENTRYDN");
+ assertThat(entry)
+ .isEqualTo(
+ valueOfLDIFEntry("dn: uid=test1,ou=People,dc=example,dc=com",
+ "commonName: test user 1",
+ "ENTRYDN: uid=test1,ou=People,dc=example,dc=com"));
+ assertThat(entry.getAttribute("cn").getAttributeDescriptionAsString()).isEqualTo(
+ "commonName");
+ assertThat(entry.getAttribute("entryDN").getAttributeDescriptionAsString()).isEqualTo(
+ "ENTRYDN");
+ }
+
+ @Test
+ public void testSearchAttributesUser() throws Exception {
+ final Connection connection = getConnection();
+ assertThat(connection.readEntry("uid=test1,ou=People,dc=example,dc=com", "*")).isEqualTo(
+ getUser1Entry());
+ }
+
+ @Test
public void testSearchBase() throws Exception {
final Connection connection = getConnection();
assertThat(connection.readEntry("dc=example,dc=com")).isEqualTo(
@@ -410,6 +467,8 @@
"objectClass: domain",
"objectClass: top",
"dc: example",
+ "entryDN: dc=example,dc=com",
+ "entryUUID: fc252fd9-b982-3ed6-b42a-c76d2546312c",
"",
"dn: ou=People,dc=example,dc=com",
"objectClass: organizationalunit",
@@ -423,6 +482,8 @@
"userpassword: password",
"cn: test user 1",
"sn: user 1",
+ "entryDN: uid=test1,ou=people,dc=example,dc=com",
+ "entryUUID: fc252fd9-b982-3ed6-b42a-c76d2546312c",
"",
"dn: uid=test2,ou=People,dc=example,dc=com",
"objectClass: top",
--
Gitblit v1.10.0