added package for password-policy specific classes and an ldaptemplate compatible context factory.

This commit is contained in:
Luke Taylor 2006-08-25 16:12:26 +00:00
parent 57a8d2adb3
commit 54fb402e60
9 changed files with 605 additions and 0 deletions

View File

@ -0,0 +1,30 @@
package org.acegisecurity.ldap;
import net.sf.ldaptemplate.ContextSource;
import javax.naming.directory.DirContext;
import org.springframework.dao.DataAccessException;
/**
* A version of InitialDirContextFactory that implements the ldaptemplate ContextSource interface.
*
* DefaultInitialDirContextFactory should be modified to implement this when it is possible to
* introduce a dependency on ldaptemplate in the main code.
*
* @author Luke
* @version $Id$
*/
public class ContextSourceInitialDirContextFactory extends DefaultInitialDirContextFactory implements ContextSource {
public ContextSourceInitialDirContextFactory(String providerUrl) {
super(providerUrl);
}
public DirContext getReadOnlyContext() throws DataAccessException {
return newInitialDirContext();
}
public DirContext getReadWriteContext() throws DataAccessException {
return newInitialDirContext();
}
}

View File

@ -0,0 +1,15 @@
package org.acegisecurity.ldap.ppolicy;
/**
* @author Luke
* @version $Id$
*/
public class AccountLockedException extends PasswordPolicyException {
public AccountLockedException(String msg) {
super(msg);
}
public AccountLockedException(String msg, Throwable t) {
super(msg, t);
}
}

View File

@ -0,0 +1,11 @@
package org.acegisecurity.ldap.ppolicy;
/**
* @author Luke
* @version $Id$
*/
public class OracleIDPasswordPolicyControl extends PasswordPolicyControl {
public String getID() {
return "2.16.840.1.113894.1.8.6";
}
}

View File

@ -0,0 +1,15 @@
package org.acegisecurity.ldap.ppolicy;
/**
* @author Luke
* @version $Id$
*/
public class PasswordExpiredException extends PasswordPolicyException {
public PasswordExpiredException(String msg) {
super(msg);
}
public PasswordExpiredException(String msg, Throwable t) {
super(msg, t);
}
}

View File

@ -0,0 +1,16 @@
package org.acegisecurity.ldap.ppolicy;
/**
* @author Luke
* @version $Id$
*/
public class PasswordInHistoryException extends PasswordPolicyException {
public PasswordInHistoryException(String msg) {
super(msg);
}
public PasswordInHistoryException(String msg, Throwable t) {
super(msg, t);
}
}

View File

@ -0,0 +1,91 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.acegisecurity.ldap.ppolicy;
import javax.naming.ldap.Control;
/**
* A Password Policy request control.<p>Based on the information in the corresponding internet draft on LDAP
* password policy.</p>
*
* @author Stefan Zoerner
* @author Luke Taylor
* @version $Id: PasswordPolicyControl.java 1496 2006-05-23 13:38:33Z benalex $
*
* @see PasswordPolicyResponseControl
* @see <a href="http://www.ietf.org/internet-drafts/draft-behera-ldap-password-policy-09.txt">Password Policy for LDAP
* Directories</a>
*/
public class PasswordPolicyControl implements Control {
//~ Static fields/initializers =====================================================================================
/** OID of the Password Policy Control */
public static final String OID = "1.3.6.1.4.1.42.2.27.8.5.1";
//~ Instance fields ================================================================================================
private boolean critical;
//~ Constructors ===================================================================================================
/**
* Creates a non-critical (request) control.
*/
public PasswordPolicyControl() {
this(Control.NONCRITICAL);
}
/**
* Creates a (request) control.
*
* @param critical indicates whether the control is
* critical for the client
*/
public PasswordPolicyControl(boolean critical) {
this.critical = critical;
}
//~ Methods ========================================================================================================
/**
* Retrieves the ASN.1 BER encoded value of the LDAP control. The request value for this control is always
* empty.
*
* @return always null
*/
public byte[] getEncodedValue() {
return null;
}
/**
* Returns the OID of the Password Policy Control.
*
* @return DOCUMENT ME!
*/
public String getID() {
return OID;
}
/**
* Returns whether the control is critical for the client.
*
* @return DOCUMENT ME!
*/
public boolean isCritical() {
return critical;
}
}

View File

@ -0,0 +1,49 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.acegisecurity.ldap.ppolicy;
import org.acegisecurity.ldap.ppolicy.PasswordPolicyControl;
import javax.naming.ldap.Control;
import javax.naming.ldap.ControlFactory;
/**
* Transforms a control object to a PasswordPolicyResponseControl object, if appropriate.
*
* @author Stefan Zoerner
* @author Luke Taylor
* @version $Id: PasswordPolicyControlFactory.java 1496 2006-05-23 13:38:33Z benalex $
*/
public class PasswordPolicyControlFactory extends ControlFactory {
//~ Methods ========================================================================================================
/**
* Creates an instance of PasswordPolicyResponseControl if the passed control is a response control of this
* type. Attributes of the result are filled with the correct values (e.g. error code).
*
* @param ctl the control the check
*
* @return a response control of type PasswordPolicyResponseControl, or null
*/
public Control getControlInstance(Control ctl) {
if (ctl.getID().equals(PasswordPolicyControl.OID)) {
return new PasswordPolicyResponseControl(ctl.getEncodedValue());
}
return null;
}
}

