diff --git a/sandbox/src/main/java/org/acegisecurity/ldap/ContextSourceInitialDirContextFactory.java b/sandbox/src/main/java/org/acegisecurity/ldap/ContextSourceInitialDirContextFactory.java new file mode 100644 index 0000000000..190f97f30a --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/ldap/ContextSourceInitialDirContextFactory.java @@ -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(); + } +} diff --git a/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/AccountLockedException.java b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/AccountLockedException.java new file mode 100644 index 0000000000..6aa12f351f --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/AccountLockedException.java @@ -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); + } +} diff --git a/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/OracleIDPasswordPolicyControl.java b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/OracleIDPasswordPolicyControl.java new file mode 100644 index 0000000000..9789994f4e --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/OracleIDPasswordPolicyControl.java @@ -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"; + } +} diff --git a/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordExpiredException.java b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordExpiredException.java new file mode 100644 index 0000000000..55c68bb590 --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordExpiredException.java @@ -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); + } +} diff --git a/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordInHistoryException.java b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordInHistoryException.java new file mode 100644 index 0000000000..e00cf51f53 --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordInHistoryException.java @@ -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); + } +} diff --git a/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordPolicyControl.java b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordPolicyControl.java new file mode 100644 index 0000000000..4eccd6cc79 --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordPolicyControl.java @@ -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.
Based on the information in the corresponding internet draft on LDAP + * password policy.
+ * + * @author Stefan Zoerner + * @author Luke Taylor + * @version $Id: PasswordPolicyControl.java 1496 2006-05-23 13:38:33Z benalex $ + * + * @see PasswordPolicyResponseControl + * @see Password Policy for LDAP + * Directories + */ +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; + } +} diff --git a/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordPolicyControlFactory.java b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordPolicyControlFactory.java new file mode 100644 index 0000000000..c3a38553b9 --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordPolicyControlFactory.java @@ -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; + } +} diff --git a/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordPolicyException.java b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordPolicyException.java new file mode 100644 index 0000000000..e80e74be4d --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordPolicyException.java @@ -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); + } +} diff --git a/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordPolicyResponseControl.java b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordPolicyResponseControl.java new file mode 100644 index 0000000000..1fe019fcd8 --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/ldap/ppolicy/PasswordPolicyResponseControl.java @@ -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 PasswordPolicyControl 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 Stefan Zoerner's IBM developerworks + * article on LDAP controls. + */ +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:
+ * 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 }
+ *
+ * @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;
+// }
+// }
+// }
+}