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}