From c8f8eff4270d19e5022b6810a47ca0504de47208 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 30 Nov 2012 00:34:59 +0000
Subject: [PATCH] Fix OPENDJ-656: Add support for generating LDAP filters and DNs from printf style templates
---
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Filter.java | 15 ++-
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AVA.java | 58 +++++++-------
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/DN.java | 109 +++++++++++++++++++++++++++
opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/FilterTestCase.java | 2
opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java | 31 +++++++
5 files changed, 181 insertions(+), 34 deletions(-)
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AVA.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AVA.java
index e46452e..54d8a9e 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AVA.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AVA.java
@@ -148,6 +148,35 @@
return new AVA(attribute, value);
}
+ static void escapeAttributeValue(final String str, final StringBuilder builder) {
+ if (str.length() > 0) {
+ char c = str.charAt(0);
+ int startPos = 0;
+ if ((c == ' ') || (c == '#')) {
+ builder.append('\\');
+ builder.append(c);
+ startPos = 1;
+ }
+ final int length = str.length();
+ for (int si = startPos; si < length; si++) {
+ c = str.charAt(si);
+ if (c < ' ') {
+ for (final byte b : getBytes(String.valueOf(c))) {
+ builder.append('\\');
+ builder.append(StaticUtils.byteToLowerHex(b));
+ }
+ } else {
+ if ((c == ' ' && si == length - 1)
+ || (c == '"' || c == '+' || c == ',' || c == ';' || c == '<'
+ || c == '=' || c == '>' || c == '\\' || c == '\u0000')) {
+ builder.append('\\');
+ }
+ builder.append(c);
+ }
+ }
+ }
+ }
+
private static void appendHexChars(final SubstringReader reader,
final StringBuilder valueBuffer, final StringBuilder hexBuffer) throws DecodeException {
final int length = hexBuffer.length();
@@ -720,34 +749,7 @@
builder.append("#");
StaticUtils.toHex(attributeValue, builder);
} else {
- final String str = attributeValue.toString();
- if (str.length() == 0) {
- return builder;
- }
- char c = str.charAt(0);
- int startPos = 0;
- if ((c == ' ') || (c == '#')) {
- builder.append('\\');
- builder.append(c);
- startPos = 1;
- }
- final int length = str.length();
- for (int si = startPos; si < length; si++) {
- c = str.charAt(si);
- if (c < ' ') {
- for (final byte b : getBytes(String.valueOf(c))) {
- builder.append('\\');
- builder.append(StaticUtils.byteToLowerHex(b));
- }
- } else {
- if ((c == ' ' && si == length - 1)
- || (c == '"' || c == '+' || c == ',' || c == ';' || c == '<'
- || c == '=' || c == '>' || c == '\\' || c == '\u0000')) {
- builder.append('\\');
- }
- builder.append(c);
- }
- }
+ escapeAttributeValue(attributeValue.toString(), builder);
}
}
return builder;
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/DN.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/DN.java
index a32ce02..8c80bb7 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/DN.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/DN.java
@@ -86,6 +86,113 @@
};
/**
+ * Returns the LDAP string representation of the provided DN attribute value
+ * in a form suitable for substitution directly into a DN string. This
+ * method may be useful in cases where a DN is to be constructed from a DN
+ * template using {@code String#format(String, Object...)}. The following
+ * example illustrates two approaches to constructing a DN:
+ *
+ * <pre>
+ * // This may contain user input.
+ * String attributeValue = ...;
+ *
+ * // Using the equality filter constructor:
+ * DN dn = DN.valueOf("ou=people,dc=example,dc=com").child("uid", attributeValue);
+ *
+ * // Using a String template:
+ * String dnTemplate = "uid=%s,ou=people,dc=example,dc=com";
+ * String dnString = String.format(dnTemplate,
+ * DN.escapeAttributeValue(attributeValue));
+ * DN dn = DN.valueOf(dnString);
+ * </pre>
+ *
+ * <b>Note:</b> attribute values do not and should not be escaped before
+ * passing them to constructors like {@link #child(String, Object)}.
+ * Escaping is only required when creating DN strings.
+ *
+ * @param attributeValue
+ * The attribute value.
+ * @return The LDAP string representation of the provided filter assertion
+ * value in a form suitable for substitution directly into a filter
+ * string.
+ */
+ public static String escapeAttributeValue(final Object attributeValue) {
+ Validator.ensureNotNull(attributeValue);
+ final String s = String.valueOf(attributeValue);
+ final StringBuilder builder = new StringBuilder(s.length());
+ AVA.escapeAttributeValue(s, builder);
+ return builder.toString();
+ }
+
+ /**
+ * Creates a new DN using the provided DN template and unescaped attribute
+ * values using the default schema. This method first escapes each of the
+ * attribute values and then substitutes them into the template using
+ * {@link String#format(String, Object...)}. Finally, the formatted string
+ * is parsed as an LDAP DN using {@link #valueOf(String)}.
+ * <p>
+ * This method may be useful in cases where the structure of a DN is not
+ * known at compile time, for example, it may be obtained from a
+ * configuration file. Example usage:
+ *
+ * <pre>
+ * String template = "uid=%s,ou=people,dc=example,dc=com";
+ * DN dn = DN.format(template, "bjensen");
+ * </pre>
+ *
+ * @param template
+ * The DN template.
+ * @param attributeValues
+ * The attribute values to be substituted into the template.
+ * @return The formatted template parsed as a {@code DN}.
+ * @throws LocalizedIllegalArgumentException
+ * If the formatted template is not a valid LDAP string
+ * representation of a DN.
+ * @see #escapeAttributeValue(Object)
+ */
+ public static DN format(final String template, final Object... attributeValues) {
+ return format(template, Schema.getDefaultSchema(), attributeValues);
+ }
+
+ /**
+ * Creates a new DN using the provided DN template and unescaped attribute
+ * values using the provided schema. This method first escapes each of the
+ * attribute values and then substitutes them into the template using
+ * {@link String#format(String, Object...)}. Finally, the formatted string
+ * is parsed as an LDAP DN using {@link #valueOf(String)}.
+ * <p>
+ * This method may be useful in cases where the structure of a DN is not
+ * known at compile time, for example, it may be obtained from a
+ * configuration file. Example usage:
+ *
+ * <pre>
+ * String template = "uid=%s,ou=people,dc=example,dc=com";
+ * DN dn = DN.format(template, "bjensen");
+ * </pre>
+ *
+ * @param template
+ * The DN template.
+ * @param schema
+ * The schema to use when parsing the DN.
+ * @param attributeValues
+ * The attribute values to be substituted into the template.
+ * @return The formatted template parsed as a {@code DN}.
+ * @throws LocalizedIllegalArgumentException
+ * If the formatted template is not a valid LDAP string
+ * representation of a DN.
+ * @see #escapeAttributeValue(Object)
+ */
+ public static DN format(final String template, final Schema schema,
+ final Object... attributeValues) {
+ final String[] attributeValueStrings = new String[attributeValues.length];
+ for (int i = 0; i < attributeValues.length; i++) {
+ attributeValueStrings[i] = escapeAttributeValue(attributeValues[i]);
+ }
+ final String dnString = String.format(template, (Object[]) attributeValueStrings);
+ return valueOf(dnString, schema);
+ }
+
+ /**
* Returns the Root DN. The Root DN does not contain and RDN components and
* is superior to all other DNs.
*
@@ -107,6 +214,7 @@
* DN.
* @throws NullPointerException
* If {@code dn} was {@code null}.
+ * @see #format(String, Object...)
*/
public static DN valueOf(final String dn) {
return valueOf(dn, Schema.getDefaultSchema());
@@ -126,6 +234,7 @@
* DN.
* @throws NullPointerException
* If {@code dn} or {@code schema} was {@code null}.
+ * @see #format(String, Schema, Object...)
*/
public static DN valueOf(final String dn, final Schema schema) {
Validator.ensureNotNull(dn, schema);
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Filter.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Filter.java
index 875068d..eb2946b 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Filter.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Filter.java
@@ -66,6 +66,9 @@
* import static org.forgerock.opendj.Filter.*;
*
* Filter filter = and(equality("cn", "bjensen"), greaterOrEqual("age", 21));
+ *
+ * // Alternatively use a filter template:
+ * Filter filter = Filter.format("(&(cn=%s)(age>=%s))", "bjensen", 21);
* </pre>
*
* @see <a href="http://tools.ietf.org/html/rfc4511">RFC 4511 - Lightweight
@@ -564,20 +567,20 @@
* Filter.escapeAssertionValue(assertionValue));
* Filter filter = Filter.valueOf(filterString);
* </pre>
+ *
* If {@code assertionValue} is not an instance of {@code ByteString} then
* it will be converted using the {@link ByteString#valueOf(Object)} method.
- *
* <p>
* <b>Note:</b> assertion values do not and should not be escaped before
* passing them to constructors like {@link #equality(String, Object)}.
* Escaping is only required when creating filter strings.
- * <p>
- * <b>Note:</b>
+ *
* @param assertionValue
* The assertion value.
* @return The LDAP string representation of the provided filter assertion
* value in a form suitable for substitution directly into a filter
* string.
+ * @see #format(String, Object...)
*/
public static String escapeAssertionValue(final Object assertionValue) {
Validator.ensureNotNull(assertionValue);
@@ -881,6 +884,7 @@
* @throws LocalizedIllegalArgumentException
* If {@code string} is not a valid LDAP string representation
* of a filter.
+ * @see #format(String, Object...)
*/
public static Filter valueOf(final String string) {
Validator.ensureNotNull(string);
@@ -928,7 +932,7 @@
*
* <pre>
* String template = "(|(cn=%s)(uid=user.%s))";
- * Filter filter = Filter.valueOf(template, "alice", 123);
+ * Filter filter = Filter.format(template, "alice", 123);
* </pre>
*
* Any assertion values which are not instances of {@code ByteString} will
@@ -942,8 +946,9 @@
* @throws LocalizedIllegalArgumentException
* If the formatted template is not a valid LDAP string
* representation of a filter.
+ * @see #escapeAssertionValue(Object)
*/
- public static Filter valueOf(final String template, final Object... assertionValues) {
+ public static Filter format(final String template, final Object... assertionValues) {
final String[] assertionValueStrings = new String[assertionValues.length];
for (int i = 0; i < assertionValues.length; i++) {
assertionValueStrings[i] = escapeAssertionValue(assertionValues[i]);
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java
index fef9be6..1c542b6 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java
@@ -979,4 +979,35 @@
assertEquals(DN.valueOf(dn).rename(DN.valueOf(fromDN), DN.valueOf(toDN)), DN
.valueOf(expectedDN));
}
+
+ /**
+ * Tests the {@link DN#format(String, Object...)} method.
+ */
+ @Test
+ public void testFormatNoEscape() {
+ DN actual = DN.format("deviceId=%s,uid=%s,dc=test", 123, "bjensen");
+ DN expected = DN.valueOf("dc=test").child("uid", "bjensen").child("deviceId", 123);
+ assertEquals(actual, expected);
+ assertEquals(actual.toString(), "deviceId=123,uid=bjensen,dc=test");
+ }
+
+ /**
+ * Tests the {@link DN#format(String, Object...)} method.
+ */
+ @Test
+ public void testFormatEscape() {
+ DN actual = DN.format("uid=%s,dc=test", "#cn=foo+sn=bar");
+ DN expected = DN.valueOf("dc=test").child("uid", "#cn=foo+sn=bar");
+ assertEquals(actual, expected);
+ assertEquals(actual.toString(), "uid=\\#cn\\=foo\\+sn\\=bar,dc=test");
+ }
+
+ /**
+ * Tests the {@link DN#escapeAttributeValue(Object)} method.
+ */
+ @Test
+ public void testEscapeAttributeValue() {
+ String actual = DN.escapeAttributeValue("#cn=foo+sn=bar");
+ assertEquals(actual, "\\#cn\\=foo\\+sn\\=bar");
+ }
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/FilterTestCase.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/FilterTestCase.java
index d6a0725..2b79e13 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/FilterTestCase.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/FilterTestCase.java
@@ -307,7 +307,7 @@
@Test(dataProvider = "getAssertionValues")
public void testValueOfTemplate(String template, List<?> assertionValues, String expected)
throws Exception {
- Filter filter = Filter.valueOf(template, assertionValues.toArray());
+ Filter filter = Filter.format(template, assertionValues.toArray());
assertEquals(filter.toString(), expected);
}
--
Gitblit v1.10.0