001    /*
002     * Copyright 2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2015 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk.unboundidds;
022    
023    
024    
025    import java.io.OutputStream;
026    import java.io.Serializable;
027    import java.util.ArrayList;
028    import java.util.LinkedHashMap;
029    import java.util.List;
030    
031    import com.unboundid.ldap.sdk.LDAPConnection;
032    import com.unboundid.ldap.sdk.LDAPException;
033    import com.unboundid.ldap.sdk.ResultCode;
034    import com.unboundid.ldap.sdk.Version;
035    import com.unboundid.ldap.sdk.unboundidds.extensions.
036                DeliverPasswordResetTokenExtendedRequest;
037    import com.unboundid.ldap.sdk.unboundidds.extensions.
038                DeliverPasswordResetTokenExtendedResult;
039    import com.unboundid.util.Debug;
040    import com.unboundid.util.LDAPCommandLineTool;
041    import com.unboundid.util.ObjectPair;
042    import com.unboundid.util.StaticUtils;
043    import com.unboundid.util.ThreadSafety;
044    import com.unboundid.util.ThreadSafetyLevel;
045    import com.unboundid.util.args.ArgumentException;
046    import com.unboundid.util.args.ArgumentParser;
047    import com.unboundid.util.args.DNArgument;
048    import com.unboundid.util.args.StringArgument;
049    
050    import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
051    
052    
053    
054    /**
055     * <BLOCKQUOTE>
056     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
057     *   LDAP SDK for Java.  It is not available for use in applications that
058     *   include only the Standard Edition of the LDAP SDK, and is not supported for
059     *   use in conjunction with non-UnboundID products.
060     * </BLOCKQUOTE>
061     * This class provides a utility that may be used to request that the Directory
062     * Server deliver a single-use password reset token to a user through some
063     * out-of-band mechanism.
064     */
065    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
066    public final class DeliverPasswordResetToken
067           extends LDAPCommandLineTool
068           implements Serializable
069    {
070      /**
071       * The serial version UID for this serializable class.
072       */
073      private static final long serialVersionUID = 5793619963770997266L;
074    
075    
076    
077      // The DN of the user to whom the password reset token should be sent.
078      private DNArgument userDN;
079    
080      // The text to include after the password reset token in the "compact"
081      // message.
082      private StringArgument compactTextAfterToken;
083    
084      // The text to include before the password reset token in the "compact"
085      // message.
086      private StringArgument compactTextBeforeToken;
087    
088      // The name of the mechanism through which the one-time password should be
089      // delivered.
090      private StringArgument deliveryMechanism;
091    
092      // The text to include after the password reset token in the "full" message.
093      private StringArgument fullTextAfterToken;
094    
095      // The text to include before the password reset token in the "full" message.
096      private StringArgument fullTextBeforeToken;
097    
098      // The subject to use for the message containing the delivered token.
099      private StringArgument messageSubject;
100    
101    
102    
103      /**
104       * Parse the provided command line arguments and perform the appropriate
105       * processing.
106       *
107       * @param  args  The command line arguments provided to this program.
108       */
109      public static void main(final String... args)
110      {
111        final ResultCode resultCode = main(args, System.out, System.err);
112        if (resultCode != ResultCode.SUCCESS)
113        {
114          System.exit(resultCode.intValue());
115        }
116      }
117    
118    
119    
120      /**
121       * Parse the provided command line arguments and perform the appropriate
122       * processing.
123       *
124       * @param  args       The command line arguments provided to this program.
125       * @param  outStream  The output stream to which standard out should be
126       *                    written.  It may be {@code null} if output should be
127       *                    suppressed.
128       * @param  errStream  The output stream to which standard error should be
129       *                    written.  It may be {@code null} if error messages
130       *                    should be suppressed.
131       *
132       * @return  A result code indicating whether the processing was successful.
133       */
134      public static ResultCode main(final String[] args,
135                                    final OutputStream outStream,
136                                    final OutputStream errStream)
137      {
138        final DeliverPasswordResetToken tool =
139             new DeliverPasswordResetToken(outStream, errStream);
140        return tool.runTool(args);
141      }
142    
143    
144    
145      /**
146       * Creates a new instance of this tool.
147       *
148       * @param  outStream  The output stream to which standard out should be
149       *                    written.  It may be {@code null} if output should be
150       *                    suppressed.
151       * @param  errStream  The output stream to which standard error should be
152       *                    written.  It may be {@code null} if error messages
153       *                    should be suppressed.
154       */
155      public DeliverPasswordResetToken(final OutputStream outStream,
156                                       final OutputStream errStream)
157      {
158        super(outStream, errStream);
159    
160        userDN                 = null;
161        compactTextAfterToken  = null;
162        compactTextBeforeToken = null;
163        deliveryMechanism      = null;
164        fullTextAfterToken     = null;
165        fullTextBeforeToken    = null;
166        messageSubject         = null;
167      }
168    
169    
170    
171      /**
172       * {@inheritDoc}
173       */
174      @Override()
175      public String getToolName()
176      {
177        return "deliver-password-reset-token";
178      }
179    
180    
181    
182      /**
183       * {@inheritDoc}
184       */
185      @Override()
186      public String getToolDescription()
187      {
188        return INFO_DELIVER_PW_RESET_TOKEN_TOOL_DESCRIPTION.get();
189      }
190    
191    
192    
193      /**
194       * {@inheritDoc}
195       */
196      @Override()
197      public String getToolVersion()
198      {
199        return Version.NUMERIC_VERSION_STRING;
200      }
201    
202    
203    
204      /**
205       * {@inheritDoc}
206       */
207      @Override()
208      public void addNonLDAPArguments(final ArgumentParser parser)
209             throws ArgumentException
210      {
211        userDN = new DNArgument('b', "userDN", true, 1,
212             INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_DN.get(),
213             INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_USER_DN.get());
214        parser.addArgument(userDN);
215    
216        deliveryMechanism = new StringArgument('m', "deliveryMechanism", false, 0,
217             INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_NAME.get(),
218             INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_MECH.get());
219        parser.addArgument(deliveryMechanism);
220    
221        messageSubject = new StringArgument('s', "messageSubject", false, 1,
222             INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_SUBJECT.get(),
223             INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_SUBJECT.get());
224        parser.addArgument(messageSubject);
225    
226        fullTextBeforeToken = new StringArgument('f', "fullTextBeforeToken", false,
227             1, INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_FULL_BEFORE.get(),
228             INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_FULL_BEFORE.get());
229        parser.addArgument(fullTextBeforeToken);
230    
231        fullTextAfterToken = new StringArgument('F', "fullTextAfterToken", false,
232             1, INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_FULL_AFTER.get(),
233             INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_FULL_AFTER.get());
234        parser.addArgument(fullTextAfterToken);
235    
236        compactTextBeforeToken = new StringArgument('c', "compactTextBeforeToken",
237             false, 1, INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_COMPACT_BEFORE.get(),
238             INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_COMPACT_BEFORE.get());
239        parser.addArgument(compactTextBeforeToken);
240    
241        compactTextAfterToken = new StringArgument('C', "compactTextAfterToken",
242             false, 1, INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_COMPACT_AFTER.get(),
243             INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_COMPACT_AFTER.get());
244        parser.addArgument(compactTextAfterToken);
245      }
246    
247    
248    
249      /**
250       * {@inheritDoc}
251       */
252      @Override()
253      public ResultCode doToolProcessing()
254      {
255        // Get the set of preferred delivery mechanisms.
256        final ArrayList<ObjectPair<String,String>> preferredDeliveryMechanisms;
257        if (deliveryMechanism.isPresent())
258        {
259          final List<String> dmList = deliveryMechanism.getValues();
260          preferredDeliveryMechanisms =
261               new ArrayList<ObjectPair<String,String>>(dmList.size());
262          for (final String s : dmList)
263          {
264            preferredDeliveryMechanisms.add(new ObjectPair<String,String>(s, null));
265          }
266        }
267        else
268        {
269          preferredDeliveryMechanisms = null;
270        }
271    
272    
273        // Get a connection to the directory server.
274        final LDAPConnection conn;
275        try
276        {
277          conn = getConnection();
278        }
279        catch (final LDAPException le)
280        {
281          Debug.debugException(le);
282          err(ERR_DELIVER_PW_RESET_TOKEN_CANNOT_GET_CONNECTION.get(
283               StaticUtils.getExceptionMessage(le)));
284          return le.getResultCode();
285        }
286    
287        try
288        {
289          // Create and send the extended request
290          final DeliverPasswordResetTokenExtendedRequest request =
291               new DeliverPasswordResetTokenExtendedRequest(userDN.getStringValue(),
292                    messageSubject.getValue(), fullTextBeforeToken.getValue(),
293                    fullTextAfterToken.getValue(),
294                    compactTextBeforeToken.getValue(),
295                    compactTextAfterToken.getValue(), preferredDeliveryMechanisms);
296          final DeliverPasswordResetTokenExtendedResult result;
297          try
298          {
299            result = (DeliverPasswordResetTokenExtendedResult)
300                 conn.processExtendedOperation(request);
301          }
302          catch (final LDAPException le)
303          {
304            Debug.debugException(le);
305            err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_PROCESSING_EXTOP.get(
306                 StaticUtils.getExceptionMessage(le)));
307            return le.getResultCode();
308          }
309    
310          if (result.getResultCode() == ResultCode.SUCCESS)
311          {
312            final String mechanism = result.getDeliveryMechanism();
313            final String id = result.getRecipientID();
314            if (id == null)
315            {
316              out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_RESULT_WITHOUT_ID.get(
317                   mechanism));
318            }
319            else
320            {
321              out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_RESULT_WITH_ID.get(mechanism,
322                   id));
323            }
324    
325            final String message = result.getDeliveryMessage();
326            if (message != null)
327            {
328              out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_MESSAGE.get(message));
329            }
330          }
331          else
332          {
333            if (result.getDiagnosticMessage() == null)
334            {
335              err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_RESULT_NO_MESSAGE.get(
336                   String.valueOf(result.getResultCode())));
337            }
338            else
339            {
340              err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_RESULT.get(
341                   String.valueOf(result.getResultCode()),
342                   result.getDiagnosticMessage()));
343            }
344          }
345    
346          return result.getResultCode();
347        }
348        finally
349        {
350          conn.close();
351        }
352      }
353    
354    
355    
356      /**
357       * {@inheritDoc}
358       */
359      @Override()
360      public LinkedHashMap<String[],String> getExampleUsages()
361      {
362        final LinkedHashMap<String[],String> exampleMap =
363             new LinkedHashMap<String[],String>(1);
364    
365        final String[] args =
366        {
367          "--hostname", "server.example.com",
368          "--port", "389",
369          "--bindDN", "uid=password.admin,ou=People,dc=example,dc=com",
370          "--bindPassword", "password",
371          "--userDN", "uid=test.user,ou=People,dc=example,dc=com",
372          "--deliveryMechanism", "SMS",
373          "--deliveryMechanism", "E-Mail",
374          "--messageSubject", "Your password reset token",
375          "--fullTextBeforeToken", "Your single-use password reset token is '",
376          "--fullTextAfterToken", "'.",
377          "--compactTextBeforeToken", "Your single-use password reset token is '",
378          "--compactTextAfterToken", "'.",
379        };
380        exampleMap.put(args,
381             INFO_DELIVER_PW_RESET_TOKEN_EXAMPLE.get());
382    
383        return exampleMap;
384      }
385    }