001/* 002 * Copyright 2015-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2015-2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2015-2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds; 037 038 039 040import java.io.OutputStream; 041import java.io.Serializable; 042import java.util.ArrayList; 043import java.util.LinkedHashMap; 044import java.util.List; 045 046import com.unboundid.ldap.sdk.LDAPConnection; 047import com.unboundid.ldap.sdk.LDAPException; 048import com.unboundid.ldap.sdk.ResultCode; 049import com.unboundid.ldap.sdk.Version; 050import com.unboundid.ldap.sdk.unboundidds.extensions. 051 DeliverPasswordResetTokenExtendedRequest; 052import com.unboundid.ldap.sdk.unboundidds.extensions. 053 DeliverPasswordResetTokenExtendedResult; 054import com.unboundid.util.Debug; 055import com.unboundid.util.LDAPCommandLineTool; 056import com.unboundid.util.ObjectPair; 057import com.unboundid.util.StaticUtils; 058import com.unboundid.util.ThreadSafety; 059import com.unboundid.util.ThreadSafetyLevel; 060import com.unboundid.util.args.ArgumentException; 061import com.unboundid.util.args.ArgumentParser; 062import com.unboundid.util.args.DNArgument; 063import com.unboundid.util.args.StringArgument; 064 065import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*; 066 067 068 069/** 070 * This class provides a utility that may be used to request that the Directory 071 * Server deliver a single-use password reset token to a user through some 072 * out-of-band mechanism. 073 * <BR> 074 * <BLOCKQUOTE> 075 * <B>NOTE:</B> This class, and other classes within the 076 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 077 * supported for use against Ping Identity, UnboundID, and 078 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 079 * for proprietary functionality or for external specifications that are not 080 * considered stable or mature enough to be guaranteed to work in an 081 * interoperable way with other types of LDAP servers. 082 * </BLOCKQUOTE> 083 */ 084@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 085public final class DeliverPasswordResetToken 086 extends LDAPCommandLineTool 087 implements Serializable 088{ 089 /** 090 * The serial version UID for this serializable class. 091 */ 092 private static final long serialVersionUID = 5793619963770997266L; 093 094 095 096 // The DN of the user to whom the password reset token should be sent. 097 private DNArgument userDN; 098 099 // The text to include after the password reset token in the "compact" 100 // message. 101 private StringArgument compactTextAfterToken; 102 103 // The text to include before the password reset token in the "compact" 104 // message. 105 private StringArgument compactTextBeforeToken; 106 107 // The name of the mechanism through which the one-time password should be 108 // delivered. 109 private StringArgument deliveryMechanism; 110 111 // The text to include after the password reset token in the "full" message. 112 private StringArgument fullTextAfterToken; 113 114 // The text to include before the password reset token in the "full" message. 115 private StringArgument fullTextBeforeToken; 116 117 // The subject to use for the message containing the delivered token. 118 private StringArgument messageSubject; 119 120 121 122 /** 123 * Parse the provided command line arguments and perform the appropriate 124 * processing. 125 * 126 * @param args The command line arguments provided to this program. 127 */ 128 public static void main(final String... args) 129 { 130 final ResultCode resultCode = main(args, System.out, System.err); 131 if (resultCode != ResultCode.SUCCESS) 132 { 133 System.exit(resultCode.intValue()); 134 } 135 } 136 137 138 139 /** 140 * Parse the provided command line arguments and perform the appropriate 141 * processing. 142 * 143 * @param args The command line arguments provided to this program. 144 * @param outStream The output stream to which standard out should be 145 * written. It may be {@code null} if output should be 146 * suppressed. 147 * @param errStream The output stream to which standard error should be 148 * written. It may be {@code null} if error messages 149 * should be suppressed. 150 * 151 * @return A result code indicating whether the processing was successful. 152 */ 153 public static ResultCode main(final String[] args, 154 final OutputStream outStream, 155 final OutputStream errStream) 156 { 157 final DeliverPasswordResetToken tool = 158 new DeliverPasswordResetToken(outStream, errStream); 159 return tool.runTool(args); 160 } 161 162 163 164 /** 165 * Creates a new instance of this tool. 166 * 167 * @param outStream The output stream to which standard out should be 168 * written. It may be {@code null} if output should be 169 * suppressed. 170 * @param errStream The output stream to which standard error should be 171 * written. It may be {@code null} if error messages 172 * should be suppressed. 173 */ 174 public DeliverPasswordResetToken(final OutputStream outStream, 175 final OutputStream errStream) 176 { 177 super(outStream, errStream); 178 179 userDN = null; 180 compactTextAfterToken = null; 181 compactTextBeforeToken = null; 182 deliveryMechanism = null; 183 fullTextAfterToken = null; 184 fullTextBeforeToken = null; 185 messageSubject = null; 186 } 187 188 189 190 /** 191 * {@inheritDoc} 192 */ 193 @Override() 194 public String getToolName() 195 { 196 return "deliver-password-reset-token"; 197 } 198 199 200 201 /** 202 * {@inheritDoc} 203 */ 204 @Override() 205 public String getToolDescription() 206 { 207 return INFO_DELIVER_PW_RESET_TOKEN_TOOL_DESCRIPTION.get(); 208 } 209 210 211 212 /** 213 * {@inheritDoc} 214 */ 215 @Override() 216 public String getToolVersion() 217 { 218 return Version.NUMERIC_VERSION_STRING; 219 } 220 221 222 223 /** 224 * {@inheritDoc} 225 */ 226 @Override() 227 public void addNonLDAPArguments(final ArgumentParser parser) 228 throws ArgumentException 229 { 230 userDN = new DNArgument('b', "userDN", true, 1, 231 INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_DN.get(), 232 INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_USER_DN.get()); 233 userDN.setArgumentGroupName(INFO_DELIVER_PW_RESET_TOKEN_GROUP_ID.get()); 234 userDN.addLongIdentifier("user-dn", true); 235 parser.addArgument(userDN); 236 237 deliveryMechanism = new StringArgument('m', "deliveryMechanism", false, 0, 238 INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_NAME.get(), 239 INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_MECH.get()); 240 deliveryMechanism.setArgumentGroupName( 241 INFO_DELIVER_PW_RESET_TOKEN_GROUP_DELIVERY_MECH.get()); 242 deliveryMechanism.addLongIdentifier("delivery-mechanism", true); 243 parser.addArgument(deliveryMechanism); 244 245 messageSubject = new StringArgument('s', "messageSubject", false, 1, 246 INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_SUBJECT.get(), 247 INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_SUBJECT.get()); 248 messageSubject.setArgumentGroupName( 249 INFO_DELIVER_PW_RESET_TOKEN_GROUP_DELIVERY_MECH.get()); 250 messageSubject.addLongIdentifier("message-subject", true); 251 parser.addArgument(messageSubject); 252 253 fullTextBeforeToken = new StringArgument('f', "fullTextBeforeToken", false, 254 1, INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_FULL_BEFORE.get(), 255 INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_FULL_BEFORE.get()); 256 fullTextBeforeToken.setArgumentGroupName( 257 INFO_DELIVER_PW_RESET_TOKEN_GROUP_DELIVERY_MECH.get()); 258 fullTextBeforeToken.addLongIdentifier("full-text-before-token", true); 259 parser.addArgument(fullTextBeforeToken); 260 261 fullTextAfterToken = new StringArgument('F', "fullTextAfterToken", false, 262 1, INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_FULL_AFTER.get(), 263 INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_FULL_AFTER.get()); 264 fullTextAfterToken.setArgumentGroupName( 265 INFO_DELIVER_PW_RESET_TOKEN_GROUP_DELIVERY_MECH.get()); 266 fullTextAfterToken.addLongIdentifier("full-text-after-token", true); 267 parser.addArgument(fullTextAfterToken); 268 269 compactTextBeforeToken = new StringArgument('c', "compactTextBeforeToken", 270 false, 1, INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_COMPACT_BEFORE.get(), 271 INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_COMPACT_BEFORE.get()); 272 compactTextBeforeToken.setArgumentGroupName( 273 INFO_DELIVER_PW_RESET_TOKEN_GROUP_DELIVERY_MECH.get()); 274 compactTextBeforeToken.addLongIdentifier("compact-text-before-token", true); 275 parser.addArgument(compactTextBeforeToken); 276 277 compactTextAfterToken = new StringArgument('C', "compactTextAfterToken", 278 false, 1, INFO_DELIVER_PW_RESET_TOKEN_PLACEHOLDER_COMPACT_AFTER.get(), 279 INFO_DELIVER_PW_RESET_TOKEN_DESCRIPTION_COMPACT_AFTER.get()); 280 compactTextAfterToken.setArgumentGroupName( 281 INFO_DELIVER_PW_RESET_TOKEN_GROUP_DELIVERY_MECH.get()); 282 compactTextAfterToken.addLongIdentifier("compact-text-after-token", true); 283 parser.addArgument(compactTextAfterToken); 284 } 285 286 287 288 /** 289 * {@inheritDoc} 290 */ 291 @Override() 292 public boolean supportsInteractiveMode() 293 { 294 return true; 295 } 296 297 298 299 /** 300 * {@inheritDoc} 301 */ 302 @Override() 303 public boolean defaultsToInteractiveMode() 304 { 305 return true; 306 } 307 308 309 310 /** 311 * {@inheritDoc} 312 */ 313 @Override() 314 protected boolean supportsOutputFile() 315 { 316 return true; 317 } 318 319 320 321 /** 322 * {@inheritDoc} 323 */ 324 @Override() 325 protected boolean defaultToPromptForBindPassword() 326 { 327 return true; 328 } 329 330 331 332 /** 333 * Indicates whether this tool supports the use of a properties file for 334 * specifying default values for arguments that aren't specified on the 335 * command line. 336 * 337 * @return {@code true} if this tool supports the use of a properties file 338 * for specifying default values for arguments that aren't specified 339 * on the command line, or {@code false} if not. 340 */ 341 @Override() 342 public boolean supportsPropertiesFile() 343 { 344 return true; 345 } 346 347 348 349 /** 350 * Indicates whether the LDAP-specific arguments should include alternate 351 * versions of all long identifiers that consist of multiple words so that 352 * they are available in both camelCase and dash-separated versions. 353 * 354 * @return {@code true} if this tool should provide multiple versions of 355 * long identifiers for LDAP-specific arguments, or {@code false} if 356 * not. 357 */ 358 @Override() 359 protected boolean includeAlternateLongIdentifiers() 360 { 361 return true; 362 } 363 364 365 366 /** 367 * Indicates whether this tool should provide a command-line argument that 368 * allows for low-level SSL debugging. If this returns {@code true}, then an 369 * "--enableSSLDebugging}" argument will be added that sets the 370 * "javax.net.debug" system property to "all" before attempting any 371 * communication. 372 * 373 * @return {@code true} if this tool should offer an "--enableSSLDebugging" 374 * argument, or {@code false} if not. 375 */ 376 @Override() 377 protected boolean supportsSSLDebugging() 378 { 379 return true; 380 } 381 382 383 384 /** 385 * {@inheritDoc} 386 */ 387 @Override() 388 protected boolean logToolInvocationByDefault() 389 { 390 return true; 391 } 392 393 394 395 /** 396 * {@inheritDoc} 397 */ 398 @Override() 399 public ResultCode doToolProcessing() 400 { 401 // Get the set of preferred delivery mechanisms. 402 final ArrayList<ObjectPair<String,String>> preferredDeliveryMechanisms; 403 if (deliveryMechanism.isPresent()) 404 { 405 final List<String> dmList = deliveryMechanism.getValues(); 406 preferredDeliveryMechanisms = new ArrayList<>(dmList.size()); 407 for (final String s : dmList) 408 { 409 preferredDeliveryMechanisms.add(new ObjectPair<String,String>(s, null)); 410 } 411 } 412 else 413 { 414 preferredDeliveryMechanisms = null; 415 } 416 417 418 // Get a connection to the directory server. 419 final LDAPConnection conn; 420 try 421 { 422 conn = getConnection(); 423 } 424 catch (final LDAPException le) 425 { 426 Debug.debugException(le); 427 err(ERR_DELIVER_PW_RESET_TOKEN_CANNOT_GET_CONNECTION.get( 428 StaticUtils.getExceptionMessage(le))); 429 return le.getResultCode(); 430 } 431 432 try 433 { 434 // Create and send the extended request 435 final DeliverPasswordResetTokenExtendedRequest request = 436 new DeliverPasswordResetTokenExtendedRequest(userDN.getStringValue(), 437 messageSubject.getValue(), fullTextBeforeToken.getValue(), 438 fullTextAfterToken.getValue(), 439 compactTextBeforeToken.getValue(), 440 compactTextAfterToken.getValue(), preferredDeliveryMechanisms); 441 final DeliverPasswordResetTokenExtendedResult result; 442 try 443 { 444 result = (DeliverPasswordResetTokenExtendedResult) 445 conn.processExtendedOperation(request); 446 } 447 catch (final LDAPException le) 448 { 449 Debug.debugException(le); 450 err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_PROCESSING_EXTOP.get( 451 StaticUtils.getExceptionMessage(le))); 452 return le.getResultCode(); 453 } 454 455 if (result.getResultCode() == ResultCode.SUCCESS) 456 { 457 final String mechanism = result.getDeliveryMechanism(); 458 final String id = result.getRecipientID(); 459 if (id == null) 460 { 461 out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_RESULT_WITHOUT_ID.get( 462 mechanism)); 463 } 464 else 465 { 466 out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_RESULT_WITH_ID.get(mechanism, 467 id)); 468 } 469 470 final String message = result.getDeliveryMessage(); 471 if (message != null) 472 { 473 out(INFO_DELIVER_PW_RESET_TOKEN_SUCCESS_MESSAGE.get(message)); 474 } 475 } 476 else 477 { 478 if (result.getDiagnosticMessage() == null) 479 { 480 err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_RESULT_NO_MESSAGE.get( 481 String.valueOf(result.getResultCode()))); 482 } 483 else 484 { 485 err(ERR_DELIVER_PW_RESET_TOKEN_ERROR_RESULT.get( 486 String.valueOf(result.getResultCode()), 487 result.getDiagnosticMessage())); 488 } 489 } 490 491 return result.getResultCode(); 492 } 493 finally 494 { 495 conn.close(); 496 } 497 } 498 499 500 501 /** 502 * {@inheritDoc} 503 */ 504 @Override() 505 public LinkedHashMap<String[],String> getExampleUsages() 506 { 507 final LinkedHashMap<String[],String> exampleMap = 508 new LinkedHashMap<>(StaticUtils.computeMapCapacity(1)); 509 510 final String[] args = 511 { 512 "--hostname", "server.example.com", 513 "--port", "389", 514 "--bindDN", "uid=password.admin,ou=People,dc=example,dc=com", 515 "--bindPassword", "password", 516 "--userDN", "uid=test.user,ou=People,dc=example,dc=com", 517 "--deliveryMechanism", "SMS", 518 "--deliveryMechanism", "E-Mail", 519 "--messageSubject", "Your password reset token", 520 "--fullTextBeforeToken", "Your single-use password reset token is '", 521 "--fullTextAfterToken", "'.", 522 "--compactTextBeforeToken", "Your single-use password reset token is '", 523 "--compactTextAfterToken", "'.", 524 }; 525 exampleMap.put(args, 526 INFO_DELIVER_PW_RESET_TOKEN_EXAMPLE.get()); 527 528 return exampleMap; 529 } 530}