/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.ldap.ppolicy;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.ldap.Control;
import javax.naming.ldap.LdapContext;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
/**
* Extended version of the <tt>DefaultSpringSecurityContextSource</tt> which adds support
* for the use of {@link PasswordPolicyControl} to make use of user account data stored in
* the directory.
* <p>
* When binding with specific username (not the <tt>userDn</tt>) property it will connect
* first as the userDn, then reconnect as the user in order to retrieve any
* password-policy control sent with the response, even if an exception occurs.
*
* @author Luke Taylor
* @since 3.0
*/
public class PasswordPolicyAwareContextSource extends DefaultSpringSecurityContextSource {
public PasswordPolicyAwareContextSource(String providerUrl) {
super(providerUrl);
}
@Override
public DirContext getContext(String principal, String credentials)
throws PasswordPolicyException {
if (principal.equals(userDn)) {
return super.getContext(principal, credentials);
}
final boolean debug = logger.isDebugEnabled();
if (debug) {
logger.debug("Binding as '" + userDn + "', prior to reconnect as user '"
+ principal + "'");
}
// First bind as manager user before rebinding as the specific principal.
LdapContext ctx = (LdapContext) super.getContext(userDn, password);
Control[] rctls = { new PasswordPolicyControl(false) };
try {
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, principal);
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
ctx.reconnect(rctls);
}
catch (javax.naming.NamingException ne) {
PasswordPolicyResponseControl ctrl = PasswordPolicyControlExtractor
.extractControl(ctx);
if (debug) {
logger.debug("Failed to obtain context", ne);
logger.debug("Password policy response: " + ctrl);
}
LdapUtils.closeContext(ctx);
if (ctrl != null) {
if (ctrl.isLocked()) {
throw new PasswordPolicyException(ctrl.getErrorStatus());
}
}
throw LdapUtils.convertLdapException(ne);
}
if (debug) {
logger.debug("PPolicy control returned: "
+ PasswordPolicyControlExtractor.extractControl(ctx));
}
return ctx;
}
@Override
@SuppressWarnings("unchecked")
protected Hashtable getAuthenticatedEnv(String principal, String credentials) {
Hashtable env = super.getAuthenticatedEnv(principal, credentials);
env.put(LdapContext.CONTROL_FACTORIES,
PasswordPolicyControlFactory.class.getName());
return env;
}
}