001    /*
002     * Copyright 2013-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                DeliverOneTimePasswordExtendedRequest;
037    import com.unboundid.ldap.sdk.unboundidds.extensions.
038                DeliverOneTimePasswordExtendedResult;
039    import com.unboundid.util.Debug;
040    import com.unboundid.util.LDAPCommandLineTool;
041    import com.unboundid.util.ObjectPair;
042    import com.unboundid.util.PasswordReader;
043    import com.unboundid.util.StaticUtils;
044    import com.unboundid.util.ThreadSafety;
045    import com.unboundid.util.ThreadSafetyLevel;
046    import com.unboundid.util.args.ArgumentException;
047    import com.unboundid.util.args.ArgumentParser;
048    import com.unboundid.util.args.BooleanArgument;
049    import com.unboundid.util.args.DNArgument;
050    import com.unboundid.util.args.FileArgument;
051    import com.unboundid.util.args.StringArgument;
052    
053    import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
054    
055    
056    
057    /**
058     * <BLOCKQUOTE>
059     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
060     *   LDAP SDK for Java.  It is not available for use in applications that
061     *   include only the Standard Edition of the LDAP SDK, and is not supported for
062     *   use in conjunction with non-UnboundID products.
063     * </BLOCKQUOTE>
064     * This class provides a utility that may be used to request that the Directory
065     * Server deliver a one-time password to a user through some out-of-band
066     * mechanism.
067     */
068    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
069    public final class DeliverOneTimePassword
070           extends LDAPCommandLineTool
071           implements Serializable
072    {
073      /**
074       * The serial version UID for this serializable class.
075       */
076      private static final long serialVersionUID = -7414730592661321416L;
077    
078    
079    
080      // Indicates that the tool should interactively prompt the user for their
081      // bind password.
082      private BooleanArgument promptForBindPassword;
083    
084      // The DN for the user to whom the one-time password should be delivered.
085      private DNArgument bindDN;
086    
087      // The path to a file containing the static password for the user to whom the
088      // one-time password should be delivered.
089      private FileArgument bindPasswordFile;
090    
091      // The text to include after the one-time password in the "compact" message.
092      private StringArgument compactTextAfterOTP;
093    
094      // The text to include before the one-time password in the "compact" message.
095      private StringArgument compactTextBeforeOTP;
096    
097      // The name of the mechanism through which the one-time password should be
098      // delivered.
099      private StringArgument deliveryMechanism;
100    
101      // The text to include after the one-time password in the "full" message.
102      private StringArgument fullTextAfterOTP;
103    
104      // The text to include before the one-time password in the "full" message.
105      private StringArgument fullTextBeforeOTP;
106    
107      // The subject to use for the message containing the delivered token.
108      private StringArgument messageSubject;
109    
110      // The username for the user to whom the one-time password should be
111      // delivered.
112      private StringArgument userName;
113    
114      // The static password for the user to whom the one-time password should be
115      // delivered.
116      private StringArgument bindPassword;
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       */
126      public static void main(final String... args)
127      {
128        final ResultCode resultCode = main(args, System.out, System.err);
129        if (resultCode != ResultCode.SUCCESS)
130        {
131          System.exit(resultCode.intValue());
132        }
133      }
134    
135    
136    
137      /**
138       * Parse the provided command line arguments and perform the appropriate
139       * processing.
140       *
141       * @param  args       The command line arguments provided to this program.
142       * @param  outStream  The output stream to which standard out should be
143       *                    written.  It may be {@code null} if output should be
144       *                    suppressed.
145       * @param  errStream  The output stream to which standard error should be
146       *                    written.  It may be {@code null} if error messages
147       *                    should be suppressed.
148       *
149       * @return  A result code indicating whether the processing was successful.
150       */
151      public static ResultCode main(final String[] args,
152                                    final OutputStream outStream,
153                                    final OutputStream errStream)
154      {
155        final DeliverOneTimePassword tool =
156             new DeliverOneTimePassword(outStream, errStream);
157        return tool.runTool(args);
158      }
159    
160    
161    
162      /**
163       * Creates a new instance of this tool.
164       *
165       * @param  outStream  The output stream to which standard out should be
166       *                    written.  It may be {@code null} if output should be
167       *                    suppressed.
168       * @param  errStream  The output stream to which standard error should be
169       *                    written.  It may be {@code null} if error messages
170       *                    should be suppressed.
171       */
172      public DeliverOneTimePassword(final OutputStream outStream,
173                                    final OutputStream errStream)
174      {
175        super(outStream, errStream);
176    
177        promptForBindPassword = null;
178        bindDN                = null;
179        bindPasswordFile      = null;
180        bindPassword          = null;
181        compactTextAfterOTP   = null;
182        compactTextBeforeOTP  = null;
183        deliveryMechanism     = null;
184        fullTextAfterOTP      = null;
185        fullTextBeforeOTP     = null;
186        messageSubject        = null;
187        userName              = null;
188      }
189    
190    
191    
192      /**
193       * {@inheritDoc}
194       */
195      @Override()
196      public String getToolName()
197      {
198        return "deliver-one-time-password";
199      }
200    
201    
202    
203      /**
204       * {@inheritDoc}
205       */
206      @Override()
207      public String getToolDescription()
208      {
209        return INFO_DELIVER_OTP_TOOL_DESCRIPTION.get();
210      }
211    
212    
213    
214      /**
215       * {@inheritDoc}
216       */
217      @Override()
218      public String getToolVersion()
219      {
220        return Version.NUMERIC_VERSION_STRING;
221      }
222    
223    
224    
225      /**
226       * {@inheritDoc}
227       */
228      @Override()
229      public void addNonLDAPArguments(final ArgumentParser parser)
230             throws ArgumentException
231      {
232        bindDN = new DNArgument('D', "bindDN", false, 1,
233             INFO_DELIVER_OTP_PLACEHOLDER_DN.get(),
234             INFO_DELIVER_OTP_DESCRIPTION_BIND_DN.get());
235        parser.addArgument(bindDN);
236    
237        userName = new StringArgument('n', "userName", false, 1,
238             INFO_DELIVER_OTP_PLACEHOLDER_USERNAME.get(),
239             INFO_DELIVER_OTP_DESCRIPTION_USERNAME.get());
240        parser.addArgument(userName);
241    
242        bindPassword = new StringArgument('w', "bindPassword", false, 1,
243             INFO_DELIVER_OTP_PLACEHOLDER_PASSWORD.get(),
244             INFO_DELIVER_OTP_DESCRIPTION_BIND_PW.get());
245        parser.addArgument(bindPassword);
246    
247        bindPasswordFile = new FileArgument('j', "bindPasswordFile", false, 1,
248             INFO_DELIVER_OTP_PLACEHOLDER_PATH.get(),
249             INFO_DELIVER_OTP_DESCRIPTION_BIND_PW_FILE.get(), true, true, true,
250             false);
251        parser.addArgument(bindPasswordFile);
252    
253        promptForBindPassword = new BooleanArgument(null, "promptForBindPassword",
254             1, INFO_DELIVER_OTP_DESCRIPTION_BIND_PW_PROMPT.get());
255        parser.addArgument(promptForBindPassword);
256    
257        deliveryMechanism = new StringArgument('m', "deliveryMechanism", false, 0,
258             INFO_DELIVER_OTP_PLACEHOLDER_NAME.get(),
259             INFO_DELIVER_OTP_DESCRIPTION_MECH.get());
260        parser.addArgument(deliveryMechanism);
261    
262        messageSubject = new StringArgument('s', "messageSubject", false, 1,
263             INFO_DELIVER_OTP_PLACEHOLDER_SUBJECT.get(),
264             INFO_DELIVER_OTP_DESCRIPTION_SUBJECT.get());
265        parser.addArgument(messageSubject);
266    
267        fullTextBeforeOTP = new StringArgument('f', "fullTextBeforeOTP", false,
268             1, INFO_DELIVER_OTP_PLACEHOLDER_FULL_BEFORE.get(),
269             INFO_DELIVER_OTP_DESCRIPTION_FULL_BEFORE.get());
270        parser.addArgument(fullTextBeforeOTP);
271    
272        fullTextAfterOTP = new StringArgument('F', "fullTextAfterOTP", false,
273             1, INFO_DELIVER_OTP_PLACEHOLDER_FULL_AFTER.get(),
274             INFO_DELIVER_OTP_DESCRIPTION_FULL_AFTER.get());
275        parser.addArgument(fullTextAfterOTP);
276    
277        compactTextBeforeOTP = new StringArgument('c', "compactTextBeforeOTP",
278             false, 1, INFO_DELIVER_OTP_PLACEHOLDER_COMPACT_BEFORE.get(),
279             INFO_DELIVER_OTP_DESCRIPTION_COMPACT_BEFORE.get());
280        parser.addArgument(compactTextBeforeOTP);
281    
282        compactTextAfterOTP = new StringArgument('C', "compactTextAfterOTP",
283             false, 1, INFO_DELIVER_OTP_PLACEHOLDER_COMPACT_AFTER.get(),
284             INFO_DELIVER_OTP_DESCRIPTION_COMPACT_AFTER.get());
285        parser.addArgument(compactTextAfterOTP);
286    
287    
288        // Either the bind DN or username must have been provided.
289        parser.addRequiredArgumentSet(bindDN, userName);
290    
291        // Only one option may be used for specifying the user identity.
292        parser.addExclusiveArgumentSet(bindDN, userName);
293    
294        // Only one option may be used for specifying the bind password.
295        parser.addExclusiveArgumentSet(bindPassword, bindPasswordFile,
296             promptForBindPassword);
297      }
298    
299    
300    
301      /**
302       * {@inheritDoc}
303       */
304      @Override()
305      protected boolean supportsAuthentication()
306      {
307        return false;
308      }
309    
310    
311    
312      /**
313       * {@inheritDoc}
314       */
315      @Override()
316      public ResultCode doToolProcessing()
317      {
318        // Construct the authentication identity.
319        final String authID;
320        if (bindDN.isPresent())
321        {
322          authID = "dn:" + bindDN.getValue();
323        }
324        else
325        {
326          authID = "u:" + userName.getValue();
327        }
328    
329    
330        // Get the bind password.
331        final String pw;
332        if (bindPassword.isPresent())
333        {
334          pw = bindPassword.getValue();
335        }
336        else if (bindPasswordFile.isPresent())
337        {
338          try
339          {
340            pw = bindPasswordFile.getNonBlankFileLines().get(0);
341          }
342          catch (Exception e)
343          {
344            Debug.debugException(e);
345            err(ERR_DELIVER_OTP_CANNOT_READ_BIND_PW.get(
346                 StaticUtils.getExceptionMessage(e)));
347            return ResultCode.LOCAL_ERROR;
348          }
349        }
350        else
351        {
352          try
353          {
354            getOut().print(INFO_DELIVER_OTP_ENTER_PW.get());
355            pw = StaticUtils.toUTF8String(PasswordReader.readPassword());
356            getOut().println();
357          }
358          catch (final Exception e)
359          {
360            Debug.debugException(e);
361            err(ERR_DELIVER_OTP_CANNOT_READ_BIND_PW.get(
362                 StaticUtils.getExceptionMessage(e)));
363            return ResultCode.LOCAL_ERROR;
364          }
365        }
366    
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_OTP_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 DeliverOneTimePasswordExtendedRequest request =
404               new DeliverOneTimePasswordExtendedRequest(authID, pw,
405                    messageSubject.getValue(), fullTextBeforeOTP.getValue(),
406                    fullTextAfterOTP.getValue(), compactTextBeforeOTP.getValue(),
407                    compactTextAfterOTP.getValue(), preferredDeliveryMechanisms);
408          final DeliverOneTimePasswordExtendedResult result;
409          try
410          {
411            result = (DeliverOneTimePasswordExtendedResult)
412                 conn.processExtendedOperation(request);
413          }
414          catch (final LDAPException le)
415          {
416            Debug.debugException(le);
417            err(ERR_DELIVER_OTP_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_OTP_SUCCESS_RESULT_WITHOUT_ID.get(mechanism));
429            }
430            else
431            {
432              out(INFO_DELIVER_OTP_SUCCESS_RESULT_WITH_ID.get(mechanism, id));
433            }
434    
435            final String message = result.getDeliveryMessage();
436            if (message != null)
437            {
438              out(INFO_DELIVER_OTP_SUCCESS_MESSAGE.get(message));
439            }
440          }
441          else
442          {
443            if (result.getDiagnosticMessage() == null)
444            {
445              err(ERR_DELIVER_OTP_ERROR_RESULT_NO_MESSAGE.get(
446                   String.valueOf(result.getResultCode())));
447            }
448            else
449            {
450              err(ERR_DELIVER_OTP_ERROR_RESULT.get(
451                   String.valueOf(result.getResultCode()),
452                   result.getDiagnosticMessage()));
453            }
454          }
455    
456          return result.getResultCode();
457        }
458        finally
459        {
460          conn.close();
461        }
462      }
463    
464    
465    
466      /**
467       * {@inheritDoc}
468       */
469      @Override()
470      public LinkedHashMap<String[],String> getExampleUsages()
471      {
472        final LinkedHashMap<String[],String> exampleMap =
473             new LinkedHashMap<String[],String>(2);
474    
475        String[] args =
476        {
477          "--hostname", "server.example.com",
478          "--port", "389",
479          "--bindDN", "uid=test.user,ou=People,dc=example,dc=com",
480          "--bindPassword", "password",
481          "--messageSubject", "Your one-time password",
482          "--fullTextBeforeOTP", "Your one-time password is '",
483          "--fullTextAfterOTP", "'.",
484          "--compactTextBeforeOTP", "Your OTP is '",
485          "--compactTextAfterOTP", "'.",
486        };
487        exampleMap.put(args,
488             INFO_DELIVER_OTP_EXAMPLE_1.get());
489    
490        args = new String[]
491        {
492          "--hostname", "server.example.com",
493          "--port", "389",
494          "--userName", "test.user",
495          "--bindPassword", "password",
496          "--deliveryMechanism", "SMS",
497          "--deliveryMechanism", "E-Mail",
498          "--messageSubject", "Your one-time password",
499          "--fullTextBeforeOTP", "Your one-time password is '",
500          "--fullTextAfterOTP", "'.",
501          "--compactTextBeforeOTP", "Your OTP is '",
502          "--compactTextAfterOTP", "'.",
503        };
504        exampleMap.put(args,
505             INFO_DELIVER_OTP_EXAMPLE_2.get());
506    
507        return exampleMap;
508      }
509    }