From 27682b0b53d33c82ba5401c0847bb44d40774f08 Mon Sep 17 00:00:00 2001 From: Mattias Hellborg Arthursson Date: Tue, 24 Sep 2013 16:55:54 +0200 Subject: [PATCH] LDAP-263: authentication methods for LdapQuery. Documentation updated to reflect fluent API changes. --- .../ldap/AuthenticationException.java | 4 + .../AuthenticatedLdapEntryContextMapper.java | 42 ++ ...CollectingAuthenticationErrorCallback.java | 13 +- .../core/IncrementalAttributesMapper.java | 2 +- .../ldap/core/LdapOperations.java | 106 ++++- .../ldap/core/LdapTemplate.java | 147 +++++-- .../support/LookupAttemptingCallback.java | 30 +- .../ldap/query/ContainerCriteria.java | 2 +- .../ldap/query/DefaultContainerCriteria.java | 2 +- .../ldap/query/LdapQueryBuilderTest.java | 4 + .../ldap/samples/plain/dao/PersonDaoImpl.java | 7 +- src/docbkx/advancedqueries.xml | 111 ++++++ src/docbkx/basic.xml | 102 ++--- src/docbkx/index.xml | 3 +- src/docbkx/overview.xml | 376 +++--------------- src/docbkx/transactions.xml | 2 +- src/docbkx/user-authentication.xml | 127 ++---- .../LdapTemplateAuthenticationITest.java | 69 +++- 18 files changed, 591 insertions(+), 558 deletions(-) create mode 100644 core/src/main/java/org/springframework/ldap/core/AuthenticatedLdapEntryContextMapper.java rename core/src/main/java/org/springframework/ldap/core/{support => }/CollectingAuthenticationErrorCallback.java (83%) create mode 100644 src/docbkx/advancedqueries.xml diff --git a/core/src/main/java/org/springframework/ldap/AuthenticationException.java b/core/src/main/java/org/springframework/ldap/AuthenticationException.java index f8fd93b6..72e5b604 100644 --- a/core/src/main/java/org/springframework/ldap/AuthenticationException.java +++ b/core/src/main/java/org/springframework/ldap/AuthenticationException.java @@ -28,4 +28,8 @@ public class AuthenticationException extends NamingSecurityException { public AuthenticationException(javax.naming.AuthenticationException cause) { super(cause); } + + public AuthenticationException() { + this(null); + } } diff --git a/core/src/main/java/org/springframework/ldap/core/AuthenticatedLdapEntryContextMapper.java b/core/src/main/java/org/springframework/ldap/core/AuthenticatedLdapEntryContextMapper.java new file mode 100644 index 00000000..54135b6b --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/core/AuthenticatedLdapEntryContextMapper.java @@ -0,0 +1,42 @@ +/* + * 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; + +import javax.naming.directory.DirContext; + +/** + * Callback interface to be used in the authentication methods in + * {@link LdapOperations} for performing operations on individually + * authenticated contexts. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public interface AuthenticatedLdapEntryContextMapper { + /** + * Perform some LDAP operation on the supplied authenticated + * DirContext instance. The target context will be + * automatically closed. + * + * @param ctx the DirContext instance to perform an operation + * on. + * @param ldapEntryIdentification the identification of the LDAP entry used + * to authenticate the supplied DirContext. + * @return the result of the operation, if any. + */ + T mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification); +} diff --git a/core/src/main/java/org/springframework/ldap/core/support/CollectingAuthenticationErrorCallback.java b/core/src/main/java/org/springframework/ldap/core/CollectingAuthenticationErrorCallback.java similarity index 83% rename from core/src/main/java/org/springframework/ldap/core/support/CollectingAuthenticationErrorCallback.java rename to core/src/main/java/org/springframework/ldap/core/CollectingAuthenticationErrorCallback.java index 60fe300f..110bd000 100644 --- a/core/src/main/java/org/springframework/ldap/core/support/CollectingAuthenticationErrorCallback.java +++ b/core/src/main/java/org/springframework/ldap/core/CollectingAuthenticationErrorCallback.java @@ -13,9 +13,7 @@ * 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.AuthenticationErrorCallback; +package org.springframework.ldap.core; /** * Convenience implementation of AuthenticationErrorCallback that stores the @@ -47,4 +45,13 @@ public final class CollectingAuthenticationErrorCallback implements Authenticati public Exception getError() { return error; } + + /** + * Check whether this callback has collected an error. + * + * @return true if an error has been collected, false otherwise. + */ + public boolean hasError() { + return error != null; + } } \ No newline at end of file diff --git a/core/src/main/java/org/springframework/ldap/core/IncrementalAttributesMapper.java b/core/src/main/java/org/springframework/ldap/core/IncrementalAttributesMapper.java index 0b141478..d1b3fc47 100644 --- a/core/src/main/java/org/springframework/ldap/core/IncrementalAttributesMapper.java +++ b/core/src/main/java/org/springframework/ldap/core/IncrementalAttributesMapper.java @@ -27,7 +27,7 @@ import java.util.List; * @author Mattias Hellborg Arthursson * @since 1.3.2 * @see Incremental Retrieval of Multi-valued Properties - * @see {@link org.springframework.ldap.core.support.DefaultIncrementalAttributesMapper} + * @see org.springframework.ldap.core.support.DefaultIncrementalAttributesMapper */ public interface IncrementalAttributesMapper extends AttributesMapper { /** diff --git a/core/src/main/java/org/springframework/ldap/core/LdapOperations.java b/core/src/main/java/org/springframework/ldap/core/LdapOperations.java index 8006dff7..8ecdf700 100644 --- a/core/src/main/java/org/springframework/ldap/core/LdapOperations.java +++ b/core/src/main/java/org/springframework/ldap/core/LdapOperations.java @@ -1323,10 +1323,12 @@ public interface LdapOperations { * @return true if the authentication was successful, * false otherwise. * @since 1.3 + * @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} */ boolean authenticate(Name base, String filter, String password); - /** + /** * Utility method to perform a simple LDAP 'bind' authentication. Search for * the LDAP entry to authenticate using the supplied base DN and filter; use * the DN of the found entry together with the password as input to @@ -1347,6 +1349,8 @@ public interface LdapOperations { * @return true if the authentication was successful, * false otherwise. * @since 1.3 + * @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} */ boolean authenticate(String base, String filter, String password); @@ -1368,6 +1372,8 @@ public interface LdapOperations { * false otherwise. * @see #authenticate(Name, String, String) * @since 1.3 + * @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} */ boolean authenticate(Name base, String filter, String password, AuthenticatedLdapEntryContextCallback callback); @@ -1389,6 +1395,8 @@ public interface LdapOperations { * false otherwise. * @see #authenticate(String, String, String) * @since 1.3 + * @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} */ boolean authenticate(String base, String filter, String password, AuthenticatedLdapEntryContextCallback callback); @@ -1414,6 +1422,8 @@ public interface LdapOperations { * false otherwise. * @see #authenticate(Name, String, String, AuthenticatedLdapEntryContextCallback) * @since 1.3.1 + * @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} */ boolean authenticate(Name base, String filter, String password, AuthenticatedLdapEntryContextCallback callback, @@ -1441,29 +1451,33 @@ public interface LdapOperations { * false otherwise. * @see #authenticate(String, String, String, AuthenticatedLdapEntryContextCallback) * @since 1.3.1 + * @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} */ boolean authenticate(String base, String filter, String password, AuthenticatedLdapEntryContextCallback callback, AuthenticationErrorCallback errorCallback); - /** - * Utility method to perform a simple LDAP 'bind' authentication. Search for - * the LDAP entry to authenticate using the supplied base DN and filter; use - * the DN of the found entry together with the password as input to - * {@link ContextSource#getContext(String, String)}, thus authenticating the - * entry. If an exception is caught, the same exception is passed on to the given - * {@link AuthenticationErrorCallback}. This enables the caller to provide a - * callback that, for example, collects the exception for later processing. - * - * @param base the DN to use as the base of the search. - * @param filter the search filter - must result in a unique result. - * @param password the password to use for authentication. - * @param errorCallback the callback that will be called if an exception is caught. - * @return true if the authentication was successful, - * false otherwise. - * @see #authenticate(Name, String, String, AuthenticatedLdapEntryContextCallback, AuthenticationErrorCallback) - * @since 1.3.1 - */ + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. If an exception is caught, the same exception is passed on to the given + * {@link AuthenticationErrorCallback}. This enables the caller to provide a + * callback that, for example, collects the exception for later processing. + * + * @param base the DN to use as the base of the search. + * @param filter the search filter - must result in a unique result. + * @param password the password to use for authentication. + * @param errorCallback the callback that will be called if an exception is caught. + * @return true if the authentication was successful, + * false otherwise. + * @see #authenticate(Name, String, String, AuthenticatedLdapEntryContextCallback, AuthenticationErrorCallback) + * @since 1.3.1 + * @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} + */ boolean authenticate(Name base, String filter, String password, AuthenticationErrorCallback errorCallback); @@ -1483,12 +1497,64 @@ public interface LdapOperations { * @return true if the authentication was successful, * false otherwise. * @throws IncorrectResultSizeDataAccessException if more than one users were found - * @see #authenticate(String, String, String, AuthenticatedLdapEntryContextCallback, AuthenticationErrorCallback) * @since 1.3.1 + * @deprecated use {@link #authenticate(org.springframework.ldap.query.LdapQuery, String)} + * or {@link #authenticate(org.springframework.ldap.query.LdapQuery, String, AuthenticatedLdapEntryContextMapper)} */ boolean authenticate(String base, String filter, String password, AuthenticationErrorCallback errorCallback); + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied LdapQuery; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. + *

+ * Note: This method differs from the older authenticate methods in that encountered + * exceptions are thrown rather than supplied to a callback for handling. + *

+ * + * @param query the LdapQuery specifying the details of the search. + * @param password the password to use for authentication. + * @param mapper the callback that will be called to perform operations + * on the DirContext authenticated with the found user. + * false otherwise. + * @return the result from the callback. + * @throws IncorrectResultSizeDataAccessException if more than one users were found + * @throws org.springframework.dao.EmptyResultDataAccessException if only one user was found + * @throws NamingException if something went wrong in authentication. + * + * @since 2.0 + */ + T authenticate(LdapQuery query, String password, AuthenticatedLdapEntryContextMapper mapper); + + /** + * Utility method to perform a simple LDAP 'bind' authentication. Search for + * the LDAP entry to authenticate using the supplied base DN and filter; use + * the DN of the found entry together with the password as input to + * {@link ContextSource#getContext(String, String)}, thus authenticating the + * entry. If an exception is caught, the same exception is passed on to the given + * {@link AuthenticationErrorCallback}. This enables the caller to provide a + * callback that, for example, collects the exception for later processing. + *

+ * Note: This method differs from the older authenticate methods in that encountered + * exceptions are thrown rather than supplied to a callback for handling. + *

+ * + * @param query the LdapQuery specifying the details of the search. + * @param password the password to use for authentication. + * false otherwise. + * @throws IncorrectResultSizeDataAccessException if more than one users were found + * @throws org.springframework.dao.EmptyResultDataAccessException if only one user was found + * @throws NamingException if something went wrong in authentication. + * + * @since 2.0 + */ + void authenticate(LdapQuery query, String password); + + /** * Perform a search for a unique entry matching the specified search * criteria and return the found object. If no entry is found or if there diff --git a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java index 88f17f58..45445069 100644 --- a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java +++ b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java @@ -20,7 +20,9 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.ldap.AuthenticationException; import org.springframework.ldap.NamingException; +import org.springframework.ldap.UncategorizedLdapException; import org.springframework.ldap.query.LdapQuery; import org.springframework.ldap.support.LdapUtils; import org.springframework.util.Assert; @@ -1491,42 +1493,94 @@ public class LdapTemplate implements LdapOperations, InitializingBean { public boolean authenticate(Name base, String filter, String password, final AuthenticatedLdapEntryContextCallback callback, final AuthenticationErrorCallback errorCallback) { - List result = search(base, filter, new LdapEntryIdentificationContextMapper()); - if (result.size() == 0) { - String msg = "No results found for search, base: '" + base + "'; filter: '" + filter + "'."; - log.info(msg); - return false; - } else if (result.size() > 1) { - String msg = "base: '" + base + "'; filter: '" + filter + "'."; - throw new IncorrectResultSizeDataAccessException(msg, 1, result.size()); - } - - final LdapEntryIdentification entryIdentification = (LdapEntryIdentification) result.get(0); - - try { - DirContext ctx = contextSource.getContext(entryIdentification.getAbsoluteDn().toString(), password); - executeWithContext(new ContextExecutor() { - public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { - callback.executeWithContext(ctx, entryIdentification); - return null; - } - }, ctx); - return true; - } - catch (Exception e) { - log.info("Authentication failed for entry with DN '" + entryIdentification.getAbsoluteDn() + "'", e); - errorCallback.execute(e); - return false; - } + return authenticate(base, + filter, + password, + getDefaultSearchControls(defaultSearchScope, RETURN_OBJ_FLAG, null), + callback, + errorCallback); } - /* - * (non-Javadoc) - * - * @see - * org.springframework.ldap.core.LdapOperations#searchForObject(javax.naming - * .Name, java.lang.String, org.springframework.ldap.core.ContextMapper) - */ + private boolean authenticate(Name base, + String filter, + String password, + SearchControls searchControls, + final AuthenticatedLdapEntryContextCallback callback, + final AuthenticationErrorCallback errorCallback) { + + List result = search(base, filter, searchControls, new LdapEntryIdentificationContextMapper()); + if (result.size() == 0) { + String msg = "No results found for search, base: '" + base + "'; filter: '" + filter + "'."; + log.info(msg); + return false; + } else if (result.size() > 1) { + String msg = "base: '" + base + "'; filter: '" + filter + "'."; + throw new IncorrectResultSizeDataAccessException(msg, 1, result.size()); + } + + final LdapEntryIdentification entryIdentification = result.get(0); + + try { + DirContext ctx = contextSource.getContext(entryIdentification.getAbsoluteName().toString(), password); + executeWithContext(new ContextExecutor() { + public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException { + callback.executeWithContext(ctx, entryIdentification); + return null; + } + }, ctx); + return true; + } + catch (Exception e) { + log.info("Authentication failed for entry with DN '" + entryIdentification.getAbsoluteName() + "'", e); + errorCallback.execute(e); + return false; + } + } + + @Override + public T authenticate(LdapQuery query, String password, AuthenticatedLdapEntryContextMapper mapper) { + SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG); + ReturningAuthenticatedLdapEntryContext mapperCallback = + new ReturningAuthenticatedLdapEntryContext(mapper); + CollectingAuthenticationErrorCallback errorCallback = + new CollectingAuthenticationErrorCallback(); + + boolean succeeded = authenticate(query.base(), + query.filter().encode(), + password, + searchControls, + mapperCallback, + errorCallback); + + if(errorCallback.hasError()) { + Exception error = errorCallback.getError(); + + if (error instanceof NamingException) { + throw (NamingException) error; + } else { + throw new UncategorizedLdapException(error); + } + } else if(!succeeded) { + throw new AuthenticationException(); + } + + return mapperCallback.collectedObject; + } + + @Override + public void authenticate(LdapQuery query, String password) { + authenticate(query, + password, + new NullAuthenticatedLdapEntryContextCallback()); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.ldap.core.LdapOperations#searchForObject(javax.naming + * .Name, java.lang.String, org.springframework.ldap.core.ContextMapper) + */ public T searchForObject(Name base, String filter, ContextMapper mapper) { return searchForObject(base, filter, @@ -1564,12 +1618,17 @@ public class LdapTemplate implements LdapOperations, InitializingBean { } private static final class NullAuthenticatedLdapEntryContextCallback - implements AuthenticatedLdapEntryContextCallback { + implements AuthenticatedLdapEntryContextCallback, AuthenticatedLdapEntryContextMapper{ public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { // Do nothing } - } + + @Override + public Object mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { + return null; + } + } private static final class NullAuthenticationErrorCallback implements AuthenticationErrorCallback { @@ -1578,6 +1637,22 @@ public class LdapTemplate implements LdapOperations, InitializingBean { } } + private static final class ReturningAuthenticatedLdapEntryContext + implements AuthenticatedLdapEntryContextCallback { + private final AuthenticatedLdapEntryContextMapper mapper; + + private T collectedObject; + + private ReturningAuthenticatedLdapEntryContext(AuthenticatedLdapEntryContextMapper mapper) { + this.mapper = mapper; + } + + @Override + public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { + collectedObject = mapper.mapWithContext(ctx, ldapEntryIdentification); + } + } + @Override public List search(LdapQuery query, ContextMapper mapper) { SearchControls searchControls = searchControlsForQuery(query, RETURN_OBJ_FLAG); diff --git a/core/src/main/java/org/springframework/ldap/core/support/LookupAttemptingCallback.java b/core/src/main/java/org/springframework/ldap/core/support/LookupAttemptingCallback.java index 5c764307..fbe4cbd4 100644 --- a/core/src/main/java/org/springframework/ldap/core/support/LookupAttemptingCallback.java +++ b/core/src/main/java/org/springframework/ldap/core/support/LookupAttemptingCallback.java @@ -17,6 +17,8 @@ package org.springframework.ldap.core.support; import org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback; +import org.springframework.ldap.core.AuthenticatedLdapEntryContextMapper; +import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.LdapEntryIdentification; import org.springframework.ldap.support.LdapUtils; @@ -27,19 +29,29 @@ import javax.naming.directory.DirContext; * Attempts to perform an LDAP operation in the authenticated context, because * Active Directory might allow bind with incorrect password (specifically empty * password), and later refuse operations. We want to fail fast when - * authenticating. + * authenticating. {@link #mapWithContext(javax.naming.directory.DirContext, org.springframework.ldap.core.LdapEntryIdentification)} + * returns the {@link DirContextOperations} instance that results from the lookup operation. This instance + * can be used to obtain information regarding the authenticated user. * * @author Hugo Josefson + * @author Mattias Hellborg Arthursson * @since 1.3.1 */ -public class LookupAttemptingCallback implements AuthenticatedLdapEntryContextCallback { +public class LookupAttemptingCallback implements + AuthenticatedLdapEntryContextCallback, AuthenticatedLdapEntryContextMapper { + @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { - try { - ctx.lookup(ldapEntryIdentification.getRelativeName()); - } - catch (NamingException e) { - // rethrow, because we aren't allowed to throw checked exceptions. - throw LdapUtils.convertLdapException(e); - } + mapWithContext(ctx, ldapEntryIdentification); } + + @Override + public DirContextOperations mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { + try { + return (DirContextOperations) ctx.lookup(ldapEntryIdentification.getRelativeName()); + } + catch (NamingException e) { + // rethrow, because we aren't allowed to throw checked exceptions. + throw LdapUtils.convertLdapException(e); + } + } } diff --git a/core/src/main/java/org/springframework/ldap/query/ContainerCriteria.java b/core/src/main/java/org/springframework/ldap/query/ContainerCriteria.java index 134741d8..62d68dc7 100644 --- a/core/src/main/java/org/springframework/ldap/query/ContainerCriteria.java +++ b/core/src/main/java/org/springframework/ldap/query/ContainerCriteria.java @@ -17,7 +17,7 @@ package org.springframework.ldap.query; /** - * Builder for and/or filters. + * And/or filter builder support for LdapQuery. * * @author Mattias Hellborg Arthursson * @since 2.0 diff --git a/core/src/main/java/org/springframework/ldap/query/DefaultContainerCriteria.java b/core/src/main/java/org/springframework/ldap/query/DefaultContainerCriteria.java index 680a867e..77e6a158 100644 --- a/core/src/main/java/org/springframework/ldap/query/DefaultContainerCriteria.java +++ b/core/src/main/java/org/springframework/ldap/query/DefaultContainerCriteria.java @@ -94,8 +94,8 @@ class DefaultContainerCriteria implements ContainerCriteria { @Override public Filter filter() { - if(filters.size() == 1) { + // No need to wrap in And/OrFilter if there's just one condition. return filters.iterator().next(); } diff --git a/core/src/test/java/org/springframework/ldap/query/LdapQueryBuilderTest.java b/core/src/test/java/org/springframework/ldap/query/LdapQueryBuilderTest.java index 23a217d8..33646101 100644 --- a/core/src/test/java/org/springframework/ldap/query/LdapQueryBuilderTest.java +++ b/core/src/test/java/org/springframework/ldap/query/LdapQueryBuilderTest.java @@ -126,4 +126,8 @@ public class LdapQueryBuilderTest { query.base("dc=261consulting,dc=com"); } + @Test(expected = IllegalStateException.class) + public void verifyThatOperatorChangeIsIllegal() { + query().where("cn").is("John Doe").and("sn").is("Doe").or("objectclass").is("person"); + } } diff --git a/samples/plain/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java b/samples/plain/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java index 6703665d..b1e917ec 100644 --- a/samples/plain/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java +++ b/samples/plain/src/main/java/org/springframework/ldap/samples/plain/dao/PersonDaoImpl.java @@ -32,6 +32,8 @@ import javax.naming.directory.Attributes; import javax.naming.ldap.LdapName; import java.util.List; +import static org.springframework.ldap.query.LdapQueryBuilder.query; + /** * Default implementation of PersonDao. This implementation uses * DirContextAdapter for managing attribute values. We use a ContextMapper @@ -84,8 +86,9 @@ public class PersonDaoImpl implements PersonDao { @Override public List findAll() { - EqualsFilter filter = new EqualsFilter("objectclass", "person"); - return ldapTemplate.search(LdapUtils.emptyLdapName(), filter.encode(), PERSON_CONTEXT_MAPPER); + return ldapTemplate.search(query() + .where("objectclass").is("person"), + PERSON_CONTEXT_MAPPER); } @Override diff --git a/src/docbkx/advancedqueries.xml b/src/docbkx/advancedqueries.xml new file mode 100644 index 00000000..08e35fff --- /dev/null +++ b/src/docbkx/advancedqueries.xml @@ -0,0 +1,111 @@ + + + Advanced LDAP Queries + + + LDAP Query Builder Parameters + + The LdapQueryBuilder and its associated classes is intended to support all parameters + that can be supplied to an LDAP search. The following parameters are supported: + + base - specifies the root DN in the LDAP tree where the search should start. + searchScope - specifies how deep into the LDAP tree the search should traverse. + attributes - specifies the attributes to return from the search. Default is all. + countLimit - specifies the maximum number of entries to return from the search. + timeLimit - specifies the maximum time that the search may take. + Search filter - the conditions that the entries we are looking for must meet. + + + + An LdapQueryBuilder is created with a call to the query method of + LdapQueryBuilder. It's intended as a fluent builder API, where the base parameters are defined first, + followed by the filter specification calls. Once filter conditions have been started to be defined with a call to the + where method of LdapQueryBuilder, later attempts to call e.g. base + will be rejected. The base search parameters are optional, but at least one filter specification call is required. + + + Search for all entries with objectclass person + import static org.springframework.ldap.query.LdapQueryBuilder.query; +... + +List<Person> persons = ldapTemplate.search( + query().where("objectclass").is("person"), + new PersonAttributesMapper()); + + + + Search for all entries with objectclass person and cn=John Doe + import static org.springframework.ldap.query.LdapQueryBuilder.query; +... + +List<Person> persons = ldapTemplate.search( + query().where("objectclass").is("person") + .and("cn").is("John Doe"), + new PersonAttributesMapper()); + + + + Search for all entries with objectclass person starting at <literal>dc=261consulting,dc=com</literal> + import static org.springframework.ldap.query.LdapQueryBuilder.query; +... + +List<Person> persons = ldapTemplate.search( + query().base("dc=261consulting,dc=com") + .where("objectclass").is("person"), + new PersonAttributesMapper()); + + + + Search for all entries with objectclass person starting at <literal>dc=261consulting,dc=com</literal>, + only returning the cn attribute + import static org.springframework.ldap.query.LdapQueryBuilder.query; +... + +List<Person> persons = ldapTemplate.search( + query().base("dc=261consulting,dc=com") + .attributes("cn") + .where("objectclass").is("person"), + new PersonAttributesMapper()); + + + + Search for all entries with objectclass person where sn=Doe or Doo (nested query) + import static org.springframework.ldap.query.LdapQueryBuilder.query; +... +List<Person> persons = ldapTemplate.search( + query().where("objectclass").is("person"), + .and(query().where("cn").is("Doe").or("cn").is("Doo)); + new PersonAttributesMapper()); + + + + + Filter Criteria + + The examples above demonstrates simple equals conditions in LDAP filters. The LDAP query builder has support + for the following criteria types: + + is - specifies an equals condition (=). + gte - specifies a greater than or equals condition (>=). + lte - specifies a less than or equals condition (<=). + + like - specifies a "like" condition where wildcards can be included in the query, + e.g. where("cn").like("J*hn Doe") will result int the filter (cn=J*hn Doe). + + + whitespaceWildcardsLike - specifies a condition where all whitespace is replaced with wildcards, + e.g. where("cn").whitespaceWildcardsLike("John Doe") will result in the filter + (cn=*John*Doe*). + + + isPresent - specifies condition that checks for the presence of an attribute, + e.g. where("cn").isPresent() will result in the filter (cn=*). + + + not - specifies that the current condition should be negated, e.g. + where("sn").not().is("Doe) will result in the filter (!(sn=Doe)) + + + + + \ No newline at end of file diff --git a/src/docbkx/basic.xml b/src/docbkx/basic.xml index 46201622..005d7cc5 100644 --- a/src/docbkx/basic.xml +++ b/src/docbkx/basic.xml @@ -12,6 +12,7 @@ AttributesMapper that returns a single attribute package com.example.dao; +import static org.springframework.ldap.query.LdapQueryBuilder.query; public class PersonDaoImpl implements PersonDao { private LdapTemplate ldapTemplate; @@ -20,13 +21,13 @@ public class PersonDaoImpl implements PersonDao { this.ldapTemplate = ldapTemplate; } - public List getAllPersonNames() { - return ldapTemplate.search( - "", "(objectclass=person)", - new AttributesMapper() { - public Object mapFromAttributes(Attributes attrs) + public List<String> getAllPersonNames() { + return ldapTemplate.search(query() + .where("objectclass").is("person"), + new AttributesMapper<String>() { + public String mapFromAttributes(Attributes attrs) throws NamingException { - return attrs.get("cn").get(); + return (String) attrs.get("cn").get(); } }); } @@ -49,12 +50,13 @@ public class PersonDaoImpl implements PersonDao { AttributesMapper that returns a Person object package com.example.dao; +import static org.springframework.ldap.query.LdapQueryBuilder.query; public class PersonDaoImpl implements PersonDao { private LdapTemplate ldapTemplate; ... - private class PersonAttributesMapper implements AttributesMapper { - public Object mapFromAttributes(Attributes attrs) throws NamingException { + private class PersonAttributesMapper implements AttributesMapper<Person> { + public Person mapFromAttributes(Attributes attrs) throws NamingException { Person person = new Person(); person.setFullName((String)attrs.get("cn").get()); person.setLastName((String)attrs.get("sn").get()); @@ -63,8 +65,9 @@ public class PersonDaoImpl implements PersonDao { } } - public List getAllPersons() { - return ldapTemplate.search("", "(objectclass=person)", new PersonAttributesMapper()); } } @@ -85,7 +88,7 @@ public class PersonDaoImpl implements PersonDao { private LdapTemplate ldapTemplate; ... public Person findPerson(String dn) { - return (Person) ldapTemplate.lookup(dn, new PersonAttributesMapper()); + return ldapTemplate.lookup(dn, new PersonAttributesMapper()); } } @@ -95,31 +98,37 @@ public class PersonDaoImpl implements PersonDao { this case resulting in a Person object. - - Building Dynamic Filters - - We can build dynamic filters to use in searches, using the classes - from the org.springframework.ldap.filter - package. Let's say that we want the following filter: + + Building LDAP Queries + LDAP searches involve a number of parameters, e.g. Base LDAP path, + search scope, attributes to return, and search filters. + Spring LDAP provides an LdapQueryBuilder with a fluent + API for building LDAP Queries. + Let's say that we want to perform a search starting at the + base DN dc=261consulting,dc=com, limiting the returned attributes to "cn" + and "sn", with the following filter: (&(objectclass=person)(sn=?)), where we want the ? to be replaced with the value of the parameter - lastName. This is how we do it using the filter support - classes: + lastName. This is how we do it using the LdapQueryBuilder: Building a search filter dynamically package com.example.dao; +import static org.springframework.ldap.query.LdapQueryBuilder.query; public class PersonDaoImpl implements PersonDao { private LdapTemplate ldapTemplate; ... public List getPersonNamesByLastName(String lastName) { - AndFilter filter = new AndFilter(); - filter.and(new EqualsFilter("objectclass", "person")); - filter.and(new EqualsFilter("sn", lastName)); - return ldapTemplate.search( - "", filter.encode(), + + LdapQuery query = query() + .base("dc=261consulting,dc=com") + .attributes("cn", "sn") + .where("objectclass").is("person") + .and("sn").is(lastName); + + return ldapTemplate.search(query, new AttributesMapper() { public Object mapFromAttributes(Attributes attrs) throws NamingException { @@ -129,30 +138,31 @@ public class PersonDaoImpl implements PersonDao { } } - - To perform a wildcard search, it's possible to use the - WhitespaceWildcardsFilter: - - - Building a wildcard search filter - - AndFilter filter = new AndFilter(); -filter.and(new EqualsFilter("objectclass", "person")); -filter.and(new WhitespaceWildcardsFilter("cn", cn)); - - - In addition to simplifying building of complex search filters, - the Filter classes also provide proper escaping - of any unsafe characters. This prevents "ldap injection", - where a user might use such characters to inject unwanted operations - into your LDAP operations. + In addition to simplifying building of complex search parameters, + the LdapQueryBuilder and its associated classes + also provide proper escaping of any unsafe characters in search filters. + This prevents "ldap injection", where a user might use such + characters to inject unwanted operations into your LDAP operations. + + There are many overloaded methods in LdapTemplate for + performing LDAP searches. This is in order to accommodate for as many different use cases + and programming style preferences as possible. For the vast majority of use cases the ones that + take an LdapQuery as input will be the recommended methods to use. + + + The AttributesMapper is just one of the available callback interfaces to use + when handling search and lookup data. See for alternatives. + + + + For more information on the LdapQueryBuilder see . - + Dynamically Building Distinguished Names The standard Java implementation of Distinguished Name, LdapUtils.getStringValue(dn, "c")); - person.setCompany(LdapUtils.getStringValue(dn, "ou")); - person.setFullname(LdapUtils.getStringValue(dn, "cn")); + person.setCountry(LdapUtils.getStringValue(dn, "c")); + person.setCompany(LdapUtils.getStringValue(dn, "ou")); + person.setFullname(LdapUtils.getStringValue(dn, "cn")); // Populate rest of person object using attributes. return person; @@ -253,7 +263,7 @@ protected Person buildPerson(Name dn, Attributes attrs) { Since Java version <=1.4 didn't provide any public Distinguished Name implementation at all, Spring LDAP 1.3.2 and lower provided its own implementation, DistinguishedName. This implementation - suffered from a couple of shortcomings of its own, and have been deprecated in version 2.0. + suffered from a couple of shortcomings of its own, and has been deprecated in version 2.0. Users are now recommended to use LdapName along with the utilities described above instead. diff --git a/src/docbkx/index.xml b/src/docbkx/index.xml index 4cb586d7..6776b414 100644 --- a/src/docbkx/index.xml +++ b/src/docbkx/index.xml @@ -9,7 +9,7 @@ Mattias - Arthursson + Hellborg Arthursson Ulrik @@ -41,6 +41,7 @@ + diff --git a/src/docbkx/overview.xml b/src/docbkx/overview.xml index fd619324..d22161af 100644 --- a/src/docbkx/overview.xml +++ b/src/docbkx/overview.xml @@ -15,7 +15,7 @@ NamingEnumeration. It also provides a more comprehensive unchecked Exception hierarchy, built on Spring's DataAccessException. As a bonus, it also contains - classes for dynamically building LDAP filters and DNs (Distinguished + classes for dynamically building LDAP queries and DNs (Distinguished Names), LDAP attribute management, and client-side LDAP transaction management. Consider, for example, a method that should search some storage for @@ -96,6 +96,7 @@ public class TraditionalPersonDaoImpl implements PersonDao { package com.example.dao; +import static org.springframework.ldap.query.LdapQueryBuilder.query; public class PersonDaoImpl implements PersonDao { private LdapTemplate ldapTemplate; @@ -106,7 +107,7 @@ public class PersonDaoImpl implements PersonDao { public List getAllPersonNames() { return ldapTemplate.search( - "", "(objectclass=person)", + query().where("objectclass").is("person"), new AttributesMapper() { public Object mapFromAttributes(Attributes attrs) throws NamingException { @@ -202,10 +203,6 @@ public class PersonDaoImpl implements PersonDao { spring-jdbc (If you are planning to use the client side compensating transaction support) - - ldapbp (Sun LDAP Booster Pack - if you will use the LDAP v3 Server controls integration - and you're not using Java5 or higher) - commons-pool (If you are planning to use the pooling functionality) @@ -216,330 +213,63 @@ public class PersonDaoImpl implements PersonDao { - - Package structure - - This section provides an overview of the logical package structure - of the Spring LDAP codebase. The dependencies for each package are clearly - noted. - -
- Spring LDAP package structure - - - - - - - - - -
- - - org.springframework.transaction.compensating - - The transaction.compensating package contains - the generic compensating transaction support. This is not LDAP-specific - or JNDI-specific in any way. - - - - Dependencies: commons-logging - + + What's new in Spring LDAP 2.0? + + While quite significant modernizations have been made to the Spring LDAP APi in version 2.0, great care has been + taken to ensure backward compatibility as far as possible. + Code that works with Spring LDAP 1.3.x should with very few exceptions still compile and run using the 2.0 libraries + without any modifications whatsoever. + + + The exception is a small number of classes that have been moved to new packages in order to make + a couple of important refactorings possible. The moved classes are usually not part of the intended + public API, and the migration procedure should be very smooth - wherever a Spring LDAP class cannot be found + after upgrade, just organize the imports in your IDE. + + + You will probably encounter some deprecation warnings though, and there are also a lot of other API improvements. + The recommendation for getting as much as possible out of the 2.0 version is to move away from the deprecated + classes and methods and migrate to the new, improved API utilities. + + + Below is a list of the most important changes in Spring LDAP 2.0. + + + Java 1.6 is now required when using Spring LDAP. Spring versions starting at 2.0 and up are still supported. + + The central API has been updated with Java 5 features such as generics and varargs. As a consequence, + the entire spring-ldap-tiger module has been deprecated and users are encouraged to migrate + to use the core Spring LDAP classes. The parameterization of the core interfaces will most likely cause + lots of compilation warnings, and you are obviously encouraged to take appropriate action to get rid + of these warning. + + + DistinguishedName and associated classes have been deprecated in favor of standard + Java LdapName. See for information on how the library + helps working with LdapNames. + + + Fluent LDAP query support has been added. This makes for a more pleasant programming experience when + working with LDAP searches in Spring LDAP. See and + for more information about the LDAP query builder support. + + + The old authenticate methods in LdapTemplate have been deprecated + in favor of a couple of new authenticate methods that work with + LdapQuery objects and throw exceptions on authentication failure, + making it easier for the user to find out what caused an authentication attempt to fail. + - - - - org.springframework.ldap - - The ldap package contains the exceptions of - the library. These exceptions form an unchecked hierarchy that mirrors - the NamingException hierarchy. - - - - Dependencies: spring-core - - - - - - org.springframework.ldap.core - - The ldap.core package contains the central - abstractions of the library. These abstractions include - AuthenticationSource, ContextSource, DirContextProcessor, and - NameClassPairCallbackHandler. This package also contains the central - class LdapTemplate, plus various mappers and executors. - - - - Dependencies: ldap, ldap.support, spring-beans, - spring-core, spring-tx, commons-lang, commons-logging - - - - - - org.springframework.ldap.core.support - - The ldap.core.support package contains - supporting implementations of some of the core interfaces. - - - - Dependencies: ldap, ldap.core, ldap.support, spring-core, - spring-beans, spring-context, commons-lang, commons-logging - - - - - - org.springframework.ldap.core.simple - - The ldap.core.simple package contains - Java5-specific parts of Spring LDAP. It's mainly a simplification - layer that takes advantage of the generics support in Java5, in - order to get typesafe context mappers as well as typesafe search - and lookup methods. - - - - Dependencies: ldap.core - - - - - - org.springframework.ldap.pool - - The ldap.pool package contains - support for detailed pool configuration on a per-ContextSource - basis. Pooling support is provided by PoolingContextSource which - can wrap any ContextSource and pool both read-only and read-write - DirContext objects. Jakarta Commons-Pool is used to provide the - underlying pool implementation. - - - - Dependencies: ldap.core, commons-lang, commons-pool - - - - - - org.springframework.ldap.pool.factory - - The ldap.pool.factory package contains - the actual pooling context source and other classes for context creation. - - - - Dependencies: ldap, ldap.core, ldap.pool, ldap.pool.validation, - spring-beans, spring-tx, commons-lang, commons-logging, commons-pool - - - - - - org.springframework.ldap.pool.validation - - The ldap.pool.validation package contains - the connection validation support. - - - - Dependencies: ldap.pool, commons-lang, commons-logging - - - - - - org.springframework.ldap.support - - The ldap.support package contains supporting - utilities, like the exception translation mechanism. - - - - Dependencies: ldap, spring-core, commons-lang, commons-logging - - - - - - org.springframework.ldap.authentication - - The ldap.authentication package contains an - implementation of the AuthenticationSource interface that can be used - if the user should be allowed to read some information even though not - logged in. - - - - Dependencies: ldap.core, spring-beans, commons-lang - - - - - - org.springframework.ldap.control - - The ldap.control package contains an abstract - implementation of the DirContextProcessor interface that can be used as - a basis for processing RequestControls and ResponseControls. There is - also a concrete implementation that handles paged search results and one - that handles sorting. The - LDAP Booster - Pack is used to get support for controls, unless Java5 is used. - - - - Dependencies: ldap, ldap.core, LDAP booster pack (optional), spring-core, - commons-lang, commons-logging - - - - - - org.springframework.ldap.filter - - The ldap.filter package contains the Filter - abstraction and several implementations of it. - - - - Dependencies: ldap.core, spring-core, commons-lang - - - - - - org.springframework.ldap.transaction.compensating - - The ldap.transaction.compensating package contains the - core LDAP-specific implementation of compensating transactions. - - - - Dependencies: ldap.core, ldap.core.support, transaction.compensating, - spring-core, commons-lang, commons-logging - - - - - - org.springframework.ldap.transaction.compensating.manager - - The ldap.transaction.compensating.manager package contains the - core implementation classes for client-side compensating transactions. - - - - Dependencies: ldap, ldap.core, ldap.support, ldap.transaction.compensating, - ldap.transaction.compensating.support, transaction.compensating, - spring-tx, spring-jdbc, spring-orm, commons-logging - - - - - - org.springframework.ldap.transaction.compensating.support - - The ldap.transaction.compensating.support package contains - useful helper classes for client-side compensating transactions. - - - - Dependencies: ldap.core, ldap.transaction.compensating - - - - - - org.springframework.ldap.ldif - - The ldap.ldif package provides support for parsing LDIF - files. - - - - Dependencies: ldap.core - - - - - - org.springframework.ldap.ldif.batch - - The ldap.ldif.batch package provides the classes necessary to - use the LDIF parser in the Spring Batch framework. - - - - Dependencies: ldap.core, ldap.ldif.parser, spring-batch, - spring-core, spring-beans, commons-logging - - - - - - org.springframework.ldap.ldif.parser - - The ldap.ldif.parser package provides the parser classes - and interfaces. - - - - Dependencies: ldap.core, ldap.schema, ldap.ldif, ldap.ldif.support, - spring-core, spring-beans, commons-lang, commons-logging - - - - - - org.springframework.ldap.ldif.support - - The ldap.ldif.support package provides the necessary auxiliary - classes utilized by the LDIF Parser. - - - - Dependencies: ldap.core, ldap.ldif, commons-lang, commons-logging - - - - - - org.springframework.ldap.odm - - The ldap.odm package provides the classes and interfaces - enabling - annotation based object-directory mapping. - - - - Dependencies: ldap, ldap.core, ldap.core.simple, ldap.filter, spring-beans, - commons-cli, commons-logging, freemarker - - - - - For the exact list of jar dependencies, see the Spring LDAP Maven2 - Project Object Model (POM) files in the source tree.
- Support - Spring LDAP 1.3 is supported on Spring 2.0 and later. + Spring LDAP 2.0 is supported on Spring 2.0 and later. The community support forum is located at http://forum.springframework.org, + url="http://forum.spring.io/forum/spring-projects/data/ldap">http://forum.spring.io/forum/spring-projects/data/ldap, and the project web page is http://www.springframework.org/ldap. + url="http://projects.spring.io/spring-ldap/">http://projects.spring.io/spring-ldap/. diff --git a/src/docbkx/transactions.xml b/src/docbkx/transactions.xml index 0f04c4c3..8beeb459 100644 --- a/src/docbkx/transactions.xml +++ b/src/docbkx/transactions.xml @@ -80,7 +80,7 @@ <property name="ldapTemplate" ref="ldapTemplate" /> </bean> - <tx:annotation-driven> + <tx:annotation-driven /> ... While the this setup will work fine for most simple use cases, some more complex scenarios will diff --git a/src/docbkx/user-authentication.xml b/src/docbkx/user-authentication.xml index a01114f3..eae4bbf3 100644 --- a/src/docbkx/user-authentication.xml +++ b/src/docbkx/user-authentication.xml @@ -35,8 +35,7 @@ LDAP search based on e.g. the user name to get this DN: private String getDnForUser(String uid) { - Filter f = new EqualsFilter("uid", uid); - List result = ldapTemplate.search(LdapUtils.emptyLdapName(), f.toString(), + List result = ldapTemplate.search(query().where("uid").is(uid), new AbstractContextMapper() { protected Object doMapFromContext(DirContextOperations ctx) { return ctx.getNameInNamespace(); @@ -52,29 +51,15 @@ forced to concern herself with the DN of the user, she can only search for the user's uid, and the search always starts at the root of the tree (the empty path). A more flexible method would let the user specify the search - base, the search filter, and the credentials. Spring LDAP 1.3.0 introduced - new authenticate methods in LdapTemplate that provide this - functionality: - - - - boolean authenticate(Name base, String filter, String - password); - - - - boolean authenticate(String base, String filter, String - password); - - - - Using one of these methods, authentication becomes as simple as - this: + base, the search filter, and the credentials. Spring LDAP includes an authenticate + method in LdapTemplate that provide this functionality: boolean authenticate(LdapQuery query, String password); + + Using this method authentication becomes as simple as this: Authenticating a user using Spring LDAP. - boolean authenticated = ldapTemplate.authenticate("", "(uid=john.doe)", "secret"); + ldapTemplate.authenticate(query().where("uid").is("john.doe"), "secret"); As described in below, some setups may require additional operations to be performed @@ -116,25 +101,18 @@ // It is imperative that the created DirContext instance is always closed LdapUtils.closeContext(ctx); } -}It would be better if the operation could be provided as an +} + It would be better if the operation could be provided as an implementation of a callback interface, thus not limiting the operation to - always be a lookup. Spring LDAP 1.3.0 introduced the - callback interface - AuthenticatedLdapEntryContextCallback and a few - corresponding authenticate methods: + always be a lookup. Spring LDAP includes the callback interface + AuthenticatedLdapEntryContextMapper and a + corresponding authenticate method: + <T> T authenticate(LdapQuery query, String password, AuthenticatedLdapEntryContextMapper<T> mapper); - - boolean authenticate(Name base, String filter, String - password, AuthenticatedLdapEntryContextCallback - callback); - + - - boolean authenticate(String base, String filter, String - password, AuthenticatedLdapEntryContextCallback - callback); - + This opens up for any operation to be performed on the authenticated @@ -144,83 +122,30 @@ Performing an LDAP operation on the authenticated context using Spring LDAP. - AuthenticatedLdapEntryContextCallback contextCallback = new AuthenticatedLdapEntryContextCallback() { - public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { + AuthenticatedLdapEntryContextMapper<DirContextOperations> mapper = new AuthenticatedLdapEntryContextMapper<DirContextOperations>() { + public DirContextOperations mapWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { try { - ctx.lookup(ldapEntryIdentification.getRelativeDn()); + return (DirContextOperations) ctx.lookup(ldapEntryIdentification.getRelativeName()); } catch (NamingException e) { - throw new RuntimeException("Failed to lookup " + ldapEntryIdentification.getRelativeDn(), e); + throw new RuntimeException("Failed to lookup " + ldapEntryIdentification.getRelativeName(), e); } } }; -ldapTemplate.authenticate("", "(uid=john.doe)", "secret", contextCallback)); +ldapTemplate.authenticate(query().where("uid").is("john.doe"), "secret", mapper); - Retrieving the Authentication Exception + Obsolete authentication methods - So far, the methods have only been able to tell the user whether or - not the authentication succeeded. There has been no way of retrieving the - actual exception. Spring LDAP 1.3.1 introduced the - AuthenticationErrorCallback and a few more - authenticate methods: - - - - boolean authenticate(Name base, String filter, String - password, AuthenticationErrorCallback errorCallback); - - - - boolean authenticate(String base, String filter, String - password, AuthenticationErrorCallback errorCallback); - - - - boolean authenticate(Name base, String filter, String - password, AuthenticatedLdapEntryContextCallback callback, - AuthenticationErrorCallback errorCallback); - - - - boolean authenticate(String base, String filter, String - password, AuthenticatedLdapEntryContextCallback callback, - AuthenticationErrorCallback errorCallback); - - - - A convenient collecting implementation of the error callback - interface is also provided: - - public final class CollectingAuthenticationErrorCallback implements AuthenticationErrorCallback { - private Exception error; - - public void execute(Exception e) { - this.error = e; - } - - public Exception getError() { - return error; - } -}The code needed for authenticating a user and retrieving the - authentication exception in case of an error boils down to this: - - - Authenticating a user and retrieving the authentication - exception. - - import org.springframework.ldap.core.support.CollectingAuthenticationErrorCallback; -... -CollectingAuthenticationErrorCallback errorCallback = new CollectingAuthenticationErrorCallback(); -boolean result = ldapTemplate.authenticate("", filter.toString(), "invalidpassword", errorCallback); -if (!result) { - Exception error = errorCallback.getError(); - // error is likely of type org.springframework.ldap.AuthenticationException -} - + + In addition to the authenticate methods described above + there are a number of deprecated methods that can be used for authentication. + While these will work fine, the recommendation is to use the + LdapQuery methods instead. + diff --git a/test/integration-tests/src/test/java/org/springframework/ldap/itest/LdapTemplateAuthenticationITest.java b/test/integration-tests/src/test/java/org/springframework/ldap/itest/LdapTemplateAuthenticationITest.java index e8c03456..f80d6dd3 100644 --- a/test/integration-tests/src/test/java/org/springframework/ldap/itest/LdapTemplateAuthenticationITest.java +++ b/test/integration-tests/src/test/java/org/springframework/ldap/itest/LdapTemplateAuthenticationITest.java @@ -16,29 +16,31 @@ package org.springframework.ldap.itest; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; -import static org.junit.Assert.assertNotNull; - -import javax.naming.NamingException; -import javax.naming.directory.DirContext; - +import junit.framework.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.ldap.AuthenticationException; import org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback; +import org.springframework.ldap.core.CollectingAuthenticationErrorCallback; import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.LdapEntryIdentification; import org.springframework.ldap.core.LdapTemplate; -import org.springframework.ldap.core.support.CollectingAuthenticationErrorCallback; import org.springframework.ldap.core.support.LookupAttemptingCallback; import org.springframework.ldap.filter.AndFilter; import org.springframework.ldap.filter.EqualsFilter; -import org.springframework.ldap.filter.WhitespaceWildcardsFilter; import org.springframework.test.context.ContextConfiguration; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; +import static org.springframework.ldap.query.LdapQueryBuilder.query; + /** * Tests the authenticate methods of LdapTemplate. * @@ -58,14 +60,34 @@ public class LdapTemplateAuthenticationITest extends AbstractLdapTemplateIntegra assertTrue(tested.authenticate("", filter.toString(), "password")); } - @Test + @Test + public void testAuthenticateWithLdapQuery() { + AndFilter filter = new AndFilter(); + filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("uid", "some.person3")); + tested.authenticate(query() + .where("objectclass").is("person") + .and("uid").is("some.person3"), + "password"); + } + + @Test public void testAuthenticateWithInvalidPassword() { AndFilter filter = new AndFilter(); filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("uid", "some.person3")); assertFalse(tested.authenticate("", filter.toString(), "invalidpassword")); } - @Test + @Test(expected = AuthenticationException.class) + public void testAuthenticateWithLdapQueryAndInvalidPassword() { + AndFilter filter = new AndFilter(); + filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("uid", "some.person3")); + tested.authenticate(query() + .where("objectclass").is("person") + .and("uid").is("some.person3"), + "invalidpassword"); + } + + @Test public void testAuthenticateWithLookupOperationPerformedOnAuthenticatedContext() { AndFilter filter = new AndFilter(); filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("uid", "some.person3")); @@ -83,7 +105,28 @@ public class LdapTemplateAuthenticationITest extends AbstractLdapTemplateIntegra assertTrue(tested.authenticate("", filter.toString(), "password", contextCallback)); } - @Test + @Test + public void testAuthenticateWithLdapQueryAndMapper() { + DirContextOperations ctx = tested.authenticate(query() + .where("objectclass").is("person") + .and("uid").is("some.person3"), + "password", + new LookupAttemptingCallback()); + + Assert.assertNotNull(ctx); + assertEquals("some.person3", ctx.getStringAttribute("uid")); + } + + @Test(expected = AuthenticationException.class) + public void testAuthenticateWithLdapQueryAndMapperAndInvalidPassword() { + DirContextOperations ctx = tested.authenticate(query() + .where("objectclass").is("person") + .and("uid").is("some.person3"), + "invalidpassword", + new LookupAttemptingCallback()); + } + + @Test public void testAuthenticateWithInvalidPasswordAndCollectedException() { AndFilter filter = new AndFilter(); filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("uid", "some.person3"));