001/*
002 * Copyright 2015-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2019 Ping Identity Corporation
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 */
021package com.unboundid.ldap.sdk.unboundidds;
022
023
024
025import java.io.OutputStream;
026import java.io.Serializable;
027import java.util.ArrayList;
028import java.util.LinkedHashMap;
029import java.util.List;
030
031import com.unboundid.ldap.sdk.LDAPConnection;
032import com.unboundid.ldap.sdk.LDAPException;
033import com.unboundid.ldap.sdk.ResultCode;
034import com.unboundid.ldap.sdk.Version;
035import com.unboundid.ldap.sdk.unboundidds.extensions.
036            DeliverPasswordResetTokenExtendedRequest;
037import com.unboundid.ldap.sdk.unboundidds.extensions.
038            DeliverPasswordResetTokenExtendedResult;
039import com.unboundid.util.Debug;
040import com.unboundid.util.LDAPCommandLineTool;
041import com.unboundid.util.ObjectPair;
042import com.unboundid.util.StaticUtils;
043import com.unboundid.util.ThreadSafety;
044import com.unboundid.util.ThreadSafetyLevel;
045import com.unboundid.util.args.ArgumentException;
046import com.unboundid.util.args.ArgumentParser;
047import com.unboundid.util.args.DNArgument;
048import com.unboundid.util.args.StringArgument;
049
050import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
051
052
053
054/**
055 * This class provides a utility that may be used to request that the Directory
056 * Server deliver a single-use password reset token to a user through some
057 * out-of-band mechanism.
058 * <BR>
059 * <BLOCKQUOTE>
060 *   <B>NOTE:</B>  This class, and other classes within the
061 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
062 *   supported for use against Ping Identity, UnboundID, and
063 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
064 *   for proprietary functionality or for external specifications that are not
065 *   considered stable or mature enough to be guaranteed to work in an
066 *   interoperable way with other types of LDAP servers.
067 * </BLOCKQUOTE>
068 */
069@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
070public final class DeliverPasswordResetToken
071       extends LDAPCommandLineTool
072       implements Serializable
073{
074  /**
075   * The serial version UID for this serializable class.
076   */
077  private static final long serialVersionUID = 5793619963770997266L;
078
079
080
081  // The DN of the user to whom the password reset token should be sent.
082  private DNArgument userDN;
083
084  // The text to include after the password reset token in the "compact"
085  // message.
086  private StringArgument compactTextAfterToken;
087
088  // The text to include before the password reset token in the "compact"
089  // message.
090  private StringArgument compactTextBeforeToken;
091
092  // The name of the mechanism through which the one-time password should be
093  // delivered.
094  private StringArgument deliveryMechanism;
095
096  // The text to include after the password reset token in the "full" message.
097  private StringArgument fullTextAfterToken;
098
099  // The text to include before the password reset token in the "full" message.
100  private StringArgument fullTextBeforeToken;
101
102  // The subject to use for the message containing the delivered token.
103  private StringArgument messageSubject;
104
105
106
107  /**
108   * Parse the provided command line arguments and perform the appropriate
109   * processing.
110   *
111   * @param  args  The command line arguments provided to this program.
112   */
113  public static void main(final String... args)
114  {
115    final ResultCode resultCode = main(args, System.out, System.err);
116    if (resultCode != ResultCode.SUCCESS)
117    {
118      System.exit(resultCode.intValue());
119    }
120  }
121
122
123
124  /**
125   * Parse the provided command line arguments and perform the appropriate
126   * processing.
127   *
128   * @param  args       The command line arguments provided to this program.
129   * @param  outStream  The output stream to which standard out should be
130   *                    written.  It may be {@code null} if output should be
131   *                    suppressed.
132   * @param  errStream  The output stream to which standard error should be
133   *                    written.  It may be {@code null} if error messages
134   *                    should be suppressed.
135   *
136   * @return  A result code indicating whether the processing was successful.
137   */
138  public static ResultCode main(final String[] args,
139                                final OutputStream outStream,
140                                final OutputStream errStream)
141  {
142    final DeliverPasswordResetToken tool =
143         new DeliverPasswordResetToken(outStream, errStream);
144    return tool.runTool(args);
145  }
146
147
148
149  /**
150   * Creates a new instance of this tool.
151   *
152   * @param  outStream  The output stream to which standard out should be
153   *                    written.  It may be {@code null} if output should be
154   *                    suppressed.
155   * @param  errStream  The output stream to which standard error should be
156   *                    written.  It may be {@code null} if error messages
157   *                    should be suppressed.
158   */
159  public DeliverPasswordResetToken(final OutputStream outStream,
160                                   final OutputStream errStream)
161  {
162    super(outStream, errStream);
163
164    userDN                 = null;
165    compactTextAfterToken  = null;
166    compactTextBeforeToken = null;
167    deliveryMechanism      = null;
168    fullTextAfterToken     = null;
169    fullTextBeforeToken    = null;
170    messageSubject         = null;
171  }
172
173
174
175  /**
176   * {@inheritDoc}
177   */
178  @Override()
179  public String getToolName()
180  {
181    return "deliver-password-reset-token";
182  }
183
184
185
186  /**
187   * {@inheritDoc}
188   */
189  @Override()
190  public String getToolDescription()
191  {
192    return INFO_DELIVER_PW_RESET_TOKEN_TOOL_DESCRIPTION.get();
193  }
194
195
196
197  /**
198   * {@inheritDoc}
199   */
200  @Override()
201  public String getToolVersion()
202  {
203    return Version.NUMERIC_VERSION_STRING;
204  }
205
206
207
208  /**
209   * {@inheritDoc}
210   */
211  @Override()
212  public void addNonLDAPArguments(final ArgumentParser parser)
213         throws ArgumentException
214  {
215    userDN = new DNArgument('b', "userDN", true, 1,
216         INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_DN.get(),
217         INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_USER_DN.get());
218    userDN.setArgumentGroupName(INFO_DELIVER_PW_RESET_TOKEN_GROUP_ID.get());
219    userDN.addLongIdentifier("user-dn", true);
220    parser.addArgument(userDN);
221
222    deliveryMechanism = new StringArgument('m', "deliveryMechanism", false, 0,
223         INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_NAME.get(),
224         INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_MECH.get());
225    deliveryMechanism.setArgumentGroupName(
226         INFO_DELIVER_PW_RESET_TOKEN_GROUP_DELIVERY_MECH.get());
227    deliveryMechanism.addLongIdentifier("delivery-mechanism", true);
228    parser.addArgument(deliveryMechanism);
229
230    messageSubject = new StringArgument('s', "messageSubject", false, 1,
231         INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_SUBJECT.get(),
232         INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_SUBJECT.get());
233    messageSubject.setArgumentGroupName(
234         INFO_DELIVER_PW_RESET_TOKEN_GROUP_DELIVERY_MECH.get());
235    messageSubject.addLongIdentifier("message-subject", true);
236    parser.addArgument(messageSubject);
237
238    fullTextBeforeToken = new StringArgument('f', "fullTextBeforeToken", false,
239         1, INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_FULL_BEFORE.get(),
240         INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_FULL_BEFORE.get());
241    fullTextBeforeToken.setArgumentGroupName(
242         INFO_DELIVER_PW_RESET_TOKEN_GROUP_DELIVERY_MECH.get());
243    fullTextBeforeToken.addLongIdentifier("full-text-before-token", true);
244    parser.addArgument(fullTextBeforeToken);
245
246    fullTextAfterToken = new StringArgument('F', "fullTextAfterToken", false,
247         1, INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_FULL_AFTER.get(),
248         INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_FULL_AFTER.get());
249    fullTextAfterToken.setArgumentGroupName(
250         INFO_DELIVER_PW_RESET_TOKEN_GROUP_DELIVERY_MECH.get());
251    fullTextAfterToken.addLongIdentifier("full-text-after-token", true);
252    parser.addArgument(fullTextAfterToken);
253
254    compactTextBeforeToken = new StringArgument('c', "compactTextBeforeToken",
255         false, 1, INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_COMPACT_BEFORE.get(),
256         INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_COMPACT_BEFORE.get());
257    compactTextBeforeToken.setArgumentGroupName(
258         INFO_DELIVER_PW_RESET_TOKEN_GROUP_DELIVERY_MECH.get());
259    compactTextBeforeToken.addLongIdentifier("compact-text-before-token", true);
260    parser.addArgument(compactTextBeforeToken);
261
262    compactTextAfterToken = new StringArgument('C', "compactTextAfterToken",
263         false, 1, INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_COMPACT_AFTER.get(),
264         INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_COMPACT_AFTER.get());
265    compactTextAfterToken.setArgumentGroupName(
266         INFO_DELIVER_PW_RESET_TOKEN_GROUP_DELIVERY_MECH.get());
267    compactTextAfterToken.addLongIdentifier("compact-text-after-token", true);
268    parser.addArgument(compactTextAfterToken);
269  }
270
271
272
273  /**
274   * {@inheritDoc}
275   */
276  @Override()
277  public boolean supportsInteractiveMode()
278  {
279    return true;
280  }
281
282
283
284  /**
285   * {@inheritDoc}
286   */
287  @Override()
288  public boolean defaultsToInteractiveMode()
289  {
290    return true;
291  }
292
293
294
295  /**
296   * {@inheritDoc}
297   */
298  @Override()
299  protected boolean supportsOutputFile()
300  {
301    return true;
302  }
303
304
305
306  /**
307   * {@inheritDoc}
308   */
309  @Override()
310  protected boolean defaultToPromptForBindPassword()
311  {
312    return true;
313  }
314
315
316
317  /**
318   * Indicates whether this tool supports the use of a properties file for
319   * specifying default values for arguments that aren't specified on the
320   * command line.
321   *
322   * @return  {@code true} if this tool supports the use of a properties file
323   *          for specifying default values for arguments that aren't specified
324   *          on the command line, or {@code false} if not.
325   */
326  @Override()
327  public boolean supportsPropertiesFile()
328  {
329    return true;
330  }
331
332
333
334  /**
335   * Indicates whether the LDAP-specific arguments should include alternate
336   * versions of all long identifiers that consist of multiple words so that
337   * they are available in both camelCase and dash-separated versions.
338   *
339   * @return  {@code true} if this tool should provide multiple versions of
340   *          long identifiers for LDAP-specific arguments, or {@code false} if
341   *          not.
342   */
343  @Override()
344  protected boolean includeAlternateLongIdentifiers()
345  {
346    return true;
347  }
348
349
350
351  /**
352   * Indicates whether this tool should provide a command-line argument that
353   * allows for low-level SSL debugging.  If this returns {@code true}, then an
354   * "--enableSSLDebugging}" argument will be added that sets the
355   * "javax.net.debug" system property to "all" before attempting any
356   * communication.
357   *
358   * @return  {@code true} if this tool should offer an "--enableSSLDebugging"
359   *          argument, or {@code false} if not.
360   */
361  @Override()
362  protected boolean supportsSSLDebugging()
363  {
364    return true;
365  }
366
367
368
369  /**
370   * {@inheritDoc}
371   */
372  @Override()
373  protected boolean logToolInvocationByDefault()
374  {
375    return true;
376  }
377
378
379
380  /**
381   * {@inheritDoc}
382   */
383  @Override()
384  public ResultCode doToolProcessing()
385  {
386    // Get the set of preferred delivery mechanisms.
387    final ArrayList<ObjectPair<String,String>> preferredDeliveryMechanisms;
388    if (deliveryMechanism.isPresent())
389    {
390      final List<String> dmList = deliveryMechanism.getValues();
391      preferredDeliveryMechanisms = new ArrayList<>(dmList.size());
392      for (final String s : dmList)
393      {
394        preferredDeliveryMechanisms.add(new ObjectPair<String,String>(s, null));
395      }
396    }
397    else
398    {
399      preferredDeliveryMechanisms = null;
400    }
401
402
403    // Get a connection to the directory server.
404    final LDAPConnection conn;
405    try
406    {
407      conn = getConnection();
408    }
409    catch (final LDAPException le)
410    {
411      Debug.debugException(le);
412      err(ERR_DELIVER_PW_RESET_TOKEN_CANNOT_GET_CONNECTION.get(
413           StaticUtils.getExceptionMessage(le)));
414      return le.getResultCode();
415    }
416
417    try
418    {
419      // Create and send the extended request
420      final DeliverPasswordResetTokenExtendedRequest request =
421           new DeliverPasswordResetTokenExtendedRequest(userDN.getStringValue(),
422                messageSubject.getValue(), fullTextBeforeToken.getValue(),
423                fullTextAfterToken.getValue(),
424                compactTextBeforeToken.getValue(),
425                compactTextAfterToken.getValue(), preferredDeliveryMechanisms);
426      final DeliverPasswordResetTokenExtendedResult result;
427      try
428      {
429        result = (DeliverPasswordResetTokenExtendedResult)
430             conn.processExtendedOperation(request);
431      }
432      catch (final LDAPException le)
433      {
434        Debug.debugException(le);
435        err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_PROCESSING_EXTOP.get(
436             StaticUtils.getExceptionMessage(le)));
437        return le.getResultCode();
438      }
439
440      if (result.getResultCode() == ResultCode.SUCCESS)
441      {
442        final String mechanism = result.getDeliveryMechanism();
443        final String id = result.getRecipientID();
444        if (id == null)
445        {
446          out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_RESULT_WITHOUT_ID.get(
447               mechanism));
448        }
449        else
450        {
451          out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_RESULT_WITH_ID.get(mechanism,
452               id));
453        }
454
455        final String message = result.getDeliveryMessage();
456        if (message != null)
457        {
458          out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_MESSAGE.get(message));
459        }
460      }
461      else
462      {
463        if (result.getDiagnosticMessage() == null)
464        {
465          err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_RESULT_NO_MESSAGE.get(
466               String.valueOf(result.getResultCode())));
467        }
468        else
469        {
470          err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_RESULT.get(
471               String.valueOf(result.getResultCode()),
472               result.getDiagnosticMessage()));
473        }
474      }
475
476      return result.getResultCode();
477    }
478    finally
479    {
480      conn.close();
481    }
482  }
483
484
485
486  /**
487   * {@inheritDoc}
488   */
489  @Override()
490  public LinkedHashMap<String[],String> getExampleUsages()
491  {
492    final LinkedHashMap<String[],String> exampleMap =
493         new LinkedHashMap<>(StaticUtils.computeMapCapacity(1));
494
495    final String[] args =
496    {
497      "--hostname", "server.example.com",
498      "--port", "389",
499      "--bindDN", "uid=password.admin,ou=People,dc=example,dc=com",
500      "--bindPassword", "password",
501      "--userDN", "uid=test.user,ou=People,dc=example,dc=com",
502      "--deliveryMechanism", "SMS",
503      "--deliveryMechanism", "E-Mail",
504      "--messageSubject", "Your password reset token",
505      "--fullTextBeforeToken", "Your single-use password reset token is '",
506      "--fullTextAfterToken", "'.",
507      "--compactTextBeforeToken", "Your single-use password reset token is '",
508      "--compactTextAfterToken", "'.",
509    };
510    exampleMap.put(args,
511         INFO_DELIVER_PW_RESET_TOKEN_EXAMPLE.get());
512
513    return exampleMap;
514  }
515}