diff --git a/core/src/main/java/org/springframework/ldap/control/PagedResultsDirContextProcessor.java b/core/src/main/java/org/springframework/ldap/control/PagedResultsDirContextProcessor.java index 1a815a00..912fa054 100644 --- a/core/src/main/java/org/springframework/ldap/control/PagedResultsDirContextProcessor.java +++ b/core/src/main/java/org/springframework/ldap/control/PagedResultsDirContextProcessor.java @@ -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 null cookie being returned from the server. + * When this happen, the internal status will set to false. + * + * @return true if there are more results to retrieve, false 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); } diff --git a/core/src/main/java/org/springframework/ldap/control/SortControlDirContextProcessor.java b/core/src/main/java/org/springframework/ldap/control/SortControlDirContextProcessor.java index 7900494b..ce226720 100644 --- a/core/src/main/java/org/springframework/ldap/control/SortControlDirContextProcessor.java +++ b/core/src/main/java/org/springframework/ldap/control/SortControlDirContextProcessor.java @@ -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}); } diff --git a/core/src/main/java/org/springframework/ldap/core/support/LdapOperationsCallback.java b/core/src/main/java/org/springframework/ldap/core/support/LdapOperationsCallback.java new file mode 100644 index 00000000..4e7026b3 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/core/support/LdapOperationsCallback.java @@ -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 { + /** + * 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); +} diff --git a/core/src/main/java/org/springframework/ldap/core/support/SingleContextSource.java b/core/src/main/java/org/springframework/ldap/core/support/SingleContextSource.java index 2ba7e439..fc8ccc3f 100644 --- a/core/src/main/java/org/springframework/ldap/core/support/SingleContextSource.java +++ b/core/src/main/java/org/springframework/ldap/core/support/SingleContextSource.java @@ -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. + *

By default, the {@link org.springframework.ldap.core.ContextSource#getReadWriteContext()} method + * will be used to create the DirContext instance to operate on.

+ * + * @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 doWithSingleContext(ContextSource contextSource, LdapOperationsCallback 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 true, use the {@link org.springframework.ldap.core.ContextSource#getReadOnlyContext()} + * method on the target ContextSource to get the actual DirContext instance, if false, + * 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 doWithSingleContext(ContextSource contextSource, + LdapOperationsCallback 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 close operations will be performed. diff --git a/core/src/test/java/org/springframework/ldap/core/support/SingleContextSourceTest.java b/core/src/test/java/org/springframework/ldap/core/support/SingleContextSourceTest.java new file mode 100644 index 00000000..9fa00355 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/core/support/SingleContextSourceTest.java @@ -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() { + @Override + public Object doWithLdapOperations(LdapOperations operations) { + operations.executeReadOnly(new ContextExecutor() { + @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() { + @Override + public Object executeWithContext(DirContext ctx) throws NamingException { + Object targetContex = Whitebox.getInternalState(Proxy.getInvocationHandler(ctx), "target"); + assertSame(dirContextMock, targetContex); + return false; + } + }); + + return null; + } + }); + } + +} diff --git a/src/docbkx/contextprocessor.xml b/src/docbkx/contextprocessor.xml index 8c8e9495..442fe506 100644 --- a/src/docbkx/contextprocessor.xml +++ b/src/docbkx/contextprocessor.xml @@ -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) + 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) @@ -158,43 +158,49 @@ public class MyCoolRequestControl extends AbstractRequestControlDirContextProces Spring LDAP provides support for paged results by leveraging the concept for pre- and postprocessing of an LdapContext that was discussed - in the previous sections. It does so by providing two classes: - PagedResultsRequestControl and - PagedResultsCookie. The - PagedResultsRequestControl class creates a + in the previous sections. It does so using the class + PagedResultsDirContextProcessor. The + PagedResultsDirContextProcessor class creates a PagedResultsControl with the requested page size and adds it to the LdapContext. After the search, it gets - the PagedResultsResponseControl 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 - PagedResultsControl. In order to make it easy to store - this cookie between searches, Spring LDAP provides the wrapper class - PagedResultsCookie. + the PagedResultsResponseControl and retrieves the paged results + cookie, which is needed to keep the context between consecutive paged results requests. Below is an example of how the paged search results functionality may be used: - Paged results using <literal>PagedResultsRequestControl</literal> + Paged results using <literal>PagedResultsDirContextProcessor</literal> - 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()); - } + +public List<String> getAllPersonNames() { + final SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + final PagedResultsDirContextProcessor processor = new PagedResultsDirContextProcessor(PAGE_SIZE); + + return SingleContextSource.doWithSingleContext(contextSource, new LdapOperationsCallback<List<String>>() { + @Override + public List<String> doWithLdapOperations(LdapOperations operations) { + List<String> result = new LinkedList<String>(); + + do { + List<String> oneResult = operations.search( + "ou=People", + "(&(objectclass=person))", + searchControls, + CN_ATTRIBUTES_MAPPER, + processor); + result.addAll(oneResult); + } while(processor.hasMore()); + + return result; + } + }); +} + - - In the first call to this method, null 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 PagedResult) each time the - method is called. When the actual cookie is null (i.e. - pagedResult.getCookie().getCookie() returns null), - the last batch has been returned from the search. - + 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 SingleContextSource, + as demonstrated in the example. diff --git a/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckAttributesMapper.java b/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckAttributesMapper.java index 2c8af636..114db775 100644 --- a/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckAttributesMapper.java +++ b/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckAttributesMapper.java @@ -29,7 +29,7 @@ import java.util.Arrays; * * @author Mattias Hellborg Arthursson */ -public class AttributeCheckAttributesMapper implements AttributesMapper { +public class AttributeCheckAttributesMapper implements AttributesMapper { 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; diff --git a/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckContextMapper.java b/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckContextMapper.java index 47b32fee..eb81a9de 100644 --- a/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckContextMapper.java +++ b/test-support/src/main/java/org/springframework/ldap/test/AttributeCheckContextMapper.java @@ -28,14 +28,14 @@ import java.util.Arrays; * * @author Mattias Hellborg Arthursson */ -public class AttributeCheckContextMapper implements ContextMapper { +public class AttributeCheckContextMapper implements ContextMapper { 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); diff --git a/test/integration-tests-openldap/src/main/java/org/springframework/ldap/AllMatchHostnameVerifier.java b/test/integration-tests-openldap/src/main/java/org/springframework/ldap/AllMatchHostnameVerifier.java new file mode 100644 index 00000000..ec023186 --- /dev/null +++ b/test/integration-tests-openldap/src/main/java/org/springframework/ldap/AllMatchHostnameVerifier.java @@ -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; + } +} diff --git a/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/control/LdapTemplatePagedSearchITest.java b/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/control/LdapTemplatePagedSearchITest.java deleted file mode 100644 index eef211e2..00000000 --- a/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/control/LdapTemplatePagedSearchITest.java +++ /dev/null @@ -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. - *

- * 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; - } -} diff --git a/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/control/PagedSearchITest.java b/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/control/PagedSearchITest.java new file mode 100644 index 00000000..d770aa53 --- /dev/null +++ b/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/control/PagedSearchITest.java @@ -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 CN_ATTRIBUTES_MAPPER = new AttributesMapper() { + @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() { + @Override + public Object doWithLdapOperations(LdapOperations operations) { + List 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; + } + }); + + } +} diff --git a/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/core/LdapTemplateSearchResultITest.java b/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/core/LdapTemplateSearchResultITest.java index b530c746..8526b39b 100644 --- a/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/core/LdapTemplateSearchResultITest.java +++ b/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/core/LdapTemplateSearchResultITest.java @@ -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 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 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 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 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 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 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 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 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 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 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 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 list = tested.search(BASE_NAME, FILTER_STRING, SearchControls.SUBTREE_SCOPE, CN_SN_ATTRS, contextMapper); assertEquals(1, list.size()); } } diff --git a/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/core/support/DigestMd5AuthenticationITest.java b/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/core/support/DigestMd5AuthenticationITest.java index cebc5000..ca4b6d6a 100644 --- a/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/core/support/DigestMd5AuthenticationITest.java +++ b/test/integration-tests-openldap/src/test/java/org/springframework/ldap/itest/core/support/DigestMd5AuthenticationITest.java @@ -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); } } diff --git a/test/integration-tests-openldap/src/test/resources/conf/ldap.properties b/test/integration-tests-openldap/src/test/resources/conf/ldap.properties index 2737fbc7..45b03804 100644 --- a/test/integration-tests-openldap/src/test/resources/conf/ldap.properties +++ b/test/integration-tests-openldap/src/test/resources/conf/ldap.properties @@ -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 \ No newline at end of file diff --git a/test/integration-tests-openldap/src/test/resources/conf/ldapTemplateDigestMd5TestContext.xml b/test/integration-tests-openldap/src/test/resources/conf/ldapTemplateDigestMd5TestContext.xml index cecf3feb..391de88d 100644 --- a/test/integration-tests-openldap/src/test/resources/conf/ldapTemplateDigestMd5TestContext.xml +++ b/test/integration-tests-openldap/src/test/resources/conf/ldapTemplateDigestMd5TestContext.xml @@ -1,22 +1,33 @@ - - - - - - - - - - - - - - + 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"> + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/integration-tests-openldap/src/test/resources/conf/ldapTemplateTestContext-tls.xml b/test/integration-tests-openldap/src/test/resources/conf/ldapTemplateTestContext-tls.xml index c6975291..59d4ebcb 100644 --- a/test/integration-tests-openldap/src/test/resources/conf/ldapTemplateTestContext-tls.xml +++ b/test/integration-tests-openldap/src/test/resources/conf/ldapTemplateTestContext-tls.xml @@ -1,25 +1,28 @@ + 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"> - - - - + - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/test/integration-tests-openldap/src/test/resources/conf/ldapTemplateTestContext.xml b/test/integration-tests-openldap/src/test/resources/conf/ldapTemplateTestContext.xml deleted file mode 100644 index 84db7a05..00000000 --- a/test/integration-tests-openldap/src/test/resources/conf/ldapTemplateTestContext.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/integration-tests-openldap/src/test/resources/conf/pagedSearchTestContext.xml b/test/integration-tests-openldap/src/test/resources/conf/pagedSearchTestContext.xml new file mode 100644 index 00000000..a27fad1b --- /dev/null +++ b/test/integration-tests-openldap/src/test/resources/conf/pagedSearchTestContext.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/integration-tests-openldap/src/test/resources/setup_data.ldif b/test/integration-tests-openldap/src/test/resources/setup_data.ldif new file mode 100644 index 00000000..612ae2e7 --- /dev/null +++ b/test/integration-tests-openldap/src/test/resources/setup_data.ldif @@ -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