View File

@ -0,0 +1,17 @@
package org.acegisecurity.ldap.ppolicy;
import org.acegisecurity.AuthenticationException;
/**
* @author Luke
* @version $Id$
*/
public class PasswordPolicyException extends AuthenticationException {
public PasswordPolicyException(String msg) {
super(msg);
}
public PasswordPolicyException(String msg, Throwable t) {
super(msg, t);
}
}

View File

@ -0,0 +1,361 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.acegisecurity.ldap.ppolicy;
import netscape.ldap.ber.stream.BERChoice;
import netscape.ldap.ber.stream.BERElement;
import netscape.ldap.ber.stream.BEREnumerated;
import netscape.ldap.ber.stream.BERInteger;
//import com.novell.ldap.asn1.LBERDecoder;
//import com.novell.ldap.asn1.ASN1Sequence;
//import com.novell.ldap.asn1.ASN1Tagged;
//import com.novell.ldap.asn1.ASN1OctetString;
import netscape.ldap.ber.stream.BERSequence;
import netscape.ldap.ber.stream.BERTag;
import netscape.ldap.ber.stream.BERTagDecoder;
import org.acegisecurity.ldap.LdapDataAccessException;
import org.acegisecurity.ldap.ppolicy.PasswordPolicyControl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Represent the response control received when a <tt>PasswordPolicyControl</tt> is used when binding to a
* directory. Currently tested with the OpenLDAP 2.3.19 implementation of the LDAP Password Policy Draft. It extends
* the request control with the control specific data. This is accomplished by the properties timeBeforeExpiration,
* graceLoginsRemaining and errorCodes. getEncodedValue returns the unchanged value of the response control as a byte
* array.
*
* @author Stefan Zoerner
* @author Luke Taylor
* @version $Id: PasswordPolicyResponseControl.java 1496 2006-05-23 13:38:33Z benalex $
*
* @see org.acegisecurity.ldap.ppolicy.PasswordPolicyControl
* @see <a href="http://www.ibm.com/developerworks/tivoli/library/t-ldap-controls/">Stefan Zoerner's IBM developerworks
* article on LDAP controls.</a>
*/
public class PasswordPolicyResponseControl extends PasswordPolicyControl {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(PasswordPolicyResponseControl.class);
public static final int ERROR_NONE = -1;
public static final int ERROR_PASSWORD_EXPIRED = 0;
public static final int ERROR_ACCOUNT_LOCKED = 1;
public static final int WARNINGS_DEFAULT = -1;
private static final String[] errorText = {
"password expired", "account locked", "change after reset", "password mod not allowed",
"must supply old password", "invalid password syntax", "password too short", "password too young",
"password in history"
};
//~ Instance fields ================================================================================================
private byte[] encodedValue;
private int errorCode = ERROR_NONE;
private int graceLoginsRemaining = WARNINGS_DEFAULT;
private int timeBeforeExpiration = WARNINGS_DEFAULT;
//~ Constructors ===================================================================================================
public PasswordPolicyResponseControl(byte[] encodedValue) {
this.encodedValue = encodedValue;
//PPolicyDecoder decoder = new JLdapDecoder();
PPolicyDecoder decoder = new NetscapeDecoder();
try {
decoder.decode();
} catch (IOException e) {
throw new LdapDataAccessException("Failed to parse control value", e);
}
}
//~ Methods ========================================================================================================
/**
* Returns the unchanged value of the response control. Returns the unchanged value of the response
* control as byte array.
*
* @return DOCUMENT ME!
*/
public byte[] getEncodedValue() {
return encodedValue;
}
/**
* Returns the error code, or ERROR_NONE, if no error is present.
*
* @return the error code (0-8), or ERROR_NONE
*/
public int getErrorCode() {
return errorCode;
}
/**
* Decodes the Ber encoded control data. The ASN.1 value of the control data is:<pre>
* PasswordPolicyResponseValue ::= SEQUENCE { warning [0] CHOICE {
* timeBeforeExpiration [0] INTEGER (0 .. maxInt),
* graceAuthNsRemaining [1] INTEGER (0 .. maxInt) } OPTIONAL, error [1] ENUMERATED {
* passwordExpired (0), accountLocked (1),
* changeAfterReset (2), passwordModNotAllowed (3),
* mustSupplyOldPassword (4), insufficientPasswordQuality (5),
* passwordTooShort (6), passwordTooYoung (7),
* passwordInHistory (8) } OPTIONAL }</pre>
*
* @return DOCUMENT ME!
*/
/**
* Returns the graceLoginsRemaining.
*
* @return Returns the graceLoginsRemaining.
*/
public int getGraceLoginsRemaining() {
return graceLoginsRemaining;
}
/**
* Returns the timeBeforeExpiration.
*
* @return Returns the time before expiration in seconds
*/
public int getTimeBeforeExpiration() {
return timeBeforeExpiration;
}
/**
* Checks whether an error is present.
*
* @return true, if an error is present
*/
public boolean hasError() {
return this.getErrorCode() != ERROR_NONE;
}
/**
* Checks whether a warning is present.
*
* @return true, if a warning is present
*/
public boolean hasWarning() {
return (graceLoginsRemaining != WARNINGS_DEFAULT) || (timeBeforeExpiration != WARNINGS_DEFAULT);
}
public boolean isExpired() {
return errorCode == ERROR_PASSWORD_EXPIRED;
}
/**
* Determines whether an account locked error has been returned.
*
* @return true if the account is locked.
*/
public boolean isLocked() {
return errorCode == ERROR_ACCOUNT_LOCKED;
}
/**
* Create a textual representation containing error and warning messages, if any are present.
*
* @return error and warning messages
*/
public String toString() {
StringBuffer sb = new StringBuffer("PasswordPolicyResponseControl");
if (hasError()) {
sb.append(", error: ").append(errorText[errorCode]);
}
if (graceLoginsRemaining != WARNINGS_DEFAULT) {
sb.append(", warning: ").append(graceLoginsRemaining).append(" grace logins remain");
}
if (timeBeforeExpiration != WARNINGS_DEFAULT) {
sb.append(", warning: time before expiration is ").append(timeBeforeExpiration);
}
if (!hasError() && !hasWarning()) {
sb.append(" (no error, no warning)");
}
return sb.toString();
}
//~ Inner Interfaces ===============================================================================================
private interface PPolicyDecoder {
void decode() throws IOException;
}
//~ Inner Classes ==================================================================================================
/**
* Decoder based on Netscape ldapsdk library
*/
private class NetscapeDecoder implements PPolicyDecoder {
public void decode() throws IOException {
int[] bread = {0};
BERSequence seq = (BERSequence) BERElement.getElement(new SpecificTagDecoder(),
new ByteArrayInputStream(encodedValue), bread);
int size = seq.size();
if (logger.isDebugEnabled()) {
logger.debug("PasswordPolicyResponse, ASN.1 sequence has " + size + " elements");
}
for (int i = 0; i < seq.size(); i++) {
BERTag elt = (BERTag) seq.elementAt(i);
int tag = elt.getTag() & 0x1F;
if (tag == 0) {
BERChoice warning = (BERChoice) elt.getValue();
BERTag content = (BERTag) warning.getValue();
int value = ((BERInteger) content.getValue()).getValue();
if ((content.getTag() & 0x1F) == 0) {
timeBeforeExpiration = value;
} else {
graceLoginsRemaining = value;
}
} else if (tag == 1) {
BEREnumerated error = (BEREnumerated) elt.getValue();
errorCode = error.getValue();
}
}
}
class SpecificTagDecoder extends BERTagDecoder {
/** Allows us to remember which of the two options we're decoding */
private Boolean inChoice = null;
public BERElement getElement(BERTagDecoder decoder, int tag, InputStream stream, int[] bytesRead,
boolean[] implicit) throws IOException {
tag &= 0x1F;
implicit[0] = false;
if (tag == 0) {
// Either the choice or the time before expiry within it
if (inChoice == null) {
setInChoice(true);
// Read the choice length from the stream (ignored)
BERElement.readLengthOctets(stream, bytesRead);
int[] componentLength = new int[1];
BERElement choice = new BERChoice(decoder, stream, componentLength);
bytesRead[0] += componentLength[0];
// inChoice = null;
return choice;
} else {
// Must be time before expiry
return new BERInteger(stream, bytesRead);
}
} else if (tag == 1) {
// Either the graceLogins or the error enumeration.
if (inChoice == null) {
// The enumeration
setInChoice(false);
return new BEREnumerated(stream, bytesRead);
} else {
if (inChoice.booleanValue()) {
// graceLogins
return new BERInteger(stream, bytesRead);
}
}
}
throw new LdapDataAccessException("Unexpected tag " + tag);
}
private void setInChoice(boolean inChoice) {
this.inChoice = new Boolean(inChoice);
}
}
}
/** 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 e) {
// throw new LdapDataAccessException("Failed to parse number ", e);
// }
//
// if(contentTag == 0) {
// timeBeforeExpiration = number;
// } else if (contentTag == 1) {
// graceLoginsRemaining = number;
// }
// }
// }
}