From 110395c2a024a5481bc51c0f0d8cb8ff65be5710 Mon Sep 17 00:00:00 2001
From: Mark Craig <mark.craig@forgerock.com>
Date: Wed, 04 Sep 2013 15:53:52 +0000
Subject: [PATCH] CR-2258 Fix for OPENDJ-1113: Provide an example for resetting passwords in AD

---
 opendj3/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm                                          |    5 +
 opendj3/src/main/docbkx/dev-guide/chap-writing.xml                                                   |   21 +++++
 opendj3/src/main/docbkx/dev-guide/chap-extended-ops.xml                                              |    3 
 opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java |  180 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 209 insertions(+), 0 deletions(-)

diff --git a/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java b/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java
new file mode 100644
index 0000000..6b5b353
--- /dev/null
+++ b/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/PasswordResetForAD.java
@@ -0,0 +1,180 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *      Copyright 2013 ForgeRock AS
+ *
+ */
+
+package org.forgerock.opendj.examples;
+
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.LDAPConnectionFactory;
+import org.forgerock.opendj.ldap.LDAPOptions;
+import org.forgerock.opendj.ldap.ModificationType;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.SSLContextBuilder;
+import org.forgerock.opendj.ldap.TrustManagers;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.requests.Requests;
+
+import javax.net.ssl.SSLContext;
+import java.nio.charset.Charset;
+import java.security.GeneralSecurityException;
+
+/**
+ * This command-line client demonstrates how to reset a user password in
+ * Microsoft Active Directory.
+ * <p>
+ * The client takes as arguments the host and port of the Active Directory
+ * server, a flag indicating whether this is a self-reset (user changing own
+ * password) or an administrative reset (administrator changing a password),
+ * the DN and password of the user performing the reset, and target user DN
+ * and new user password.
+ */
+public final class PasswordResetForAD {
+
+    /**
+     * Reset a user password in Microsoft Active Directory.
+     * <p>
+     * The connection should be LDAPS, not LDAP, in order to perform the
+     * modification.
+     *
+     * @param args The command line arguments: host, port, "admin"|"self",
+     *             DN, password, targetDN, newPassword
+     */
+    public static void main(final String[] args) {
+        // --- JCite main ---
+        if (args.length != 7) {
+            System.err.println("Usage: host port \"admin\"|\"self\" DN "
+                    + "password targetDN newPassword");
+            System.err.println("For example: ad.example.com 636 admin "
+                    + "cn=administrator,cn=Users,DC=ad,DC=example,DC=com "
+                    + "Secret123 cn=testuser,cn=Users,DC=ad,DC=example,DC=com "
+                    + "NewP4s5w0rd");
+            System.exit(1);
+        }
+        final String host = args[0];
+        final int port = Integer.parseInt(args[1]);
+        final String mode = args[2];
+        final String bindDN = args[3];
+        final String bindPassword = args[4];
+        final String targetDN = args[5];
+        final String newPassword = args[6];
+
+        Connection connection = null;
+        try {
+            final LDAPConnectionFactory factory =
+                    new LDAPConnectionFactory(host, port, getTrustAllOptions());
+            connection = factory.getConnection();
+            connection.bind(bindDN, bindPassword.toCharArray());
+
+            ModifyRequest request =
+                    Requests.newModifyRequest(DN.valueOf(targetDN));
+            String passwordAttribute = "unicodePwd";
+
+            if (mode.equalsIgnoreCase("admin")) {
+                // Request modify, replacing the password with the new.
+
+                request.addModification(
+                        ModificationType.REPLACE,
+                        passwordAttribute,
+                        encodePassword(newPassword)
+                );
+            } else if (mode.equalsIgnoreCase("self")) {
+                // Request modify, deleting the old password, adding the new.
+
+                // The default password policy for Active Directory domain
+                // controller systems sets minimum password age to 1 (day).
+                // If you get a constraint violation error when trying this
+                // example, set this minimum password age to 0 by executing
+                // cmd.exe as Administrator and entering the following
+                // command at the prompt:
+                //
+                // net accounts /MINPWAGE:0
+
+                request.addModification(
+                        ModificationType.DELETE,
+                        passwordAttribute,
+                        encodePassword(bindPassword)
+                );
+                request.addModification(
+                        ModificationType.ADD,
+                        passwordAttribute,
+                        encodePassword(newPassword)
+                );
+            } else {
+                System.err.println("Mode must be admin or self, not " + mode);
+                System.exit(1);
+            }
+
+            connection.modify(request);
+
+            System.out.println("Successfully changed password for "
+                    + targetDN + " to " + newPassword + ".");
+        } catch (final ErrorResultException e) {
+            System.err.println(e.getMessage());
+            System.exit(e.getResult().getResultCode().intValue());
+        } catch (final GeneralSecurityException e) {
+            System.err.println(e.getMessage());
+            System.exit(ResultCode.CLIENT_SIDE_CONNECT_ERROR.intValue());
+        } finally {
+            if (connection != null) {
+                connection.close();
+            }
+        }
+        // --- JCite main ---
+    }
+
+    // --- JCite encodePassword ---
+    /**
+     * Encode new password in UTF-16LE format for use with Active Directory.
+     *
+     * @param password String representation of the password
+     * @return Byte array containing encoded password
+     */
+    public static byte[] encodePassword(final String password) {
+        return ("\"" + password + "\"").getBytes(Charset.forName("UTF-16LE"));
+    }
+    // --- JCite encodePassword ---
+
+    /**
+     * For SSL the connection factory needs SSL context options. This
+     * implementation simply trusts all server certificates.
+     */
+    private static LDAPOptions getTrustAllOptions() throws GeneralSecurityException {
+        LDAPOptions lo = new LDAPOptions();
+        SSLContext sslContext =
+                new SSLContextBuilder().setTrustManager(TrustManagers.trustAll())
+                        .getSSLContext();
+        lo.setSSLContext(sslContext);
+        return lo;
+    }
+
+    /**
+     * Constructor not used.
+     */
+    private PasswordResetForAD() {
+        // Not used.
+    }
+}
diff --git a/opendj3/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm b/opendj3/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm
index eee539b..090ad7c 100644
--- a/opendj3/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm
+++ b/opendj3/opendj-ldap-sdk-examples/src/site/xdoc/index.xml.vm
@@ -106,6 +106,11 @@
           <a href="xref/org/forgerock/opendj/examples/GetADChangeNotifications.html">Use <code>GenericControl</code></a>
           - illustrates how to use <code>GenericControl</code> to get change notifications from Active Directory
         </li>
