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 * {@inheritDoc} 416 */ 417 @Override() 418 protected boolean logToolInvocationByDefault() 419 { 420 return true; 421 } 422 423 424 425 /** 426 * {@inheritDoc} 427 */ 428 @Override() 429 public ResultCode doToolProcessing() 430 { 431 // Construct the authentication identity. 432 final String authID; 433 if (bindDN.isPresent()) 434 { 435 authID = "dn:" + bindDN.getValue(); 436 } 437 else 438 { 439 authID = "u:" + userName.getValue(); 440 } 441 442 443 // Get the bind password. 444 final String pw; 445 if (bindPassword.isPresent()) 446 { 447 pw = bindPassword.getValue(); 448 } 449 else if (bindPasswordFile.isPresent()) 450 { 451 try 452 { 453 pw = new String(getPasswordFileReader().readPassword( 454 bindPasswordFile.getValue())); 455 } 456 catch (final Exception e) 457 { 458 Debug.debugException(e); 459 err(ERR_DELIVER_OTP_CANNOT_READ_BIND_PW.get( 460 StaticUtils.getExceptionMessage(e))); 461 return ResultCode.LOCAL_ERROR; 462 } 463 } 464 else 465 { 466 try 467 { 468 getOut().print(INFO_DELIVER_OTP_ENTER_PW.get()); 469 pw = StaticUtils.toUTF8String(PasswordReader.readPassword()); 470 getOut().println(); 471 } 472 catch (final Exception e) 473 { 474 Debug.debugException(e); 475 err(ERR_DELIVER_OTP_CANNOT_READ_BIND_PW.get( 476 StaticUtils.getExceptionMessage(e))); 477 return ResultCode.LOCAL_ERROR; 478 } 479 } 480 481 482 // Get the set of preferred delivery mechanisms. 483 final ArrayList<ObjectPair<String,String>> preferredDeliveryMechanisms; 484 if (deliveryMechanism.isPresent()) 485 { 486 final List<String> dmList = deliveryMechanism.getValues(); 487 preferredDeliveryMechanisms = new ArrayList<>(dmList.size()); 488 for (final String s : dmList) 489 { 490 preferredDeliveryMechanisms.add(new ObjectPair<String,String>(s, null)); 491 } 492 } 493 else 494 { 495 preferredDeliveryMechanisms = null; 496 } 497 498 499 // Get a connection to the directory server. 500 final LDAPConnection conn; 501 try 502 { 503 conn = getConnection(); 504 } 505 catch (final LDAPException le) 506 { 507 Debug.debugException(le); 508 err(ERR_DELIVER_OTP_CANNOT_GET_CONNECTION.get( 509 StaticUtils.getExceptionMessage(le))); 510 return le.getResultCode(); 511 } 512 513 try 514 { 515 // Create and send the extended request 516 final DeliverOneTimePasswordExtendedRequest request = 517 new DeliverOneTimePasswordExtendedRequest(authID, pw, 518 messageSubject.getValue(), fullTextBeforeOTP.getValue(), 519 fullTextAfterOTP.getValue(), compactTextBeforeOTP.getValue(), 520 compactTextAfterOTP.getValue(), preferredDeliveryMechanisms); 521 final DeliverOneTimePasswordExtendedResult result; 522 try 523 { 524 result = (DeliverOneTimePasswordExtendedResult) 525 conn.processExtendedOperation(request); 526 } 527 catch (final LDAPException le) 528 { 529 Debug.debugException(le); 530 err(ERR_DELIVER_OTP_ERROR_PROCESSING_EXTOP.get( 531 StaticUtils.getExceptionMessage(le))); 532 return le.getResultCode(); 533 } 534 535 if (result.getResultCode() == ResultCode.SUCCESS) 536 { 537 final String mechanism = result.getDeliveryMechanism(); 538 final String id = result.getRecipientID(); 539 if (id == null) 540 { 541 out(INFO_DELIVER_OTP_SUCCESS_RESULT_WITHOUT_ID.get(mechanism)); 542 } 543 else 544 { 545 out(INFO_DELIVER_OTP_SUCCESS_RESULT_WITH_ID.get(mechanism, id)); 546 } 547 548 final String message = result.getDeliveryMessage(); 549 if (message != null) 550 { 551 out(INFO_DELIVER_OTP_SUCCESS_MESSAGE.get(message)); 552 } 553 } 554 else 555 { 556 if (result.getDiagnosticMessage() == null) 557 { 558 err(ERR_DELIVER_OTP_ERROR_RESULT_NO_MESSAGE.get( 559 String.valueOf(result.getResultCode()))); 560 } 561 else 562 { 563 err(ERR_DELIVER_OTP_ERROR_RESULT.get( 564 String.valueOf(result.getResultCode()), 565 result.getDiagnosticMessage())); 566 } 567 } 568 569 return result.getResultCode(); 570 } 571 finally 572 { 573 conn.close(); 574 } 575 } 576 577 578 579 /** 580 * {@inheritDoc} 581 */ 582 @Override() 583 public LinkedHashMap<String[],String> getExampleUsages() 584 { 585 final LinkedHashMap<String[],String> exampleMap = 586 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2)); 587 588 String[] args = 589 { 590 "--hostname", "server.example.com", 591 "--port", "389", 592 "--bindDN", "uid=test.user,ou=People,dc=example,dc=com", 593 "--bindPassword", "password", 594 "--messageSubject", "Your one-time password", 595 "--fullTextBeforeOTP", "Your one-time password is '", 596 "--fullTextAfterOTP", "'.", 597 "--compactTextBeforeOTP", "Your OTP is '", 598 "--compactTextAfterOTP", "'.", 599 }; 600 exampleMap.put(args, 601 INFO_DELIVER_OTP_EXAMPLE_1.get()); 602 603 args = new String[] 604 { 605 "--hostname", "server.example.com", 606 "--port", "389", 607 "--userName", "test.user", 608 "--bindPassword", "password", 609 "--deliveryMechanism", "SMS", 610 "--deliveryMechanism", "E-Mail", 611 "--messageSubject", "Your one-time password", 612 "--fullTextBeforeOTP", "Your one-time password is '", 613 "--fullTextAfterOTP", "'.", 614 "--compactTextBeforeOTP", "Your OTP is '", 615 "--compactTextAfterOTP", "'.", 616 }; 617 exampleMap.put(args, 618 INFO_DELIVER_OTP_EXAMPLE_2.get()); 619 620 return exampleMap; 621 } 622}