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   * {@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 = new ArrayList<>(dmList.size());
374      for (final String s : dmList)
375      {
376        preferredDeliveryMechanisms.add(new ObjectPair<String,String>(s, null));
377      }
378    }
379    else
380    {
381      preferredDeliveryMechanisms = null;
382    }
383
384
385    // Get a connection to the directory server.
386    final LDAPConnection conn;
387    try
388    {
389      conn = getConnection();
390    }
391    catch (final LDAPException le)
392    {
393      Debug.debugException(le);
394      err(ERR_DELIVER_PW_RESET_TOKEN_CANNOT_GET_CONNECTION.get(
395           StaticUtils.getExceptionMessage(le)));
396      return le.getResultCode();
397    }
398
399    try
400    {
401      // Create and send the extended request
402      final DeliverPasswordResetTokenExtendedRequest request =
403           new DeliverPasswordResetTokenExtendedRequest(userDN.getStringValue(),
404                messageSubject.getValue(), fullTextBeforeToken.getValue(),
405                fullTextAfterToken.getValue(),
406                compactTextBeforeToken.getValue(),
407                compactTextAfterToken.getValue(), preferredDeliveryMechanisms);
408      final DeliverPasswordResetTokenExtendedResult result;
409      try
410      {
411        result = (DeliverPasswordResetTokenExtendedResult)
412             conn.processExtendedOperation(request);
413      }
414      catch (final LDAPException le)
415      {
416        Debug.debugException(le);
417        err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_PROCESSING_EXTOP.get(
418             StaticUtils.getExceptionMessage(le)));
419        return le.getResultCode();
420      }
421
422      if (result.getResultCode() == ResultCode.SUCCESS)
423      {
424        final String mechanism = result.getDeliveryMechanism();
425        final String id = result.getRecipientID();
426        if (id == null)
427        {
428          out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_RESULT_WITHOUT_ID.get(
429               mechanism));
430        }
431        else
432        {
433          out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_RESULT_WITH_ID.get(mechanism,
434               id));
435        }
436
437        final String message = result.getDeliveryMessage();
438        if (message != null)
439        {
440          out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_MESSAGE.get(message));
441        }
442      }
443      else
444      {
445        if (result.getDiagnosticMessage() == null)
446        {
447          err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_RESULT_NO_MESSAGE.get(
448               String.valueOf(result.getResultCode())));
449        }
450        else
451        {
452          err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_RESULT.get(
453               String.valueOf(result.getResultCode()),
454               result.getDiagnosticMessage()));
455        }
456      }
457
458      return result.getResultCode();
459    }
460    finally
461    {
462      conn.close();
463    }
464  }
465
466
467
468  /**
469   * {@inheritDoc}
470   */
471  @Override()
472  public LinkedHashMap<String[],String> getExampleUsages()
473  {
474    final LinkedHashMap<String[],String> exampleMap =
475         new LinkedHashMap<>(StaticUtils.computeMapCapacity(1));
476
477    final String[] args =
478    {
479      "--hostname", "server.example.com",
480      "--port", "389",
481      "--bindDN", "uid=password.admin,ou=People,dc=example,dc=com",
482      "--bindPassword", "password",
483      "--userDN", "uid=test.user,ou=People,dc=example,dc=com",
484      "--deliveryMechanism", "SMS",
485      "--deliveryMechanism", "E-Mail",
486      "--messageSubject", "Your password reset token",
487      "--fullTextBeforeToken", "Your single-use password reset token is '",
488      "--fullTextAfterToken", "'.",
489      "--compactTextBeforeToken", "Your single-use password reset token is '",
490      "--compactTextAfterToken", "'.",
491    };
492    exampleMap.put(args,
493         INFO_DELIVER_PW_RESET_TOKEN_EXAMPLE.get());
494
495    return exampleMap;
496  }
497}