mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-01 09:42:13 +00:00
Polish spring-security-ldap main code
Manually polish `spring-security-ldap` following the formatting and checkstyle fixes. Issue gh-8945
This commit is contained in:
parent
48957b42fe
commit
554ef627fb
@ -47,9 +47,7 @@ public class DefaultLdapUsernameToDnMapper implements LdapUsernameToDnMapper {
|
|||||||
@Override
|
@Override
|
||||||
public DistinguishedName buildDn(String username) {
|
public DistinguishedName buildDn(String username) {
|
||||||
DistinguishedName dn = new DistinguishedName(this.userDnBase);
|
DistinguishedName dn = new DistinguishedName(this.userDnBase);
|
||||||
|
|
||||||
dn.add(this.usernameAttribute, username);
|
dn.add(this.usernameAttribute, username);
|
||||||
|
|
||||||
return dn;
|
return dn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,8 +51,6 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
|
|||||||
|
|
||||||
protected final Log logger = LogFactory.getLog(getClass());
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
private String rootDn;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create and initialize an instance which will connect to the supplied LDAP URL. If
|
* Create and initialize an instance which will connect to the supplied LDAP URL. If
|
||||||
* you want to use more than one server for fail-over, rather use the
|
* you want to use more than one server for fail-over, rather use the
|
||||||
@ -62,44 +60,36 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
|
|||||||
*/
|
*/
|
||||||
public DefaultSpringSecurityContextSource(String providerUrl) {
|
public DefaultSpringSecurityContextSource(String providerUrl) {
|
||||||
Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied.");
|
Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied.");
|
||||||
|
StringTokenizer tokenizer = new StringTokenizer(providerUrl);
|
||||||
StringTokenizer st = new StringTokenizer(providerUrl);
|
|
||||||
|
|
||||||
ArrayList<String> urls = new ArrayList<>();
|
ArrayList<String> urls = new ArrayList<>();
|
||||||
|
|
||||||
// Work out rootDn from the first URL and check that the other URLs (if any) match
|
// Work out rootDn from the first URL and check that the other URLs (if any) match
|
||||||
while (st.hasMoreTokens()) {
|
String rootDn = null;
|
||||||
String url = st.nextToken();
|
while (tokenizer.hasMoreTokens()) {
|
||||||
|
String url = tokenizer.nextToken();
|
||||||
String urlRootDn = LdapUtils.parseRootDnFromUrl(url);
|
String urlRootDn = LdapUtils.parseRootDnFromUrl(url);
|
||||||
|
|
||||||
urls.add(url.substring(0, url.lastIndexOf(urlRootDn)));
|
urls.add(url.substring(0, url.lastIndexOf(urlRootDn)));
|
||||||
|
|
||||||
this.logger.info(" URL '" + url + "', root DN is '" + urlRootDn + "'");
|
this.logger.info(" URL '" + url + "', root DN is '" + urlRootDn + "'");
|
||||||
|
Assert.isTrue(rootDn == null || rootDn.equals(urlRootDn),
|
||||||
if (this.rootDn == null) {
|
"Root DNs must be the same when using multiple URLs");
|
||||||
this.rootDn = urlRootDn;
|
rootDn = (rootDn != null) ? rootDn : urlRootDn;
|
||||||
}
|
|
||||||
else if (!this.rootDn.equals(urlRootDn)) {
|
|
||||||
throw new IllegalArgumentException("Root DNs must be the same when using multiple URLs");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setUrls(urls.toArray(new String[0]));
|
setUrls(urls.toArray(new String[0]));
|
||||||
setBase(this.rootDn);
|
setBase(rootDn);
|
||||||
setPooled(true);
|
setPooled(true);
|
||||||
setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() {
|
setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public void setupEnvironment(Hashtable env, String dn, String password) {
|
public void setupEnvironment(Hashtable env, String dn, String password) {
|
||||||
super.setupEnvironment(env, dn, password);
|
super.setupEnvironment(env, dn, password);
|
||||||
// Remove the pooling flag unless we are authenticating as the 'manager'
|
// Remove the pooling flag unless authenticating as the 'manager' user.
|
||||||
// user.
|
|
||||||
if (!DefaultSpringSecurityContextSource.this.userDn.equals(dn)
|
if (!DefaultSpringSecurityContextSource.this.userDn.equals(dn)
|
||||||
&& env.containsKey(SUN_LDAP_POOLING_FLAG)) {
|
&& env.containsKey(SUN_LDAP_POOLING_FLAG)) {
|
||||||
DefaultSpringSecurityContextSource.this.logger.debug("Removing pooling flag for user " + dn);
|
DefaultSpringSecurityContextSource.this.logger.debug("Removing pooling flag for user " + dn);
|
||||||
env.remove(SUN_LDAP_POOLING_FLAG);
|
env.remove(SUN_LDAP_POOLING_FLAG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,16 +136,13 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
|
|||||||
private static String buildProviderUrl(List<String> urls, String baseDn) {
|
private static String buildProviderUrl(List<String> urls, String baseDn) {
|
||||||
Assert.notNull(baseDn, "The Base DN for the LDAP server must not be null.");
|
Assert.notNull(baseDn, "The Base DN for the LDAP server must not be null.");
|
||||||
Assert.notEmpty(urls, "At least one LDAP server URL must be provided.");
|
Assert.notEmpty(urls, "At least one LDAP server URL must be provided.");
|
||||||
|
|
||||||
String trimmedBaseDn = baseDn.trim();
|
String trimmedBaseDn = baseDn.trim();
|
||||||
StringBuilder providerUrl = new StringBuilder();
|
StringBuilder providerUrl = new StringBuilder();
|
||||||
|
|
||||||
for (String serverUrl : urls) {
|
for (String serverUrl : urls) {
|
||||||
String trimmedUrl = serverUrl.trim();
|
String trimmedUrl = serverUrl.trim();
|
||||||
if ("".equals(trimmedUrl)) {
|
if ("".equals(trimmedUrl)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
providerUrl.append(trimmedUrl);
|
providerUrl.append(trimmedUrl);
|
||||||
if (!trimmedUrl.endsWith("/")) {
|
if (!trimmedUrl.endsWith("/")) {
|
||||||
providerUrl.append("/");
|
providerUrl.append("/");
|
||||||
@ -163,7 +150,6 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
|
|||||||
providerUrl.append(trimmedBaseDn);
|
providerUrl.append(trimmedBaseDn);
|
||||||
providerUrl.append(" ");
|
providerUrl.append(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
return providerUrl.toString();
|
return providerUrl.toString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -34,18 +34,11 @@ final class LdapEncoder {
|
|||||||
private static final int HEX = 16;
|
private static final int HEX = 16;
|
||||||
|
|
||||||
private static String[] NAME_ESCAPE_TABLE = new String[96];
|
private static String[] NAME_ESCAPE_TABLE = new String[96];
|
||||||
|
|
||||||
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
|
||||||
// Name encoding table -------------------------------------
|
|
||||||
|
|
||||||
// all below 0x20 (control chars)
|
// all below 0x20 (control chars)
|
||||||
for (char c = 0; c < ' '; c++) {
|
for (char c = 0; c < ' '; c++) {
|
||||||
NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(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[';'] = "\\;";
|
||||||
@ -55,21 +48,21 @@ final class LdapEncoder {
|
|||||||
NAME_ESCAPE_TABLE['>'] = "\\>";
|
NAME_ESCAPE_TABLE['>'] = "\\>";
|
||||||
NAME_ESCAPE_TABLE['\"'] = "\\\"";
|
NAME_ESCAPE_TABLE['\"'] = "\\\"";
|
||||||
NAME_ESCAPE_TABLE['\\'] = "\\\\";
|
NAME_ESCAPE_TABLE['\\'] = "\\\\";
|
||||||
|
}
|
||||||
|
|
||||||
// Filter encoding table -------------------------------------
|
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
|
||||||
|
|
||||||
|
static {
|
||||||
// fill with char itself
|
// fill with char itself
|
||||||
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
|
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
|
||||||
FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
|
FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
// escapes (RFC2254)
|
// escapes (RFC2254)
|
||||||
FILTER_ESCAPE_TABLE['*'] = "\\2a";
|
FILTER_ESCAPE_TABLE['*'] = "\\2a";
|
||||||
FILTER_ESCAPE_TABLE['('] = "\\28";
|
FILTER_ESCAPE_TABLE['('] = "\\28";
|
||||||
FILTER_ESCAPE_TABLE[')'] = "\\29";
|
FILTER_ESCAPE_TABLE[')'] = "\\29";
|
||||||
FILTER_ESCAPE_TABLE['\\'] = "\\5c";
|
FILTER_ESCAPE_TABLE['\\'] = "\\5c";
|
||||||
FILTER_ESCAPE_TABLE[0] = "\\00";
|
FILTER_ESCAPE_TABLE[0] = "\\00";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,15 +72,8 @@ final class LdapEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static String toTwoCharHex(char c) {
|
protected static String toTwoCharHex(char c) {
|
||||||
|
|
||||||
String raw = Integer.toHexString(c).toUpperCase();
|
String raw = Integer.toHexString(c).toUpperCase();
|
||||||
|
return (raw.length() > 1) ? raw : "0" + raw;
|
||||||
if (raw.length() > 1) {
|
|
||||||
return raw;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "0" + raw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,29 +82,15 @@ final class LdapEncoder {
|
|||||||
* @return a properly escaped representation of the supplied value.
|
* @return a properly escaped representation of the supplied value.
|
||||||
*/
|
*/
|
||||||
static String filterEncode(String value) {
|
static String filterEncode(String value) {
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make buffer roomy
|
|
||||||
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
|
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
|
||||||
|
|
||||||
int length = value.length();
|
int length = value.length();
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
|
char ch = value.charAt(i);
|
||||||
char c = value.charAt(i);
|
encodedValue.append((ch < FILTER_ESCAPE_TABLE.length) ? FILTER_ESCAPE_TABLE[ch] : ch);
|
||||||
|
|
||||||
if (c < FILTER_ESCAPE_TABLE.length) {
|
|
||||||
encodedValue.append(FILTER_ESCAPE_TABLE[c]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// default: add the char
|
|
||||||
encodedValue.append(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return encodedValue.toString();
|
return encodedValue.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,43 +113,31 @@ final class LdapEncoder {
|
|||||||
* @return The escaped value.
|
* @return The escaped value.
|
||||||
*/
|
*/
|
||||||
static String nameEncode(String value) {
|
static String nameEncode(String value) {
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make buffer roomy
|
|
||||||
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
|
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
|
||||||
|
|
||||||
int length = value.length();
|
int length = value.length();
|
||||||
int last = length - 1;
|
int last = length - 1;
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
|
|
||||||
char c = value.charAt(i);
|
char c = value.charAt(i);
|
||||||
|
|
||||||
// space first or last
|
// space first or last
|
||||||
if (c == ' ' && (i == 0 || i == last)) {
|
if (c == ' ' && (i == 0 || i == last)) {
|
||||||
encodedValue.append("\\ ");
|
encodedValue.append("\\ ");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// check in table for escapes
|
||||||
if (c < NAME_ESCAPE_TABLE.length) {
|
if (c < NAME_ESCAPE_TABLE.length) {
|
||||||
// check in table for escapes
|
|
||||||
String esc = NAME_ESCAPE_TABLE[c];
|
String esc = NAME_ESCAPE_TABLE[c];
|
||||||
|
|
||||||
if (esc != null) {
|
if (esc != null) {
|
||||||
encodedValue.append(esc);
|
encodedValue.append(esc);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// default: add the char
|
// default: add the char
|
||||||
encodedValue.append(c);
|
encodedValue.append(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
return encodedValue.toString();
|
return encodedValue.toString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,43 +148,32 @@ final class LdapEncoder {
|
|||||||
* @throws BadLdapGrammarException
|
* @throws BadLdapGrammarException
|
||||||
*/
|
*/
|
||||||
static String nameDecode(String value) throws BadLdapGrammarException {
|
static String nameDecode(String value) throws BadLdapGrammarException {
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make buffer same size
|
|
||||||
StringBuilder decoded = new StringBuilder(value.length());
|
StringBuilder decoded = new StringBuilder(value.length());
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < value.length()) {
|
while (i < value.length()) {
|
||||||
char currentChar = value.charAt(i);
|
char currentChar = value.charAt(i);
|
||||||
if (currentChar == '\\') {
|
if (currentChar == '\\') {
|
||||||
|
// Ending with a single backslash is not allowed
|
||||||
if (value.length() <= i + 1) {
|
if (value.length() <= i + 1) {
|
||||||
// Ending with a single backslash is not allowed
|
|
||||||
throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'");
|
throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'");
|
||||||
}
|
}
|
||||||
|
char nextChar = value.charAt(i + 1);
|
||||||
|
if (isNormalBackslashEscape(nextChar)) {
|
||||||
|
decoded.append(nextChar);
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
char nextChar = value.charAt(i + 1);
|
if (value.length() <= i + 2) {
|
||||||
if (nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>'
|
throw new BadLdapGrammarException(
|
||||||
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"'
|
"Unexpected end of value " + "expected special or hex, found '" + 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// This should be a hex value
|
||||||
|
String hexString = "" + nextChar + value.charAt(i + 2);
|
||||||
|
decoded.append((char) Integer.parseInt(hexString, HEX));
|
||||||
|
i += 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -238,4 +187,9 @@ final class LdapEncoder {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isNormalBackslashEscape(char nextChar) {
|
||||||
|
return nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>'
|
||||||
|
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"' || nextChar == ' ';
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,6 @@ public final class LdapUtils {
|
|||||||
if (ctx instanceof DirContextAdapter) {
|
if (ctx instanceof DirContextAdapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (ctx != null) {
|
if (ctx != null) {
|
||||||
ctx.close();
|
ctx.close();
|
||||||
@ -81,24 +80,17 @@ public final class LdapUtils {
|
|||||||
* @throws NamingException any exceptions thrown by the context are propagated.
|
* @throws NamingException any exceptions thrown by the context are propagated.
|
||||||
*/
|
*/
|
||||||
public static String getRelativeName(String fullDn, Context baseCtx) throws NamingException {
|
public static String getRelativeName(String fullDn, Context baseCtx) throws NamingException {
|
||||||
|
|
||||||
String baseDn = baseCtx.getNameInNamespace();
|
String baseDn = baseCtx.getNameInNamespace();
|
||||||
|
|
||||||
if (baseDn.length() == 0) {
|
if (baseDn.length() == 0) {
|
||||||
return fullDn;
|
return fullDn;
|
||||||
}
|
}
|
||||||
|
|
||||||
DistinguishedName base = new DistinguishedName(baseDn);
|
DistinguishedName base = new DistinguishedName(baseDn);
|
||||||
DistinguishedName full = new DistinguishedName(fullDn);
|
DistinguishedName full = new DistinguishedName(fullDn);
|
||||||
|
|
||||||
if (base.equals(full)) {
|
if (base.equals(full)) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.isTrue(full.startsWith(base), "Full DN does not start with base DN");
|
Assert.isTrue(full.startsWith(base), "Full DN does not start with base DN");
|
||||||
|
|
||||||
full.removeFirst(base);
|
full.removeFirst(base);
|
||||||
|
|
||||||
return full.toString();
|
return full.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,28 +100,22 @@ public final class LdapUtils {
|
|||||||
*/
|
*/
|
||||||
public static DistinguishedName getFullDn(DistinguishedName dn, Context baseCtx) throws NamingException {
|
public static DistinguishedName getFullDn(DistinguishedName dn, Context baseCtx) throws NamingException {
|
||||||
DistinguishedName baseDn = new DistinguishedName(baseCtx.getNameInNamespace());
|
DistinguishedName baseDn = new DistinguishedName(baseCtx.getNameInNamespace());
|
||||||
|
|
||||||
if (dn.contains(baseDn)) {
|
if (dn.contains(baseDn)) {
|
||||||
return dn;
|
return dn;
|
||||||
}
|
}
|
||||||
|
|
||||||
baseDn.append(dn);
|
baseDn.append(dn);
|
||||||
|
|
||||||
return baseDn;
|
return baseDn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String convertPasswordToString(Object passObj) {
|
public static String convertPasswordToString(Object passObj) {
|
||||||
Assert.notNull(passObj, "Password object to convert must not be null");
|
Assert.notNull(passObj, "Password object to convert must not be null");
|
||||||
|
|
||||||
if (passObj instanceof byte[]) {
|
if (passObj instanceof byte[]) {
|
||||||
return Utf8.decode((byte[]) passObj);
|
return Utf8.decode((byte[]) passObj);
|
||||||
}
|
}
|
||||||
else if (passObj instanceof String) {
|
if (passObj instanceof String) {
|
||||||
return (String) passObj;
|
return (String) passObj;
|
||||||
}
|
}
|
||||||
else {
|
throw new IllegalArgumentException("Password object was not a String or byte array.");
|
||||||
throw new IllegalArgumentException("Password object was not a String or byte array.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -143,9 +129,7 @@ public final class LdapUtils {
|
|||||||
*/
|
*/
|
||||||
public static String parseRootDnFromUrl(String url) {
|
public static String parseRootDnFromUrl(String url) {
|
||||||
Assert.hasLength(url, "url must have length");
|
Assert.hasLength(url, "url must have length");
|
||||||
|
|
||||||
String urlRootDn;
|
String urlRootDn;
|
||||||
|
|
||||||
if (url.startsWith("ldap:") || url.startsWith("ldaps:")) {
|
if (url.startsWith("ldap:") || url.startsWith("ldaps:")) {
|
||||||
URI uri = parseLdapUrl(url);
|
URI uri = parseLdapUrl(url);
|
||||||
urlRootDn = uri.getRawPath();
|
urlRootDn = uri.getRawPath();
|
||||||
@ -154,11 +138,9 @@ public final class LdapUtils {
|
|||||||
// Assume it's an embedded server
|
// Assume it's an embedded server
|
||||||
urlRootDn = url;
|
urlRootDn = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlRootDn.startsWith("/")) {
|
if (urlRootDn.startsWith("/")) {
|
||||||
urlRootDn = urlRootDn.substring(1);
|
urlRootDn = urlRootDn.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return urlRootDn;
|
return urlRootDn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,14 +155,11 @@ public final class LdapUtils {
|
|||||||
|
|
||||||
private static URI parseLdapUrl(String url) {
|
private static URI parseLdapUrl(String url) {
|
||||||
Assert.hasLength(url, "url must have length");
|
Assert.hasLength(url, "url must have length");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return new URI(url);
|
return new URI(url);
|
||||||
}
|
}
|
||||||
catch (URISyntaxException ex) {
|
catch (URISyntaxException ex) {
|
||||||
IllegalArgumentException iae = new IllegalArgumentException("Unable to parse url: " + url);
|
throw new IllegalArgumentException("Unable to parse url: " + url, ex);
|
||||||
iae.initCause(ex);
|
|
||||||
throw iae;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ import javax.naming.directory.SearchResult;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||||
import org.springframework.ldap.core.ContextExecutor;
|
import org.springframework.ldap.core.ContextExecutor;
|
||||||
import org.springframework.ldap.core.ContextMapper;
|
import org.springframework.ldap.core.ContextMapper;
|
||||||
@ -46,6 +47,7 @@ import org.springframework.ldap.core.DirContextOperations;
|
|||||||
import org.springframework.ldap.core.DistinguishedName;
|
import org.springframework.ldap.core.DistinguishedName;
|
||||||
import org.springframework.ldap.core.LdapTemplate;
|
import org.springframework.ldap.core.LdapTemplate;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extension of Spring LDAP's LdapTemplate class which adds extra functionality required
|
* Extension of Spring LDAP's LdapTemplate class which adds extra functionality required
|
||||||
@ -76,7 +78,6 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
|||||||
public SpringSecurityLdapTemplate(ContextSource contextSource) {
|
public SpringSecurityLdapTemplate(ContextSource contextSource) {
|
||||||
Assert.notNull(contextSource, "ContextSource cannot be null");
|
Assert.notNull(contextSource, "ContextSource cannot be null");
|
||||||
setContextSource(contextSource);
|
setContextSource(contextSource);
|
||||||
|
|
||||||
this.searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
this.searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,31 +89,18 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
|||||||
* @param value the value to be checked against the directory value
|
* @param value the value to be checked against the directory value
|
||||||
* @return true if the supplied value matches that in the directory
|
* @return true if the supplied value matches that in the directory
|
||||||
*/
|
*/
|
||||||
public boolean compare(final String dn, final String attributeName, final Object value) {
|
public boolean compare(String dn, String attributeName, Object value) {
|
||||||
final String comparisonFilter = "(" + attributeName + "={0})";
|
String comparisonFilter = "(" + attributeName + "={0})";
|
||||||
|
return executeReadOnly((ctx) -> {
|
||||||
class LdapCompareCallback implements ContextExecutor {
|
SearchControls searchControls = new SearchControls();
|
||||||
|
searchControls.setReturningAttributes(NO_ATTRS);
|
||||||
@Override
|
searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
|
||||||
public Object executeWithContext(DirContext ctx) throws NamingException {
|
Object[] params = new Object[] { value };
|
||||||
SearchControls ctls = new SearchControls();
|
NamingEnumeration<SearchResult> results = ctx.search(dn, comparisonFilter, params, searchControls);
|
||||||
ctls.setReturningAttributes(NO_ATTRS);
|
Boolean match = results.hasMore();
|
||||||
ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
|
LdapUtils.closeEnumeration(results);
|
||||||
|
return match;
|
||||||
NamingEnumeration<SearchResult> results = ctx.search(dn, comparisonFilter, new Object[] { value },
|
});
|
||||||
ctls);
|
|
||||||
|
|
||||||
Boolean match = results.hasMore();
|
|
||||||
LdapUtils.closeEnumeration(results);
|
|
||||||
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Boolean matches = (Boolean) executeReadOnly(new LdapCompareCallback());
|
|
||||||
|
|
||||||
return matches;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,12 +111,8 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
|||||||
* @return the object created by the mapper
|
* @return the object created by the mapper
|
||||||
*/
|
*/
|
||||||
public DirContextOperations retrieveEntry(final String dn, final String[] attributesToRetrieve) {
|
public DirContextOperations retrieveEntry(final String dn, final String[] attributesToRetrieve) {
|
||||||
|
|
||||||
return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> {
|
return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> {
|
||||||
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
|
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
|
||||||
|
|
||||||
// Object object = ctx.lookup(LdapUtils.getRelativeName(dn, ctx));
|
|
||||||
|
|
||||||
return new DirContextAdapter(attrs, new DistinguishedName(dn),
|
return new DirContextAdapter(attrs, new DistinguishedName(dn),
|
||||||
new DistinguishedName(ctx.getNameInNamespace()));
|
new DistinguishedName(ctx.getNameInNamespace()));
|
||||||
});
|
});
|
||||||
@ -174,27 +158,23 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
|||||||
* entries. The attribute name is the key for each set of values. In addition each map
|
* entries. The attribute name is the key for each set of values. In addition each map
|
||||||
* contains the DN as a String with the key predefined key {@link #DN_KEY}.
|
* contains the DN as a String with the key predefined key {@link #DN_KEY}.
|
||||||
*/
|
*/
|
||||||
public Set<Map<String, List<String>>> searchForMultipleAttributeValues(final String base, final String filter,
|
public Set<Map<String, List<String>>> searchForMultipleAttributeValues(String base, String filter, Object[] params,
|
||||||
final Object[] params, final String[] attributeNames) {
|
String[] attributeNames) {
|
||||||
// Escape the params acording to RFC2254
|
// Escape the params acording to RFC2254
|
||||||
Object[] encodedParams = new String[params.length];
|
Object[] encodedParams = new String[params.length];
|
||||||
|
|
||||||
for (int i = 0; i < params.length; i++) {
|
for (int i = 0; i < params.length; i++) {
|
||||||
encodedParams[i] = LdapEncoder.filterEncode(params[i].toString());
|
encodedParams[i] = LdapEncoder.filterEncode(params[i].toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
String formattedFilter = MessageFormat.format(filter, encodedParams);
|
String formattedFilter = MessageFormat.format(filter, encodedParams);
|
||||||
logger.debug("Using filter: " + formattedFilter);
|
logger.debug(LogMessage.format("Using filter: %s", formattedFilter));
|
||||||
|
HashSet<Map<String, List<String>>> result = new HashSet<>();
|
||||||
final HashSet<Map<String, List<String>>> set = new HashSet<>();
|
|
||||||
|
|
||||||
ContextMapper roleMapper = (ctx) -> {
|
ContextMapper roleMapper = (ctx) -> {
|
||||||
DirContextAdapter adapter = (DirContextAdapter) ctx;
|
DirContextAdapter adapter = (DirContextAdapter) ctx;
|
||||||
Map<String, List<String>> record = new HashMap<>();
|
Map<String, List<String>> record = new HashMap<>();
|
||||||
if (attributeNames == null || attributeNames.length == 0) {
|
if (ObjectUtils.isEmpty(attributeNames)) {
|
||||||
try {
|
try {
|
||||||
for (NamingEnumeration ae = adapter.getAttributes().getAll(); ae.hasMore();) {
|
for (NamingEnumeration enumeration = adapter.getAttributes().getAll(); enumeration.hasMore();) {
|
||||||
Attribute attr = (Attribute) ae.next();
|
Attribute attr = (Attribute) enumeration.next();
|
||||||
extractStringAttributeValues(adapter, record, attr.getID());
|
extractStringAttributeValues(adapter, record, attr.getID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,17 +188,14 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
record.put(DN_KEY, Arrays.asList(getAdapterDN(adapter)));
|
record.put(DN_KEY, Arrays.asList(getAdapterDN(adapter)));
|
||||||
set.add(record);
|
result.add(record);
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
SearchControls ctls = new SearchControls();
|
SearchControls ctls = new SearchControls();
|
||||||
ctls.setSearchScope(this.searchControls.getSearchScope());
|
ctls.setSearchScope(this.searchControls.getSearchScope());
|
||||||
ctls.setReturningAttributes((attributeNames != null && attributeNames.length > 0) ? attributeNames : null);
|
ctls.setReturningAttributes((attributeNames != null && attributeNames.length > 0) ? attributeNames : null);
|
||||||
|
|
||||||
search(base, formattedFilter, ctls, roleMapper);
|
search(base, formattedFilter, ctls, roleMapper);
|
||||||
|
return result;
|
||||||
return set;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -246,27 +223,23 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
|||||||
String attributeName) {
|
String attributeName) {
|
||||||
Object[] values = adapter.getObjectAttributes(attributeName);
|
Object[] values = adapter.getObjectAttributes(attributeName);
|
||||||
if (values == null || values.length == 0) {
|
if (values == null || values.length == 0) {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug(LogMessage.format("No attribute value found for '%s'", attributeName));
|
||||||
logger.debug("No attribute value found for '" + attributeName + "'");
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<String> svalues = new ArrayList<>();
|
List<String> stringValues = new ArrayList<>();
|
||||||
for (Object o : values) {
|
for (Object value : values) {
|
||||||
if (o != null) {
|
if (value != null) {
|
||||||
if (String.class.isAssignableFrom(o.getClass())) {
|
if (String.class.isAssignableFrom(value.getClass())) {
|
||||||
svalues.add((String) o);
|
stringValues.add((String) value);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug(LogMessage.format("Attribute:%s contains a non string value of type[%s]",
|
||||||
logger.debug("Attribute:" + attributeName + " contains a non string value of type["
|
attributeName, value.getClass()));
|
||||||
+ o.getClass() + "]");
|
stringValues.add(value.toString());
|
||||||
}
|
|
||||||
svalues.add(o.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
record.put(attributeName, svalues);
|
record.put(attributeName, stringValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -283,8 +256,7 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
|||||||
* @throws IncorrectResultSizeDataAccessException if no results are found or the
|
* @throws IncorrectResultSizeDataAccessException if no results are found or the
|
||||||
* search returns more than one result.
|
* search returns more than one result.
|
||||||
*/
|
*/
|
||||||
public DirContextOperations searchForSingleEntry(final String base, final String filter, final Object[] params) {
|
public DirContextOperations searchForSingleEntry(String base, String filter, Object[] params) {
|
||||||
|
|
||||||
return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> searchForSingleEntryInternal(ctx,
|
return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> searchForSingleEntryInternal(ctx,
|
||||||
this.searchControls, base, filter, params));
|
this.searchControls, base, filter, params));
|
||||||
}
|
}
|
||||||
@ -298,22 +270,15 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
|||||||
final DistinguishedName searchBaseDn = new DistinguishedName(base);
|
final DistinguishedName searchBaseDn = new DistinguishedName(base);
|
||||||
final NamingEnumeration<SearchResult> resultsEnum = ctx.search(searchBaseDn, filter, params,
|
final NamingEnumeration<SearchResult> resultsEnum = ctx.search(searchBaseDn, filter, params,
|
||||||
buildControls(searchControls));
|
buildControls(searchControls));
|
||||||
|
logger.debug(LogMessage.format("Searching for entry under DN '%s', base = '%s', filter = '%s'", ctxBaseDn,
|
||||||
if (logger.isDebugEnabled()) {
|
searchBaseDn, filter));
|
||||||
logger.debug("Searching for entry under DN '" + ctxBaseDn + "', base = '" + searchBaseDn + "', filter = '"
|
|
||||||
+ filter + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<DirContextOperations> results = new HashSet<>();
|
Set<DirContextOperations> results = new HashSet<>();
|
||||||
try {
|
try {
|
||||||
while (resultsEnum.hasMore()) {
|
while (resultsEnum.hasMore()) {
|
||||||
SearchResult searchResult = resultsEnum.next();
|
SearchResult searchResult = resultsEnum.next();
|
||||||
DirContextAdapter dca = (DirContextAdapter) searchResult.getObject();
|
DirContextAdapter dca = (DirContextAdapter) searchResult.getObject();
|
||||||
Assert.notNull(dca, "No object returned by search, DirContext is not correctly configured");
|
Assert.notNull(dca, "No object returned by search, DirContext is not correctly configured");
|
||||||
|
logger.debug(LogMessage.format("Found DN: %s", dca.getDn()));
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Found DN: " + dca.getDn());
|
|
||||||
}
|
|
||||||
results.add(dca);
|
results.add(dca);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,15 +286,9 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
|||||||
LdapUtils.closeEnumeration(resultsEnum);
|
LdapUtils.closeEnumeration(resultsEnum);
|
||||||
logger.info("Ignoring PartialResultException");
|
logger.info("Ignoring PartialResultException");
|
||||||
}
|
}
|
||||||
|
if (results.size() != 1) {
|
||||||
if (results.size() == 0) {
|
|
||||||
throw new IncorrectResultSizeDataAccessException(1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.size() > 1) {
|
|
||||||
throw new IncorrectResultSizeDataAccessException(1, results.size());
|
throw new IncorrectResultSizeDataAccessException(1, results.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
return results.iterator().next();
|
return results.iterator().next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.context.MessageSourceAware;
|
import org.springframework.context.MessageSourceAware;
|
||||||
import org.springframework.context.support.MessageSourceAccessor;
|
import org.springframework.context.support.MessageSourceAccessor;
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.ldap.core.DirContextOperations;
|
import org.springframework.ldap.core.DirContextOperations;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.authentication.BadCredentialsException;
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
@ -64,33 +65,22 @@ public abstract class AbstractLdapAuthenticationProvider implements Authenticati
|
|||||||
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
|
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
|
||||||
() -> this.messages.getMessage("LdapAuthenticationProvider.onlySupports",
|
() -> this.messages.getMessage("LdapAuthenticationProvider.onlySupports",
|
||||||
"Only UsernamePasswordAuthenticationToken is supported"));
|
"Only UsernamePasswordAuthenticationToken is supported"));
|
||||||
|
UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken) authentication;
|
||||||
final UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken) authentication;
|
|
||||||
|
|
||||||
String username = userToken.getName();
|
String username = userToken.getName();
|
||||||
String password = (String) authentication.getCredentials();
|
String password = (String) authentication.getCredentials();
|
||||||
|
this.logger.debug(LogMessage.format("Processing authentication request for user: %s", username));
|
||||||
if (this.logger.isDebugEnabled()) {
|
|
||||||
this.logger.debug("Processing authentication request for user: " + username);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!StringUtils.hasLength(username)) {
|
if (!StringUtils.hasLength(username)) {
|
||||||
throw new BadCredentialsException(
|
throw new BadCredentialsException(
|
||||||
this.messages.getMessage("LdapAuthenticationProvider.emptyUsername", "Empty Username"));
|
this.messages.getMessage("LdapAuthenticationProvider.emptyUsername", "Empty Username"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!StringUtils.hasLength(password)) {
|
if (!StringUtils.hasLength(password)) {
|
||||||
throw new BadCredentialsException(
|
throw new BadCredentialsException(
|
||||||
this.messages.getMessage("AbstractLdapAuthenticationProvider.emptyPassword", "Empty Password"));
|
this.messages.getMessage("AbstractLdapAuthenticationProvider.emptyPassword", "Empty Password"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.notNull(password, "Null password was supplied in authentication token");
|
Assert.notNull(password, "Null password was supplied in authentication token");
|
||||||
|
|
||||||
DirContextOperations userData = doAuthentication(userToken);
|
DirContextOperations userData = doAuthentication(userToken);
|
||||||
|
|
||||||
UserDetails user = this.userDetailsContextMapper.mapUserFromContext(userData, authentication.getName(),
|
UserDetails user = this.userDetailsContextMapper.mapUserFromContext(userData, authentication.getName(),
|
||||||
loadUserAuthorities(userData, authentication.getName(), (String) authentication.getCredentials()));
|
loadUserAuthorities(userData, authentication.getName(), (String) authentication.getCredentials()));
|
||||||
|
|
||||||
return createSuccessfulAuthentication(userToken, user);
|
return createSuccessfulAuthentication(userToken, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,11 +101,9 @@ public abstract class AbstractLdapAuthenticationProvider implements Authenticati
|
|||||||
UserDetails user) {
|
UserDetails user) {
|
||||||
Object password = this.useAuthenticationRequestCredentials ? authentication.getCredentials()
|
Object password = this.useAuthenticationRequestCredentials ? authentication.getCredentials()
|
||||||
: user.getPassword();
|
: user.getPassword();
|
||||||
|
|
||||||
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, password,
|
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, password,
|
||||||
this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
|
this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
|
||||||
result.setDetails(authentication.getDetails());
|
result.setDetails(authentication.getDetails());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,16 +91,13 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
|
|||||||
if (this.userDnFormat == null) {
|
if (this.userDnFormat == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> userDns = new ArrayList<>(this.userDnFormat.length);
|
List<String> userDns = new ArrayList<>(this.userDnFormat.length);
|
||||||
String[] args = new String[] { LdapEncoder.nameEncode(username) };
|
String[] args = new String[] { LdapEncoder.nameEncode(username) };
|
||||||
|
|
||||||
synchronized (this.userDnFormat) {
|
synchronized (this.userDnFormat) {
|
||||||
for (MessageFormat formatter : this.userDnFormat) {
|
for (MessageFormat formatter : this.userDnFormat) {
|
||||||
userDns.add(formatter.format(args));
|
userDns.add(formatter.format(args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return userDns;
|
return userDns;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +131,6 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
|
|||||||
Assert.notNull(dnPattern, "The array of DN patterns cannot be set to null");
|
Assert.notNull(dnPattern, "The array of DN patterns cannot be set to null");
|
||||||
// this.userDnPattern = dnPattern;
|
// this.userDnPattern = dnPattern;
|
||||||
this.userDnFormat = new MessageFormat[dnPattern.length];
|
this.userDnFormat = new MessageFormat[dnPattern.length];
|
||||||
|
|
||||||
for (int i = 0; i < dnPattern.length; i++) {
|
for (int i = 0; i < dnPattern.length; i++) {
|
||||||
this.userDnFormat[i] = new MessageFormat(dnPattern[i]);
|
this.userDnFormat[i] = new MessageFormat(dnPattern[i]);
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import javax.naming.directory.DirContext;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.ldap.NamingException;
|
import org.springframework.ldap.NamingException;
|
||||||
import org.springframework.ldap.core.DirContextAdapter;
|
import org.springframework.ldap.core.DirContextAdapter;
|
||||||
import org.springframework.ldap.core.DirContextOperations;
|
import org.springframework.ldap.core.DirContextOperations;
|
||||||
@ -51,7 +52,6 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
|
|||||||
* provided.
|
* provided.
|
||||||
* @param contextSource the BaseLdapPathContextSource instance against which bind
|
* @param contextSource the BaseLdapPathContextSource instance against which bind
|
||||||
* operations will be performed.
|
* operations will be performed.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public BindAuthenticator(BaseLdapPathContextSource contextSource) {
|
public BindAuthenticator(BaseLdapPathContextSource contextSource) {
|
||||||
super(contextSource);
|
super(contextSource);
|
||||||
@ -62,37 +62,30 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
|
|||||||
DirContextOperations user = null;
|
DirContextOperations user = null;
|
||||||
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
|
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
|
||||||
"Can only process UsernamePasswordAuthenticationToken objects");
|
"Can only process UsernamePasswordAuthenticationToken objects");
|
||||||
|
|
||||||
String username = authentication.getName();
|
String username = authentication.getName();
|
||||||
String password = (String) authentication.getCredentials();
|
String password = (String) authentication.getCredentials();
|
||||||
|
|
||||||
if (!StringUtils.hasLength(password)) {
|
if (!StringUtils.hasLength(password)) {
|
||||||
logger.debug("Rejecting empty password for user " + username);
|
logger.debug(LogMessage.format("Rejecting empty password for user %s", username));
|
||||||
throw new BadCredentialsException(
|
throw new BadCredentialsException(
|
||||||
this.messages.getMessage("BindAuthenticator.emptyPassword", "Empty Password"));
|
this.messages.getMessage("BindAuthenticator.emptyPassword", "Empty Password"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If DN patterns are configured, try authenticating with them directly
|
// If DN patterns are configured, try authenticating with them directly
|
||||||
for (String dn : getUserDns(username)) {
|
for (String dn : getUserDns(username)) {
|
||||||
user = bindWithDn(dn, username, password);
|
user = bindWithDn(dn, username, password);
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise use the configured search object to find the user and authenticate
|
// Otherwise use the configured search object to find the user and authenticate
|
||||||
// with the returned DN.
|
// with the returned DN.
|
||||||
if (user == null && getUserSearch() != null) {
|
if (user == null && getUserSearch() != null) {
|
||||||
DirContextOperations userFromSearch = getUserSearch().searchForUser(username);
|
DirContextOperations userFromSearch = getUserSearch().searchForUser(username);
|
||||||
user = bindWithDn(userFromSearch.getDn().toString(), username, password, userFromSearch.getAttributes());
|
user = bindWithDn(userFromSearch.getDn().toString(), username, password, userFromSearch.getAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new BadCredentialsException(
|
throw new BadCredentialsException(
|
||||||
this.messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
|
this.messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,26 +98,20 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
|
|||||||
DistinguishedName userDn = new DistinguishedName(userDnStr);
|
DistinguishedName userDn = new DistinguishedName(userDnStr);
|
||||||
DistinguishedName fullDn = new DistinguishedName(userDn);
|
DistinguishedName fullDn = new DistinguishedName(userDn);
|
||||||
fullDn.prepend(ctxSource.getBaseLdapPath());
|
fullDn.prepend(ctxSource.getBaseLdapPath());
|
||||||
|
logger.debug(LogMessage.format("Attempting to bind as %s", fullDn));
|
||||||
logger.debug("Attempting to bind as " + fullDn);
|
|
||||||
|
|
||||||
DirContext ctx = null;
|
DirContext ctx = null;
|
||||||
try {
|
try {
|
||||||
ctx = getContextSource().getContext(fullDn.toString(), password);
|
ctx = getContextSource().getContext(fullDn.toString(), password);
|
||||||
// Check for password policy control
|
// Check for password policy control
|
||||||
PasswordPolicyControl ppolicy = PasswordPolicyControlExtractor.extractControl(ctx);
|
PasswordPolicyControl ppolicy = PasswordPolicyControlExtractor.extractControl(ctx);
|
||||||
|
|
||||||
logger.debug("Retrieving attributes...");
|
logger.debug("Retrieving attributes...");
|
||||||
if (attrs == null || attrs.size() == 0) {
|
if (attrs == null || attrs.size() == 0) {
|
||||||
attrs = ctx.getAttributes(userDn, getUserAttributes());
|
attrs = ctx.getAttributes(userDn, getUserAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
DirContextAdapter result = new DirContextAdapter(attrs, userDn, ctxSource.getBaseLdapPath());
|
DirContextAdapter result = new DirContextAdapter(attrs, userDn, ctxSource.getBaseLdapPath());
|
||||||
|
|
||||||
if (ppolicy != null) {
|
if (ppolicy != null) {
|
||||||
result.setAttributeValue(ppolicy.getID(), ppolicy);
|
result.setAttributeValue(ppolicy.getID(), ppolicy);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (NamingException ex) {
|
catch (NamingException ex) {
|
||||||
@ -145,7 +132,6 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
|
|||||||
finally {
|
finally {
|
||||||
LdapUtils.closeContext(ctx);
|
LdapUtils.closeContext(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,9 +141,7 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
|
|||||||
* logger.
|
* logger.
|
||||||
*/
|
*/
|
||||||
protected void handleBindException(String userDn, String username, Throwable cause) {
|
protected void handleBindException(String userDn, String username, Throwable cause) {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug(LogMessage.format("Failed to bind as %s: %s", userDn, cause));
|
||||||
logger.debug("Failed to bind as " + userDn + ": " + cause);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -173,23 +173,21 @@ public class LdapAuthenticationProvider extends AbstractLdapAuthenticationProvid
|
|||||||
try {
|
try {
|
||||||
return getAuthenticator().authenticate(authentication);
|
return getAuthenticator().authenticate(authentication);
|
||||||
}
|
}
|
||||||
catch (PasswordPolicyException ppe) {
|
catch (PasswordPolicyException ex) {
|
||||||
// The only reason a ppolicy exception can occur during a bind is that the
|
// The only reason a ppolicy exception can occur during a bind is that the
|
||||||
// account is locked.
|
// account is locked.
|
||||||
throw new LockedException(
|
throw new LockedException(
|
||||||
this.messages.getMessage(ppe.getStatus().getErrorCode(), ppe.getStatus().getDefaultMessage()));
|
this.messages.getMessage(ex.getStatus().getErrorCode(), ex.getStatus().getDefaultMessage()));
|
||||||
}
|
}
|
||||||
catch (UsernameNotFoundException notFound) {
|
catch (UsernameNotFoundException ex) {
|
||||||
if (this.hideUserNotFoundExceptions) {
|
if (this.hideUserNotFoundExceptions) {
|
||||||
throw new BadCredentialsException(
|
throw new BadCredentialsException(
|
||||||
this.messages.getMessage("LdapAuthenticationProvider.badCredentials", "Bad credentials"));
|
this.messages.getMessage("LdapAuthenticationProvider.badCredentials", "Bad credentials"));
|
||||||
}
|
}
|
||||||
else {
|
throw ex;
|
||||||
throw notFound;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (NamingException ldapAccessFailure) {
|
catch (NamingException ex) {
|
||||||
throw new InternalAuthenticationServiceException(ldapAccessFailure.getMessage(), ldapAccessFailure);
|
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,18 +34,11 @@ final class LdapEncoder {
|
|||||||
private static final int HEX = 16;
|
private static final int HEX = 16;
|
||||||
|
|
||||||
private static String[] NAME_ESCAPE_TABLE = new String[96];
|
private static String[] NAME_ESCAPE_TABLE = new String[96];
|
||||||
|
|
||||||
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
|
||||||
// Name encoding table -------------------------------------
|
|
||||||
|
|
||||||
// all below 0x20 (control chars)
|
// all below 0x20 (control chars)
|
||||||
for (char c = 0; c < ' '; c++) {
|
for (char c = 0; c < ' '; c++) {
|
||||||
NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(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[';'] = "\\;";
|
||||||
@ -55,21 +48,21 @@ final class LdapEncoder {
|
|||||||
NAME_ESCAPE_TABLE['>'] = "\\>";
|
NAME_ESCAPE_TABLE['>'] = "\\>";
|
||||||
NAME_ESCAPE_TABLE['\"'] = "\\\"";
|
NAME_ESCAPE_TABLE['\"'] = "\\\"";
|
||||||
NAME_ESCAPE_TABLE['\\'] = "\\\\";
|
NAME_ESCAPE_TABLE['\\'] = "\\\\";
|
||||||
|
}
|
||||||
|
|
||||||
// Filter encoding table -------------------------------------
|
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
|
||||||
|
|
||||||
|
static {
|
||||||
// fill with char itself
|
// fill with char itself
|
||||||
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
|
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
|
||||||
FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
|
FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
// escapes (RFC2254)
|
// escapes (RFC2254)
|
||||||
FILTER_ESCAPE_TABLE['*'] = "\\2a";
|
FILTER_ESCAPE_TABLE['*'] = "\\2a";
|
||||||
FILTER_ESCAPE_TABLE['('] = "\\28";
|
FILTER_ESCAPE_TABLE['('] = "\\28";
|
||||||
FILTER_ESCAPE_TABLE[')'] = "\\29";
|
FILTER_ESCAPE_TABLE[')'] = "\\29";
|
||||||
FILTER_ESCAPE_TABLE['\\'] = "\\5c";
|
FILTER_ESCAPE_TABLE['\\'] = "\\5c";
|
||||||
FILTER_ESCAPE_TABLE[0] = "\\00";
|
FILTER_ESCAPE_TABLE[0] = "\\00";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,15 +72,8 @@ final class LdapEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static String toTwoCharHex(char c) {
|
protected static String toTwoCharHex(char c) {
|
||||||
|
|
||||||
String raw = Integer.toHexString(c).toUpperCase();
|
String raw = Integer.toHexString(c).toUpperCase();
|
||||||
|
return (raw.length() > 1) ? raw : "0" + raw;
|
||||||
if (raw.length() > 1) {
|
|
||||||
return raw;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "0" + raw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,29 +82,15 @@ final class LdapEncoder {
|
|||||||
* @return a properly escaped representation of the supplied value.
|
* @return a properly escaped representation of the supplied value.
|
||||||
*/
|
*/
|
||||||
static String filterEncode(String value) {
|
static String filterEncode(String value) {
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make buffer roomy
|
|
||||||
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
|
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
|
||||||
|
|
||||||
int length = value.length();
|
int length = value.length();
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
|
char ch = value.charAt(i);
|
||||||
char c = value.charAt(i);
|
encodedValue.append((ch < FILTER_ESCAPE_TABLE.length) ? FILTER_ESCAPE_TABLE[ch] : ch);
|
||||||
|
|
||||||
if (c < FILTER_ESCAPE_TABLE.length) {
|
|
||||||
encodedValue.append(FILTER_ESCAPE_TABLE[c]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// default: add the char
|
|
||||||
encodedValue.append(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return encodedValue.toString();
|
return encodedValue.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,43 +113,31 @@ final class LdapEncoder {
|
|||||||
* @return The escaped value.
|
* @return The escaped value.
|
||||||
*/
|
*/
|
||||||
static String nameEncode(String value) {
|
static String nameEncode(String value) {
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make buffer roomy
|
|
||||||
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
|
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
|
||||||
|
|
||||||
int length = value.length();
|
int length = value.length();
|
||||||
int last = length - 1;
|
int last = length - 1;
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
|
|
||||||
char c = value.charAt(i);
|
char c = value.charAt(i);
|
||||||
|
|
||||||
// space first or last
|
// space first or last
|
||||||
if (c == ' ' && (i == 0 || i == last)) {
|
if (c == ' ' && (i == 0 || i == last)) {
|
||||||
encodedValue.append("\\ ");
|
encodedValue.append("\\ ");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// check in table for escapes
|
||||||
if (c < NAME_ESCAPE_TABLE.length) {
|
if (c < NAME_ESCAPE_TABLE.length) {
|
||||||
// check in table for escapes
|
|
||||||
String esc = NAME_ESCAPE_TABLE[c];
|
String esc = NAME_ESCAPE_TABLE[c];
|
||||||
|
|
||||||
if (esc != null) {
|
if (esc != null) {
|
||||||
encodedValue.append(esc);
|
encodedValue.append(esc);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// default: add the char
|
// default: add the char
|
||||||
encodedValue.append(c);
|
encodedValue.append(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
return encodedValue.toString();
|
return encodedValue.toString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,43 +148,32 @@ final class LdapEncoder {
|
|||||||
* @throws BadLdapGrammarException
|
* @throws BadLdapGrammarException
|
||||||
*/
|
*/
|
||||||
static String nameDecode(String value) throws BadLdapGrammarException {
|
static String nameDecode(String value) throws BadLdapGrammarException {
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make buffer same size
|
|
||||||
StringBuilder decoded = new StringBuilder(value.length());
|
StringBuilder decoded = new StringBuilder(value.length());
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < value.length()) {
|
while (i < value.length()) {
|
||||||
char currentChar = value.charAt(i);
|
char currentChar = value.charAt(i);
|
||||||
if (currentChar == '\\') {
|
if (currentChar == '\\') {
|
||||||
|
// Ending with a single backslash is not allowed
|
||||||
if (value.length() <= i + 1) {
|
if (value.length() <= i + 1) {
|
||||||
// Ending with a single backslash is not allowed
|
|
||||||
throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'");
|
throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'");
|
||||||
}
|
}
|
||||||
|
char nextChar = value.charAt(i + 1);
|
||||||
|
if (isNormalBackslashEscape(nextChar)) {
|
||||||
|
decoded.append(nextChar);
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
char nextChar = value.charAt(i + 1);
|
if (value.length() <= i + 2) {
|
||||||
if (nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>'
|
throw new BadLdapGrammarException(
|
||||||
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"'
|
"Unexpected end of value " + "expected special or hex, found '" + 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// This should be a hex value
|
||||||
|
String hexString = "" + nextChar + value.charAt(i + 2);
|
||||||
|
decoded.append((char) Integer.parseInt(hexString, HEX));
|
||||||
|
i += 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -238,4 +187,9 @@ final class LdapEncoder {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isNormalBackslashEscape(char nextChar) {
|
||||||
|
return nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>'
|
||||||
|
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"' || nextChar == ' ';
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package org.springframework.security.ldap.authentication;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.ldap.NameNotFoundException;
|
import org.springframework.ldap.NameNotFoundException;
|
||||||
import org.springframework.ldap.core.DirContextOperations;
|
import org.springframework.ldap.core.DirContextOperations;
|
||||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
|
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
|
||||||
@ -66,13 +67,10 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic
|
|||||||
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
|
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
|
||||||
"Can only process UsernamePasswordAuthenticationToken objects");
|
"Can only process UsernamePasswordAuthenticationToken objects");
|
||||||
// locate the user and check the password
|
// locate the user and check the password
|
||||||
|
|
||||||
DirContextOperations user = null;
|
DirContextOperations user = null;
|
||||||
String username = authentication.getName();
|
String username = authentication.getName();
|
||||||
String password = (String) authentication.getCredentials();
|
String password = (String) authentication.getCredentials();
|
||||||
|
|
||||||
SpringSecurityLdapTemplate ldapTemplate = new SpringSecurityLdapTemplate(getContextSource());
|
SpringSecurityLdapTemplate ldapTemplate = new SpringSecurityLdapTemplate(getContextSource());
|
||||||
|
|
||||||
for (String userDn : getUserDns(username)) {
|
for (String userDn : getUserDns(username)) {
|
||||||
try {
|
try {
|
||||||
user = ldapTemplate.retrieveEntry(userDn, getUserAttributes());
|
user = ldapTemplate.retrieveEntry(userDn, getUserAttributes());
|
||||||
@ -83,24 +81,20 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user == null && getUserSearch() != null) {
|
if (user == null && getUserSearch() != null) {
|
||||||
user = getUserSearch().searchForUser(username);
|
user = getUserSearch().searchForUser(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new UsernameNotFoundException("User not found: " + username);
|
throw new UsernameNotFoundException("User not found: " + username);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Performing LDAP compare of password attribute '" + this.passwordAttributeName + "' for user '"
|
logger.debug(LogMessage.format("Performing LDAP compare of password attribute '%s' for user '%s'",
|
||||||
+ user.getDn() + "'");
|
this.passwordAttributeName, user.getDn()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.usePasswordAttrCompare && isPasswordAttrCompare(user, password)) {
|
if (this.usePasswordAttrCompare && isPasswordAttrCompare(user, password)) {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
else if (isLdapPasswordCompare(user, ldapTemplate, password)) {
|
if (isLdapPasswordCompare(user, ldapTemplate, password)) {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
throw new BadCredentialsException(
|
throw new BadCredentialsException(
|
||||||
|
@ -47,28 +47,21 @@ public class SpringSecurityAuthenticationSource implements AuthenticationSource
|
|||||||
@Override
|
@Override
|
||||||
public String getPrincipal() {
|
public String getPrincipal() {
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
if (authentication == null) {
|
if (authentication == null) {
|
||||||
log.warn("No Authentication object set in SecurityContext - returning empty String as Principal");
|
log.warn("No Authentication object set in SecurityContext - returning empty String as Principal");
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
|
|
||||||
if (principal instanceof LdapUserDetails) {
|
if (principal instanceof LdapUserDetails) {
|
||||||
LdapUserDetails details = (LdapUserDetails) principal;
|
LdapUserDetails details = (LdapUserDetails) principal;
|
||||||
return details.getDn();
|
return details.getDn();
|
||||||
}
|
}
|
||||||
else if (authentication instanceof AnonymousAuthenticationToken) {
|
if (authentication instanceof AnonymousAuthenticationToken) {
|
||||||
if (log.isDebugEnabled()) {
|
log.debug("Anonymous Authentication, returning empty String as Principal");
|
||||||
log.debug("Anonymous Authentication, returning empty String as Principal");
|
|
||||||
}
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
else {
|
throw new IllegalArgumentException(
|
||||||
throw new IllegalArgumentException(
|
"The principal property of the authentication object" + "needs to be an LdapUserDetails.");
|
||||||
"The principal property of the authentication object" + "needs to be an LdapUserDetails.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,12 +70,10 @@ public class SpringSecurityAuthenticationSource implements AuthenticationSource
|
|||||||
@Override
|
@Override
|
||||||
public String getCredentials() {
|
public String getCredentials() {
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
if (authentication == null) {
|
if (authentication == null) {
|
||||||
log.warn("No Authentication object set in SecurityContext - returning empty String as Credentials");
|
log.warn("No Authentication object set in SecurityContext - returning empty String as Credentials");
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return (String) authentication.getCredentials();
|
return (String) authentication.getCredentials();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -34,6 +35,7 @@ import javax.naming.directory.DirContext;
|
|||||||
import javax.naming.directory.SearchControls;
|
import javax.naming.directory.SearchControls;
|
||||||
import javax.naming.ldap.InitialLdapContext;
|
import javax.naming.ldap.InitialLdapContext;
|
||||||
|
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||||
import org.springframework.ldap.CommunicationException;
|
import org.springframework.ldap.CommunicationException;
|
||||||
import org.springframework.ldap.core.DirContextOperations;
|
import org.springframework.ldap.core.DirContextOperations;
|
||||||
@ -161,7 +163,6 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
String username = auth.getName();
|
String username = auth.getName();
|
||||||
String password = (String) auth.getCredentials();
|
String password = (String) auth.getCredentials();
|
||||||
DirContext ctx = null;
|
DirContext ctx = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ctx = bindAsUser(username, password);
|
ctx = bindAsUser(username, password);
|
||||||
return searchForUser(ctx, username);
|
return searchForUser(ctx, username);
|
||||||
@ -186,30 +187,23 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username,
|
protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username,
|
||||||
String password) {
|
String password) {
|
||||||
String[] groups = userData.getStringAttributes("memberOf");
|
String[] groups = userData.getStringAttributes("memberOf");
|
||||||
|
|
||||||
if (groups == null) {
|
if (groups == null) {
|
||||||
this.logger.debug("No values for 'memberOf' attribute.");
|
this.logger.debug("No values for 'memberOf' attribute.");
|
||||||
|
|
||||||
return AuthorityUtils.NO_AUTHORITIES;
|
return AuthorityUtils.NO_AUTHORITIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.logger.isDebugEnabled()) {
|
if (this.logger.isDebugEnabled()) {
|
||||||
this.logger.debug("'memberOf' attribute values: " + Arrays.asList(groups));
|
this.logger.debug("'memberOf' attribute values: " + Arrays.asList(groups));
|
||||||
}
|
}
|
||||||
|
List<GrantedAuthority> authorities = new ArrayList<>(groups.length);
|
||||||
ArrayList<GrantedAuthority> authorities = new ArrayList<>(groups.length);
|
|
||||||
|
|
||||||
for (String group : groups) {
|
for (String group : groups) {
|
||||||
authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue()));
|
authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return authorities;
|
return authorities;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DirContext bindAsUser(String username, String password) {
|
private DirContext bindAsUser(String username, String password) {
|
||||||
// TODO. add DNS lookup based on domain
|
// TODO. add DNS lookup based on domain
|
||||||
final String bindUrl = this.url;
|
final String bindUrl = this.url;
|
||||||
|
|
||||||
Hashtable<String, Object> env = new Hashtable<>();
|
Hashtable<String, Object> env = new Hashtable<>();
|
||||||
env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
||||||
String bindPrincipal = createBindPrincipal(username);
|
String bindPrincipal = createBindPrincipal(username);
|
||||||
@ -219,7 +213,6 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
||||||
env.put(Context.OBJECT_FACTORIES, DefaultDirObjectFactory.class.getName());
|
env.put(Context.OBJECT_FACTORIES, DefaultDirObjectFactory.class.getName());
|
||||||
env.putAll(this.contextEnvironmentProperties);
|
env.putAll(this.contextEnvironmentProperties);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return this.contextFactory.createContext(env);
|
return this.contextFactory.createContext(env);
|
||||||
}
|
}
|
||||||
@ -228,28 +221,20 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
handleBindException(bindPrincipal, ex);
|
handleBindException(bindPrincipal, ex);
|
||||||
throw badCredentials(ex);
|
throw badCredentials(ex);
|
||||||
}
|
}
|
||||||
else {
|
throw LdapUtils.convertLdapException(ex);
|
||||||
throw LdapUtils.convertLdapException(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleBindException(String bindPrincipal, NamingException exception) {
|
private void handleBindException(String bindPrincipal, NamingException exception) {
|
||||||
if (this.logger.isDebugEnabled()) {
|
this.logger.debug(LogMessage.format("Authentication for %s failed:%s", bindPrincipal, exception));
|
||||||
this.logger.debug("Authentication for " + bindPrincipal + " failed:" + exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleResolveObj(exception);
|
handleResolveObj(exception);
|
||||||
|
|
||||||
int subErrorCode = parseSubErrorCode(exception.getMessage());
|
int subErrorCode = parseSubErrorCode(exception.getMessage());
|
||||||
|
|
||||||
if (subErrorCode <= 0) {
|
if (subErrorCode <= 0) {
|
||||||
this.logger.debug("Failed to locate AD-specific sub-error code in message");
|
this.logger.debug("Failed to locate AD-specific sub-error code in message");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.logger.info(
|
||||||
this.logger.info("Active Directory authentication failed: " + subCodeToLogMessage(subErrorCode));
|
LogMessage.of(() -> "Active Directory authentication failed: " + subCodeToLogMessage(subErrorCode)));
|
||||||
|
|
||||||
if (this.convertSubErrorCodesToExceptions) {
|
if (this.convertSubErrorCodesToExceptions) {
|
||||||
raiseExceptionForErrorCode(subErrorCode, exception);
|
raiseExceptionForErrorCode(subErrorCode, exception);
|
||||||
}
|
}
|
||||||
@ -264,12 +249,10 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int parseSubErrorCode(String message) {
|
private int parseSubErrorCode(String message) {
|
||||||
Matcher m = SUB_ERROR_CODE.matcher(message);
|
Matcher matcher = SUB_ERROR_CODE.matcher(message);
|
||||||
|
if (matcher.matches()) {
|
||||||
if (m.matches()) {
|
return Integer.parseInt(matcher.group(1), 16);
|
||||||
return Integer.parseInt(m.group(1), 16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +296,6 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
case ACCOUNT_LOCKED:
|
case ACCOUNT_LOCKED:
|
||||||
return "Account locked";
|
return "Account locked";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Unknown (error code " + Integer.toHexString(code) + ")";
|
return "Unknown (error code " + Integer.toHexString(code) + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,7 +316,6 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
private DirContextOperations searchForUser(DirContext context, String username) throws NamingException {
|
private DirContextOperations searchForUser(DirContext context, String username) throws NamingException {
|
||||||
SearchControls searchControls = new SearchControls();
|
SearchControls searchControls = new SearchControls();
|
||||||
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
|
||||||
|
|
||||||
String bindPrincipal = createBindPrincipal(username);
|
String bindPrincipal = createBindPrincipal(username);
|
||||||
String searchRoot = (this.rootDn != null) ? this.rootDn : searchRootFromPrincipal(bindPrincipal);
|
String searchRoot = (this.rootDn != null) ? this.rootDn : searchRootFromPrincipal(bindPrincipal);
|
||||||
|
|
||||||
@ -342,45 +323,40 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context, searchControls, searchRoot,
|
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context, searchControls, searchRoot,
|
||||||
this.searchFilter, new Object[] { bindPrincipal, username });
|
this.searchFilter, new Object[] { bindPrincipal, username });
|
||||||
}
|
}
|
||||||
catch (CommunicationException ldapCommunicationException) {
|
catch (CommunicationException ex) {
|
||||||
throw badLdapConnection(ldapCommunicationException);
|
throw badLdapConnection(ex);
|
||||||
}
|
}
|
||||||
catch (IncorrectResultSizeDataAccessException incorrectResults) {
|
catch (IncorrectResultSizeDataAccessException ex) {
|
||||||
// Search should never return multiple results if properly configured - just
|
// Search should never return multiple results if properly configured -
|
||||||
// rethrow
|
if (ex.getActualSize() != 0) {
|
||||||
if (incorrectResults.getActualSize() != 0) {
|
throw ex;
|
||||||
throw incorrectResults;
|
|
||||||
}
|
}
|
||||||
// If we found no results, then the username/password did not match
|
// If we found no results, then the username/password did not match
|
||||||
UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException(
|
UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException(
|
||||||
"User " + username + " not found in directory.", incorrectResults);
|
"User " + username + " not found in directory.", ex);
|
||||||
throw badCredentials(userNameNotFoundException);
|
throw badCredentials(userNameNotFoundException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String searchRootFromPrincipal(String bindPrincipal) {
|
private String searchRootFromPrincipal(String bindPrincipal) {
|
||||||
int atChar = bindPrincipal.lastIndexOf('@');
|
int atChar = bindPrincipal.lastIndexOf('@');
|
||||||
|
|
||||||
if (atChar < 0) {
|
if (atChar < 0) {
|
||||||
this.logger.debug("User principal '" + bindPrincipal
|
this.logger.debug("User principal '" + bindPrincipal
|
||||||
+ "' does not contain the domain, and no domain has been configured");
|
+ "' does not contain the domain, and no domain has been configured");
|
||||||
throw badCredentials();
|
throw badCredentials();
|
||||||
}
|
}
|
||||||
|
|
||||||
return rootDnFromDomain(bindPrincipal.substring(atChar + 1, bindPrincipal.length()));
|
return rootDnFromDomain(bindPrincipal.substring(atChar + 1, bindPrincipal.length()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String rootDnFromDomain(String domain) {
|
private String rootDnFromDomain(String domain) {
|
||||||
String[] tokens = StringUtils.tokenizeToStringArray(domain, ".");
|
String[] tokens = StringUtils.tokenizeToStringArray(domain, ".");
|
||||||
StringBuilder root = new StringBuilder();
|
StringBuilder root = new StringBuilder();
|
||||||
|
|
||||||
for (String token : tokens) {
|
for (String token : tokens) {
|
||||||
if (root.length() > 0) {
|
if (root.length() > 0) {
|
||||||
root.append(',');
|
root.append(',');
|
||||||
}
|
}
|
||||||
root.append("dc=").append(token);
|
root.append("dc=").append(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
return root.toString();
|
return root.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,7 +364,6 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
|||||||
if (this.domain == null || username.toLowerCase().endsWith(this.domain)) {
|
if (this.domain == null || username.toLowerCase().endsWith(this.domain)) {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
return username + "@" + this.domain;
|
return username + "@" + this.domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import javax.naming.directory.DirContext;
|
|||||||
import javax.naming.ldap.Control;
|
import javax.naming.ldap.Control;
|
||||||
import javax.naming.ldap.LdapContext;
|
import javax.naming.ldap.LdapContext;
|
||||||
|
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.ldap.support.LdapUtils;
|
import org.springframework.ldap.support.LdapUtils;
|
||||||
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
|
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
|
||||||
|
|
||||||
@ -49,45 +50,30 @@ public class PasswordPolicyAwareContextSource extends DefaultSpringSecurityConte
|
|||||||
if (principal.equals(this.userDn)) {
|
if (principal.equals(this.userDn)) {
|
||||||
return super.getContext(principal, credentials);
|
return super.getContext(principal, credentials);
|
||||||
}
|
}
|
||||||
|
this.logger
|
||||||
final boolean debug = this.logger.isDebugEnabled();
|
.debug(LogMessage.format("Binding as '%s', prior to reconnect as user '%s'", this.userDn, principal));
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
this.logger.debug("Binding as '" + this.userDn + "', prior to reconnect as user '" + principal + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// First bind as manager user before rebinding as the specific principal.
|
// First bind as manager user before rebinding as the specific principal.
|
||||||
LdapContext ctx = (LdapContext) super.getContext(this.userDn, this.password);
|
LdapContext ctx = (LdapContext) super.getContext(this.userDn, this.password);
|
||||||
|
|
||||||
Control[] rctls = { new PasswordPolicyControl(false) };
|
Control[] rctls = { new PasswordPolicyControl(false) };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, principal);
|
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, principal);
|
||||||
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
|
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
|
||||||
ctx.reconnect(rctls);
|
ctx.reconnect(rctls);
|
||||||
}
|
}
|
||||||
catch (javax.naming.NamingException ne) {
|
catch (javax.naming.NamingException ex) {
|
||||||
PasswordPolicyResponseControl ctrl = PasswordPolicyControlExtractor.extractControl(ctx);
|
PasswordPolicyResponseControl ctrl = PasswordPolicyControlExtractor.extractControl(ctx);
|
||||||
if (debug) {
|
if (this.logger.isDebugEnabled()) {
|
||||||
this.logger.debug("Failed to obtain context", ne);
|
this.logger.debug("Failed to obtain context", ex);
|
||||||
this.logger.debug("Password policy response: " + ctrl);
|
this.logger.debug("Password policy response: " + ctrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
LdapUtils.closeContext(ctx);
|
LdapUtils.closeContext(ctx);
|
||||||
|
if (ctrl != null && ctrl.isLocked()) {
|
||||||
if (ctrl != null) {
|
throw new PasswordPolicyException(ctrl.getErrorStatus());
|
||||||
if (ctrl.isLocked()) {
|
|
||||||
throw new PasswordPolicyException(ctrl.getErrorStatus());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
throw LdapUtils.convertLdapException(ex);
|
||||||
throw LdapUtils.convertLdapException(ne);
|
|
||||||
}
|
}
|
||||||
|
this.logger.debug(
|
||||||
if (debug) {
|
LogMessage.of(() -> "PPolicy control returned: " + PasswordPolicyControlExtractor.extractControl(ctx)));
|
||||||
this.logger.debug("PPolicy control returned: " + PasswordPolicyControlExtractor.extractControl(ctx));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,9 +81,7 @@ public class PasswordPolicyAwareContextSource extends DefaultSpringSecurityConte
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected Hashtable getAuthenticatedEnv(String principal, String credentials) {
|
protected Hashtable getAuthenticatedEnv(String principal, String credentials) {
|
||||||
Hashtable env = super.getAuthenticatedEnv(principal, credentials);
|
Hashtable env = super.getAuthenticatedEnv(principal, credentials);
|
||||||
|
|
||||||
env.put(LdapContext.CONTROL_FACTORIES, PasswordPolicyControlFactory.class.getName());
|
env.put(LdapContext.CONTROL_FACTORIES, PasswordPolicyControlFactory.class.getName());
|
||||||
|
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,9 @@ import javax.naming.ldap.Control;
|
|||||||
*/
|
*/
|
||||||
public class PasswordPolicyControl implements Control {
|
public class PasswordPolicyControl implements Control {
|
||||||
|
|
||||||
/** OID of the Password Policy Control */
|
/**
|
||||||
|
* OID of the Password Policy Control
|
||||||
|
*/
|
||||||
public static final String OID = "1.3.6.1.4.1.42.2.27.8.5.1";
|
public static final String OID = "1.3.6.1.4.1.42.2.27.8.5.1";
|
||||||
|
|
||||||
private final boolean critical;
|
private final boolean critical;
|
||||||
|
@ -45,13 +45,11 @@ public final class PasswordPolicyControlExtractor {
|
|||||||
catch (javax.naming.NamingException ex) {
|
catch (javax.naming.NamingException ex) {
|
||||||
logger.error("Failed to obtain response controls", ex);
|
logger.error("Failed to obtain response controls", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; ctrls != null && i < ctrls.length; i++) {
|
for (int i = 0; ctrls != null && i < ctrls.length; i++) {
|
||||||
if (ctrls[i] instanceof PasswordPolicyResponseControl) {
|
if (ctrls[i] instanceof PasswordPolicyResponseControl) {
|
||||||
return (PasswordPolicyResponseControl) ctrls[i];
|
return (PasswordPolicyResponseControl) ctrls[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,6 @@ public class PasswordPolicyControlFactory extends ControlFactory {
|
|||||||
if (ctl.getID().equals(PasswordPolicyControl.OID)) {
|
if (ctl.getID().equals(PasswordPolicyControl.OID)) {
|
||||||
return new PasswordPolicyResponseControl(ctl.getEncodedValue());
|
return new PasswordPolicyResponseControl(ctl.getEncodedValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,20 +41,24 @@ package org.springframework.security.ldap.ppolicy;
|
|||||||
*/
|
*/
|
||||||
public enum PasswordPolicyErrorStatus {
|
public enum PasswordPolicyErrorStatus {
|
||||||
|
|
||||||
PASSWORD_EXPIRED("ppolicy.expired", "Your password has expired"), ACCOUNT_LOCKED("ppolicy.locked",
|
PASSWORD_EXPIRED("ppolicy.expired", "Your password has expired"),
|
||||||
"Account is locked"), CHANGE_AFTER_RESET("ppolicy.change.after.reset",
|
|
||||||
"Your password must be changed after being reset"), PASSWORD_MOD_NOT_ALLOWED(
|
ACCOUNT_LOCKED("ppolicy.locked", "Account is locked"),
|
||||||
"ppolicy.mod.not.allowed",
|
|
||||||
"Password cannot be changed"), MUST_SUPPLY_OLD_PASSWORD("ppolicy.must.supply.old.password",
|
CHANGE_AFTER_RESET("ppolicy.change.after.reset", "Your password must be changed after being reset"),
|
||||||
"The old password must be supplied"), INSUFFICIENT_PASSWORD_QUALITY(
|
|
||||||
"ppolicy.insufficient.password.quality",
|
PASSWORD_MOD_NOT_ALLOWED("ppolicy.mod.not.allowed", "Password cannot be changed"),
|
||||||
"The supplied password is of insufficient quality"), PASSWORD_TOO_SHORT(
|
|
||||||
"ppolicy.password.too.short",
|
MUST_SUPPLY_OLD_PASSWORD("ppolicy.must.supply.old.password", "The old password must be supplied"),
|
||||||
"The supplied password is too short"), PASSWORD_TOO_YOUNG(
|
|
||||||
"ppolicy.password.too.young",
|
INSUFFICIENT_PASSWORD_QUALITY("ppolicy.insufficient.password.quality",
|
||||||
"Your password was changed too recently to be changed again"), PASSWORD_IN_HISTORY(
|
"The supplied password is of insufficient quality"),
|
||||||
"ppolicy.password.in.history",
|
|
||||||
"The supplied password has already been used");
|
PASSWORD_TOO_SHORT("ppolicy.password.too.short", "The supplied password is too short"),
|
||||||
|
|
||||||
|
PASSWORD_TOO_YOUNG("ppolicy.password.too.young", "Your password was changed too recently to be changed again"),
|
||||||
|
|
||||||
|
PASSWORD_IN_HISTORY("ppolicy.password.in.history", "The supplied password has already been used");
|
||||||
|
|
||||||
private final String errorCode;
|
private final String errorCode;
|
||||||
|
|
||||||
|
@ -77,10 +77,7 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
|
|||||||
*/
|
*/
|
||||||
public PasswordPolicyResponseControl(byte[] encodedValue) {
|
public PasswordPolicyResponseControl(byte[] encodedValue) {
|
||||||
this.encodedValue = encodedValue;
|
this.encodedValue = encodedValue;
|
||||||
|
|
||||||
// PPolicyDecoder decoder = new JLdapDecoder();
|
|
||||||
PPolicyDecoder decoder = new NetscapeDecoder();
|
PPolicyDecoder decoder = new NetscapeDecoder();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
decoder.decode();
|
decoder.decode();
|
||||||
}
|
}
|
||||||
@ -162,23 +159,18 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder("PasswordPolicyResponseControl");
|
StringBuilder sb = new StringBuilder("PasswordPolicyResponseControl");
|
||||||
|
|
||||||
if (hasError()) {
|
if (hasError()) {
|
||||||
sb.append(", error: ").append(this.errorStatus.getDefaultMessage());
|
sb.append(", error: ").append(this.errorStatus.getDefaultMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.graceLoginsRemaining != Integer.MAX_VALUE) {
|
if (this.graceLoginsRemaining != Integer.MAX_VALUE) {
|
||||||
sb.append(", warning: ").append(this.graceLoginsRemaining).append(" grace logins remain");
|
sb.append(", warning: ").append(this.graceLoginsRemaining).append(" grace logins remain");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.timeBeforeExpiration != Integer.MAX_VALUE) {
|
if (this.timeBeforeExpiration != Integer.MAX_VALUE) {
|
||||||
sb.append(", warning: time before expiration is ").append(this.timeBeforeExpiration);
|
sb.append(", warning: time before expiration is ").append(this.timeBeforeExpiration);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasError() && !hasWarning()) {
|
if (!hasError() && !hasWarning()) {
|
||||||
sb.append(" (no error, no warning)");
|
sb.append(" (no error, no warning)");
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,24 +190,17 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
|
|||||||
int[] bread = { 0 };
|
int[] bread = { 0 };
|
||||||
BERSequence seq = (BERSequence) BERElement.getElement(new SpecificTagDecoder(),
|
BERSequence seq = (BERSequence) BERElement.getElement(new SpecificTagDecoder(),
|
||||||
new ByteArrayInputStream(PasswordPolicyResponseControl.this.encodedValue), bread);
|
new ByteArrayInputStream(PasswordPolicyResponseControl.this.encodedValue), bread);
|
||||||
|
|
||||||
int size = seq.size();
|
int size = seq.size();
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("PasswordPolicyResponse, ASN.1 sequence has " + size + " elements");
|
logger.debug("PasswordPolicyResponse, ASN.1 sequence has " + size + " elements");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < seq.size(); i++) {
|
for (int i = 0; i < seq.size(); i++) {
|
||||||
BERTag elt = (BERTag) seq.elementAt(i);
|
BERTag elt = (BERTag) seq.elementAt(i);
|
||||||
|
|
||||||
int tag = elt.getTag() & 0x1F;
|
int tag = elt.getTag() & 0x1F;
|
||||||
|
|
||||||
if (tag == 0) {
|
if (tag == 0) {
|
||||||
BERChoice warning = (BERChoice) elt.getValue();
|
BERChoice warning = (BERChoice) elt.getValue();
|
||||||
|
|
||||||
BERTag content = (BERTag) warning.getValue();
|
BERTag content = (BERTag) warning.getValue();
|
||||||
int value = ((BERInteger) content.getValue()).getValue();
|
int value = ((BERInteger) content.getValue()).getValue();
|
||||||
|
|
||||||
if ((content.getTag() & 0x1F) == 0) {
|
if ((content.getTag() & 0x1F) == 0) {
|
||||||
PasswordPolicyResponseControl.this.timeBeforeExpiration = value;
|
PasswordPolicyResponseControl.this.timeBeforeExpiration = value;
|
||||||
}
|
}
|
||||||
@ -241,19 +226,15 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
|
|||||||
boolean[] implicit) throws IOException {
|
boolean[] implicit) throws IOException {
|
||||||
tag &= 0x1F;
|
tag &= 0x1F;
|
||||||
implicit[0] = false;
|
implicit[0] = false;
|
||||||
|
|
||||||
if (tag == 0) {
|
if (tag == 0) {
|
||||||
// Either the choice or the time before expiry within it
|
// Either the choice or the time before expiry within it
|
||||||
if (this.inChoice == null) {
|
if (this.inChoice == null) {
|
||||||
setInChoice(true);
|
setInChoice(true);
|
||||||
|
|
||||||
// Read the choice length from the stream (ignored)
|
// Read the choice length from the stream (ignored)
|
||||||
BERElement.readLengthOctets(stream, bytesRead);
|
BERElement.readLengthOctets(stream, bytesRead);
|
||||||
|
|
||||||
int[] componentLength = new int[1];
|
int[] componentLength = new int[1];
|
||||||
BERElement choice = new BERChoice(decoder, stream, componentLength);
|
BERElement choice = new BERChoice(decoder, stream, componentLength);
|
||||||
bytesRead[0] += componentLength[0];
|
bytesRead[0] += componentLength[0];
|
||||||
|
|
||||||
// inChoice = null;
|
// inChoice = null;
|
||||||
return choice;
|
return choice;
|
||||||
}
|
}
|
||||||
@ -267,7 +248,6 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
|
|||||||
if (this.inChoice == null) {
|
if (this.inChoice == null) {
|
||||||
// The enumeration
|
// The enumeration
|
||||||
setInChoice(false);
|
setInChoice(false);
|
||||||
|
|
||||||
return new BEREnumerated(stream, bytesRead);
|
return new BEREnumerated(stream, bytesRead);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -277,7 +257,6 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new DataRetrievalFailureException("Unexpected tag " + tag);
|
throw new DataRetrievalFailureException("Unexpected tag " + tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,67 +268,4 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Decoder based on the OpenLDAP/Novell JLDAP library */
|
|
||||||
|
|
||||||
// private class JLdapDecoder implements PPolicyDecoder {
|
|
||||||
//
|
|
||||||
// public void decode() throws IOException {
|
|
||||||
//
|
|
||||||
// LBERDecoder decoder = new LBERDecoder();
|
|
||||||
//
|
|
||||||
// ASN1Sequence seq = (ASN1Sequence)decoder.decode(encodedValue);
|
|
||||||
//
|
|
||||||
// if(seq == null) {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// int size = seq.size();
|
|
||||||
//
|
|
||||||
// if(logger.isDebugEnabled()) {
|
|
||||||
// logger.debug("PasswordPolicyResponse, ASN.1 sequence has " +
|
|
||||||
// size + " elements");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for(int i=0; i < size; i++) {
|
|
||||||
//
|
|
||||||
// ASN1Tagged taggedObject = (ASN1Tagged)seq.get(i);
|
|
||||||
//
|
|
||||||
// int tag = taggedObject.getIdentifier().getTag();
|
|
||||||
//
|
|
||||||
// ASN1OctetString value = (ASN1OctetString)taggedObject.taggedValue();
|
|
||||||
// byte[] content = value.byteValue();
|
|
||||||
//
|
|
||||||
// if(tag == 0) {
|
|
||||||
// parseWarning(content, decoder);
|
|
||||||
//
|
|
||||||
// } else if(tag == 1) {
|
|
||||||
// // Error: set the code to the value
|
|
||||||
// errorCode = content[0];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void parseWarning(byte[] content, LBERDecoder decoder) {
|
|
||||||
// // It's the warning (choice). Parse the number and set either the
|
|
||||||
// // expiry time or number of logins remaining.
|
|
||||||
// ASN1Tagged taggedObject = (ASN1Tagged)decoder.decode(content);
|
|
||||||
// int contentTag = taggedObject.getIdentifier().getTag();
|
|
||||||
// content = ((ASN1OctetString)taggedObject.taggedValue()).byteValue();
|
|
||||||
// int number;
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// number = ((Long)decoder.decodeNumeric(new ByteArrayInputStream(content),
|
|
||||||
// content.length)).intValue();
|
|
||||||
// } catch(IOException ex) {
|
|
||||||
// throw new LdapDataAccessException("Failed to parse number ", ex);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if(contentTag == 0) {
|
|
||||||
// timeBeforeExpiration = number;
|
|
||||||
// } else if (contentTag == 1) {
|
|
||||||
// graceLoginsRemaining = number;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import javax.naming.directory.SearchControls;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||||
import org.springframework.ldap.core.ContextSource;
|
import org.springframework.ldap.core.ContextSource;
|
||||||
import org.springframework.ldap.core.DirContextOperations;
|
import org.springframework.ldap.core.DirContextOperations;
|
||||||
@ -73,13 +74,10 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
|
|||||||
Assert.notNull(contextSource, "contextSource must not be null");
|
Assert.notNull(contextSource, "contextSource must not be null");
|
||||||
Assert.notNull(searchFilter, "searchFilter must not be null.");
|
Assert.notNull(searchFilter, "searchFilter must not be null.");
|
||||||
Assert.notNull(searchBase, "searchBase must not be null (an empty string is acceptable).");
|
Assert.notNull(searchBase, "searchBase must not be null (an empty string is acceptable).");
|
||||||
|
|
||||||
this.searchFilter = searchFilter;
|
this.searchFilter = searchFilter;
|
||||||
this.contextSource = contextSource;
|
this.contextSource = contextSource;
|
||||||
this.searchBase = searchBase;
|
this.searchBase = searchBase;
|
||||||
|
|
||||||
setSearchSubtree(true);
|
setSearchSubtree(true);
|
||||||
|
|
||||||
if (searchBase.length() == 0) {
|
if (searchBase.length() == 0) {
|
||||||
logger.info(
|
logger.info(
|
||||||
"SearchBase not set. Searches will be performed from the root: " + contextSource.getBaseLdapPath());
|
"SearchBase not set. Searches will be performed from the root: " + contextSource.getBaseLdapPath());
|
||||||
@ -95,26 +93,18 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public DirContextOperations searchForUser(String username) {
|
public DirContextOperations searchForUser(String username) {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug(LogMessage.of(() -> "Searching for user '" + username + "', with user search " + this));
|
||||||
logger.debug("Searching for user '" + username + "', with user search " + this);
|
|
||||||
}
|
|
||||||
|
|
||||||
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(this.contextSource);
|
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(this.contextSource);
|
||||||
|
|
||||||
template.setSearchControls(this.searchControls);
|
template.setSearchControls(this.searchControls);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
return template.searchForSingleEntry(this.searchBase, this.searchFilter, new String[] { username });
|
return template.searchForSingleEntry(this.searchBase, this.searchFilter, new String[] { username });
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (IncorrectResultSizeDataAccessException notFound) {
|
catch (IncorrectResultSizeDataAccessException ex) {
|
||||||
if (notFound.getActualSize() == 0) {
|
if (ex.getActualSize() == 0) {
|
||||||
throw new UsernameNotFoundException("User " + username + " not found in directory.");
|
throw new UsernameNotFoundException("User " + username + " not found in directory.");
|
||||||
}
|
}
|
||||||
// Search should never return multiple results if properly configured, so just
|
// Search should never return multiple results if properly configured
|
||||||
// rethrow
|
throw ex;
|
||||||
throw notFound;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +151,6 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
sb.append("[ searchFilter: '").append(this.searchFilter).append("', ");
|
sb.append("[ searchFilter: '").append(this.searchFilter).append("', ");
|
||||||
sb.append("searchBase: '").append(this.searchBase).append("'");
|
sb.append("searchBase: '").append(this.searchBase).append("'");
|
||||||
sb.append(", scope: ").append(
|
sb.append(", scope: ").append(
|
||||||
|
@ -113,22 +113,12 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
|
|||||||
this.ldifResources = ldifs;
|
this.ldifResources = ldifs;
|
||||||
this.service = new DefaultDirectoryService();
|
this.service = new DefaultDirectoryService();
|
||||||
List<Interceptor> list = new ArrayList<>();
|
List<Interceptor> list = new ArrayList<>();
|
||||||
|
|
||||||
list.add(new NormalizationInterceptor());
|
list.add(new NormalizationInterceptor());
|
||||||
list.add(new AuthenticationInterceptor());
|
list.add(new AuthenticationInterceptor());
|
||||||
list.add(new ReferralInterceptor());
|
list.add(new ReferralInterceptor());
|
||||||
// list.add( new AciAuthorizationInterceptor() );
|
|
||||||
// list.add( new DefaultAuthorizationInterceptor() );
|
|
||||||
list.add(new ExceptionInterceptor());
|
list.add(new ExceptionInterceptor());
|
||||||
// list.add( new ChangeLogInterceptor() );
|
|
||||||
list.add(new OperationalAttributeInterceptor());
|
list.add(new OperationalAttributeInterceptor());
|
||||||
// list.add( new SchemaInterceptor() );
|
|
||||||
list.add(new SubentryInterceptor());
|
list.add(new SubentryInterceptor());
|
||||||
// list.add( new CollectiveAttributeInterceptor() );
|
|
||||||
// list.add( new EventInterceptor() );
|
|
||||||
// list.add( new TriggerInterceptor() );
|
|
||||||
// list.add( new JournalInterceptor() );
|
|
||||||
|
|
||||||
this.service.setInterceptors(list);
|
this.service.setInterceptors(list);
|
||||||
this.partition = new JdbmPartition();
|
this.partition = new JdbmPartition();
|
||||||
this.partition.setId("rootPartition");
|
this.partition.setId("rootPartition");
|
||||||
@ -145,21 +135,16 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
|
|||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
if (this.workingDir == null) {
|
if (this.workingDir == null) {
|
||||||
String apacheWorkDir = System.getProperty("apacheDSWorkDir");
|
String apacheWorkDir = System.getProperty("apacheDSWorkDir");
|
||||||
|
|
||||||
if (apacheWorkDir == null) {
|
if (apacheWorkDir == null) {
|
||||||
apacheWorkDir = createTempDirectory("apacheds-spring-security-");
|
apacheWorkDir = createTempDirectory("apacheds-spring-security-");
|
||||||
}
|
}
|
||||||
|
|
||||||
setWorkingDirectory(new File(apacheWorkDir));
|
setWorkingDirectory(new File(apacheWorkDir));
|
||||||
}
|
}
|
||||||
if (this.ldapOverSslEnabled && this.keyStoreFile == null) {
|
Assert.isTrue(!this.ldapOverSslEnabled || this.keyStoreFile != null,
|
||||||
throw new IllegalArgumentException("When LdapOverSsl is enabled, the keyStoreFile property must be set.");
|
"When LdapOverSsl is enabled, the keyStoreFile property must be set.");
|
||||||
}
|
|
||||||
|
|
||||||
this.server = new LdapServer();
|
this.server = new LdapServer();
|
||||||
this.server.setDirectoryService(this.service);
|
this.server.setDirectoryService(this.service);
|
||||||
// AbstractLdapIntegrationTests assume IPv4, so we specify the same here
|
// AbstractLdapIntegrationTests assume IPv4, so we specify the same here
|
||||||
|
|
||||||
this.transport = new TcpTransport(this.port);
|
this.transport = new TcpTransport(this.port);
|
||||||
if (this.ldapOverSslEnabled) {
|
if (this.ldapOverSslEnabled) {
|
||||||
this.transport.setEnableSSL(true);
|
this.transport.setEnableSSL(true);
|
||||||
@ -182,18 +167,13 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
|
|||||||
|
|
||||||
public void setWorkingDirectory(File workingDir) {
|
public void setWorkingDirectory(File workingDir) {
|
||||||
Assert.notNull(workingDir, "workingDir cannot be null");
|
Assert.notNull(workingDir, "workingDir cannot be null");
|
||||||
|
|
||||||
this.logger.info("Setting working directory for LDAP_PROVIDER: " + workingDir.getAbsolutePath());
|
this.logger.info("Setting working directory for LDAP_PROVIDER: " + workingDir.getAbsolutePath());
|
||||||
|
Assert.isTrue(!workingDir.exists(),
|
||||||
if (workingDir.exists()) {
|
"The specified working directory '" + workingDir.getAbsolutePath()
|
||||||
throw new IllegalArgumentException("The specified working directory '" + workingDir.getAbsolutePath()
|
+ "' already exists. Another directory service instance may be using it or it may be from a "
|
||||||
+ "' already exists. Another directory service instance may be using it or it may be from a "
|
+ " previous unclean shutdown. Please confirm and delete it or configure a different "
|
||||||
+ " previous unclean shutdown. Please confirm and delete it or configure a different "
|
+ "working directory");
|
||||||
+ "working directory");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.workingDir = workingDir;
|
this.workingDir = workingDir;
|
||||||
|
|
||||||
this.service.setWorkingDirectory(workingDir);
|
this.service.setWorkingDirectory(workingDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,11 +230,7 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
|
|||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Assert.state(!this.service.isStarted(), "DirectoryService is already running.");
|
||||||
if (this.service.isStarted()) {
|
|
||||||
throw new IllegalStateException("DirectoryService is already running.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info("Starting directory server...");
|
this.logger.info("Starting directory server...");
|
||||||
try {
|
try {
|
||||||
this.service.startup();
|
this.service.startup();
|
||||||
@ -263,7 +239,6 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
|
|||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
throw new RuntimeException("Server startup failed", ex);
|
throw new RuntimeException("Server startup failed", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.service.getAdminSession().lookup(this.partition.getSuffixDn());
|
this.service.getAdminSession().lookup(this.partition.getSuffixDn());
|
||||||
}
|
}
|
||||||
@ -273,13 +248,10 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
|
|||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
this.logger.error("Lookup failed", ex);
|
this.logger.error("Lookup failed", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketAcceptor socketAcceptor = this.server.getSocketAcceptor(this.transport);
|
SocketAcceptor socketAcceptor = this.server.getSocketAcceptor(this.transport);
|
||||||
InetSocketAddress localAddress = socketAcceptor.getLocalAddress();
|
InetSocketAddress localAddress = socketAcceptor.getLocalAddress();
|
||||||
this.localPort = localAddress.getPort();
|
this.localPort = localAddress.getPort();
|
||||||
|
|
||||||
this.running = true;
|
this.running = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
importLdifs();
|
importLdifs();
|
||||||
}
|
}
|
||||||
@ -308,7 +280,6 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
|
|||||||
if (!isRunning()) {
|
if (!isRunning()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info("Shutting down directory server ...");
|
this.logger.info("Shutting down directory server ...");
|
||||||
try {
|
try {
|
||||||
this.server.stop();
|
this.server.stop();
|
||||||
@ -318,9 +289,7 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
|
|||||||
this.logger.error("Shutdown failed", ex);
|
this.logger.error("Shutdown failed", ex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.running = false;
|
this.running = false;
|
||||||
|
|
||||||
if (this.workingDir.exists()) {
|
if (this.workingDir.exists()) {
|
||||||
this.logger.info("Deleting working directory " + this.workingDir.getAbsolutePath());
|
this.logger.info("Deleting working directory " + this.workingDir.getAbsolutePath());
|
||||||
deleteDir(this.workingDir);
|
deleteDir(this.workingDir);
|
||||||
@ -329,43 +298,31 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
|
|||||||
|
|
||||||
private void importLdifs() throws Exception {
|
private void importLdifs() throws Exception {
|
||||||
// Import any ldif files
|
// Import any ldif files
|
||||||
Resource[] ldifs;
|
Resource[] ldifs = (this.ctxt != null) ? this.ctxt.getResources(this.ldifResources)
|
||||||
|
: new PathMatchingResourcePatternResolver().getResources(this.ldifResources);
|
||||||
if (this.ctxt == null) {
|
|
||||||
// Not running within an app context
|
|
||||||
ldifs = new PathMatchingResourcePatternResolver().getResources(this.ldifResources);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ldifs = this.ctxt.getResources(this.ldifResources);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that we can't just import using the ServerContext returned
|
// Note that we can't just import using the ServerContext returned
|
||||||
// from starting Apache DS, apparently because of the long-running issue
|
// from starting Apache DS, apparently because of the long-running issue
|
||||||
// DIRSERVER-169.
|
// DIRSERVER-169.
|
||||||
// We need a standard context.
|
// We need a standard context.
|
||||||
// DirContext dirContext = contextSource.getReadWriteContext();
|
// DirContext dirContext = contextSource.getReadWriteContext();
|
||||||
|
|
||||||
if (ldifs == null || ldifs.length == 0) {
|
if (ldifs == null || ldifs.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Assert.isTrue(ldifs.length == 1, () -> "More than one LDIF resource found with the supplied pattern:"
|
||||||
|
+ this.ldifResources + " Got " + Arrays.toString(ldifs));
|
||||||
|
String ldifFile = getLdifFile(ldifs);
|
||||||
|
this.logger.info("Loading LDIF file: " + ldifFile);
|
||||||
|
LdifFileLoader loader = new LdifFileLoader(this.service.getAdminSession(), new File(ldifFile), null,
|
||||||
|
getClass().getClassLoader());
|
||||||
|
loader.execute();
|
||||||
|
}
|
||||||
|
|
||||||
if (ldifs.length == 1) {
|
private String getLdifFile(Resource[] ldifs) throws IOException {
|
||||||
String ldifFile;
|
try {
|
||||||
|
return ldifs[0].getFile().getAbsolutePath();
|
||||||
try {
|
|
||||||
ldifFile = ldifs[0].getFile().getAbsolutePath();
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
ldifFile = ldifs[0].getURI().toString();
|
|
||||||
}
|
|
||||||
this.logger.info("Loading LDIF file: " + ldifFile);
|
|
||||||
LdifFileLoader loader = new LdifFileLoader(this.service.getAdminSession(), new File(ldifFile), null,
|
|
||||||
getClass().getClassLoader());
|
|
||||||
loader.execute();
|
|
||||||
}
|
}
|
||||||
else {
|
catch (IOException ex) {
|
||||||
throw new IllegalArgumentException("More than one LDIF resource found with the supplied pattern:"
|
return ldifs[0].getURI().toString();
|
||||||
+ this.ldifResources + " Got " + Arrays.toString(ldifs));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,7 +330,6 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
|
|||||||
String parentTempDir = System.getProperty("java.io.tmpdir");
|
String parentTempDir = System.getProperty("java.io.tmpdir");
|
||||||
String fileNamePrefix = prefix + System.nanoTime();
|
String fileNamePrefix = prefix + System.nanoTime();
|
||||||
String fileName = fileNamePrefix;
|
String fileName = fileNamePrefix;
|
||||||
|
|
||||||
for (int i = 0; i < 1000; i++) {
|
for (int i = 0; i < 1000; i++) {
|
||||||
File tempDir = new File(parentTempDir, fileName);
|
File tempDir = new File(parentTempDir, fileName);
|
||||||
if (!tempDir.exists()) {
|
if (!tempDir.exists()) {
|
||||||
@ -381,7 +337,6 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
|
|||||||
}
|
}
|
||||||
fileName = fileNamePrefix + "~" + i;
|
fileName = fileNamePrefix + "~" + i;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IOException(
|
throw new IOException(
|
||||||
"Failed to create a temporary directory for file at " + new File(parentTempDir, fileNamePrefix));
|
"Failed to create a temporary directory for file at " + new File(parentTempDir, fileNamePrefix));
|
||||||
}
|
}
|
||||||
@ -396,7 +351,6 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dir.delete();
|
return dir.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,20 +85,16 @@ public class UnboundIdContainer implements InitializingBean, DisposableBean, Lif
|
|||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(this.defaultPartitionSuffix);
|
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(this.defaultPartitionSuffix);
|
||||||
config.addAdditionalBindCredentials("uid=admin,ou=system", "secret");
|
config.addAdditionalBindCredentials("uid=admin,ou=system", "secret");
|
||||||
|
|
||||||
config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", this.port));
|
config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", this.port));
|
||||||
config.setEnforceSingleStructuralObjectClass(false);
|
config.setEnforceSingleStructuralObjectClass(false);
|
||||||
config.setEnforceAttributeSyntaxCompliance(true);
|
config.setEnforceAttributeSyntaxCompliance(true);
|
||||||
|
|
||||||
DN dn = new DN(this.defaultPartitionSuffix);
|
DN dn = new DN(this.defaultPartitionSuffix);
|
||||||
Entry entry = new Entry(dn);
|
Entry entry = new Entry(dn);
|
||||||
entry.addAttribute("objectClass", "top", "domain", "extensibleObject");
|
entry.addAttribute("objectClass", "top", "domain", "extensibleObject");
|
||||||
entry.addAttribute("dc", dn.getRDN().getAttributeValues()[0]);
|
entry.addAttribute("dc", dn.getRDN().getAttributeValues()[0]);
|
||||||
|
|
||||||
InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config);
|
InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config);
|
||||||
directoryServer.add(entry);
|
directoryServer.add(entry);
|
||||||
importLdif(directoryServer);
|
importLdif(directoryServer);
|
||||||
@ -110,7 +106,6 @@ public class UnboundIdContainer implements InitializingBean, DisposableBean, Lif
|
|||||||
catch (LDAPException ex) {
|
catch (LDAPException ex) {
|
||||||
throw new RuntimeException("Server startup failed", ex);
|
throw new RuntimeException("Server startup failed", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importLdif(InMemoryDirectoryServer directoryServer) {
|
private void importLdif(InMemoryDirectoryServer directoryServer) {
|
||||||
|
@ -29,6 +29,7 @@ import javax.naming.directory.SearchControls;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.ldap.core.ContextSource;
|
import org.springframework.ldap.core.ContextSource;
|
||||||
import org.springframework.ldap.core.DirContextOperations;
|
import org.springframework.ldap.core.DirContextOperations;
|
||||||
import org.springframework.ldap.core.LdapTemplate;
|
import org.springframework.ldap.core.LdapTemplate;
|
||||||
@ -161,21 +162,17 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
|||||||
this.ldapTemplate = new SpringSecurityLdapTemplate(contextSource);
|
this.ldapTemplate = new SpringSecurityLdapTemplate(contextSource);
|
||||||
getLdapTemplate().setSearchControls(getSearchControls());
|
getLdapTemplate().setSearchControls(getSearchControls());
|
||||||
this.groupSearchBase = groupSearchBase;
|
this.groupSearchBase = groupSearchBase;
|
||||||
|
|
||||||
if (groupSearchBase == null) {
|
if (groupSearchBase == null) {
|
||||||
logger.info("groupSearchBase is null. No group search will be performed.");
|
logger.info("groupSearchBase is null. No group search will be performed.");
|
||||||
}
|
}
|
||||||
else if (groupSearchBase.length() == 0) {
|
else if (groupSearchBase.length() == 0) {
|
||||||
logger.info("groupSearchBase is empty. Searches will be performed from the context source base");
|
logger.info("groupSearchBase is empty. Searches will be performed from the context source base");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.authorityMapper = (record) -> {
|
this.authorityMapper = (record) -> {
|
||||||
String role = record.get(this.groupRoleAttribute).get(0);
|
String role = record.get(this.groupRoleAttribute).get(0);
|
||||||
|
|
||||||
if (this.convertToUpperCase) {
|
if (this.convertToUpperCase) {
|
||||||
role = role.toUpperCase();
|
role = role.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SimpleGrantedAuthority(this.rolePrefix + role);
|
return new SimpleGrantedAuthority(this.rolePrefix + role);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -202,26 +199,17 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
|||||||
@Override
|
@Override
|
||||||
public final Collection<GrantedAuthority> getGrantedAuthorities(DirContextOperations user, String username) {
|
public final Collection<GrantedAuthority> getGrantedAuthorities(DirContextOperations user, String username) {
|
||||||
String userDn = user.getNameInNamespace();
|
String userDn = user.getNameInNamespace();
|
||||||
|
logger.debug(LogMessage.format("Getting authorities for user %s", userDn));
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Getting authorities for user " + userDn);
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<GrantedAuthority> roles = getGroupMembershipRoles(userDn, username);
|
Set<GrantedAuthority> roles = getGroupMembershipRoles(userDn, username);
|
||||||
|
|
||||||
Set<GrantedAuthority> extraRoles = getAdditionalRoles(user, username);
|
Set<GrantedAuthority> extraRoles = getAdditionalRoles(user, username);
|
||||||
|
|
||||||
if (extraRoles != null) {
|
if (extraRoles != null) {
|
||||||
roles.addAll(extraRoles);
|
roles.addAll(extraRoles);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.defaultRole != null) {
|
if (this.defaultRole != null) {
|
||||||
roles.add(this.defaultRole);
|
roles.add(this.defaultRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<GrantedAuthority> result = new ArrayList<>(roles.size());
|
List<GrantedAuthority> result = new ArrayList<>(roles.size());
|
||||||
result.addAll(roles);
|
result.addAll(roles);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,26 +217,16 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
|||||||
if (getGroupSearchBase() == null) {
|
if (getGroupSearchBase() == null) {
|
||||||
return new HashSet<>();
|
return new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<GrantedAuthority> authorities = new HashSet<>();
|
Set<GrantedAuthority> authorities = new HashSet<>();
|
||||||
|
logger.debug(LogMessage.of(() -> "Searching for roles for user '" + username + "', DN = " + "'" + userDn
|
||||||
if (logger.isDebugEnabled()) {
|
+ "', with filter " + this.groupSearchFilter + " in search base '" + getGroupSearchBase() + "'"));
|
||||||
logger.debug("Searching for roles for user '" + username + "', DN = " + "'" + userDn + "', with filter "
|
|
||||||
+ this.groupSearchFilter + " in search base '" + getGroupSearchBase() + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
|
Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
|
||||||
getGroupSearchBase(), this.groupSearchFilter, new String[] { userDn, username },
|
getGroupSearchBase(), this.groupSearchFilter, new String[] { userDn, username },
|
||||||
new String[] { this.groupRoleAttribute });
|
new String[] { this.groupRoleAttribute });
|
||||||
|
logger.debug(LogMessage.of(() -> "Roles from search: " + userRoles));
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Roles from search: " + userRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map<String, List<String>> role : userRoles) {
|
for (Map<String, List<String>> role : userRoles) {
|
||||||
authorities.add(this.authorityMapper.apply(role));
|
authorities.add(this.authorityMapper.apply(role));
|
||||||
}
|
}
|
||||||
|
|
||||||
return authorities;
|
return authorities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ import org.springframework.security.core.SpringSecurityCoreVersion;
|
|||||||
* <p>
|
* <p>
|
||||||
* The username will be mapped from the <tt>uid</tt> attribute by default.
|
* The username will be mapped from the <tt>uid</tt> attribute by default.
|
||||||
*
|
*
|
||||||
* @author Luke
|
* @author Luke Taylor
|
||||||
*/
|
*/
|
||||||
public class InetOrgPerson extends Person {
|
public class InetOrgPerson extends Person {
|
||||||
|
|
||||||
|
@ -33,10 +33,8 @@ public class InetOrgPersonContextMapper implements UserDetailsContextMapper {
|
|||||||
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
|
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
|
||||||
Collection<? extends GrantedAuthority> authorities) {
|
Collection<? extends GrantedAuthority> authorities) {
|
||||||
InetOrgPerson.Essence p = new InetOrgPerson.Essence(ctx);
|
InetOrgPerson.Essence p = new InetOrgPerson.Essence(ctx);
|
||||||
|
|
||||||
p.setUsername(username);
|
p.setUsername(username);
|
||||||
p.setAuthorities(authorities);
|
p.setAuthorities(authorities);
|
||||||
|
|
||||||
return p.createUserDetails();
|
return p.createUserDetails();
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -44,7 +42,6 @@ public class InetOrgPersonContextMapper implements UserDetailsContextMapper {
|
|||||||
@Override
|
@Override
|
||||||
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
|
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
|
||||||
Assert.isInstanceOf(InetOrgPerson.class, user, "UserDetails must be an InetOrgPerson instance");
|
Assert.isInstanceOf(InetOrgPerson.class, user, "UserDetails must be an InetOrgPerson instance");
|
||||||
|
|
||||||
InetOrgPerson p = (InetOrgPerson) user;
|
InetOrgPerson p = (InetOrgPerson) user;
|
||||||
p.populateContext(ctx);
|
p.populateContext(ctx);
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,6 @@ public class LdapAuthority implements GrantedAuthority {
|
|||||||
public LdapAuthority(String role, String dn, Map<String, List<String>> attributes) {
|
public LdapAuthority(String role, String dn, Map<String, List<String>> attributes) {
|
||||||
Assert.notNull(role, "role can not be null");
|
Assert.notNull(role, "role can not be null");
|
||||||
Assert.notNull(dn, "dn can not be null");
|
Assert.notNull(dn, "dn can not be null");
|
||||||
|
|
||||||
this.role = role;
|
this.role = role;
|
||||||
this.dn = dn;
|
this.dn = dn;
|
||||||
this.attributes = attributes;
|
this.attributes = attributes;
|
||||||
@ -87,10 +86,7 @@ public class LdapAuthority implements GrantedAuthority {
|
|||||||
if (this.attributes != null) {
|
if (this.attributes != null) {
|
||||||
result = this.attributes.get(name);
|
result = this.attributes.get(name);
|
||||||
}
|
}
|
||||||
if (result == null) {
|
return (result != null) ? result : Collections.emptyList();
|
||||||
result = Collections.emptyList();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,17 +96,9 @@ public class LdapAuthority implements GrantedAuthority {
|
|||||||
*/
|
*/
|
||||||
public String getFirstAttributeValue(String name) {
|
public String getFirstAttributeValue(String name) {
|
||||||
List<String> result = getAttributeValues(name);
|
List<String> result = getAttributeValues(name);
|
||||||
if (result.isEmpty()) {
|
return (!result.isEmpty()) ? result.get(0) : null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return result.get(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String getAuthority() {
|
public String getAuthority() {
|
||||||
return this.role;
|
return this.role;
|
||||||
@ -118,23 +106,21 @@ public class LdapAuthority implements GrantedAuthority {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares the LdapAuthority based on {@link #getAuthority()} and {@link #getDn()}
|
* Compares the LdapAuthority based on {@link #getAuthority()} and {@link #getDn()}
|
||||||
* values {@inheritDoc}
|
* values.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object obj) {
|
||||||
if (this == o) {
|
if (this == obj) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!(o instanceof LdapAuthority)) {
|
if (!(obj instanceof LdapAuthority)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
LdapAuthority other = (LdapAuthority) obj;
|
||||||
LdapAuthority that = (LdapAuthority) o;
|
if (!this.dn.equals(other.dn)) {
|
||||||
|
|
||||||
if (!this.dn.equals(that.dn)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.role.equals(that.role);
|
return this.role.equals(other.role);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -154,11 +154,9 @@ public class LdapUserDetailsImpl implements LdapUserDetails, PasswordPolicyData
|
|||||||
sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; ");
|
sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; ");
|
||||||
sb.append("CredentialsNonExpired: ").append(this.credentialsNonExpired).append("; ");
|
sb.append("CredentialsNonExpired: ").append(this.credentialsNonExpired).append("; ");
|
||||||
sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; ");
|
sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; ");
|
||||||
|
|
||||||
if (this.getAuthorities() != null && !this.getAuthorities().isEmpty()) {
|
if (this.getAuthorities() != null && !this.getAuthorities().isEmpty()) {
|
||||||
sb.append("Granted Authorities: ");
|
sb.append("Granted Authorities: ");
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
|
|
||||||
for (Object authority : this.getAuthorities()) {
|
for (Object authority : this.getAuthorities()) {
|
||||||
if (first) {
|
if (first) {
|
||||||
first = false;
|
first = false;
|
||||||
@ -166,14 +164,12 @@ public class LdapUserDetailsImpl implements LdapUserDetails, PasswordPolicyData
|
|||||||
else {
|
else {
|
||||||
sb.append(", ");
|
sb.append(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.append(authority.toString());
|
sb.append(authority.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sb.append("Not granted any authorities");
|
sb.append("Not granted any authorities");
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,13 +227,9 @@ public class LdapUserDetailsImpl implements LdapUserDetails, PasswordPolicyData
|
|||||||
Assert.notNull(this.instance, "Essence can only be used to create a single instance");
|
Assert.notNull(this.instance, "Essence can only be used to create a single instance");
|
||||||
Assert.notNull(this.instance.username, "username must not be null");
|
Assert.notNull(this.instance.username, "username must not be null");
|
||||||
Assert.notNull(this.instance.getDn(), "Distinguished name must not be null");
|
Assert.notNull(this.instance.getDn(), "Distinguished name must not be null");
|
||||||
|
|
||||||
this.instance.authorities = Collections.unmodifiableList(this.mutableAuthorities);
|
this.instance.authorities = Collections.unmodifiableList(this.mutableAuthorities);
|
||||||
|
|
||||||
LdapUserDetails newInstance = this.instance;
|
LdapUserDetails newInstance = this.instance;
|
||||||
|
|
||||||
this.instance = null;
|
this.instance = null;
|
||||||
|
|
||||||
return newInstance;
|
return newInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ import javax.naming.ldap.LdapContext;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.ldap.core.AttributesMapper;
|
import org.springframework.ldap.core.AttributesMapper;
|
||||||
import org.springframework.ldap.core.AttributesMapperCallbackHandler;
|
import org.springframework.ldap.core.AttributesMapperCallbackHandler;
|
||||||
import org.springframework.ldap.core.ContextExecutor;
|
import org.springframework.ldap.core.ContextExecutor;
|
||||||
@ -116,12 +117,9 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
/** Default context mapper used to create a set of roles from a list of attributes */
|
/** Default context mapper used to create a set of roles from a list of attributes */
|
||||||
private AttributesMapper roleMapper = (attributes) -> {
|
private AttributesMapper roleMapper = (attributes) -> {
|
||||||
Attribute roleAttr = attributes.get(this.groupRoleAttributeName);
|
Attribute roleAttr = attributes.get(this.groupRoleAttributeName);
|
||||||
|
|
||||||
NamingEnumeration<?> ne = roleAttr.getAll();
|
NamingEnumeration<?> ne = roleAttr.getAll();
|
||||||
// assert ne.hasMore();
|
|
||||||
Object group = ne.next();
|
Object group = ne.next();
|
||||||
String role = group.toString();
|
String role = group.toString();
|
||||||
|
|
||||||
return new SimpleGrantedAuthority(this.rolePrefix + role.toUpperCase());
|
return new SimpleGrantedAuthority(this.rolePrefix + role.toUpperCase());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -137,11 +135,8 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
public UserDetails loadUserByUsername(String username) {
|
public UserDetails loadUserByUsername(String username) {
|
||||||
DistinguishedName dn = this.usernameMapper.buildDn(username);
|
DistinguishedName dn = this.usernameMapper.buildDn(username);
|
||||||
List<GrantedAuthority> authorities = getUserAuthorities(dn, username);
|
List<GrantedAuthority> authorities = getUserAuthorities(dn, username);
|
||||||
|
this.logger.debug(LogMessage.format("Loading user '%s' with DN '%s'", username, dn));
|
||||||
this.logger.debug("Loading user '" + username + "' with DN '" + dn + "'");
|
|
||||||
|
|
||||||
DirContextAdapter userCtx = loadUserAsContext(dn, username);
|
DirContextAdapter userCtx = loadUserAsContext(dn, username);
|
||||||
|
|
||||||
return this.userDetailsMapper.mapUserFromContext(userCtx, username, authorities);
|
return this.userDetailsMapper.mapUserFromContext(userCtx, username, authorities);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,8 +146,8 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
Attributes attrs = ctx.getAttributes(dn, this.attributesToRetrieve);
|
Attributes attrs = ctx.getAttributes(dn, this.attributesToRetrieve);
|
||||||
return new DirContextAdapter(attrs, LdapUtils.getFullDn(dn, ctx));
|
return new DirContextAdapter(attrs, LdapUtils.getFullDn(dn, ctx));
|
||||||
}
|
}
|
||||||
catch (NameNotFoundException notFound) {
|
catch (NameNotFoundException ex) {
|
||||||
throw new UsernameNotFoundException("User " + username + " not found", notFound);
|
throw new UsernameNotFoundException("User " + username + " not found", ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -187,13 +182,9 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
Assert.notNull(authentication,
|
Assert.notNull(authentication,
|
||||||
"No authentication object found in security context. Can't change current user's password!");
|
"No authentication object found in security context. Can't change current user's password!");
|
||||||
|
|
||||||
String username = authentication.getName();
|
String username = authentication.getName();
|
||||||
|
this.logger.debug(LogMessage.format("Changing password for user '%s'", username));
|
||||||
this.logger.debug("Changing password for user '" + username);
|
|
||||||
|
|
||||||
DistinguishedName userDn = this.usernameMapper.buildDn(username);
|
DistinguishedName userDn = this.usernameMapper.buildDn(username);
|
||||||
|
|
||||||
if (this.usePasswordModifyExtensionOperation) {
|
if (this.usePasswordModifyExtensionOperation) {
|
||||||
changePasswordUsingExtensionOperation(userDn, oldPassword, newPassword);
|
changePasswordUsingExtensionOperation(userDn, oldPassword, newPassword);
|
||||||
}
|
}
|
||||||
@ -214,13 +205,10 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
DistinguishedName fullDn = LdapUtils.getFullDn(dn, ctx);
|
DistinguishedName fullDn = LdapUtils.getFullDn(dn, ctx);
|
||||||
SearchControls ctrls = new SearchControls();
|
SearchControls ctrls = new SearchControls();
|
||||||
ctrls.setReturningAttributes(new String[] { this.groupRoleAttributeName });
|
ctrls.setReturningAttributes(new String[] { this.groupRoleAttributeName });
|
||||||
|
|
||||||
return ctx.search(this.groupSearchBase, this.groupSearchFilter, new String[] { fullDn.toUrl(), username },
|
return ctx.search(this.groupSearchBase, this.groupSearchFilter, new String[] { fullDn.toUrl(), username },
|
||||||
ctrls);
|
ctrls);
|
||||||
};
|
};
|
||||||
|
|
||||||
AttributesMapperCallbackHandler roleCollector = new AttributesMapperCallbackHandler(this.roleMapper);
|
AttributesMapperCallbackHandler roleCollector = new AttributesMapperCallbackHandler(this.roleMapper);
|
||||||
|
|
||||||
this.template.search(se, roleCollector);
|
this.template.search(se, roleCollector);
|
||||||
return roleCollector.getList();
|
return roleCollector.getList();
|
||||||
}
|
}
|
||||||
@ -230,38 +218,28 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
DirContextAdapter ctx = new DirContextAdapter();
|
DirContextAdapter ctx = new DirContextAdapter();
|
||||||
copyToContext(user, ctx);
|
copyToContext(user, ctx);
|
||||||
DistinguishedName dn = this.usernameMapper.buildDn(user.getUsername());
|
DistinguishedName dn = this.usernameMapper.buildDn(user.getUsername());
|
||||||
|
this.logger.debug(LogMessage.format("Creating new user '%s' with DN '%s'", user.getUsername(), dn));
|
||||||
this.logger.debug("Creating new user '" + user.getUsername() + "' with DN '" + dn + "'");
|
|
||||||
|
|
||||||
this.template.bind(dn, ctx, null);
|
this.template.bind(dn, ctx, null);
|
||||||
|
// Check for any existing authorities which might be set for this
|
||||||
// Check for any existing authorities which might be set for this DN and remove
|
// DN and remove them
|
||||||
// them
|
|
||||||
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
|
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
|
||||||
|
|
||||||
if (authorities.size() > 0) {
|
if (authorities.size() > 0) {
|
||||||
removeAuthorities(dn, authorities);
|
removeAuthorities(dn, authorities);
|
||||||
}
|
}
|
||||||
|
|
||||||
addAuthorities(dn, user.getAuthorities());
|
addAuthorities(dn, user.getAuthorities());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateUser(UserDetails user) {
|
public void updateUser(UserDetails user) {
|
||||||
DistinguishedName dn = this.usernameMapper.buildDn(user.getUsername());
|
DistinguishedName dn = this.usernameMapper.buildDn(user.getUsername());
|
||||||
|
this.logger.debug(LogMessage.format("Updating new user '%s' with DN '%s'", user.getUsername(), dn));
|
||||||
this.logger.debug("Updating user '" + user.getUsername() + "' with DN '" + dn + "'");
|
|
||||||
|
|
||||||
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
|
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
|
||||||
|
|
||||||
DirContextAdapter ctx = loadUserAsContext(dn, user.getUsername());
|
DirContextAdapter ctx = loadUserAsContext(dn, user.getUsername());
|
||||||
ctx.setUpdateMode(true);
|
ctx.setUpdateMode(true);
|
||||||
copyToContext(user, ctx);
|
copyToContext(user, ctx);
|
||||||
|
|
||||||
// Remove the objectclass attribute from the list of mods (if present).
|
// Remove the objectclass attribute from the list of mods (if present).
|
||||||
List<ModificationItem> mods = new LinkedList<>(Arrays.asList(ctx.getModificationItems()));
|
List<ModificationItem> mods = new LinkedList<>(Arrays.asList(ctx.getModificationItems()));
|
||||||
ListIterator<ModificationItem> modIt = mods.listIterator();
|
ListIterator<ModificationItem> modIt = mods.listIterator();
|
||||||
|
|
||||||
while (modIt.hasNext()) {
|
while (modIt.hasNext()) {
|
||||||
ModificationItem mod = modIt.next();
|
ModificationItem mod = modIt.next();
|
||||||
Attribute a = mod.getAttribute();
|
Attribute a = mod.getAttribute();
|
||||||
@ -269,9 +247,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
modIt.remove();
|
modIt.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.template.modifyAttributes(dn, mods.toArray(new ModificationItem[0]));
|
this.template.modifyAttributes(dn, mods.toArray(new ModificationItem[0]));
|
||||||
|
|
||||||
// template.rebind(dn, ctx, null);
|
// template.rebind(dn, ctx, null);
|
||||||
// Remove the old authorities and replace them with the new one
|
// Remove the old authorities and replace them with the new one
|
||||||
removeAuthorities(dn, authorities);
|
removeAuthorities(dn, authorities);
|
||||||
@ -288,7 +264,6 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
@Override
|
@Override
|
||||||
public boolean userExists(String username) {
|
public boolean userExists(String username) {
|
||||||
DistinguishedName dn = this.usernameMapper.buildDn(username);
|
DistinguishedName dn = this.usernameMapper.buildDn(username);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Object obj = this.template.lookup(dn);
|
Object obj = this.template.lookup(dn);
|
||||||
if (obj instanceof Context) {
|
if (obj instanceof Context) {
|
||||||
@ -309,7 +284,6 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
protected DistinguishedName buildGroupDn(String group) {
|
protected DistinguishedName buildGroupDn(String group) {
|
||||||
DistinguishedName dn = new DistinguishedName(this.groupSearchBase);
|
DistinguishedName dn = new DistinguishedName(this.groupSearchBase);
|
||||||
dn.add(this.groupRoleAttributeName, group.toLowerCase());
|
dn.add(this.groupRoleAttributeName, group.toLowerCase());
|
||||||
|
|
||||||
return dn;
|
return dn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,7 +307,6 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
DistinguishedName fullDn = LdapUtils.getFullDn(userDn, ctx);
|
DistinguishedName fullDn = LdapUtils.getFullDn(userDn, ctx);
|
||||||
ModificationItem addGroup = new ModificationItem(modType,
|
ModificationItem addGroup = new ModificationItem(modType,
|
||||||
new BasicAttribute(this.groupMemberAttributeName, fullDn.toUrl()));
|
new BasicAttribute(this.groupMemberAttributeName, fullDn.toUrl()));
|
||||||
|
|
||||||
ctx.modifyAttributes(buildGroupDn(group), new ModificationItem[] { addGroup });
|
ctx.modifyAttributes(buildGroupDn(group), new ModificationItem[] { addGroup });
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -342,11 +315,9 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
|
|
||||||
private String convertAuthorityToGroup(GrantedAuthority authority) {
|
private String convertAuthorityToGroup(GrantedAuthority authority) {
|
||||||
String group = authority.getAuthority();
|
String group = authority.getAuthority();
|
||||||
|
|
||||||
if (group.startsWith(this.rolePrefix)) {
|
if (group.startsWith(this.rolePrefix)) {
|
||||||
group = group.substring(this.rolePrefix.length());
|
group = group.substring(this.rolePrefix.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,15 +390,12 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
|
|
||||||
private void changePasswordUsingAttributeModification(DistinguishedName userDn, String oldPassword,
|
private void changePasswordUsingAttributeModification(DistinguishedName userDn, String oldPassword,
|
||||||
String newPassword) {
|
String newPassword) {
|
||||||
|
ModificationItem[] passwordChange = new ModificationItem[] { new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
|
||||||
final ModificationItem[] passwordChange = new ModificationItem[] { new ModificationItem(
|
new BasicAttribute(this.passwordAttributeName, newPassword)) };
|
||||||
DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(this.passwordAttributeName, newPassword)) };
|
|
||||||
|
|
||||||
if (oldPassword == null) {
|
if (oldPassword == null) {
|
||||||
this.template.modifyAttributes(userDn, passwordChange);
|
this.template.modifyAttributes(userDn, passwordChange);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.template.executeReadWrite((dirCtx) -> {
|
this.template.executeReadWrite((dirCtx) -> {
|
||||||
LdapContext ctx = (LdapContext) dirCtx;
|
LdapContext ctx = (LdapContext) dirCtx;
|
||||||
ctx.removeFromEnvironment("com.sun.jndi.ldap.connect.pool");
|
ctx.removeFromEnvironment("com.sun.jndi.ldap.connect.pool");
|
||||||
@ -440,23 +408,17 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
catch (javax.naming.AuthenticationException ex) {
|
catch (javax.naming.AuthenticationException ex) {
|
||||||
throw new BadCredentialsException("Authentication for password change failed.");
|
throw new BadCredentialsException("Authentication for password change failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.modifyAttributes(userDn, passwordChange);
|
ctx.modifyAttributes(userDn, passwordChange);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changePasswordUsingExtensionOperation(DistinguishedName userDn, String oldPassword,
|
private void changePasswordUsingExtensionOperation(DistinguishedName userDn, String oldPassword,
|
||||||
String newPassword) {
|
String newPassword) {
|
||||||
|
|
||||||
this.template.executeReadWrite((dirCtx) -> {
|
this.template.executeReadWrite((dirCtx) -> {
|
||||||
LdapContext ctx = (LdapContext) dirCtx;
|
LdapContext ctx = (LdapContext) dirCtx;
|
||||||
|
|
||||||
String userIdentity = LdapUtils.getFullDn(userDn, ctx).encode();
|
String userIdentity = LdapUtils.getFullDn(userDn, ctx).encode();
|
||||||
PasswordModifyRequest request = new PasswordModifyRequest(userIdentity, oldPassword, newPassword);
|
PasswordModifyRequest request = new PasswordModifyRequest(userIdentity, oldPassword, newPassword);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return ctx.extendedOperation(request);
|
return ctx.extendedOperation(request);
|
||||||
}
|
}
|
||||||
@ -493,19 +455,15 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
|
|
||||||
PasswordModifyRequest(String userIdentity, String oldPassword, String newPassword) {
|
PasswordModifyRequest(String userIdentity, String oldPassword, String newPassword) {
|
||||||
ByteArrayOutputStream elements = new ByteArrayOutputStream();
|
ByteArrayOutputStream elements = new ByteArrayOutputStream();
|
||||||
|
|
||||||
if (userIdentity != null) {
|
if (userIdentity != null) {
|
||||||
berEncode(USER_IDENTITY_OCTET_TYPE, userIdentity.getBytes(), elements);
|
berEncode(USER_IDENTITY_OCTET_TYPE, userIdentity.getBytes(), elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldPassword != null) {
|
if (oldPassword != null) {
|
||||||
berEncode(OLD_PASSWORD_OCTET_TYPE, oldPassword.getBytes(), elements);
|
berEncode(OLD_PASSWORD_OCTET_TYPE, oldPassword.getBytes(), elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPassword != null) {
|
if (newPassword != null) {
|
||||||
berEncode(NEW_PASSWORD_OCTET_TYPE, newPassword.getBytes(), elements);
|
berEncode(NEW_PASSWORD_OCTET_TYPE, newPassword.getBytes(), elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
berEncode(SEQUENCE_TYPE, elements.toByteArray(), this.value);
|
berEncode(SEQUENCE_TYPE, elements.toByteArray(), this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,9 +490,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
*/
|
*/
|
||||||
private void berEncode(byte type, byte[] src, ByteArrayOutputStream dest) {
|
private void berEncode(byte type, byte[] src, ByteArrayOutputStream dest) {
|
||||||
int length = src.length;
|
int length = src.length;
|
||||||
|
|
||||||
dest.write(type);
|
dest.write(type);
|
||||||
|
|
||||||
if (length < 128) {
|
if (length < 128) {
|
||||||
dest.write(length);
|
dest.write(length);
|
||||||
}
|
}
|
||||||
@ -560,7 +516,6 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
|||||||
dest.write((byte) ((length >> 8) & 0xFF));
|
dest.write((byte) ((length >> 8) & 0xFF));
|
||||||
dest.write((byte) (length & 0xFF));
|
dest.write((byte) (length & 0xFF));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
dest.write(src);
|
dest.write(src);
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import java.util.Collection;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.ldap.core.DirContextAdapter;
|
import org.springframework.ldap.core.DirContextAdapter;
|
||||||
import org.springframework.ldap.core.DirContextOperations;
|
import org.springframework.ldap.core.DirContextOperations;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
@ -53,56 +54,41 @@ public class LdapUserDetailsMapper implements UserDetailsContextMapper {
|
|||||||
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
|
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
|
||||||
Collection<? extends GrantedAuthority> authorities) {
|
Collection<? extends GrantedAuthority> authorities) {
|
||||||
String dn = ctx.getNameInNamespace();
|
String dn = ctx.getNameInNamespace();
|
||||||
|
this.logger.debug(LogMessage.format("Mapping user details from context with DN: %s", dn));
|
||||||
this.logger.debug("Mapping user details from context with DN: " + dn);
|
|
||||||
|
|
||||||
LdapUserDetailsImpl.Essence essence = new LdapUserDetailsImpl.Essence();
|
LdapUserDetailsImpl.Essence essence = new LdapUserDetailsImpl.Essence();
|
||||||
essence.setDn(dn);
|
essence.setDn(dn);
|
||||||
|
|
||||||
Object passwordValue = ctx.getObjectAttribute(this.passwordAttributeName);
|
Object passwordValue = ctx.getObjectAttribute(this.passwordAttributeName);
|
||||||
|
|
||||||
if (passwordValue != null) {
|
if (passwordValue != null) {
|
||||||
essence.setPassword(mapPassword(passwordValue));
|
essence.setPassword(mapPassword(passwordValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
essence.setUsername(username);
|
essence.setUsername(username);
|
||||||
|
|
||||||
// Map the roles
|
// Map the roles
|
||||||
for (int i = 0; (this.roleAttributes != null) && (i < this.roleAttributes.length); i++) {
|
for (int i = 0; (this.roleAttributes != null) && (i < this.roleAttributes.length); i++) {
|
||||||
String[] rolesForAttribute = ctx.getStringAttributes(this.roleAttributes[i]);
|
String[] rolesForAttribute = ctx.getStringAttributes(this.roleAttributes[i]);
|
||||||
|
|
||||||
if (rolesForAttribute == null) {
|
if (rolesForAttribute == null) {
|
||||||
this.logger.debug("Couldn't read role attribute '" + this.roleAttributes[i] + "' for user " + dn);
|
this.logger.debug(
|
||||||
|
LogMessage.format("Couldn't read role attribute '%s' for user $s", this.roleAttributes[i], dn));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String role : rolesForAttribute) {
|
for (String role : rolesForAttribute) {
|
||||||
GrantedAuthority authority = createAuthority(role);
|
GrantedAuthority authority = createAuthority(role);
|
||||||
|
|
||||||
if (authority != null) {
|
if (authority != null) {
|
||||||
essence.addAuthority(authority);
|
essence.addAuthority(authority);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the supplied authorities
|
// Add the supplied authorities
|
||||||
|
|
||||||
for (GrantedAuthority authority : authorities) {
|
for (GrantedAuthority authority : authorities) {
|
||||||
essence.addAuthority(authority);
|
essence.addAuthority(authority);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for PPolicy data
|
// Check for PPolicy data
|
||||||
|
|
||||||
PasswordPolicyResponseControl ppolicy = (PasswordPolicyResponseControl) ctx
|
PasswordPolicyResponseControl ppolicy = (PasswordPolicyResponseControl) ctx
|
||||||
.getObjectAttribute(PasswordPolicyControl.OID);
|
.getObjectAttribute(PasswordPolicyControl.OID);
|
||||||
|
|
||||||
if (ppolicy != null) {
|
if (ppolicy != null) {
|
||||||
essence.setTimeBeforeExpiration(ppolicy.getTimeBeforeExpiration());
|
essence.setTimeBeforeExpiration(ppolicy.getTimeBeforeExpiration());
|
||||||
essence.setGraceLoginsRemaining(ppolicy.getGraceLoginsRemaining());
|
essence.setGraceLoginsRemaining(ppolicy.getGraceLoginsRemaining());
|
||||||
}
|
}
|
||||||
|
|
||||||
return essence.createUserDetails();
|
return essence.createUserDetails();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -118,12 +104,10 @@ public class LdapUserDetailsMapper implements UserDetailsContextMapper {
|
|||||||
* @return a String representation of the password.
|
* @return a String representation of the password.
|
||||||
*/
|
*/
|
||||||
protected String mapPassword(Object passwordValue) {
|
protected String mapPassword(Object passwordValue) {
|
||||||
|
|
||||||
if (!(passwordValue instanceof String)) {
|
if (!(passwordValue instanceof String)) {
|
||||||
// Assume it's binary
|
// Assume it's binary
|
||||||
passwordValue = new String((byte[]) passwordValue);
|
passwordValue = new String((byte[]) passwordValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (String) passwordValue;
|
return (String) passwordValue;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,6 @@ public class LdapUserDetailsService implements UserDetailsService {
|
|||||||
@Override
|
@Override
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
DirContextOperations userData = this.userSearch.searchForUser(username);
|
DirContextOperations userData = this.userSearch.searchForUser(username);
|
||||||
|
|
||||||
return this.userDetailsMapper.mapUserFromContext(userData, username,
|
return this.userDetailsMapper.mapUserFromContext(userData, username,
|
||||||
this.authoritiesPopulator.getGrantedAuthorities(userData, username));
|
this.authoritiesPopulator.getGrantedAuthorities(userData, username));
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import java.util.Set;
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
import org.springframework.ldap.core.ContextSource;
|
import org.springframework.ldap.core.ContextSource;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
||||||
@ -144,19 +145,13 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
|
|||||||
super(contextSource, groupSearchBase);
|
super(contextSource, groupSearchBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) {
|
public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) {
|
||||||
if (getGroupSearchBase() == null) {
|
if (getGroupSearchBase() == null) {
|
||||||
return new HashSet<>();
|
return new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<GrantedAuthority> authorities = new HashSet<>();
|
Set<GrantedAuthority> authorities = new HashSet<>();
|
||||||
|
|
||||||
performNestedSearch(userDn, username, authorities, getMaxSearchDepth());
|
performNestedSearch(userDn, username, authorities, getMaxSearchDepth());
|
||||||
|
|
||||||
return authorities;
|
return authorities;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,34 +166,23 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
|
|||||||
private void performNestedSearch(String userDn, String username, Set<GrantedAuthority> authorities, int depth) {
|
private void performNestedSearch(String userDn, String username, Set<GrantedAuthority> authorities, int depth) {
|
||||||
if (depth == 0) {
|
if (depth == 0) {
|
||||||
// back out of recursion
|
// back out of recursion
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug(LogMessage.of(() -> "Search aborted, max depth reached," + " for roles for user '" + username
|
||||||
logger.debug("Search aborted, max depth reached," + " for roles for user '" + username + "', DN = "
|
+ "', DN = " + "'" + userDn + "', with filter " + getGroupSearchFilter() + " in search base '"
|
||||||
+ "'" + userDn + "', with filter " + getGroupSearchFilter() + " in search base '"
|
+ getGroupSearchBase() + "'"));
|
||||||
+ getGroupSearchBase() + "'");
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
logger.debug(LogMessage.of(() -> "Searching for roles for user '" + username + "', DN = " + "'" + userDn
|
||||||
if (logger.isDebugEnabled()) {
|
+ "', with filter " + getGroupSearchFilter() + " in search base '" + getGroupSearchBase() + "'"));
|
||||||
logger.debug("Searching for roles for user '" + username + "', DN = " + "'" + userDn + "', with filter "
|
|
||||||
+ getGroupSearchFilter() + " in search base '" + getGroupSearchBase() + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getAttributeNames() == null) {
|
if (getAttributeNames() == null) {
|
||||||
setAttributeNames(new HashSet<>());
|
setAttributeNames(new HashSet<>());
|
||||||
}
|
}
|
||||||
if (StringUtils.hasText(getGroupRoleAttribute()) && !getAttributeNames().contains(getGroupRoleAttribute())) {
|
if (StringUtils.hasText(getGroupRoleAttribute()) && !getAttributeNames().contains(getGroupRoleAttribute())) {
|
||||||
getAttributeNames().add(getGroupRoleAttribute());
|
getAttributeNames().add(getGroupRoleAttribute());
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
|
Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
|
||||||
getGroupSearchBase(), getGroupSearchFilter(), new String[] { userDn, username },
|
getGroupSearchBase(), getGroupSearchFilter(), new String[] { userDn, username },
|
||||||
getAttributeNames().toArray(new String[0]));
|
getAttributeNames().toArray(new String[0]));
|
||||||
|
logger.debug(LogMessage.format("Roles from search: %s", userRoles));
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Roles from search: " + userRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map<String, List<String>> record : userRoles) {
|
for (Map<String, List<String>> record : userRoles) {
|
||||||
boolean circular = false;
|
boolean circular = false;
|
||||||
String dn = record.get(SpringSecurityLdapTemplate.DN_KEY).get(0);
|
String dn = record.get(SpringSecurityLdapTemplate.DN_KEY).get(0);
|
||||||
@ -220,7 +204,6 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
|
|||||||
if (!circular) {
|
if (!circular) {
|
||||||
performNestedSearch(dn, roleName, authorities, (depth - 1));
|
performNestedSearch(dn, roleName, authorities, (depth - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,6 @@ public class Person extends LdapUserDetailsImpl {
|
|||||||
adapter.setAttributeValues("cn", getCn());
|
adapter.setAttributeValues("cn", getCn());
|
||||||
adapter.setAttributeValue("description", getDescription());
|
adapter.setAttributeValue("description", getDescription());
|
||||||
adapter.setAttributeValue("telephoneNumber", getTelephoneNumber());
|
adapter.setAttributeValue("telephoneNumber", getTelephoneNumber());
|
||||||
|
|
||||||
if (getPassword() != null) {
|
if (getPassword() != null) {
|
||||||
adapter.setAttributeValue("userPassword", getPassword());
|
adapter.setAttributeValue("userPassword", getPassword());
|
||||||
}
|
}
|
||||||
@ -95,11 +94,9 @@ public class Person extends LdapUserDetailsImpl {
|
|||||||
setSn(ctx.getStringAttribute("sn"));
|
setSn(ctx.getStringAttribute("sn"));
|
||||||
setDescription(ctx.getStringAttribute("description"));
|
setDescription(ctx.getStringAttribute("description"));
|
||||||
setTelephoneNumber(ctx.getStringAttribute("telephoneNumber"));
|
setTelephoneNumber(ctx.getStringAttribute("telephoneNumber"));
|
||||||
Object passo = ctx.getObjectAttribute("userPassword");
|
Object password = ctx.getObjectAttribute("userPassword");
|
||||||
|
if (password != null) {
|
||||||
if (passo != null) {
|
setPassword(LdapUtils.convertPasswordToString(password));
|
||||||
String password = LdapUtils.convertPasswordToString(passo);
|
|
||||||
setPassword(password);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,18 +33,14 @@ public class PersonContextMapper implements UserDetailsContextMapper {
|
|||||||
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
|
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
|
||||||
Collection<? extends GrantedAuthority> authorities) {
|
Collection<? extends GrantedAuthority> authorities) {
|
||||||
Person.Essence p = new Person.Essence(ctx);
|
Person.Essence p = new Person.Essence(ctx);
|
||||||
|
|
||||||
p.setUsername(username);
|
p.setUsername(username);
|
||||||
p.setAuthorities(authorities);
|
p.setAuthorities(authorities);
|
||||||
|
|
||||||
return p.createUserDetails();
|
return p.createUserDetails();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
|
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
|
||||||
Assert.isInstanceOf(Person.class, user, "UserDetails must be a Person instance");
|
Assert.isInstanceOf(Person.class, user, "UserDetails must be a Person instance");
|
||||||
|
|
||||||
Person p = (Person) user;
|
Person p = (Person) user;
|
||||||
p.populateContext(ctx);
|
p.populateContext(ctx);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user