Skip to content

Commit 78d2be9

Browse files
committed
Polish LDAP Module
Issue gh-3834
1 parent 55895f3 commit 78d2be9

25 files changed

+97
-321
lines changed

ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.security.ldap;
1818

19-
import java.io.UnsupportedEncodingException;
2019
import java.net.URLDecoder;
2120
import java.net.URLEncoder;
2221
import java.nio.charset.StandardCharsets;
@@ -84,7 +83,7 @@ public DefaultSpringSecurityContextSource(String providerUrl) {
8483
setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() {
8584

8685
@Override
87-
@SuppressWarnings("rawtypes")
86+
@SuppressWarnings("unchecked")
8887
public void setupEnvironment(Hashtable env, String dn, String password) {
8988
super.setupEnvironment(env, dn, password);
9089
// Remove the pooling flag unless authenticating as the 'manager' user.
@@ -145,7 +144,7 @@ private static String buildProviderUrl(List<String> urls, String baseDn) {
145144
StringBuilder providerUrl = new StringBuilder();
146145
for (String serverUrl : urls) {
147146
String trimmedUrl = serverUrl.trim();
148-
if ("".equals(trimmedUrl)) {
147+
if (trimmedUrl.isEmpty()) {
149148
continue;
150149
}
151150
providerUrl.append(trimmedUrl);
@@ -160,21 +159,11 @@ private static String buildProviderUrl(List<String> urls, String baseDn) {
160159
}
161160

162161
private static String encodeUrl(String url) {
163-
try {
164-
return URLEncoder.encode(url, StandardCharsets.UTF_8.toString());
165-
}
166-
catch (UnsupportedEncodingException ex) {
167-
throw new IllegalStateException(ex);
168-
}
162+
return URLEncoder.encode(url, StandardCharsets.UTF_8);
169163
}
170164

171165
private String decodeUrl(String url) {
172-
try {
173-
return URLDecoder.decode(url, StandardCharsets.UTF_8.toString());
174-
}
175-
catch (UnsupportedEncodingException ex) {
176-
throw new IllegalStateException(ex);
177-
}
166+
return URLDecoder.decode(url, StandardCharsets.UTF_8);
178167
}
179168

180169
}
Lines changed: 2 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2005-2010 the original author or authors.
2+
* Copyright 2005-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.security.ldap;
1818

19-
import org.springframework.ldap.BadLdapGrammarException;
20-
2119
/**
2220
* Helper class to encode and decode ldap names and values.
2321
*
@@ -31,26 +29,7 @@
3129
*/
3230
final class LdapEncoder {
3331

34-
private static final int HEX = 16;
35-
36-
private static String[] NAME_ESCAPE_TABLE = new String[96];
37-
static {
38-
// all below 0x20 (control chars)
39-
for (char c = 0; c < ' '; c++) {
40-
NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(c);
41-
}
42-
NAME_ESCAPE_TABLE['#'] = "\\#";
43-
NAME_ESCAPE_TABLE[','] = "\\,";
44-
NAME_ESCAPE_TABLE[';'] = "\\;";
45-
NAME_ESCAPE_TABLE['='] = "\\=";
46-
NAME_ESCAPE_TABLE['+'] = "\\+";
47-
NAME_ESCAPE_TABLE['<'] = "\\<";
48-
NAME_ESCAPE_TABLE['>'] = "\\>";
49-
NAME_ESCAPE_TABLE['\"'] = "\\\"";
50-
NAME_ESCAPE_TABLE['\\'] = "\\\\";
51-
}
52-
53-
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
32+
private static final String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
5433

5534
static {
5635
// fill with char itself
@@ -71,11 +50,6 @@ final class LdapEncoder {
7150
private LdapEncoder() {
7251
}
7352

74-
protected static String toTwoCharHex(char c) {
75-
String raw = Integer.toHexString(c).toUpperCase();
76-
return (raw.length() > 1) ? raw : "0" + raw;
77-
}
78-
7953
/**
8054
* Escape a value for use in a filter.
8155
* @param value the value to escape.
@@ -94,102 +68,4 @@ static String filterEncode(String value) {
9468
return encodedValue.toString();
9569
}
9670

97-
/**
98-
* LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI!
99-
*
100-
* <br/>
101-
* Escapes:<br/>
102-
* ' ' [space] - "\ " [if first or last] <br/>
103-
* '#' [hash] - "\#" <br/>
104-
* ',' [comma] - "\," <br/>
105-
* ';' [semicolon] - "\;" <br/>
106-
* '= [equals] - "\=" <br/>
107-
* '+' [plus] - "\+" <br/>
108-
* '&lt;' [less than] - "\&lt;" <br/>
109-
* '&gt;' [greater than] - "\&gt;" <br/>
110-
* '"' [double quote] - "\"" <br/>
111-
* '\' [backslash] - "\\" <br/>
112-
* @param value the value to escape.
113-
* @return The escaped value.
114-
*/
115-
static String nameEncode(String value) {
116-
if (value == null) {
117-
return null;
118-
}
119-
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
120-
int length = value.length();
121-
int last = length - 1;
122-
for (int i = 0; i < length; i++) {
123-
char c = value.charAt(i);
124-
// space first or last
125-
if (c == ' ' && (i == 0 || i == last)) {
126-
encodedValue.append("\\ ");
127-
continue;
128-
}
129-
// check in table for escapes
130-
if (c < NAME_ESCAPE_TABLE.length) {
131-
String esc = NAME_ESCAPE_TABLE[c];
132-
if (esc != null) {
133-
encodedValue.append(esc);
134-
continue;
135-
}
136-
}
137-
// default: add the char
138-
encodedValue.append(c);
139-
}
140-
return encodedValue.toString();
141-
}
142-
143-
/**
144-
* Decodes a value. Converts escaped chars to ordinary chars.
145-
* @param value Trimmed value, so no leading an trailing blanks, except an escaped
146-
* space last.
147-
* @return The decoded value as a string.
148-
* @throws BadLdapGrammarException
149-
*/
150-
static String nameDecode(String value) throws BadLdapGrammarException {
151-
if (value == null) {
152-
return null;
153-
}
154-
StringBuilder decoded = new StringBuilder(value.length());
155-
int i = 0;
156-
while (i < value.length()) {
157-
char currentChar = value.charAt(i);
158-
if (currentChar == '\\') {
159-
// Ending with a single backslash is not allowed
160-
if (value.length() <= i + 1) {
161-
throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'");
162-
}
163-
char nextChar = value.charAt(i + 1);
164-
if (isNormalBackslashEscape(nextChar)) {
165-
decoded.append(nextChar);
166-
i += 2;
167-
}
168-
else {
169-
if (value.length() <= i + 2) {
170-
throw new BadLdapGrammarException(
171-
"Unexpected end of value " + "expected special or hex, found '" + nextChar + "'");
172-
}
173-
// This should be a hex value
174-
String hexString = "" + nextChar + value.charAt(i + 2);
175-
decoded.append((char) Integer.parseInt(hexString, HEX));
176-
i += 3;
177-
}
178-
}
179-
else {
180-
// This character wasn't escaped - just append it
181-
decoded.append(currentChar);
182-
i++;
183-
}
184-
}
185-
186-
return decoded.toString();
187-
188-
}
189-
190-
private static boolean isNormalBackslashEscape(char nextChar) {
191-
return nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>'
192-
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"' || nextChar == ' ';
193-
}
194-
19571
}

ldap/src/main/java/org/springframework/security/ldap/LdapUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public static void closeEnumeration(NamingEnumeration ne) {
8383
*/
8484
public static String getRelativeName(String fullDn, Context baseCtx) throws NamingException {
8585
String baseDn = baseCtx.getNameInNamespace();
86-
if (baseDn.length() == 0) {
86+
if (baseDn.isEmpty()) {
8787
return fullDn;
8888
}
8989
LdapName base = LdapNameBuilder.newInstance(baseDn).build();

ldap/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
1818

1919
import java.text.MessageFormat;
2020
import java.util.ArrayList;
21-
import java.util.Arrays;
21+
import java.util.Collections;
2222
import java.util.HashMap;
2323
import java.util.HashSet;
2424
import java.util.List;
@@ -40,7 +40,6 @@
4040

4141
import org.springframework.core.log.LogMessage;
4242
import org.springframework.dao.IncorrectResultSizeDataAccessException;
43-
import org.springframework.ldap.core.ContextExecutor;
4443
import org.springframework.ldap.core.ContextMapper;
4544
import org.springframework.ldap.core.ContextSource;
4645
import org.springframework.ldap.core.DirContextAdapter;
@@ -98,7 +97,7 @@ public boolean compare(String dn, String attributeName, Object value) {
9897
searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
9998
Object[] params = new Object[] { value };
10099
NamingEnumeration<SearchResult> results = ctx.search(dn, comparisonFilter, params, searchControls);
101-
Boolean match = results.hasMore();
100+
boolean match = results.hasMore();
102101
LdapUtils.closeEnumeration(results);
103102
return match;
104103
});
@@ -112,7 +111,7 @@ public boolean compare(String dn, String attributeName, Object value) {
112111
* @return the object created by the mapper
113112
*/
114113
public DirContextOperations retrieveEntry(final String dn, final String[] attributesToRetrieve) {
115-
return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> {
114+
return executeReadOnly((ctx) -> {
116115
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
117116
return new DirContextAdapter(attrs, LdapNameBuilder.newInstance(dn).build(),
118117
LdapNameBuilder.newInstance(ctx.getNameInNamespace()).build());
@@ -169,26 +168,27 @@ public Set<Map<String, List<String>>> searchForMultipleAttributeValues(String ba
169168
String formattedFilter = MessageFormat.format(filter, encodedParams);
170169
logger.trace(LogMessage.format("Using filter: %s", formattedFilter));
171170
HashSet<Map<String, List<String>>> result = new HashSet<>();
172-
ContextMapper roleMapper = (ctx) -> {
171+
ContextMapper<?> roleMapper = (ctx) -> {
173172
DirContextAdapter adapter = (DirContextAdapter) ctx;
174173
Map<String, List<String>> record = new HashMap<>();
175174
if (ObjectUtils.isEmpty(attributeNames)) {
176175
try {
177-
for (NamingEnumeration enumeration = adapter.getAttributes().getAll(); enumeration.hasMore();) {
178-
Attribute attr = (Attribute) enumeration.next();
176+
for (NamingEnumeration<? extends Attribute> enumeration = adapter.getAttributes()
177+
.getAll(); enumeration.hasMore();) {
178+
Attribute attr = enumeration.next();
179179
extractStringAttributeValues(adapter, record, attr.getID());
180180
}
181181
}
182182
catch (NamingException ex) {
183-
org.springframework.ldap.support.LdapUtils.convertLdapException(ex);
183+
throw org.springframework.ldap.support.LdapUtils.convertLdapException(ex);
184184
}
185185
}
186186
else {
187187
for (String attributeName : attributeNames) {
188188
extractStringAttributeValues(adapter, record, attributeName);
189189
}
190190
}
191-
record.put(DN_KEY, Arrays.asList(getAdapterDN(adapter)));
191+
record.put(DN_KEY, Collections.singletonList(getAdapterDN(adapter)));
192192
result.add(record);
193193
return null;
194194
};
@@ -258,8 +258,7 @@ private void extractStringAttributeValues(DirContextAdapter adapter, Map<String,
258258
* search returns more than one result.
259259
*/
260260
public DirContextOperations searchForSingleEntry(String base, String filter, Object[] params) {
261-
return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> searchForSingleEntryInternal(ctx,
262-
this.searchControls, base, filter, params));
261+
return executeReadOnly((ctx) -> searchForSingleEntryInternal(ctx, this.searchControls, base, filter, params));
263262
}
264263

265264
/**
@@ -296,8 +295,9 @@ public static DirContextOperations searchForSingleEntryInternal(DirContext ctx,
296295
/**
297296
* We need to make sure the search controls has the return object flag set to true, in
298297
* order for the search to return DirContextAdapter instances.
299-
* @param originalControls
300-
* @return
298+
* @param originalControls the {@link SearchControls} that might have the return
299+
* object flag set to true
300+
* @return a {@link SearchControls} that does have the return object flag set to true
301301
*/
302302
private static SearchControls buildControls(SearchControls originalControls) {
303303
return new SearchControls(originalControls.getSearchScope(), originalControls.getCountLimit(),

ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
import org.springframework.context.MessageSource;
2525
import org.springframework.context.MessageSourceAware;
2626
import org.springframework.context.support.MessageSourceAccessor;
27+
import org.springframework.lang.NonNull;
2728
import org.springframework.ldap.core.DirContextOperations;
2829
import org.springframework.security.authentication.AuthenticationProvider;
2930
import org.springframework.security.authentication.BadCredentialsException;
@@ -117,14 +118,15 @@ public boolean supports(Class<?> authentication) {
117118
* obtained from the UserDetails object created by the configured
118119
* {@code UserDetailsContextMapper}. Often it will not be possible to read the
119120
* password from the directory, so defaults to true.
120-
* @param useAuthenticationRequestCredentials
121+
* @param useAuthenticationRequestCredentials whether to use the credentials in the
122+
* authentication request
121123
*/
122124
public void setUseAuthenticationRequestCredentials(boolean useAuthenticationRequestCredentials) {
123125
this.useAuthenticationRequestCredentials = useAuthenticationRequestCredentials;
124126
}
125127

126128
@Override
127-
public void setMessageSource(MessageSource messageSource) {
129+
public void setMessageSource(@NonNull MessageSource messageSource) {
128130
this.messages = new MessageSourceAccessor(messageSource);
129131
}
130132

0 commit comments

Comments
 (0)