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 }