Added the PasswordExpired control defined in
https://docs.ldap.com/specs/draft-vchu-ldap-pwd-policy-00.txt.
Code mostly provided by Jan Zelmer
diff --git a/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java b/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java
index a231ac8..976017d 100644
--- a/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java
+++ b/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java
@@ -355,6 +355,7 @@
ERR_08107_AD_DIR_SYNC_PARENTS_FIRST_DECODING_ERROR( "ERR_08107_AD_DIR_SYNC_PARENTS_FIRST_DECODING_ERROR" ),
ERR_08108_AD_DIR_SYNC_MAX_ATTRIBUTE_COUNT_DECODING_ERROR( "ERR_08107_AD_DIR_SYNC_MAX_ATTRIBUTE_COUNT_DECODING_ERROR" ),
ERR_08109_BAD_CONTROL_VALUE( "ERR_08109_BAD_CONTROL_VALUE" ),
+ ERR_08110_BAD_PASSWORD_EXPIRED_VALUE( "ERR_08110_BAD_PASSWORD_EXPIRED_VALUE" ),
// extended 8200-8399
ERR_08200_CANCELID_DECODING_FAILED( "ERR_08200_CANCELID_DECODING_FAILED" ),
diff --git a/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties b/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties
index 4c0c052..b72ab87 100644
--- a/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties
+++ b/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties
@@ -199,7 +199,6 @@
ERR_04171_CANNOT_PARSE_MATCHED_DN=Could not parse matchedDn while transforming Codec value to Internal: {0}
ERR_04172_KEYSTORE_INIT_FAILURE=Keystore initialisation failure
ERR_04173_ALGORITHM_NOT_FOUND=Not TrustManagerFactory found for algorithm ''{0}''
-
ERR_04174_INPUT_FILE_NAME_NULL=LdapClientTrustStoreManager constructor : input file name is null
ERR_04175_TRUST_STORE_FILE_NULL=TrustStoreFile : file not found
ERR_04176_TRUST_MANAGER_NOT_FOUND=LdapClientTrustStoreManager.getTrustManagers : file not found
@@ -346,6 +345,7 @@
ERR_08107_AD_DIR_SYNC_PARENTS_FIRST_DECODING_ERROR=Error while decoding the AdDirSync parentsFirst value: {0}
ERR_08108_AD_DIR_SYNC_MAX_ATTRIBUTE_COUNT_DECODING_ERROR=Error while decoding the AdDirSync maxAttributeCount value: {0}
ERR_08109_BAD_CONTROL_VALUE=Bad control value: {0}
+ERR_08110_BAD_PASSWORD_EXPIRED_VALUE=An error occurred during decoding the response message: found a non zero value {0} for the password expired control value. According to the LDAP reference guide, only values of zero are valid.
# api-ldap-extras-codec extended 8200-8399
ERR_08200_CANCELID_DECODING_FAILED=failed to decode the cancelId, the value should be between 0 and 2^31-1, it is ''{0}''
diff --git a/integ-osgi/src/test/java/org/apache/directory/api/osgi/ApiLdapExtrasCodecApiOsgiTest.java b/integ-osgi/src/test/java/org/apache/directory/api/osgi/ApiLdapExtrasCodecApiOsgiTest.java
index 9db48a7..aea2e9d 100644
--- a/integ-osgi/src/test/java/org/apache/directory/api/osgi/ApiLdapExtrasCodecApiOsgiTest.java
+++ b/integ-osgi/src/test/java/org/apache/directory/api/osgi/ApiLdapExtrasCodecApiOsgiTest.java
@@ -27,6 +27,7 @@
import org.apache.directory.api.ldap.extras.controls.ad.AdPolicyHintsImpl;
import org.apache.directory.api.ldap.extras.controls.changeNotifications.ChangeNotificationsImpl;
+import org.apache.directory.api.ldap.extras.controls.passwordExpired.PasswordExpiredResponseImpl;
import org.apache.directory.api.ldap.extras.controls.permissiveModify.PermissiveModifyImpl;
import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyRequestImpl;
import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyResponseImpl;
@@ -70,6 +71,7 @@
new AdPolicyHintsImpl().getOid();
new AdShowDeletedImpl().getOid();
new ChangeNotificationsImpl().getOid();
+ new PasswordExpiredResponseImpl().getTimeBeforeExpiration();
new PasswordPolicyRequestImpl().getOid();
new PasswordPolicyResponseImpl().getGraceAuthNRemaining();
new PermissiveModifyImpl().getOid();
diff --git a/integ-osgi/src/test/java/org/apache/directory/api/osgi/ApiLdapExtrasCodecOsgiTest.java b/integ-osgi/src/test/java/org/apache/directory/api/osgi/ApiLdapExtrasCodecOsgiTest.java
index 49f8438..8ad1d0e 100644
--- a/integ-osgi/src/test/java/org/apache/directory/api/osgi/ApiLdapExtrasCodecOsgiTest.java
+++ b/integ-osgi/src/test/java/org/apache/directory/api/osgi/ApiLdapExtrasCodecOsgiTest.java
@@ -26,6 +26,7 @@
import javax.inject.Inject;
import org.apache.directory.api.ldap.codec.api.LdapApiService;
+import org.apache.directory.api.ldap.extras.controls.passwordExpired.PasswordExpiredResponse;
import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyRequest;
import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyResponse;
import org.apache.directory.api.ldap.extras.extended.startTls.StartTlsRequest;
@@ -55,6 +56,11 @@
@Override
protected void useBundleClasses() throws Exception
{
+ Control peResponse = ldapApiService.getResponseControlFactories()
+ .get( PasswordExpiredResponse.OID ).newControl();
+ assertNotNull( peResponse );
+ assertTrue( peResponse instanceof PasswordExpiredResponse );
+
Control ppRequest = ldapApiService.getRequestControlFactories()
.get( PasswordPolicyRequest.OID ).newControl();
assertNotNull( ppRequest );
@@ -85,5 +91,4 @@
assertNotNull( syncInfoResponse );
assertTrue( syncInfoResponse instanceof SyncInfoValue );
}
-
}
diff --git a/integ/src/test/java/org/apache/directory/api/ldap/codec/api/StandaloneLdapCodecServiceTest.java b/integ/src/test/java/org/apache/directory/api/ldap/codec/api/StandaloneLdapCodecServiceTest.java
index 6030077..5a9a675 100644
--- a/integ/src/test/java/org/apache/directory/api/ldap/codec/api/StandaloneLdapCodecServiceTest.java
+++ b/integ/src/test/java/org/apache/directory/api/ldap/codec/api/StandaloneLdapCodecServiceTest.java
@@ -71,6 +71,7 @@
+ "org.apache.directory.api.ldap.codec.controls.search.pagedSearch.PagedResultsFactory,"
+ "org.apache.directory.api.ldap.codec.controls.sort.SortResponseFactory,"
+ "org.apache.directory.api.ldap.extras.controls.ad_impl.AdDirSyncResponseFactory,"
+ + "org.apache.directory.api.ldap.extras.controls.passwordExpired_impl.PasswordExpiredResponseFactory,"
+ "org.apache.directory.api.ldap.extras.controls.ppolicy_impl.PasswordPolicyResponseFactory,"
+ "org.apache.directory.api.ldap.extras.controls.syncrepl_impl.SyncDoneValueFactory,"
+ "org.apache.directory.api.ldap.extras.controls.syncrepl_impl.SyncStateValueFactory,"
diff --git a/ldap/extras/codec-api/src/main/java/org/apache/directory/api/ldap/extras/controls/passwordExpired/PasswordExpiredResponse.java b/ldap/extras/codec-api/src/main/java/org/apache/directory/api/ldap/extras/controls/passwordExpired/PasswordExpiredResponse.java
new file mode 100644
index 0000000..bfe145e
--- /dev/null
+++ b/ldap/extras/codec-api/src/main/java/org/apache/directory/api/ldap/extras/controls/passwordExpired/PasswordExpiredResponse.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.directory.api.ldap.extras.controls.passwordExpired;
+
+import org.apache.directory.api.ldap.model.message.Control;
+
+/**
+ * The PasswordPolicy expired response control, as defined by
+ * https://docs.ldap.com/specs/draft-vchu-ldap-pwd-policy-00.txt
+ *
+ * <pre>
+ * controlType: 2.16.840.1.113730.3.4.5,
+ *
+ * controlValue: an octet string to indicate the time in seconds until
+ * the password expires.
+ *
+ * criticality: false
+ * </pre>
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public interface PasswordExpiredResponse extends Control
+{
+ /** This control OID */
+ String OID = "2.16.840.1.113730.3.4.4";
+
+
+ /**
+ * Returns the time in seconds before the password expires. Default to 0
+ *
+ * @return The time before expiration of the password
+ */
+ int getTimeBeforeExpiration();
+}
diff --git a/ldap/extras/codec-api/src/main/java/org/apache/directory/api/ldap/extras/controls/passwordExpired/PasswordExpiredResponseImpl.java b/ldap/extras/codec-api/src/main/java/org/apache/directory/api/ldap/extras/controls/passwordExpired/PasswordExpiredResponseImpl.java
new file mode 100644
index 0000000..1a020f0
--- /dev/null
+++ b/ldap/extras/codec-api/src/main/java/org/apache/directory/api/ldap/extras/controls/passwordExpired/PasswordExpiredResponseImpl.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.directory.api.ldap.extras.controls.passwordExpired;
+
+import org.apache.directory.api.ldap.model.message.controls.AbstractControl;
+
+/**
+ * A PasswordExpiredResponse control implementation.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class PasswordExpiredResponseImpl extends AbstractControl implements PasswordExpiredResponse
+{
+ /** time before expiration of the password */
+ private int timeBeforeExpiration = -1;
+
+ /**
+ * Creates a new instance of a PasswordExpired Control without any
+ * response data associated with it.
+ */
+ public PasswordExpiredResponseImpl()
+ {
+ super( OID );
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getTimeBeforeExpiration()
+ {
+ return timeBeforeExpiration;
+ }
+
+ /**
+ * Return a String representing this PasswordExpiredControl.
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append( " Password Expired Response Control\n" );
+ sb.append( " oid : " ).append( getOid() ).append( '\n' );
+ sb.append( " critical : " ).append( isCritical() ).append( '\n' );
+
+ return sb.toString();
+ }
+}
diff --git a/ldap/extras/codec/pom.xml b/ldap/extras/codec/pom.xml
index 313cb91..84da577 100644
--- a/ldap/extras/codec/pom.xml
+++ b/ldap/extras/codec/pom.xml
@@ -114,6 +114,7 @@
<Export-Package>
org.apache.directory.api.ldap.extras.controls.ad_impl;version=${project.version};-noimport:=true,
org.apache.directory.api.ldap.extras.controls.changeNotifications_impl;version=${project.version};-noimport:=true,
+ org.apache.directory.api.ldap.extras.controls.passwordExpired_impl;version=${project.version};-noimport:=true,
org.apache.directory.api.ldap.extras.controls.permissiveModify_impl;version=${project.version};-noimport:=true,
org.apache.directory.api.ldap.extras.controls.ppolicy_impl;version=${project.version};-noimport:=true,
org.apache.directory.api.ldap.extras.controls.syncrepl_impl;version=${project.version};-noimport:=true,
@@ -144,6 +145,7 @@
org.apache.directory.api.ldap.extras.controls;version=${project.version},
org.apache.directory.api.ldap.extras.controls.ad;version=${project.version},
org.apache.directory.api.ldap.extras.controls.changeNotifications;version=${project.version},
+ org.apache.directory.api.ldap.extras.controls.passwordExpired_impl;version=${project.version},
org.apache.directory.api.ldap.extras.controls.permissiveModify;version=${project.version},
org.apache.directory.api.ldap.extras.controls.ppolicy;version=${project.version},
org.apache.directory.api.ldap.extras.controls.syncrepl.syncDone;version=${project.version},
diff --git a/ldap/extras/codec/src/main/java/org/apache/directory/api/ldap/extras/ExtrasCodecFactoryUtil.java b/ldap/extras/codec/src/main/java/org/apache/directory/api/ldap/extras/ExtrasCodecFactoryUtil.java
index 770729b..b1d6f3a 100644
--- a/ldap/extras/codec/src/main/java/org/apache/directory/api/ldap/extras/ExtrasCodecFactoryUtil.java
+++ b/ldap/extras/codec/src/main/java/org/apache/directory/api/ldap/extras/ExtrasCodecFactoryUtil.java
@@ -37,6 +37,8 @@
import org.apache.directory.api.ldap.extras.controls.ad_impl.AdShowDeletedFactory;
import org.apache.directory.api.ldap.extras.controls.changeNotifications.ChangeNotifications;
import org.apache.directory.api.ldap.extras.controls.changeNotifications_impl.ChangeNotificationsFactory;
+import org.apache.directory.api.ldap.extras.controls.passwordExpired.PasswordExpiredResponse;
+import org.apache.directory.api.ldap.extras.controls.passwordExpired_impl.PasswordExpiredResponseFactory;
import org.apache.directory.api.ldap.extras.controls.permissiveModify.PermissiveModify;
import org.apache.directory.api.ldap.extras.controls.permissiveModify_impl.PermissiveModifyFactory;
import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyRequest;
@@ -146,6 +148,15 @@
LOG.info( I18n.msg( I18n.MSG_06000_REGISTERED_CONTROL_FACTORY, changeNotificationsFactory.getOid() ) );
}
+ // PasswordExpired response
+ ControlFactory<PasswordExpiredResponse> passwordExpiredResponseFactory = new PasswordExpiredResponseFactory( apiService );
+ responseControlFactories.put( passwordExpiredResponseFactory.getOid(), passwordExpiredResponseFactory );
+
+ if ( LOG.isInfoEnabled() )
+ {
+ LOG.info( I18n.msg( I18n.MSG_06000_REGISTERED_CONTROL_FACTORY, passwordExpiredResponseFactory.getOid() ) );
+ }
+
// PasswordPolicy request
ControlFactory<PasswordPolicyRequest> passwordPolicyRequestFactory = new PasswordPolicyRequestFactory( apiService );
requestControlFactories.put( passwordPolicyRequestFactory.getOid(), passwordPolicyRequestFactory );
diff --git a/ldap/extras/codec/src/main/java/org/apache/directory/api/ldap/extras/controls/passwordExpired_impl/PasswordExpiredResponseFactory.java b/ldap/extras/codec/src/main/java/org/apache/directory/api/ldap/extras/controls/passwordExpired_impl/PasswordExpiredResponseFactory.java
new file mode 100644
index 0000000..cb4bc17
--- /dev/null
+++ b/ldap/extras/codec/src/main/java/org/apache/directory/api/ldap/extras/controls/passwordExpired_impl/PasswordExpiredResponseFactory.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.directory.api.ldap.extras.controls.passwordExpired_impl;
+
+import org.apache.directory.api.asn1.DecoderException;
+import org.apache.directory.api.asn1.util.Asn1Buffer;
+import org.apache.directory.api.i18n.I18n;
+import org.apache.directory.api.ldap.codec.api.AbstractControlFactory;
+import org.apache.directory.api.ldap.codec.api.ControlFactory;
+import org.apache.directory.api.ldap.codec.api.LdapApiService;
+import org.apache.directory.api.ldap.extras.controls.passwordExpired.PasswordExpiredResponse;
+import org.apache.directory.api.ldap.extras.controls.passwordExpired.PasswordExpiredResponseImpl;
+import org.apache.directory.api.ldap.model.message.Control;
+import org.apache.directory.api.util.Strings;
+
+/**
+ * A {@link ControlFactory} which creates {@link PasswordExpiredResponse} controls.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+public class PasswordExpiredResponseFactory extends AbstractControlFactory<PasswordExpiredResponse>
+{
+ /**
+ * Creates a new instance of PasswordExpiredResponseFactory.
+ *
+ * @param codec The LDAP codec.
+ */
+ public PasswordExpiredResponseFactory( LdapApiService codec )
+ {
+ super( codec, PasswordExpiredResponse.OID );
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public PasswordExpiredResponse newControl()
+ {
+ return new PasswordExpiredResponseImpl();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void decodeValue( Control control, byte[] controlBytes ) throws DecoderException
+ {
+ try
+ {
+ if ( !Strings.utf8ToString( controlBytes ).equals( "0" ) )
+ {
+ throw new DecoderException( I18n.err( I18n.ERR_08110_BAD_PASSWORD_EXPIRED_VALUE, Strings.dumpBytes( controlBytes ) ) );
+ }
+ }
+ catch ( RuntimeException re )
+ {
+ throw new DecoderException( re.getMessage() );
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void encodeValue( Asn1Buffer buffer, Control control )
+ {
+ // Always '0'
+ buffer.put( ( byte ) 0x30 );
+ }
+}
diff --git a/ldap/extras/codec/src/test/java/org/apache/directory/api/ldap/extras/controls/passwordExpired/PasswordExpiredResponseTest.java b/ldap/extras/codec/src/test/java/org/apache/directory/api/ldap/extras/controls/passwordExpired/PasswordExpiredResponseTest.java
new file mode 100644
index 0000000..c5366c5
--- /dev/null
+++ b/ldap/extras/codec/src/test/java/org/apache/directory/api/ldap/extras/controls/passwordExpired/PasswordExpiredResponseTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.directory.api.ldap.extras.controls.passwordExpired;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+import java.nio.ByteBuffer;
+
+import org.apache.directory.api.asn1.DecoderException;
+import org.apache.directory.api.asn1.EncoderException;
+import org.apache.directory.api.asn1.util.Asn1Buffer;
+import org.apache.directory.api.ldap.codec.api.LdapApiService;
+import org.apache.directory.api.ldap.codec.osgi.DefaultLdapCodecService;
+import org.apache.directory.api.ldap.extras.controls.passwordExpired_impl.PasswordExpiredResponseFactory;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+
+/**
+ * PasswordExpiredResponseControlTest.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+@Execution( ExecutionMode.CONCURRENT)
+public class PasswordExpiredResponseTest
+{
+ private static LdapApiService codec;
+
+ @BeforeAll
+ public static void init()
+ {
+ codec = new DefaultLdapCodecService();
+ codec.registerResponseControl( new PasswordExpiredResponseFactory( codec ) );
+ }
+
+
+
+
+ @Test
+ public void testDecodeRespWithValueZero() throws DecoderException, EncoderException
+ {
+ ByteBuffer bb = ByteBuffer.allocate( 0x1 );
+
+ bb.put( new byte[]
+ {
+ 0x30
+ } );
+
+ bb.flip();
+
+ PasswordExpiredResponseFactory factory = ( PasswordExpiredResponseFactory ) codec.getResponseControlFactories().
+ get( PasswordExpiredResponse.OID );
+ PasswordExpiredResponse passwordExpiredResponse = factory.newControl();
+ factory.decodeValue( passwordExpiredResponse, bb.array() );
+
+
+ // Check the reverse encoding
+ Asn1Buffer asn1Buffer = new Asn1Buffer();
+
+ factory.encodeValue( asn1Buffer, passwordExpiredResponse );
+
+ assertArrayEquals( bb.array(), asn1Buffer.getBytes().array() );
+ }
+}