+        <li>
+          <a href="xref/org/forgerock/opendj/examples/PasswordResetForAD.html">Reset AD user password</a>
+          - illustrates how to reset a user password in Active Directory as Administrator,
+            or change the password as the user
+        </li>
       </ul>
     </section>
     <section name="Get the OpenDJ LDAP SDK Examples">
diff --git a/opendj3/src/main/docbkx/dev-guide/chap-extended-ops.xml b/opendj3/src/main/docbkx/dev-guide/chap-extended-ops.xml
index 1d00f76..629dc8c 100644
--- a/opendj3/src/main/docbkx/dev-guide/chap-extended-ops.xml
+++ b/opendj3/src/main/docbkx/dev-guide/chap-extended-ops.xml
@@ -171,6 +171,9 @@
    <primary>Modifications</primary>
    <secondary>Password modify</secondary>
   </indexterm>
+  <indexterm>
+   <primary>Passwords</primary>
+  </indexterm>
 
   <para>RFC 3062, <link xlink:href="http://tools.ietf.org/html/rfc3062"
   xlink:show="new"><citetitle>LDAP Password Modify Extended
diff --git a/opendj3/src/main/docbkx/dev-guide/chap-writing.xml b/opendj3/src/main/docbkx/dev-guide/chap-writing.xml
index 47ba251..edc8154 100644
--- a/opendj3/src/main/docbkx/dev-guide/chap-writing.xml
+++ b/opendj3/src/main/docbkx/dev-guide/chap-writing.xml
@@ -164,6 +164,27 @@
   entry, but instead by specifying individual changes. See a demonstration
   of this technique in <xref linkend="updating-static-groups" />.</para>
 
+  <indexterm>
+   <primary>Passwords</primary>
+  </indexterm>
+
+  <para>You can also construct a <literal>ModifyRequest</literal> for example
+  to change a user password in Active Directory, as demonstrated in the
+  following excerpt. When working with OpenDJ directory server, consider using
+  the LDAP Password Modify extended operation instead as shown in the section,
+  <link xlink:show="new" xlink:href="dev-guide#use-password-modify-extended-operation"
+  xlink:role="http://docbook.org/xlink/role/olink"><citetitle>Password Modify
+  Extended Operation</citetitle></link>.</para>
+
+  <programlisting language="java"
+  >[jcp:org.forgerock.opendj.examples.PasswordResetForAD:--- JCite main ---]</programlisting>
+
+  <para>To make the modification, the example connects to Active Directory over
+  LDAPS, and provides the password value in UTF-16LE format.</para>
+
+  <programlisting language="java"
+  >[jcp:org.forgerock.opendj.examples.PasswordResetForAD:--- JCite encodePassword ---]</programlisting>
+
   <para>If the modifications are easier to construct in LDIF, you can write the
   LDIF to the directory server as shown in the chapter, <link xlink:show="new"
   xlink:href="dev-guide#chap-ldif" xlink:role="http://docbook.org/xlink/role/olink"

--
Gitblit v1.10.0