LDAP-142: Added a couple utility methods for working with SingleContextSource. Also cleaned up OpenLdap integration tests.

This commit is contained in:
Mattias Hellborg Arthursson
2013-09-10 11:40:53 +02:00
parent 14bf8b48a2
commit 8b6f586fd5
19 changed files with 661 additions and 324 deletions

View File

@@ -47,6 +47,8 @@ public class PagedResultsDirContextProcessor extends AbstractFallbackRequestAndR
private int resultSize;
private boolean more = true;
/**
* Constructs a new instance. This constructor should be used when
* performing the first paged search operation, when no other results have
@@ -122,6 +124,18 @@ public class PagedResultsDirContextProcessor extends AbstractFallbackRequestAndR
new Object[] {pageSize, actualCookie, critical});
}
/**
* Check whether there are more results to retrieved. When there are no more results to retrieve,
* this is indicated by a <code>null</code> cookie being returned from the server.
* When this happen, the internal status will set to false.
*
* @return <code>true</code> if there are more results to retrieve, <code>false</code> otherwise.
* @since 2.0
*/
public boolean hasMore() {
return more;
}
/*
* @seeorg.springframework.ldap.control.
* AbstractFallbackRequestAndResponseControlDirContextProcessor
@@ -129,6 +143,9 @@ public class PagedResultsDirContextProcessor extends AbstractFallbackRequestAndR
*/
protected void handleResponse(Object control) {
byte[] result = (byte[]) invokeMethod("getCookie", responseControlClass, control);
if(result == null) {
more = false;
}
this.cookie = new PagedResultsCookie(result);
this.resultSize = (Integer) invokeMethod("getResultSize", responseControlClass, control);
}

View File

