diff --git a/build.gradle b/build.gradle
index 10e7f313fa..d12b00b60d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -27,12 +27,15 @@ allprojects {
ext.snapshotBuild = version.endsWith('SNAPSHOT')
ext.springVersion = '3.2.7.RELEASE'
ext.spring4Version = '4.0.1.RELEASE'
+ ext.springLdapVersion = '1.3.2.RELEASE'
+ ext.springLdap2Version = '2.0.1.CI-SNAPSHOT'
group = 'org.springframework.security'
repositories {
mavenCentral()
- maven { url "http://repo.springsource.org/plugins-release" }
+ maven { url "https://repo.spring.io/libs-snapshot" }
+ maven { url "https://repo.spring.io/plugins-release" }
maven { url "http://repo.terracotta.org/maven2/" }
}
@@ -117,6 +120,9 @@ configure(coreModuleProjects) {
if (details.requested.name == 'ehcache-terracotta') {
details.useVersion '2.1.1'
}
+ if (details.requested.group == 'org.springframework.ldap') {
+ details.useVersion springLdap2Version
+ }
}
}
diff --git a/gradle/javaprojects.gradle b/gradle/javaprojects.gradle
index 86e7e69129..5f20df2e69 100644
--- a/gradle/javaprojects.gradle
+++ b/gradle/javaprojects.gradle
@@ -11,7 +11,6 @@ apply plugin: 'propdeps-eclipse'
sourceCompatibility = 1.5
targetCompatibility = 1.5
-ext.springLdapVersion = '1.3.2.RELEASE'
ext.ehcacheVersion = '1.6.2'
ext.aspectjVersion = '1.6.10'
ext.apacheDsVersion = '1.5.5'
diff --git a/ldap/src/main/java/org/springframework/security/ldap/LdapEncoder.java b/ldap/src/main/java/org/springframework/security/ldap/LdapEncoder.java
new file mode 100644
index 0000000000..406a98d30f
--- /dev/null
+++ b/ldap/src/main/java/org/springframework/security/ldap/LdapEncoder.java
@@ -0,0 +1,237 @@
+/*
+ * 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.security.ldap;
+
+import org.springframework.ldap.BadLdapGrammarException;
+
+/**
+ * Helper class to encode and decode ldap names and values.
+ *
+ *
+ * NOTE: This is a copy from Spring LDAP so that both Spring LDAP 1.x and 2.x
+ * can be supported without reflection.
+ *
+ *
+ * @author Adam Skogman
+ * @author Mattias Hellborg Arthursson
+ */
+final class LdapEncoder {
+
+ private static final int HEX = 16;
+ private static String[] NAME_ESCAPE_TABLE = new String[96];
+
+ private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
+
+ static {
+
+ // Name encoding table -------------------------------------
+
+ // all below 0x20 (control chars)
+ for (char c = 0; c < ' '; c++) {
+ NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(c);
+ }
+
+ NAME_ESCAPE_TABLE['#'] = "\\#";
+ NAME_ESCAPE_TABLE[','] = "\\,";
+ NAME_ESCAPE_TABLE[';'] = "\\;";
+ NAME_ESCAPE_TABLE['='] = "\\=";
+ NAME_ESCAPE_TABLE['+'] = "\\+";
+ NAME_ESCAPE_TABLE['<'] = "\\<";
+ NAME_ESCAPE_TABLE['>'] = "\\>";
+ NAME_ESCAPE_TABLE['\"'] = "\\\"";
+ NAME_ESCAPE_TABLE['\\'] = "\\\\";
+
+ // Filter encoding table -------------------------------------
+
+ // fill with char itself
+ for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
+ FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
+ }
+
+ // escapes (RFC2254)
+ FILTER_ESCAPE_TABLE['*'] = "\\2a";
+ FILTER_ESCAPE_TABLE['('] = "\\28";
+ FILTER_ESCAPE_TABLE[')'] = "\\29";
+ FILTER_ESCAPE_TABLE['\\'] = "\\5c";
+ FILTER_ESCAPE_TABLE[0] = "\\00";
+
+ }
+
+ /**
+ * All static methods - not to be instantiated.
+ */
+ private LdapEncoder() {
+ }
+
+ protected static String toTwoCharHex(char c) {
+
+ String raw = Integer.toHexString(c).toUpperCase();
+
+ if (raw.length() > 1) {
+ return raw;
+ } else {
+ return "0" + raw;
+ }
+ }
+
+ /**
+ * Escape a value for use in a filter.
+ *
+ * @param value
+ * the value to escape.
+ * @return a properly escaped representation of the supplied value.
+ */
+ public static String filterEncode(String value) {
+
+ if (value == null)
+ return null;
+
+ // make buffer roomy
+ StringBuilder encodedValue = new StringBuilder(value.length() * 2);
+
+ int length = value.length();
+
+ for (int i = 0; i < length; i++) {
+
+ char c = value.charAt(i);
+
+ if (c < FILTER_ESCAPE_TABLE.length) {
+ encodedValue.append(FILTER_ESCAPE_TABLE[c]);
+ } else {
+ // default: add the char
+ encodedValue.append(c);
+ }
+ }
+
+ return encodedValue.toString();
+ }
+
+ /**
+ * LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI!
+ *
+ *
Escapes:
' ' [space] - "\ " [if first or last]
'#'
+ * [hash] - "\#"
',' [comma] - "\,"
';' [semicolon] - "\;"
'=
+ * [equals] - "\="
'+' [plus] - "\+"
'<' [less than] -
+ * "\<"
'>' [greater than] - "\>"
'"' [double quote] -
+ * "\""
'\' [backslash] - "\\"
+ *
+ * @param value
+ * the value to escape.
+ * @return The escaped value.
+ */
+ public static String nameEncode(String value) {
+
+ if (value == null)
+ return null;
+
+ // make buffer roomy
+ StringBuilder encodedValue = new StringBuilder(value.length() * 2);
+
+ int length = value.length();
+ int last = length - 1;
+
+ for (int i = 0; i < length; i++) {
+
+ char c = value.charAt(i);
+
+ // space first or last
+ if (c == ' ' && (i == 0 || i == last)) {
+ encodedValue.append("\\ ");
+ continue;
+ }
+
+ if (c < NAME_ESCAPE_TABLE.length) {
+ // check in table for escapes
+ String esc = NAME_ESCAPE_TABLE[c];
+
+ if (esc != null) {
+ encodedValue.append(esc);
+ continue;
+ }
+ }
+
+ // default: add the char
+ encodedValue.append(c);
+ }
+
+ return encodedValue.toString();
+
+ }
+
+ /**
+ * Decodes a value. Converts escaped chars to ordinary chars.
+ *
+ * @param value
+ * Trimmed value, so no leading an trailing blanks, except an
+ * escaped space last.
+ * @return The decoded value as a string.
+ * @throws BadLdapGrammarException
+ */
+ static public String nameDecode(String value)
+ throws BadLdapGrammarException {
+
+ if (value == null)
+ return null;
+
+ // make buffer same size
+ StringBuilder decoded = new StringBuilder(value.length());
+
+ int i = 0;
+ while (i < value.length()) {
+ char currentChar = value.charAt(i);
+ if (currentChar == '\\') {
+ if (value.length() <= i + 1) {
+ // Ending with a single backslash is not allowed
+ throw new BadLdapGrammarException(
+ "Unexpected end of value " + "unterminated '\\'");
+ } else {
+ char nextChar = value.charAt(i + 1);
+ if (nextChar == ',' || nextChar == '=' || nextChar == '+'
+ || nextChar == '<' || nextChar == '>'
+ || nextChar == '#' || nextChar == ';'
+ || nextChar == '\\' || nextChar == '\"'
+ || nextChar == ' ') {
+ // Normal backslash escape
+ decoded.append(nextChar);
+ i += 2;
+ } else {
+ if (value.length() <= i + 2) {
+ throw new BadLdapGrammarException(
+ "Unexpected end of value "
+ + "expected special or hex, found '"
+ + nextChar + "'");
+ } else {
+ // This should be a hex value
+ String hexString = "" + nextChar
+ + value.charAt(i + 2);
+ decoded.append((char) Integer.parseInt(hexString,
+ HEX));
+ i += 3;
+ }
+ }
+ }
+ } else {
+ // This character wasn't escaped - just append it
+ decoded.append(currentChar);
+ i++;
+ }
+ }
+
+ return decoded.toString();
+
+ }
+}
diff --git a/ldap/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java b/ldap/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java
index f6fd2d3a60..e5fd840c1d 100644
--- a/ldap/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java
+++ b/ldap/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java
@@ -24,7 +24,6 @@ import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
-import org.springframework.ldap.core.LdapEncoder;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.util.Assert;
diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticator.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticator.java
index 23e3072223..27ee5e0c1d 100644
--- a/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticator.java
+++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticator.java
@@ -15,7 +15,6 @@
package org.springframework.security.ldap.authentication;
-import org.springframework.ldap.core.LdapEncoder;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.ldap.search.LdapUserSearch;
import org.springframework.beans.factory.InitializingBean;
diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapEncoder.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapEncoder.java
new file mode 100644
index 0000000000..71f5ca9575
--- /dev/null
+++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapEncoder.java
@@ -0,0 +1,237 @@
+/*
+ * 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.security.ldap.authentication;
+
+import org.springframework.ldap.BadLdapGrammarException;
+
+/**
+ * Helper class to encode and decode ldap names and values.
+ *
+ *
+ * NOTE: This is a copy from Spring LDAP so that both Spring LDAP 1.x and 2.x
+ * can be supported without reflection.
+ *
+ *
+ * @author Adam Skogman
+ * @author Mattias Hellborg Arthursson
+ */
+final class LdapEncoder {
+
+ private static final int HEX = 16;
+ private static String[] NAME_ESCAPE_TABLE = new String[96];
+
+ private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
+
+ static {
+
+ // Name encoding table -------------------------------------
+
+ // all below 0x20 (control chars)
+ for (char c = 0; c < ' '; c++) {
+ NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(c);
+ }
+
+ NAME_ESCAPE_TABLE['#'] = "\\#";
+ NAME_ESCAPE_TABLE[','] = "\\,";
+ NAME_ESCAPE_TABLE[';'] = "\\;";
+ NAME_ESCAPE_TABLE['='] = "\\=";
+ NAME_ESCAPE_TABLE['+'] = "\\+";
+ NAME_ESCAPE_TABLE['<'] = "\\<";
+ NAME_ESCAPE_TABLE['>'] = "\\>";
+ NAME_ESCAPE_TABLE['\"'] = "\\\"";
+ NAME_ESCAPE_TABLE['\\'] = "\\\\";
+
+ // Filter encoding table -------------------------------------
+
+ // fill with char itself
+ for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
+ FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
+ }
+
+ // escapes (RFC2254)
+ FILTER_ESCAPE_TABLE['*'] = "\\2a";
+ FILTER_ESCAPE_TABLE['('] = "\\28";
+ FILTER_ESCAPE_TABLE[')'] = "\\29";
+ FILTER_ESCAPE_TABLE['\\'] = "\\5c";
+ FILTER_ESCAPE_TABLE[0] = "\\00";
+
+ }
+
+ /**
+ * All static methods - not to be instantiated.
+ */
+ private LdapEncoder() {
+ }
+
+ protected static String toTwoCharHex(char c) {
+
+ String raw = Integer.toHexString(c).toUpperCase();
+
+ if (raw.length() > 1) {
+ return raw;
+ } else {
+ return "0" + raw;
+ }
+ }
+
+ /**
+ * Escape a value for use in a filter.
+ *
+ * @param value
+ * the value to escape.
+ * @return a properly escaped representation of the supplied value.
+ */
+ public static String filterEncode(String value) {
+
+ if (value == null)
+ return null;
+
+ // make buffer roomy
+ StringBuilder encodedValue = new StringBuilder(value.length() * 2);
+
+ int length = value.length();
+
+ for (int i = 0; i < length; i++) {
+
+ char c = value.charAt(i);
+
+ if (c < FILTER_ESCAPE_TABLE.length) {
+ encodedValue.append(FILTER_ESCAPE_TABLE[c]);
+ } else {
+ // default: add the char
+ encodedValue.append(c);
+ }
+ }
+
+ return encodedValue.toString();
+ }
+
+ /**
+ * LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI!
+ *
+ *
Escapes:
' ' [space] - "\ " [if first or last]
'#'
+ * [hash] - "\#"
',' [comma] - "\,"
';' [semicolon] - "\;"
'=
+ * [equals] - "\="
'+' [plus] - "\+"
'<' [less than] -
+ * "\<"
'>' [greater than] - "\>"
'"' [double quote] -
+ * "\""
'\' [backslash] - "\\"
+ *
+ * @param value
+ * the value to escape.
+ * @return The escaped value.
+ */
+ public static String nameEncode(String value) {
+
+ if (value == null)
+ return null;
+
+ // make buffer roomy
+ StringBuilder encodedValue = new StringBuilder(value.length() * 2);
+
+ int length = value.length();
+ int last = length - 1;
+
+ for (int i = 0; i < length; i++) {
+
+ char c = value.charAt(i);
+
+ // space first or last
+ if (c == ' ' && (i == 0 || i == last)) {
+ encodedValue.append("\\ ");
+ continue;
+ }
+
+ if (c < NAME_ESCAPE_TABLE.length) {
+ // check in table for escapes
+ String esc = NAME_ESCAPE_TABLE[c];
+
+ if (esc != null) {
+ encodedValue.append(esc);
+ continue;
+ }
+ }
+
+ // default: add the char
+ encodedValue.append(c);
+ }
+
+ return encodedValue.toString();
+
+ }
+
+ /**
+ * Decodes a value. Converts escaped chars to ordinary chars.
+ *
+ * @param value
+ * Trimmed value, so no leading an trailing blanks, except an
+ * escaped space last.
+ * @return The decoded value as a string.
+ * @throws BadLdapGrammarException
+ */
+ static public String nameDecode(String value)
+ throws BadLdapGrammarException {
+
+ if (value == null)
+ return null;
+
+ // make buffer same size
+ StringBuilder decoded = new StringBuilder(value.length());
+
+ int i = 0;
+ while (i < value.length()) {
+ char currentChar = value.charAt(i);
+ if (currentChar == '\\') {
+ if (value.length() <= i + 1) {
+ // Ending with a single backslash is not allowed
+ throw new BadLdapGrammarException(
+ "Unexpected end of value " + "unterminated '\\'");
+ } else {
+ char nextChar = value.charAt(i + 1);
+ if (nextChar == ',' || nextChar == '=' || nextChar == '+'
+ || nextChar == '<' || nextChar == '>'
+ || nextChar == '#' || nextChar == ';'
+ || nextChar == '\\' || nextChar == '\"'
+ || nextChar == ' ') {
+ // Normal backslash escape
+ decoded.append(nextChar);
+ i += 2;
+ } else {
+ if (value.length() <= i + 2) {
+ throw new BadLdapGrammarException(
+ "Unexpected end of value "
+ + "expected special or hex, found '"
+ + nextChar + "'");
+ } else {
+ // This should be a hex value
+ String hexString = "" + nextChar
+ + value.charAt(i + 2);
+ decoded.append((char) Integer.parseInt(hexString,
+ HEX));
+ i += 3;
+ }
+ }
+ }
+ } else {
+ // This character wasn't escaped - just append it
+ decoded.append(currentChar);
+ i++;
+ }
+ }
+
+ return decoded.toString();
+
+ }
+}