001/*
002 * Copyright 2015-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2018 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 Alcatel-Lucent 8661
063 *   server products.  These classes provide support for proprietary
064 *   functionality or for external specifications that are not considered stable
065 *   or mature enough to be guaranteed to work in an interoperable way with
066 *   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   * {@inheritDoc}
353   */
354  @Override()
355  protected boolean logToolInvocationByDefault()
356  {
357    return true;
358  }
359
360
361
362  /**
363   * {@inheritDoc}
364   */
365  @Override()
366  public ResultCode doToolProcessing()
367  {
368    // Get the set of preferred delivery mechanisms.
369    final ArrayList<ObjectPair<String,String>> preferredDeliveryMechanisms;
370    if (deliveryMechanism.isPresent())
371    {
372      final List<String> dmList = deliveryMechanism.getValues();
373      preferredDeliveryMechanisms =
374           new ArrayList<ObjectPair<String,String>>(dmList.size());
375      for (final String s : dmList)
376      {
377        preferredDeliveryMechanisms.add(new ObjectPair<String,String>(s, null));
378      }
379    }
380    else
381    {
382      preferredDeliveryMechanisms = null;
383    }
384
385
386    // Get a connection to the directory server.
387    final LDAPConnection conn;
388    try
389    {
390      conn = getConnection();
391    }
392    catch (final LDAPException le)
393    {
394      Debug.debugException(le);
395      err(ERR_DELIVER_PW_RESET_TOKEN_CANNOT_GET_CONNECTION.get(
396           StaticUtils.getExceptionMessage(le)));
397      return le.getResultCode();
398    }
399
400    try
401    {
402      // Create and send the extended request
403      final DeliverPasswordResetTokenExtendedRequest request =
404           new DeliverPasswordResetTokenExtendedRequest(userDN.getStringValue(),
405                messageSubject.getValue(), fullTextBeforeToken.getValue(),
406                fullTextAfterToken.getValue(),
407                compactTextBeforeToken.getValue(),
408                compactTextAfterToken.getValue(), preferredDeliveryMechanisms);
409      final DeliverPasswordResetTokenExtendedResult result;
410      try
411      {
412        result = (DeliverPasswordResetTokenExtendedResult)
413             conn.processExtendedOperation(request);
414      }
415      catch (final LDAPException le)
416      {
417        Debug.debugException(le);
418        err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_PROCESSING_EXTOP.get(
419             StaticUtils.getExceptionMessage(le)));
420        return le.getResultCode();
421      }
422
423      if (result.getResultCode() == ResultCode.SUCCESS)
424      {
425        final String mechanism = result.getDeliveryMechanism();
426        final String id = result.getRecipientID();
427        if (id == null)
428        {
429          out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_RESULT_WITHOUT_ID.get(
430               mechanism));
431        }
432        else
433        {
434          out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_RESULT_WITH_ID.get(mechanism,
435               id));
436        }
437
438        final String message = result.getDeliveryMessage();
439        if (message != null)
440        {
441          out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_MESSAGE.get(message));
442        }
443      }
444      else
445      {
446        if (result.getDiagnosticMessage() == null)
447        {
448          err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_RESULT_NO_MESSAGE.get(
449               String.valueOf(result.getResultCode())));
450        }
451        else
452        {
453          err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_RESULT.get(
454               String.valueOf(result.getResultCode()),
455               result.getDiagnosticMessage()));
456        }
457      }
458
459      return result.getResultCode();
460    }
461    finally
462    {
463      conn.close();
464    }
465  }
466
467
468
469  /**
470   * {@inheritDoc}
471   */
472  @Override()
473  public LinkedHashMap<String[],String> getExampleUsages()
474  {
475    final LinkedHashMap<String[],String> exampleMap =
476         new LinkedHashMap<String[],String>(1);
477
478    final String[] args =
479    {
480      "--hostname", "server.example.com",
481      "--port", "389",
482      "--bindDN", "uid=password.admin,ou=People,dc=example,dc=com",
483      "--bindPassword", "password",
484      "--userDN", "uid=test.user,ou=People,dc=example,dc=com",
485      "--deliveryMechanism", "SMS",
486      "--deliveryMechanism", "E-Mail",
487      "--messageSubject", "Your password reset token",
488      "--fullTextBeforeToken", "Your single-use password reset token is '",
489      "--fullTextAfterToken", "'.",
490      "--compactTextBeforeToken", "Your single-use password reset token is '",
491      "--compactTextAfterToken", "'.",
492    };
493    exampleMap.put(args,
494         INFO_DELIVER_PW_RESET_TOKEN_EXAMPLE.get());
495
496    return exampleMap;
497  }
498}