@@ -103,7 +103,7 @@ public class SortControlDirContextProcessor extends AbstractFallbackRequestAndRe
* #createRequestControl()
*/
public Control createRequestControl() {
return super.createRequestControl(new Class[] { String[].class, boolean.class }, new Object[] {
return super.createRequestControl(new Class<?>[] { String[].class, boolean.class }, new Object[] {
new String[] { sortKey }, critical});
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2005-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap.core.support;
import org.springframework.ldap.core.LdapOperations;
/**
* Callback interface to be used together with {@link SingleContextSource}.
*
* @author Mattias Hellborg Arthursson
* @since 2.0
* @see SingleContextSource#doWithSingleContext(org.springframework.ldap.core.ContextSource, LdapOperationsCallback)
* @see SingleContextSource#doWithSingleContext(org.springframework.ldap.core.ContextSource, LdapOperationsCallback, boolean, boolean, boolean)
*/
public interface LdapOperationsCallback<T> {
/**
* Perform a sequence of LDAP operations on the supplied LdapOperations instance. The underlying DirContext
* that the operations will work on is guaranteed to always be exact same instance during the lifetime of this
* method.
*
* @param operations the LdapOperations instance to perform operations on.
* @return The aggregated result of all the performed operations.
*/
T doWithLdapOperations(LdapOperations operations);
}

View File

@@ -21,6 +21,7 @@ import org.springframework.beans.factory.DisposableBean;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextProxy;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.support.LdapUtils;
import javax.naming.directory.DirContext;
@@ -39,6 +40,9 @@ import java.lang.reflect.Proxy;
public class SingleContextSource implements ContextSource, DisposableBean {
private static final Log log = LogFactory.getLog(SingleContextSource.class);
private static final boolean DONT_USE_READ_ONLY = false;
private static final boolean DONT_IGNORE_PARTIAL_RESULT = false;
private static final boolean DONT_IGNORE_NAME_NOT_FOUND = false;
private final DirContext ctx;
@@ -94,6 +98,66 @@ public class SingleContextSource implements ContextSource, DisposableBean {
}
}
/**
* Construct a SingleContextSource and execute the LdapOperationsCallback using the created instance.
* This makes sure the same connection will be used for all operations inside the LdapOperationsCallback,
* which is particularly useful when working with e.g. Paged Results as these typically require the exact
* same connection to be used for all requests involving the same cookie.
* The SingleContextSource instance will be properly disposed of once the operation has been completed.
* <p>By default, the {@link org.springframework.ldap.core.ContextSource#getReadWriteContext()} method
* will be used to create the DirContext instance to operate on.</p>
*
* @param contextSource The target ContextSource to retrieve a DirContext from.
* @param callback the callback to perform the Ldap operations.
* @return the result returned from the callback.
* @see #doWithSingleContext(org.springframework.ldap.core.ContextSource, LdapOperationsCallback, boolean, boolean, boolean)
* @since 2.0
*/
public static <T> T doWithSingleContext(ContextSource contextSource, LdapOperationsCallback<T> callback) {
return doWithSingleContext(contextSource, callback, DONT_USE_READ_ONLY, DONT_IGNORE_PARTIAL_RESULT, DONT_IGNORE_NAME_NOT_FOUND);
}
/**
* Construct a SingleContextSource and execute the LdapOperationsCallback using the created instance.
* This makes sure the same connection will be used for all operations inside the LdapOperationsCallback,
* which is particularly useful when working with e.g. Paged Results as these typically require the exact
* same connection to be used for all requests involving the same cookie..
* The SingleContextSource instance will be properly disposed of once the operation has been completed.
*
* @param contextSource The target ContextSource to retrieve a DirContext from
* @param callback the callback to perform the Ldap operations
* @param useReadOnly if <code>true</code>, use the {@link org.springframework.ldap.core.ContextSource#getReadOnlyContext()}
* method on the target ContextSource to get the actual DirContext instance, if <code>false</code>,
* use {@link org.springframework.ldap.core.ContextSource#getReadWriteContext()}.
* @param ignorePartialResultException Used for populating this property on the created LdapTemplate instance.
* @param ignoreNameNotFoundException Used for populating this property on the created LdapTemplate instance.
* @return the result returned from the callback.
* @since 2.0
*/
public static <T> T doWithSingleContext(ContextSource contextSource,
LdapOperationsCallback<T> callback,
boolean useReadOnly,
boolean ignorePartialResultException,
boolean ignoreNameNotFoundException) {
SingleContextSource singleContextSource;
if (useReadOnly) {
singleContextSource = new SingleContextSource(contextSource.getReadOnlyContext());
} else {
singleContextSource = new SingleContextSource(contextSource.getReadWriteContext());
}
LdapTemplate ldapTemplate = new LdapTemplate(singleContextSource);
ldapTemplate.setIgnorePartialResultException(ignorePartialResultException);
ldapTemplate.setIgnoreNameNotFoundException(ignoreNameNotFoundException);
try {
return callback.doWithLdapOperations(ldapTemplate);
} finally {
singleContextSource.destroy();
}
}
/**
* A proxy for DirContext forwarding all operation to the target DirContext,
* but making sure that no <code>close</code> operations will be performed.

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2005-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap.core.support;
import org.junit.Before;
import org.junit.Test;
import org.mockito.internal.util.reflection.Whitebox;
import org.springframework.ldap.core.ContextExecutor;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.LdapOperations;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import java.lang.reflect.Proxy;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
/**
* @author Mattias Hellborg Arthursson
*/
public class SingleContextSourceTest {
private ContextSource contextSourceMock;
private DirContext dirContextMock;
@Before
public void prepareMocks() {
contextSourceMock = mock(ContextSource.class);
dirContextMock = mock(DirContext.class);
}
@Test
public void testDoWithSingleContext() {
when(contextSourceMock.getReadWriteContext()).thenReturn(dirContextMock);
verifyNoMoreInteractions(contextSourceMock);
SingleContextSource.doWithSingleContext(contextSourceMock, new LdapOperationsCallback<Object>() {
@Override
public Object doWithLdapOperations(LdapOperations operations) {
operations.executeReadOnly(new ContextExecutor<Object>() {
@Override
public Object executeWithContext(DirContext ctx) throws NamingException {
Object targetContex = Whitebox.getInternalState(Proxy.getInvocationHandler(ctx), "target");
assertSame(dirContextMock, targetContex);
return false;
}
});
// Second operation will have retrieved new DirContext from the SingleContextSource.
// It should be the same instance.
operations.executeReadOnly(new ContextExecutor<Object>() {
@Override
public Object executeWithContext(DirContext ctx) throws NamingException {
Object targetContex = Whitebox.getInternalState(Proxy.getInvocationHandler(ctx), "target");
assertSame(dirContextMock, targetContex);
return false;
}
});
return null;
}
});
}
}

View File

@@ -43,19 +43,19 @@
SearchControls controls, NameClassPairCallbackHandler handler, DirContextProcessor processor)
public void search(String base, String filter,
SearchControls controls, NameClassPairCallbackHandler handler, DirContextProcessor processor)
public void search(Name base, String filter,
SearchControls controls, AttributesMapper mapper, DirContextProcessor processor)
public void search(String base, String filter,
SearchControls controls, AttributesMapper mapper, DirContextProcessor processor)
public void search(Name base, String filter,
SearchControls controls, ContextMapper mapper, DirContextProcessor processor)
public void search(String base, String filter,
SearchControls controls, ContextMapper mapper, DirContextProcessor processor)</programlisting>
SearchControls controls, NameClassPairCallbackHandler handler, DirContextProcessor processor)
public void search(Name base, String filter,
SearchControls controls, AttributesMapper mapper, DirContextProcessor processor)
public void search(String base, String filter,
SearchControls controls, AttributesMapper mapper, DirContextProcessor processor)
public void search(Name base, String filter,
SearchControls controls, ContextMapper mapper, DirContextProcessor processor)
public void search(String base, String filter,
SearchControls controls, ContextMapper mapper, DirContextProcessor processor)</programlisting>
</informalexample>
</sect1>
@@ -158,43 +158,49 @@ public class MyCoolRequestControl extends AbstractRequestControlDirContextProces
<para>Spring LDAP provides support for paged results by leveraging the
concept for pre- and postprocessing of an <literal>LdapContext</literal> that was discussed
in the previous sections. It does so by providing two classes:
<literal>PagedResultsRequestControl</literal> and
<literal>PagedResultsCookie</literal>. The
<literal>PagedResultsRequestControl</literal> class creates a
in the previous sections. It does so using the class
<literal>PagedResultsDirContextProcessor</literal>. The
<literal>PagedResultsDirContextProcessor</literal> class creates a
<literal>PagedResultsControl</literal> with the requested page size and
adds it to the <literal>LdapContext</literal>. After the search, it gets
the <literal>PagedResultsResponseControl</literal> and retrieves two
pieces of information from it: the estimated total result size and a
cookie. This cookie is a byte array containing information that the server
needs the next time it is called with a
<literal>PagedResultsControl</literal>. In order to make it easy to store
this cookie between searches, Spring LDAP provides the wrapper class
<literal>PagedResultsCookie</literal>.</para>
the <literal>PagedResultsResponseControl</literal> and retrieves the paged results
cookie, which is needed to keep the context between consecutive paged results requests.</para>
<para>Below is an example of how the paged search results functionality may
be used:</para>
<example>
<title>Paged results using <literal>PagedResultsRequestControl</literal></title>
<title>Paged results using <literal>PagedResultsDirContextProcessor</literal></title>
<programlisting>public PagedResult getAllPersons(PagedResultsCookie cookie) {
PagedResultsRequestControl control = new PagedResultsRequestControl(PAGE_SIZE, cookie);
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
List persons = ldapTemplate.search("", "objectclass=person", searchControls, control);
return new PagedResult(persons, control.getCookie());
}</programlisting>
<programlisting>
public List&lt;String&gt; getAllPersonNames() {
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
final PagedResultsDirContextProcessor processor = new PagedResultsDirContextProcessor(PAGE_SIZE);
return SingleContextSource.doWithSingleContext(contextSource, new LdapOperationsCallback&lt;List&lt;String&gt;&gt;() {
@Override
public List&lt;String&gt; doWithLdapOperations(LdapOperations operations) {
List&lt;String&gt; result = new LinkedList&lt;String&gt;();
do {
List&lt;String&gt; oneResult = operations.search(
"ou=People",
"(&amp;(objectclass=person))",
searchControls,
CN_ATTRIBUTES_MAPPER,
processor);
result.addAll(oneResult);
} while(processor.hasMore());
return result;
}
});
}
</programlisting>
</example>
<para>In the first call to this method, <literal>null</literal> will be supplied as
the cookie parameter. On subsequent calls the client will need to supply the cookie from
the last search (returned wrapped in the <literal>PagedResult</literal>) each time the
method is called. When the actual cookie is <literal>null</literal> (i.e.
<literal>pagedResult.getCookie().getCookie()</literal> returns <literal>null</literal>),
the last batch has been returned from the search.</para>
<note>In order for a paged results cookie to continue being valid, it is imperative that the same underlying
connection is used for each paged results call. This can be accomplished using the <literal>SingleContextSource</literal>,
as demonstrated in the example.</note>
</sect1>
</chapter>

View File

@@ -29,7 +29,7 @@ import java.util.Arrays;
*
* @author Mattias Hellborg Arthursson
*/
public class AttributeCheckAttributesMapper implements AttributesMapper {
public class AttributeCheckAttributesMapper implements AttributesMapper<Object> {
private String[] expectedAttributes = new String[0];
private String[] expectedValues = new String[0];;
@@ -47,8 +47,8 @@ public class AttributeCheckAttributesMapper implements AttributesMapper {
Assert.assertEquals(expectedValues[i], attribute.get());
}
for (int i = 0; i < absentAttributes.length; i++) {
Assert.assertNull(attributes.get(absentAttributes[i]));
for (String absentAttribute : absentAttributes) {
Assert.assertNull(attributes.get(absentAttribute));
}
return null;

View File

@@ -28,14 +28,14 @@ import java.util.Arrays;
*
* @author Mattias Hellborg Arthursson
*/
public class AttributeCheckContextMapper implements ContextMapper {
public class AttributeCheckContextMapper implements ContextMapper<DirContextAdapter> {
private String[] expectedAttributes = new String[0];
private String[] expectedValues = new String[0];
private String[] absentAttributes = new String[0];
public Object mapFromContext(Object ctx) {
public DirContextAdapter mapFromContext(Object ctx) {
DirContextAdapter adapter = (DirContextAdapter) ctx;
Assert.assertEquals("Values and attributes need to have the same length ",
expectedAttributes.length, expectedValues.length);

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2005-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
/**
* @author Mattias Hellborg Arthursson
*/
public class AllMatchHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
}

View File

@@ -1,182 +0,0 @@
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap.itest.control;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.control.PagedResultsCookie;
import org.springframework.ldap.control.PagedResultsRequestControl;
import org.springframework.ldap.control.Person;
import org.springframework.ldap.core.CollectingNameClassPairCallbackHandler;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.ContextMapperCallbackHandler;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.SearchExecutor;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import javax.naming.Name;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import java.util.List;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
/**
* Tests the paged search result capability of LdapTemplate.
* <p>
* Note: Currently, ApacheDS does not support paged results controls, so this
* test must be run under another directory server, for example OpenLdap. This
* test will not run under ApacheDS, and the other integration tests assume
* ApacheDS and will probably not run under OpenLdap.
*
* @author Ulrik Sandberg
*/
@ContextConfiguration(locations = { "/conf/ldapTemplateTestContext.xml" })
public class LdapTemplatePagedSearchITest extends AbstractJUnit4SpringContextTests {
private static final Name BASE = DistinguishedName.EMPTY_PATH;
private static final String FILTER_STRING = "(&(objectclass=person))";
@Autowired
private LdapTemplate tested;
private CollectingNameClassPairCallbackHandler callbackHandler;
private SearchControls searchControls;
@Before
public void onSetUp() throws Exception {
PersonContextMapper mapper = new PersonContextMapper();
callbackHandler = new ContextMapperCallbackHandler(mapper);
searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningObjFlag(true);
}
@After
public void onTearDown() throws Exception {
callbackHandler = null;
tested = null;
searchControls = null;
}
@Test
public void testSearch_PagedResult() {
SearchExecutor searchExecutor = new SearchExecutor() {
public NamingEnumeration executeSearch(DirContext ctx) throws NamingException {
return ctx.search(BASE, FILTER_STRING, searchControls);
}
};
Person person;
List list;
PagedResultsCookie cookie;
PagedResultsRequestControl requestControl;
// Prepare for first search
requestControl = new PagedResultsRequestControl(3);
tested.search(searchExecutor, callbackHandler, requestControl);
cookie = requestControl.getCookie();
assertNotNull("Cookie should not be null yet", cookie.getCookie());
list = callbackHandler.getList();
assertEquals(3, list.size());
person = (Person) list.get(0);
assertEquals("Some Person", person.getFullName());
assertEquals("+46 555-123456", person.getPhone());
person = (Person) list.get(1);
assertEquals("Some Person2", person.getFullName());
assertEquals("+46 555-654321", person.getPhone());
person = (Person) list.get(2);
assertEquals("Some Person3", person.getFullName());
assertEquals("+46 555-123654", person.getPhone());
// Prepare for second and last search
requestControl = new PagedResultsRequestControl(3, cookie);
tested.search(searchExecutor, callbackHandler, requestControl);
cookie = requestControl.getCookie();
assertNull("Cookie should be null now", cookie.getCookie());
assertEquals(5, list.size());
person = (Person) list.get(3);
assertEquals("Some Person", person.getFullName());
assertEquals("+46 555-456321", person.getPhone());
person = (Person) list.get(4);
assertEquals("Some Person", person.getFullName());
assertEquals("+45 555-654123", person.getPhone());
}
@Test
public void testSearch_PagedResult_ConvenienceMethod() {
Person person;
List list;
PagedResultsCookie cookie;
PagedResultsRequestControl requestControl;
// Prepare for first search
requestControl = new PagedResultsRequestControl(3);
tested.search(BASE, FILTER_STRING, searchControls, callbackHandler, requestControl);
cookie = requestControl.getCookie();
assertNotNull("Cookie should not be null yet", cookie.getCookie());
list = callbackHandler.getList();
assertEquals(3, list.size());
person = (Person) list.get(0);
assertEquals("Some Person", person.getFullName());
person = (Person) list.get(1);
assertEquals("Some Person2", person.getFullName());
person = (Person) list.get(2);
assertEquals("Some Person3", person.getFullName());
// Prepare for second and last search
requestControl = new PagedResultsRequestControl(3, cookie);
tested.search(BASE, FILTER_STRING, searchControls, callbackHandler, requestControl);
cookie = requestControl.getCookie();
assertNull("Cookie should be null now", cookie.getCookie());
assertEquals(5, list.size());
person = (Person) list.get(3);
assertEquals("Some Person", person.getFullName());
person = (Person) list.get(4);
assertEquals("Some Person", person.getFullName());
}
private static class PersonContextMapper implements ContextMapper {
public Object mapFromContext(Object ctx) {
DirContextAdapter context = (DirContextAdapter) ctx;
DistinguishedName dn = new DistinguishedName(context.getDn());
Person person = new Person();
person.setCountry(dn.getLdapRdn(0).getComponent().getValue());
person.setCompany(dn.getLdapRdn(1).getComponent().getValue());
person.setFullName(context.getStringAttribute("cn"));
person.setLastName(context.getStringAttribute("sn"));
person.setDescription(context.getStringAttribute("description"));
person.setPhone(context.getStringAttribute("telephoneNumber"));
return person;
}
}
public void setTested(LdapTemplate tested) {
this.tested = tested;
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2005-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap.itest.control;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ldap.control.PagedResultsDirContextProcessor;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.ldap.core.support.LdapOperationsCallback;
import org.springframework.ldap.core.support.SingleContextSource;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.ldap.test.LdapTestUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import java.io.IOException;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
/**
* @author Mattias Hellborg Arthursson
*/
@ContextConfiguration("classpath:/conf/pagedSearchTestContext.xml")
public class PagedSearchITest extends AbstractJUnit4SpringContextTests {
@Autowired
private ContextSource contextSource;
private static final AttributesMapper<String> CN_ATTRIBUTES_MAPPER = new AttributesMapper<String>() {
@Override
public String mapFromAttributes(Attributes attributes) throws NamingException {
return attributes.get("cn").get().toString();
}
};
@Before
public void prepareTestedData() throws IOException, NamingException {
LdapTestUtils.cleanAndSetup(
contextSource,
LdapUtils.newLdapName("ou=People"),
new ClassPathResource("/setup_data.ldif"));
}
@After
public void cleanup() throws NamingException {
LdapTestUtils.clearSubContexts(contextSource, LdapUtils.newLdapName("ou=People"));
}
@Test
public void testPaged() {
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
// There should be three pages of three entries, and one final page with one entry
final PagedResultsDirContextProcessor processor = new PagedResultsDirContextProcessor(3);
SingleContextSource.doWithSingleContext(contextSource, new LdapOperationsCallback<Object>() {
@Override
public Object doWithLdapOperations(LdapOperations operations) {
List<String> result = operations.search(
"ou=People",
"(&(objectclass=person))",
searchControls,
CN_ATTRIBUTES_MAPPER,
processor);
assertEquals(3, result.size());
result = operations.search(
"ou=People",
"(&(objectclass=person))",
searchControls,
CN_ATTRIBUTES_MAPPER,
processor);
assertEquals(3, result.size());
result = operations.search(
"ou=People",
"(&(objectclass=person))",
searchControls,
CN_ATTRIBUTES_MAPPER,
processor);
assertEquals(3, result.size());
result = operations.search(
"ou=People",
"(&(objectclass=person))",
searchControls,
CN_ATTRIBUTES_MAPPER,
processor);
assertEquals(1, result.size());
assertFalse(processor.hasMore());
return null;
}
});
}
}

View File

@@ -19,10 +19,15 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.ldap.test.AttributeCheckAttributesMapper;
import org.springframework.ldap.test.AttributeCheckContextMapper;
import org.springframework.ldap.test.LdapTestUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
@@ -43,6 +48,9 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
@Autowired
private LdapTemplate tested;
@Autowired
private ContextSource contextSource;
private AttributeCheckAttributesMapper attributesMapper;
private AttributeCheckContextMapper contextMapper;
@@ -56,7 +64,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
private static final String[] CN_SN_VALUES = { "Some Person2", "Person2" };
private static final String[] ALL_VALUES = { "Some Person2", "Person2", "Sweden, Company1, Some Person2",
"+46 555-654321" };
"+46 555-123458" };
private static final String BASE_STRING = "";
@@ -64,14 +72,20 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
private static final Name BASE_NAME = new DistinguishedName(BASE_STRING);
@Before
@Before
public void prepareTestedInstance() throws Exception {
LdapTestUtils.cleanAndSetup(
contextSource,
LdapUtils.newLdapName("ou=People"),
new ClassPathResource("/setup_data.ldif"));
attributesMapper = new AttributeCheckAttributesMapper();
contextMapper = new AttributeCheckContextMapper();
}
@After
public void cleanup() throws Exception {
LdapTestUtils.clearSubContexts(contextSource, LdapUtils.newLdapName("ou=People"));
attributesMapper = null;
contextMapper = null;
}
@@ -80,7 +94,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
public void testSearch_AttributesMapper() {
attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES);
attributesMapper.setExpectedValues(ALL_VALUES);
List list = tested.search(BASE_STRING, FILTER_STRING, attributesMapper);
List<Object> list = tested.search(BASE_STRING, FILTER_STRING, attributesMapper);
assertEquals(1, list.size());
}
@@ -88,7 +102,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
public void testSearch_SearchScope_AttributesMapper() {
attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES);
attributesMapper.setExpectedValues(ALL_VALUES);
List list = tested.search(BASE_STRING, FILTER_STRING, SearchControls.SUBTREE_SCOPE, attributesMapper);
List<Object> list = tested.search(BASE_STRING, FILTER_STRING, SearchControls.SUBTREE_SCOPE, attributesMapper);
assertEquals(1, list.size());
}
@@ -97,7 +111,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
attributesMapper.setExpectedAttributes(CN_SN_ATTRS);
attributesMapper.setExpectedValues(CN_SN_VALUES);
attributesMapper.setAbsentAttributes(ABSENT_ATTRIBUTES);
List list = tested.search(BASE_STRING, FILTER_STRING, SearchControls.SUBTREE_SCOPE, CN_SN_ATTRS,
List<Object> list = tested.search(BASE_STRING, FILTER_STRING, SearchControls.SUBTREE_SCOPE, CN_SN_ATTRS,
attributesMapper);
assertEquals(1, list.size());
}
@@ -106,7 +120,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
public void testSearch_AttributesMapper_Name() {
attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES);
attributesMapper.setExpectedValues(ALL_VALUES);
List list = tested.search(BASE_NAME, FILTER_STRING, attributesMapper);
List<Object> list = tested.search(BASE_NAME, FILTER_STRING, attributesMapper);
assertEquals(1, list.size());
}
@@ -114,7 +128,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
public void testSearch_SearchScope_AttributesMapper_Name() {
attributesMapper.setExpectedAttributes(ALL_ATTRIBUTES);
attributesMapper.setExpectedValues(ALL_VALUES);
List list = tested.search(BASE_NAME, FILTER_STRING, SearchControls.SUBTREE_SCOPE, attributesMapper);
List<Object> list = tested.search(BASE_NAME, FILTER_STRING, SearchControls.SUBTREE_SCOPE, attributesMapper);
assertEquals(1, list.size());
}
@@ -123,7 +137,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
attributesMapper.setExpectedAttributes(CN_SN_ATTRS);
attributesMapper.setExpectedValues(CN_SN_VALUES);
attributesMapper.setAbsentAttributes(ABSENT_ATTRIBUTES);
List list = tested
List<Object> list = tested
.search(BASE_NAME, FILTER_STRING, SearchControls.SUBTREE_SCOPE, CN_SN_ATTRS, attributesMapper);
assertEquals(1, list.size());
}
@@ -132,7 +146,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
public void testSearch_ContextMapper() {
contextMapper.setExpectedAttributes(ALL_ATTRIBUTES);
contextMapper.setExpectedValues(ALL_VALUES);
List list = tested.search(BASE_STRING, FILTER_STRING, contextMapper);
List<DirContextAdapter> list = tested.search(BASE_STRING, FILTER_STRING, contextMapper);
assertEquals(1, list.size());
}
@@ -140,7 +154,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
public void testSearch_SearchScope_ContextMapper() {
contextMapper.setExpectedAttributes(ALL_ATTRIBUTES);
contextMapper.setExpectedValues(ALL_VALUES);
List list = tested.search(BASE_STRING, FILTER_STRING, SearchControls.SUBTREE_SCOPE, contextMapper);
List<DirContextAdapter> list = tested.search(BASE_STRING, FILTER_STRING, SearchControls.SUBTREE_SCOPE, contextMapper);
assertEquals(1, list.size());
}
@@ -149,7 +163,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
contextMapper.setExpectedAttributes(CN_SN_ATTRS);
contextMapper.setExpectedValues(CN_SN_VALUES);
contextMapper.setAbsentAttributes(ABSENT_ATTRIBUTES);
List list = tested.search(BASE_STRING, FILTER_STRING, SearchControls.SUBTREE_SCOPE, CN_SN_ATTRS, contextMapper);
List<DirContextAdapter> list = tested.search(BASE_STRING, FILTER_STRING, SearchControls.SUBTREE_SCOPE, CN_SN_ATTRS, contextMapper);
assertEquals(1, list.size());
}
@@ -157,7 +171,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
public void testSearch_ContextMapper_Name() {
contextMapper.setExpectedAttributes(ALL_ATTRIBUTES);
contextMapper.setExpectedValues(ALL_VALUES);
List list = tested.search(BASE_NAME, FILTER_STRING, contextMapper);
List<DirContextAdapter> list = tested.search(BASE_NAME, FILTER_STRING, contextMapper);
assertEquals(1, list.size());
}
@@ -165,7 +179,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
public void testSearch_SearchScope_ContextMapper_Name() {
contextMapper.setExpectedAttributes(ALL_ATTRIBUTES);
contextMapper.setExpectedValues(ALL_VALUES);
List list = tested.search(BASE_NAME, FILTER_STRING, SearchControls.SUBTREE_SCOPE, contextMapper);
List<DirContextAdapter> list = tested.search(BASE_NAME, FILTER_STRING, SearchControls.SUBTREE_SCOPE, contextMapper);
assertEquals(1, list.size());
}
@@ -174,7 +188,7 @@ public class LdapTemplateSearchResultITest extends AbstractJUnit4SpringContextTe
contextMapper.setExpectedAttributes(CN_SN_ATTRS);
contextMapper.setExpectedValues(CN_SN_VALUES);
contextMapper.setAbsentAttributes(ABSENT_ATTRIBUTES);
List list = tested.search(BASE_NAME, FILTER_STRING, SearchControls.SUBTREE_SCOPE, CN_SN_ATTRS, contextMapper);
List<DirContextAdapter> list = tested.search(BASE_NAME, FILTER_STRING, SearchControls.SUBTREE_SCOPE, CN_SN_ATTRS, contextMapper);
assertEquals(1, list.size());
}
}

View File

@@ -5,16 +5,22 @@
*/
package org.springframework.ldap.itest.core.support;
import javax.naming.directory.DirContext;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.AuthenticationException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.ldap.test.LdapTestUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import javax.naming.directory.DirContext;
/**
* Integration test to verify DIGEST-MD5 authentication support.
*
@@ -25,14 +31,26 @@ public class DigestMd5AuthenticationITest extends AbstractJUnit4SpringContextTes
@Autowired
private LdapTemplate ldapTemplate;
@Test
@Autowired
@Qualifier("populateContextSource")
private ContextSource contextSource;
@Before
public void prepareTestedInstance() throws Exception {
LdapTestUtils.cleanAndSetup(
contextSource,
LdapUtils.newLdapName("ou=People"),
new ClassPathResource("/setup_data.ldif"));
}
@After
public void cleanup() throws Exception {
LdapTestUtils.clearSubContexts(contextSource, LdapUtils.newLdapName("ou=People"));
}
@Test
public void testAuthenticate() {
try {
DirContext ctxt = ldapTemplate.getContextSource().getContext("some.person", "password");
Assert.assertNotNull(ctxt);
}
catch (AuthenticationException e) {
Assert.fail(e.getMessage());
}
DirContext ctxt = ldapTemplate.getContextSource().getContext("some.person1", "password");
Assert.assertNotNull(ctxt);
}
}

View File

@@ -1,8 +1,4 @@
itest.openldap.serverAddress=spring-ldap-test.dyndns.org
userDn=cn=admin,dc=jayway,dc=se
password=secret
base=dc=jayway,dc=se
aws.ami=ami-889c6be1
aws.keypair=spring-ldap-keypair
aws.security.group=spring-ldap
itest.openldap.url=ldap://localhost:389
itest.openldap.user.dn=cn=admin,dc=261consulting,dc=com
itest.openldap.user.password=secret
itest.openldap.base=dc=261consulting,dc=com

View File

@@ -1,22 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="/conf/ldap.properties" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
</bean>
<bean id="contextSource" class="org.springframework.ldap.DigestMd5ContextSourceEc2InstanceLaunchingFactoryBean">
<property name="awsKey" value="${AWS_KEY}" />
<property name="awsSecretKey" value="${AWS_SECRET_KEY}" />
<property name="imageName" value="${aws.ami}" />
<property name="groupName" value="${aws.security.group}" />
<property name="keypairName" value="${aws.keypair}" />
<property name="base" value="dc=jayway,dc=se" />
<property name="userDn" value="${userDn}" />
<property name="password" value="${password}" />
</bean>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder ignore-resource-not-found="true"
system-properties-mode="OVERRIDE"
location="classpath:/conf/ldap.properties" />
<bean class="org.springframework.ldap.core.support.LdapContextSource" id="populateContextSource">
<property name="url" value="${itest.openldap.url}" />
<property name="userDn" value="${itest.openldap.user.dn}"/>
<property name="password" value="${itest.openldap.user.password}"/>
<property name="base" value="${itest.openldap.base}" />
<property name="pooled" value="false" />
</bean>
<bean class="org.springframework.ldap.core.support.LdapContextSource" id="contextSource">
<property name="url" value="${itest.openldap.url}" />
<property name="userDn" value="${itest.openldap.user.dn}"/>
<property name="password" value="${itest.openldap.user.password}"/>
<property name="base" value="${itest.openldap.base}" />
<property name="pooled" value="false" />
<property name="authenticationStrategy">
<bean class="org.springframework.ldap.core.support.DigestMd5DirContextAuthenticationStrategy" />
</property>
</bean>
<bean id="ldapTemplate"
class="org.springframework.ldap.core.LdapTemplate">
<constructor-arg ref="contextSource" />

View File

@@ -1,25 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="/conf/ldap.properties" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
</bean>
<context:property-placeholder ignore-resource-not-found="true"
system-properties-mode="OVERRIDE"
location="classpath:/conf/ldap.properties" />
<bean id="contextSource" class="org.springframework.ldap.TlsContextSourceEc2InstanceLaunchingFactoryBean">
<property name="awsKey" value="${AWS_KEY}" />
<property name="awsSecretKey" value="${AWS_SECRET_KEY}" />
<property name="imageName" value="${aws.ami}" />
<property name="groupName" value="${aws.security.group}" />
<property name="keypairName" value="${aws.keypair}" />
<property name="base" value="dc=jayway,dc=se" />
<property name="userDn" value="${userDn}" />
<property name="password" value="${password}" />
</bean>
<bean class="org.springframework.ldap.core.support.LdapContextSource" id="contextSource">
<property name="url" value="${itest.openldap.url}" />
<property name="userDn" value="${itest.openldap.user.dn}"/>
<property name="password" value="${itest.openldap.user.password}"/>
<property name="base" value="${itest.openldap.base}" />
<property name="pooled" value="false" />
<property name="authenticationStrategy">
<bean class="org.springframework.ldap.core.support.DefaultTlsDirContextAuthenticationStrategy">
<property name="hostnameVerifier">
<bean class="org.springframework.ldap.AllMatchHostnameVerifier" />
</property>
</bean>
</property>
</bean>
<bean id="ldapTemplate"
class="org.springframework.ldap.core.LdapTemplate">

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="/conf/ldap.properties" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
</bean>
<bean id="contextSource"
class="org.springframework.ldap.test.ContextSourceEc2InstanceLaunchingFactoryBean">
<property name="awsKey" value="${AWS_KEY}" />
<property name="awsSecretKey" value="${AWS_SECRET_KEY}" />
<property name="imageName" value="${aws.ami}" />
<property name="groupName" value="${aws.security.group}" />
<property name="keypairName" value="${aws.keypair}" />
<property name="base" value="dc=jayway,dc=se" />
<property name="userDn" value="${userDn}" />
<property name="password" value="${password}" />
<property name="pooled" value="true" />
</bean>
<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
<constructor-arg ref="contextSource" />
</bean>
</beans>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder ignore-resource-not-found="true"
system-properties-mode="OVERRIDE"
location="classpath:/conf/ldap.properties" />
<bean class="org.springframework.ldap.core.support.LdapContextSource" id="contextSource">
<property name="url" value="${itest.openldap.url}" />
<property name="userDn" value="${itest.openldap.user.dn}"/>
<property name="password" value="${itest.openldap.user.password}"/>
<property name="base" value="${itest.openldap.base}" />
<property name="pooled" value="false" />
</bean>
<bean class="org.springframework.ldap.core.LdapTemplate">
<property name="contextSource" ref="contextSource"/>
</bean>
</beans>

View File

@@ -0,0 +1,119 @@
dn: uid=some.person1,ou=People,dc=261consulting,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person1
userPassword: password
cn: Some Person1
sn: Person1
description: Sweden, Company1, Some Person1
telephoneNumber: +46 555-123459
dn: cn=Some Person2,ou=People,dc=261consulting,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person2
userPassword: password
cn: Some Person2
sn: Person2
description: Sweden, Company1, Some Person2
telephoneNumber: +46 555-123458
dn: cn=Some Person3,ou=People,dc=261consulting,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person3
userPassword: password
cn: Some Person3
sn: Person3
description: Sweden, Company1, Some Person3
telephoneNumber: +46 555-123457
dn: cn=Some Person4,ou=People,dc=261consulting,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person4
userPassword: password
cn: Some Person4
sn: Person4
description: Sweden, Company1, Some Person4
telephoneNumber: +46 555-123456
dn: cn=Some Person5,ou=People,dc=261consulting,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person5
userPassword: password
cn: Some Person5
sn: Person5
description: Sweden, Company1, Some Person5
telephoneNumber: +46 555-123455
dn: cn=Some Person6,ou=People,dc=261consulting,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person6
userPassword: password
cn: Some Person6
sn: Person6
description: Sweden, Company1, Some Person6
telephoneNumber: +46 555-123454
dn: cn=Some Person7,ou=People,dc=261consulting,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person7
userPassword: password
cn: Some Person7
sn: Person7
description: Sweden, Company1, Some Person7
telephoneNumber: +46 555-123453
dn: cn=Some Person8,ou=People,dc=261consulting,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person8
userPassword: password
cn: Some Person8
sn: Person8
description: Sweden, Company1, Some Person8
telephoneNumber: +46 555-123452
dn: cn=Some Person9,ou=People,dc=261consulting,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person9
userPassword: password
cn: Some Person9
sn: Person9
description: Sweden, Company1, Some Person9
telephoneNumber: +46 555-123451
dn: cn=Some Person10,ou=People,dc=261consulting,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: some.person10
userPassword: password
cn: Some Person10
sn: Person10
description: Sweden, Company1, Some Person10
telephoneNumber: +46 555-123450