001/* 002 * Copyright 2013-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 DeliverOneTimePasswordExtendedRequest; 037import com.unboundid.ldap.sdk.unboundidds.extensions. 038 DeliverOneTimePasswordExtendedResult; 039import com.unboundid.util.Debug; 040import com.unboundid.util.LDAPCommandLineTool; 041import com.unboundid.util.ObjectPair; 042import com.unboundid.util.PasswordReader; 043import com.unboundid.util.StaticUtils; 044import com.unboundid.util.ThreadSafety; 045import com.unboundid.util.ThreadSafetyLevel; 046import com.unboundid.util.args.ArgumentException; 047import com.unboundid.util.args.ArgumentParser; 048import com.unboundid.util.args.BooleanArgument; 049import com.unboundid.util.args.DNArgument; 050import com.unboundid.util.args.FileArgument; 051import com.unboundid.util.args.StringArgument; 052 053import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*; 054 055 056 057/** 058 * This class provides a utility that may be used to request that the Directory 059 * Server deliver a one-time password to a user through some out-of-band 060 * mechanism. 061 * <BR> 062 * <BLOCKQUOTE> 063 * <B>NOTE:</B> This class, and other classes within the 064 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 065 * supported for use against Ping Identity, UnboundID, and 066 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 067 * for proprietary functionality or for external specifications that are not 068 * considered stable or mature enough to be guaranteed to work in an 069 * interoperable way with other types of LDAP servers. 070 * </BLOCKQUOTE> 071 */ 072@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 073public final class DeliverOneTimePassword 074 extends LDAPCommandLineTool 075 implements Serializable 076{ 077 /** 078 * The serial version UID for this serializable class. 079 */ 080 private static final long serialVersionUID = -7414730592661321416L; 081 082 083 084 // Indicates that the tool should interactively prompt the user for their 085 // bind password. 086 private BooleanArgument promptForBindPassword; 087 088 // The DN for the user to whom the one-time password should be delivered. 089 private DNArgument bindDN; 090 091 // The path to a file containing the static password for the user to whom the 092 // one-time password should be delivered. 093 private FileArgument bindPasswordFile; 094 095 // The text to include after the one-time password in the "compact" message. 096 private StringArgument compactTextAfterOTP; 097 098 // The text to include before the one-time password in the "compact" message. 099 private StringArgument compactTextBeforeOTP; 100 101 // The name of the mechanism through which the one-time password should be 102 // delivered. 103 private StringArgument deliveryMechanism; 104 105 // The text to include after the one-time password in the "full" message. 106 private StringArgument fullTextAfterOTP; 107 108 // The text to include before the one-time password in the "full" message. 109 private StringArgument fullTextBeforeOTP; 110 111 // The subject to use for the message containing the delivered token. 112 private StringArgument messageSubject; 113 114 // The username for the user to whom the one-time password should be 115 // delivered. 116 private StringArgument userName; 117 118 // The static password for the user to whom the one-time password should be 119 // delivered. 120 private StringArgument bindPassword; 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 */ 130 public static void main(final String... args) 131 { 132 final ResultCode resultCode = main(args, System.out, System.err); 133 if (resultCode != ResultCode.SUCCESS) 134 { 135 System.exit(resultCode.intValue()); 136 } 137 } 138 139 140 141 /** 142 * Parse the provided command line arguments and perform the appropriate 143 * processing. 144 * 145 * @param args The command line arguments provided to this program. 146 * @param outStream The output stream to which standard out should be 147 * written. It may be {@code null} if output should be 148 * suppressed. 149 * @param errStream The output stream to which standard error should be 150 * written. It may be {@code null} if error messages 151 * should be suppressed. 152 * 153 * @return A result code indicating whether the processing was successful. 154 */ 155 public static ResultCode main(final String[] args, 156 final OutputStream outStream, 157 final OutputStream errStream) 158 { 159 final DeliverOneTimePassword tool = 160 new DeliverOneTimePassword(outStream, errStream); 161 return tool.runTool(args); 162 } 163 164 165 166 /** 167 * Creates a new instance of this tool. 168 * 169 * @param outStream The output stream to which standard out should be 170 * written. It may be {@code null} if output should be 171 * suppressed. 172 * @param errStream The output stream to which standard error should be 173 * written. It may be {@code null} if error messages 174 * should be suppressed. 175 */ 176 public DeliverOneTimePassword(final OutputStream outStream, 177 final OutputStream errStream) 178 { 179 super(outStream, errStream); 180 181 promptForBindPassword = null; 182 bindDN = null; 183 bindPasswordFile = null; 184 bindPassword = null; 185 compactTextAfterOTP = null; 186 compactTextBeforeOTP = null; 187 deliveryMechanism = null; 188 fullTextAfterOTP = null; 189 fullTextBeforeOTP = null; 190 messageSubject = null; 191 userName = null; 192 } 193 194 195 196 /** 197 * {@inheritDoc} 198 */ 199 @Override() 200 public String getToolName() 201 { 202 return "deliver-one-time-password"; 203 } 204 205 206 207 /** 208 * {@inheritDoc} 209 */ 210 @Override() 211 public String getToolDescription() 212 { 213 return INFO_DELIVER_OTP_TOOL_DESCRIPTION.get(); 214 } 215 216 217 218 /** 219 * {@inheritDoc} 220 */ 221 @Override() 222 public String getToolVersion() 223 { 224 return Version.NUMERIC_VERSION_STRING; 225 } 226 227 228 229 /** 230 * {@inheritDoc} 231 */ 232 @Override() 233 public void addNonLDAPArguments(final ArgumentParser parser) 234 throws ArgumentException 235 { 236 bindDN = new DNArgument('D', "bindDN", false, 1, 237 INFO_DELIVER_OTP_PLACEHOLDER_DN.get(), 238 INFO_DELIVER_OTP_DESCRIPTION_BIND_DN.get()); 239 bindDN.setArgumentGroupName(INFO_DELIVER_OTP_GROUP_ID_AND_AUTH.get()); 240 bindDN.addLongIdentifier("bind-dn", true); 241 parser.addArgument(bindDN); 242 243 userName = new StringArgument('n', "userName", false, 1, 244 INFO_DELIVER_OTP_PLACEHOLDER_USERNAME.get(), 245 INFO_DELIVER_OTP_DESCRIPTION_USERNAME.get()); 246 userName.setArgumentGroupName(INFO_DELIVER_OTP_GROUP_ID_AND_AUTH.get()); 247 userName.addLongIdentifier("user-name", true); 248 parser.addArgument(userName); 249 250 bindPassword = new StringArgument('w', "bindPassword", false, 1, 251 INFO_DELIVER_OTP_PLACEHOLDER_PASSWORD.get(), 252 INFO_DELIVER_OTP_DESCRIPTION_BIND_PW.get()); 253 bindPassword.setSensitive(true); 254 bindPassword.setArgumentGroupName(INFO_DELIVER_OTP_GROUP_ID_AND_AUTH.get()); 255 bindPassword.addLongIdentifier("bind-password", true); 256 parser.addArgument(bindPassword); 257 258 bindPasswordFile = new FileArgument('j', "bindPasswordFile", false, 1, 259 INFO_DELIVER_OTP_PLACEHOLDER_PATH.get(), 260 INFO_DELIVER_OTP_DESCRIPTION_BIND_PW_FILE.get(), true, true, true, 261 false); 262 bindPasswordFile.setArgumentGroupName( 263 INFO_DELIVER_OTP_GROUP_ID_AND_AUTH.get()); 264 bindPasswordFile.addLongIdentifier("bind-password-file", true); 265 parser.addArgument(bindPasswordFile); 266 267 promptForBindPassword = new BooleanArgument(null, "promptForBindPassword", 268 1, INFO_DELIVER_OTP_DESCRIPTION_BIND_PW_PROMPT.get()); 269 promptForBindPassword.setArgumentGroupName( 270 INFO_DELIVER_OTP_GROUP_ID_AND_AUTH.get()); 271 promptForBindPassword.addLongIdentifier("prompt-for-bind-password", true); 272 parser.addArgument(promptForBindPassword); 273 274 deliveryMechanism = new StringArgument('m', "deliveryMechanism", false, 0, 275 INFO_DELIVER_OTP_PLACEHOLDER_NAME.get(), 276 INFO_DELIVER_OTP_DESCRIPTION_MECH.get()); 277 deliveryMechanism.setArgumentGroupName( 278 INFO_DELIVER_OTP_GROUP_DELIVERY_MECH.get()); 279 deliveryMechanism.addLongIdentifier("delivery-mechanism", true); 280 parser.addArgument(deliveryMechanism); 281 282 messageSubject = new StringArgument('s', "messageSubject", false, 1, 283 INFO_DELIVER_OTP_PLACEHOLDER_SUBJECT.get(), 284 INFO_DELIVER_OTP_DESCRIPTION_SUBJECT.get()); 285 messageSubject.setArgumentGroupName( 286 INFO_DELIVER_OTP_GROUP_DELIVERY_MECH.get()); 287 messageSubject.addLongIdentifier("message-subject", true); 288 parser.addArgument(messageSubject); 289 290 fullTextBeforeOTP = new StringArgument('f', "fullTextBeforeOTP", false, 291 1, INFO_DELIVER_OTP_PLACEHOLDER_FULL_BEFORE.get(), 292 INFO_DELIVER_OTP_DESCRIPTION_FULL_BEFORE.get()); 293 fullTextBeforeOTP.setArgumentGroupName( 294 INFO_DELIVER_OTP_GROUP_DELIVERY_MECH.get()); 295 fullTextBeforeOTP.addLongIdentifier("full-text-before-otp", true); 296 parser.addArgument(fullTextBeforeOTP); 297 298 fullTextAfterOTP = new StringArgument('F', "fullTextAfterOTP", false, 299 1, INFO_DELIVER_OTP_PLACEHOLDER_FULL_AFTER.get(), 300 INFO_DELIVER_OTP_DESCRIPTION_FULL_AFTER.get()); 301 fullTextAfterOTP.setArgumentGroupName( 302 INFO_DELIVER_OTP_GROUP_DELIVERY_MECH.get()); 303 fullTextAfterOTP.addLongIdentifier("full-text-after-otp", true); 304 parser.addArgument(fullTextAfterOTP); 305 306 compactTextBeforeOTP = new StringArgument('c', "compactTextBeforeOTP", 307 false, 1, INFO_DELIVER_OTP_PLACEHOLDER_COMPACT_BEFORE.get(), 308 INFO_DELIVER_OTP_DESCRIPTION_COMPACT_BEFORE.get()); 309 compactTextBeforeOTP.setArgumentGroupName( 310 INFO_DELIVER_OTP_GROUP_DELIVERY_MECH.get()); 311 compactTextBeforeOTP.addLongIdentifier("compact-text-before-otp", true); 312 parser.addArgument(compactTextBeforeOTP); 313 314 compactTextAfterOTP = new StringArgument('C', "compactTextAfterOTP", 315 false, 1, INFO_DELIVER_OTP_PLACEHOLDER_COMPACT_AFTER.get(), 316 INFO_DELIVER_OTP_DESCRIPTION_COMPACT_AFTER.get()); 317 compactTextAfterOTP.setArgumentGroupName( 318 INFO_DELIVER_OTP_GROUP_DELIVERY_MECH.get()); 319 compactTextAfterOTP.addLongIdentifier("compact-text-after-otp", true); 320 parser.addArgument(compactTextAfterOTP); 321 322 323 // Either the bind DN or username must have been provided. 324 parser.addRequiredArgumentSet(bindDN, userName); 325 326 // Only one option may be used for specifying the user identity. 327 parser.addExclusiveArgumentSet(bindDN, userName); 328 329 // Only one option may be used for specifying the bind password. 330 parser.addExclusiveArgumentSet(bindPassword, bindPasswordFile, 331 promptForBindPassword); 332 } 333 334 335 336 /** 337 * {@inheritDoc} 338 */ 339 @Override() 340 protected boolean supportsAuthentication() 341 { 342 return false; 343 } 344 345 346 347 /** 348 * {@inheritDoc} 349 */ 350 @Override() 351 public boolean supportsInteractiveMode() 352 { 353 return true; 354 } 355 356 357 358 /** 359 * {@inheritDoc} 360 */ 361 @Override() 362 public boolean defaultsToInteractiveMode() 363 { 364 return true; 365 } 366 367 368 369 /** 370 * {@inheritDoc} 371 */ 372 @Override() 373 protected boolean supportsOutputFile() 374 { 375 return true; 376 } 377 378 379 380 /** 381 * Indicates whether this tool supports the use of a properties file for 382 * specifying default values for arguments that aren't specified on the 383 * command line. 384 * 385 * @return {@code true} if this tool supports the use of a properties file 386 * for specifying default values for arguments that aren't specified 387 * on the command line, or {@code false} if not. 388 */ 389 @Override() 390 public boolean supportsPropertiesFile() 391 { 392 return true; 393 } 394 395 396 397 /** 398 * Indicates whether the LDAP-specific arguments should include alternate 399 * versions of all long identifiers that consist of multiple words so that 400 * they are available in both camelCase and dash-separated versions. 401 * 402 * @return {@code true} if this tool should provide multiple versions of 403 * long identifiers for LDAP-specific arguments, or {@code false} if 404 * not. 405 */ 406 @Override() 407 protected boolean includeAlternateLongIdentifiers() 408 { 409 return true; 410 } 411 412 413 414 /** 415 * Indicates whether this tool should provide a command-line argument that 416 * allows for low-level SSL debugging. If this returns {@code true}, then an 417 * "--enableSSLDebugging}" argument will be added that sets the 418 * "javax.net.debug" system property to "all" before attempting any 419 * communication. 420 * 421 * @return {@code true} if this tool should offer an "--enableSSLDebugging" 422 * argument, or {@code false} if not. 423 */ 424 @Override() 425 protected boolean supportsSSLDebugging() 426 { 427 return true; 428 } 429 430 431 432 /** 433 * {@inheritDoc} 434 */ 435 @Override() 436 protected boolean logToolInvocationByDefault() 437 { 438 return true; 439 } 440 441 442 443 /** 444 * {@inheritDoc} 445 */ 446 @Override() 447 public ResultCode doToolProcessing() 448 { 449 // Construct the authentication identity. 450 final String authID; 451 if (bindDN.isPresent()) 452 { 453 authID = "dn:" + bindDN.getValue(); 454 } 455 else 456 { 457 authID = "u:" + userName.getValue(); 458 } 459 460 461 // Get the bind password. 462 final String pw; 463 if (bindPassword.isPresent()) 464 { 465 pw = bindPassword.getValue(); 466 } 467 else if (bindPasswordFile.isPresent()) 468 { 469 try 470 { 471 pw = new String(getPasswordFileReader().readPassword( 472 bindPasswordFile.getValue())); 473 } 474 catch (final Exception e) 475 { 476 Debug.debugException(e); 477 err(ERR_DELIVER_OTP_CANNOT_READ_BIND_PW.get( 478 StaticUtils.getExceptionMessage(e))); 479 return ResultCode.LOCAL_ERROR; 480 } 481 } 482 else 483 { 484 try 485 { 486 getOut().print(INFO_DELIVER_OTP_ENTER_PW.get()); 487 pw = StaticUtils.toUTF8String(PasswordReader.readPassword()); 488 getOut().println(); 489 } 490 catch (final Exception e) 491 { 492 Debug.debugException(e); 493 err(ERR_DELIVER_OTP_CANNOT_READ_BIND_PW.get( 494 StaticUtils.getExceptionMessage(e))); 495 return ResultCode.LOCAL_ERROR; 496 } 497 } 498 499 500 // Get the set of preferred delivery mechanisms. 501 final ArrayList<ObjectPair<String,String>> preferredDeliveryMechanisms; 502 if (deliveryMechanism.isPresent()) 503 { 504 final List<String> dmList = deliveryMechanism.getValues(); 505 preferredDeliveryMechanisms = new ArrayList<>(dmList.size()); 506 for (final String s : dmList) 507 { 508 preferredDeliveryMechanisms.add(new ObjectPair<String,String>(s, null)); 509 } 510 } 511 else 512 { 513 preferredDeliveryMechanisms = null; 514 } 515 516 517 // Get a connection to the directory server. 518 final LDAPConnection conn; 519 try 520 { 521 conn = getConnection(); 522 } 523 catch (final LDAPException le) 524 { 525 Debug.debugException(le); 526 err(ERR_DELIVER_OTP_CANNOT_GET_CONNECTION.get( 527 StaticUtils.getExceptionMessage(le))); 528 return le.getResultCode(); 529 } 530 531 try 532 { 533 // Create and send the extended request 534 final DeliverOneTimePasswordExtendedRequest request = 535 new DeliverOneTimePasswordExtendedRequest(authID, pw, 536 messageSubject.getValue(), fullTextBeforeOTP.getValue(), 537 fullTextAfterOTP.getValue(), compactTextBeforeOTP.getValue(), 538 compactTextAfterOTP.getValue(), preferredDeliveryMechanisms); 539 final DeliverOneTimePasswordExtendedResult result; 540 try 541 { 542 result = (DeliverOneTimePasswordExtendedResult) 543 conn.processExtendedOperation(request); 544 } 545 catch (final LDAPException le) 546 { 547 Debug.debugException(le); 548 err(ERR_DELIVER_OTP_ERROR_PROCESSING_EXTOP.get( 549 StaticUtils.getExceptionMessage(le))); 550 return le.getResultCode(); 551 } 552 553 if (result.getResultCode() == ResultCode.SUCCESS) 554 { 555 final String mechanism = result.getDeliveryMechanism(); 556 final String id = result.getRecipientID(); 557 if (id == null) 558 { 559 out(INFO_DELIVER_OTP_SUCCESS_RESULT_WITHOUT_ID.get(mechanism)); 560 } 561 else 562 { 563 out(INFO_DELIVER_OTP_SUCCESS_RESULT_WITH_ID.get(mechanism, id)); 564 } 565 566 final String message = result.getDeliveryMessage(); 567 if (message != null) 568 { 569 out(INFO_DELIVER_OTP_SUCCESS_MESSAGE.get(message)); 570 } 571 } 572 else 573 { 574 if (result.getDiagnosticMessage() == null) 575 { 576 err(ERR_DELIVER_OTP_ERROR_RESULT_NO_MESSAGE.get( 577 String.valueOf(result.getResultCode()))); 578 } 579 else 580 { 581 err(ERR_DELIVER_OTP_ERROR_RESULT.get( 582 String.valueOf(result.getResultCode()), 583 result.getDiagnosticMessage())); 584 } 585 } 586 587 return result.getResultCode(); 588 } 589 finally 590 { 591 conn.close(); 592 } 593 } 594 595 596 597 /** 598 * {@inheritDoc} 599 */ 600 @Override() 601 public LinkedHashMap<String[],String> getExampleUsages() 602 { 603 final LinkedHashMap<String[],String> exampleMap = 604 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2)); 605 606 String[] args = 607 { 608 "--hostname", "server.example.com", 609 "--port", "389", 610 "--bindDN", "uid=test.user,ou=People,dc=example,dc=com", 611 "--bindPassword", "password", 612 "--messageSubject", "Your one-time password", 613 "--fullTextBeforeOTP", "Your one-time password is '", 614 "--fullTextAfterOTP", "'.", 615 "--compactTextBeforeOTP", "Your OTP is '", 616 "--compactTextAfterOTP", "'.", 617 }; 618 exampleMap.put(args, 619 INFO_DELIVER_OTP_EXAMPLE_1.get()); 620 621 args = new String[] 622 { 623 "--hostname", "server.example.com", 624 "--port", "389", 625 "--userName", "test.user", 626 "--bindPassword", "password", 627 "--deliveryMechanism", "SMS", 628 "--deliveryMechanism", "E-Mail", 629 "--messageSubject", "Your one-time password", 630 "--fullTextBeforeOTP", "Your one-time password is '", 631 "--fullTextAfterOTP", "'.", 632 "--compactTextBeforeOTP", "Your OTP is '", 633 "--compactTextAfterOTP", "'.", 634 }; 635 exampleMap.put(args, 636 INFO_DELIVER_OTP_EXAMPLE_2.get()); 637 638 return exampleMap; 639 } 640}