LDAP-263: Added LdapQuery method to OdmManager. Added methods for hardcoded filters in LdapQueryBuilder.

This commit is contained in:
Mattias Hellborg Arthursson
2013-09-25 10:59:23 +02:00
parent 27682b0b53
commit bed57b9ef8
9 changed files with 215 additions and 22 deletions

View File

@@ -17,9 +17,12 @@
package org.springframework.ldap.query;
import org.springframework.ldap.filter.Filter;
import org.springframework.ldap.filter.HardcodedFilter;
import org.springframework.ldap.support.LdapEncoder;
import org.springframework.ldap.support.LdapUtils;
import javax.naming.Name;
import java.text.MessageFormat;
/**
* Builder of LdapQueries. Start with a call to {@link #query()}, proceed with specifying the
@@ -150,6 +153,7 @@ public class LdapQueryBuilder implements LdapQuery {
*
* @param attribute The attribute that the first part of the filter should test against.
* @return A ConditionCriteria instance for specifying the compare operation.
* @throws IllegalStateException if a filter has already been specified.
*/
public ConditionCriteria where(String attribute) {
assertFilterNotStarted();
@@ -157,6 +161,44 @@ public class LdapQueryBuilder implements LdapQuery {
return new DefaultConditionCriteria(rootContainer, attribute);
}
/**
* Specify a hardcoded filter. Please note that using this method, the filter string will not be
* validated or escaped in any way. <b>Never</b> use direct user input and use it concatenating strings
* to use as LDAP filters. Doing so opens up for &quot;LDAP injection&quot;, where malicious user
* may inject specifically constructed data to form filters at their convenience. When user input is used
* consider using {@link #where(String)} or {@link #filter(String, String...)} instead.
*
* @param hardcodedFilter The hardcoded filter string to use in the search.
* @return this instance.
* @throws IllegalStateException if a filter has already been specified.
*/
public LdapQuery filter(String hardcodedFilter) {
assertFilterNotStarted();
rootContainer = new DefaultContainerCriteria(this);
rootContainer.append(new HardcodedFilter(hardcodedFilter));
return this;
}
/**
* Specify a hardcoded filter using the specified parameters. The parameters will be properly encoded using
* {@link LdapEncoder#filterEncode(String)} to make sure no malicious data gets through. The <code>filterFormat</code>
* String should be formatted for input to {@link MessageFormat#format(String, Object...)}.
*
* @param filterFormat the filter format string, formatted for input to {@link MessageFormat#format(String, Object...)}.
* @param params the parameters that will be used for building the final filter. All parameters will be properly encoded.
* @return this instance.
* @throws IllegalStateException if a filter has already been specified.
*/
public LdapQuery filter(String filterFormat, String... params) {
Object[] encodedParams = new String[params.length];
for (int i=0; i < params.length; i++) {
encodedParams[i] = LdapEncoder.filterEncode(params[i]);
}
return filter(MessageFormat.format(filterFormat, encodedParams));
}
private void assertFilterNotStarted() {
if(rootContainer != null) {
throw new IllegalStateException("Invalid operation - filter condition specification already started");

View File

@@ -59,6 +59,34 @@ public class LdapQueryBuilderTest {
assertEquals("(cn=*)", result.filter().encode());
}
@Test
public void buildHardcodedFilter() {
LdapQuery result = query().filter("(cn=Person*)");
assertEquals("(cn=Person*)", result.filter().encode());
}
@Test(expected = IllegalStateException.class)
public void verifyThatHardcodedFilterFailsIfFilterAlreadySpecified() {
LdapQueryBuilder query = query();
query.where("sn").is("Doe");
query.filter("(cn=Person*)");
}
@Test(expected = IllegalStateException.class)
public void verifyThatFilterFormatFailsIfFilterAlreadySpecified() {
LdapQueryBuilder query = query();
query.where("sn").is("Doe");
query.filter("(|(cn={0})(cn={1}))", "Person*", "Parson*");
}
@Test
public void buildFilterFormat() {
LdapQuery result = query().filter("(|(cn={0})(cn={1}))", "Person*", "Parson*");
assertEquals("(|(cn=Person\\2a)(cn=Parson\\2a))", result.filter().encode());
}
@Test
public void testBuildSimpleAnd() {
LdapQuery query = query()

View File

@@ -1,9 +1,26 @@
/*
* 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.odm.core;
import java.util.List;
import org.springframework.ldap.query.LdapQuery;
import javax.naming.Name;
import javax.naming.directory.SearchControls;
import java.util.List;
/**
* The OdmManager interface provides generic CRUD (create/read/update/delete)
@@ -13,6 +30,7 @@ import javax.naming.directory.SearchControls;
* {@link org.springframework.ldap.odm.annotations}.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
* @author Mattias Hellborg Arthursson
*
* @see org.springframework.ldap.odm.annotations.Entry
* @see org.springframework.ldap.odm.annotations.Attribute
@@ -94,4 +112,19 @@ public interface OdmManager {
* @see <a href="http://www.rfc-editor.org/rfc/rfc4515.txt">LDAP: String Representation of Search Filters RFC.</a>
*/
<T> List<T> search(Class<T> clazz, Name base, String filter, SearchControls searchControls);
/**
* Search for entries in the LDAP directory.
* <p>
* Only those entries that both match the query search filter and
* are represented by the given Java class are returned.
*
* @param <T> The Java type to return
* @param clazz The Java type to return
* @param query the LDAP query specification
* @return All matching entries.
*
* @exception org.springframework.ldap.NamingException on error.
*/
<T> List<T> search(Class<T> clazz, LdapQuery query);
}

View File

@@ -29,6 +29,8 @@ import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.odm.core.OdmException;
import org.springframework.ldap.odm.core.OdmManager;
import org.springframework.ldap.odm.typeconversion.ConverterManager;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.query.SearchScope;
import org.springframework.ldap.support.LdapUtils;
import javax.naming.Name;
@@ -52,6 +54,7 @@ import java.util.Set;
* convert between Java and LDAP representations of attribute values.
*
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
* @author Mattias Hellborg Arthursson
*
*/
public final class OdmManagerImpl implements OdmManager {
@@ -272,11 +275,32 @@ public final class OdmManagerImpl implements OdmManager {
return result;
}
/*
* (non-Javadoc)
* @see org.springframework.ldap.odm.core.OdmManager#findAll(javax.naming.Name, javax.naming.directory.SearchControls)
*/
@Override
public <T> List<T> search(Class<T> clazz, LdapQuery query) {
SearchControls searchControls = new SearchControls();
SearchScope searchScope = query.searchScope();
if(searchScope == null) {
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
} else {
searchControls.setSearchScope(searchScope.getId());
}
Integer countLimit = query.countLimit();
if(countLimit != null) {
searchControls.setCountLimit(countLimit);
}
Integer timeLimit = query.timeLimit();
if(timeLimit != null) {
searchControls.setCountLimit(timeLimit);
}
// Defaults to null which means 'all', so if it's not set we're still good.
searchControls.setReturningAttributes(query.attributes());
return search(clazz, query.base(), query.filter().encode(), searchControls);
}
public <T> List<T> findAll(Class<T> managedClass, Name base, SearchControls scope) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Searching for all Entries with objectClass=%1$s, with base=%2$s, scope=%3$s",

View File

@@ -67,6 +67,7 @@ import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
// Tests all OdmManager functions
public final class TestLdap {
@@ -361,6 +362,16 @@ public final class TestLdap {
assertEquals("Hartnell", foundPerson.getSurname());
}
@Test
public void verifySearchWithLdapQuery() {
List<Person> result = odmManager.search(Person.class, query().base(baseName).where("cn").is("William Hartnell"));
assertEquals(1, result.size());
Person foundPerson = result.get(0);
assertEquals("William Hartnell", foundPerson.getCn());
assertEquals("Hartnell", foundPerson.getSurname());
}
@Test
public void updatePlainPerson() {
List<PlainPerson> result = odmManager.search(PlainPerson.class, baseName, "(cn=William Hartnell)", searchControls);

View File

@@ -1,3 +1,19 @@
/*
* 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.odm.sample;
import org.springframework.context.ApplicationContext;
@@ -5,17 +21,14 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.ldap.odm.core.OdmManager;
import org.springframework.ldap.support.LdapUtils;
import javax.naming.directory.SearchControls;
import javax.naming.ldap.LdapName;
import java.util.List;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
// A very simple example - just showing how little code you actually need to write
// when using Spring LDAP ODM
public class SearchForPeople {
private static final SearchControls searchControls =
new SearchControls(SearchControls.SUBTREE_SCOPE, 100, 10000,
null, true, false);
private static final LdapName baseDn = LdapUtils.newLdapName("o=Whoniverse");
private static void print(List<SimplePerson> personList) {
@@ -31,7 +44,7 @@ public class SearchForPeople {
OdmManager odmManager = (OdmManager)context.getBean("odmManager");
// Find people with a surname of Harvey
List<SimplePerson> searchResults = odmManager.search(SimplePerson.class, baseDn, "sn=Harvey", searchControls);
List<SimplePerson> searchResults = odmManager.search(SimplePerson.class, query().base(baseDn).where("sn").is("Harvey"));
// Print the results
print(searchResults);

View File

@@ -1,19 +1,34 @@
/*
* 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.odm.sample;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.HashSet;
import java.util.ArrayList;
import javax.naming.Name;
import static org.springframework.ldap.odm.annotations.Attribute.*;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
import javax.naming.Name;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import static org.springframework.ldap.odm.annotations.Attribute.Type;
/**
* Automatically generated to represent the LDAP object classes
* "person", "top".

View File

@@ -108,4 +108,28 @@ List&lt;Person&gt; persons = ldapTemplate.search(
</itemizedlist>
</para>
</sect1>
<sect1>
<title>Hardcoded Filters</title>
<para>
There are occasions when you will want to specify a hardcoded filter as input to an <literal>LdapQuery</literal>.
<literal>LdapQueryBuilder</literal> has two methods for this purpose:
<itemizedlist>
<listitem>
<literal>filter(String hardcodedFilter)</literal> - uses the specified string as filter.
Note that the specified input string will not be touched in any way, meaning that this method is not
particularly well suited if you are building filters from user input.
</listitem>
<listitem>
<literal>filter(String filterFormat, String... params)</literal> - uses the specified string as input
to <literal>MessageFormat</literal>, properly encoding the parameters and inserting them at the
specified places in the filter string.
</listitem>
</itemizedlist>
</para>
<para>
You cannot mix the hardcoded filter methods with the <literal>where</literal> approach described above; it's
either one or the other. What this means is that if you specified a filter using <literal>filter()</literal>
you will get an exception if you try to call <literal>where</literal> afterwards.
</para>
</sect1>
</chapter>

View File

@@ -50,6 +50,9 @@
<para><code>&lt;T&gt; List&lt;T&gt; search(Class&lt;T&gt; clazz, Name
base, String filter, SearchControls searchControls)</code></para>
</listitem>
<listitem>
<para><code>&lt;T&gt; List&lt;T&gt; search(Class&lt;T&gt; clazz, LdapQuery query)</code></para>
</listitem>
</itemizedlist>
<para>A reference to an implementation of this interface can be obtained