/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * docs/licenses/cddl.txt * or http://www.opensource.org/licenses/cddl1.php. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * docs/licenses/cddl.txt. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2010-2023 Ping Identity Corporation */ package com.unboundid.directory.sdk.examples; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Random; import com.unboundid.asn1.ASN1OctetString; import com.unboundid.directory.sdk.common.types.Entry; import com.unboundid.directory.sdk.ds.api.PasswordGenerator; import com.unboundid.directory.sdk.ds.config.PasswordGeneratorConfig; import com.unboundid.directory.sdk.ds.types.DirectoryServerContext; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.util.ByteString; import com.unboundid.util.args.ArgumentException; import com.unboundid.util.args.ArgumentParser; import com.unboundid.util.args.IntegerArgument; import com.unboundid.util.args.StringArgument; /** * This class provides a simple example of a password generator which may be * used to generate a password with a specified length using a given character * set. It has two configuration arguments: * <UL> * <LI>charset -- The set of characters that may be included in the generated * password. If this is not specified, then it will default to the set of * lowercase and uppercase ASCII letters and ASCII numeric digits.</LI> * <LI>length -- The number of characters to include in the generated * password. If this is not specified, then it will default to a value of * eight.</LI> * </UL> */ public final class ExamplePasswordGenerator extends PasswordGenerator { /** * The name of the argument that will be used for the argument used to specify * the character set for the generated passwords. */ private static final String ARG_NAME_CHARSET = "charset"; /** * The name of the argument that will be used for the argument used to specify * the length for generated passwords. */ private static final String ARG_NAME_LENGTH = "length"; /** * The default character set that will be used for generated passwords. */ private static final String DEFAULT_CHARSET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; /** * The default length to use for generated passwords. */ private static final int DEFAULT_LENGTH = 8; // The set of characters to use in generated passwords. private volatile char[] passwordChars; // The server context for the server in which this extension is running. private DirectoryServerContext serverContext; // The length to use for generated passwords. private volatile int passwordLength; /** * Creates a new instance of this password generator. All password generator * implementations must include a default constructor, but any initialization * should generally be done in the {@code initializePasswordGenerator} method. */ public ExamplePasswordGenerator() { // No implementation required. } /** * Retrieves a human-readable name for this extension. * * @return A human-readable name for this extension. */ @Override() public String getExtensionName() { return "Example Password Generator"; } /** * Retrieves a human-readable description for this extension. Each element * of the array that is returned will be considered a separate paragraph in * generated documentation. * * @return A human-readable description for this extension, or {@code null} * or an empty array if no description should be available. */ @Override() public String[] getExtensionDescription() { return new String[] { "This password generator serves an example that may be used to " + "demonstrate the process for creating a third-party password " + "generator. It will generate passwords containing a specified " + "number of randomly-selected characters from a given character set." }; } /** * Updates the provided argument parser to define any configuration arguments * which may be used by this password generator. The argument parser may also * be updated to define relationships between arguments (e.g., to specify * required, exclusive, or dependent argument sets). * * @param parser The argument parser to be updated with the configuration * arguments which may be used by this password generator. * * @throws ArgumentException If a problem is encountered while updating the * provided argument parser. */ @Override() public void defineConfigArguments(final ArgumentParser parser) throws ArgumentException { // Add an argument that allows you to specify the set of allowed characters. Character shortIdentifier = null; String longIdentifier = ARG_NAME_CHARSET; boolean required = true; int maxOccurrences = 1; String placeholder = "{charset}"; String description = "The set of characters that may be included " + "in generated passwords."; String defaultValueStr = DEFAULT_CHARSET; parser.addArgument(new StringArgument(shortIdentifier, longIdentifier, required, maxOccurrences, placeholder, description, defaultValueStr)); // Add an argument that allows you to specify the length of generated // passwords. shortIdentifier = null; longIdentifier = ARG_NAME_LENGTH; required = true; maxOccurrences = 1; placeholder = "{length}"; description = "The number of characters to include in generated " + "passwords."; int lowerBound = 1; int upperBound = Integer.MAX_VALUE; int defaultValueInt = DEFAULT_LENGTH; parser.addArgument(new IntegerArgument(shortIdentifier, longIdentifier, required, maxOccurrences, placeholder, description, lowerBound, upperBound, defaultValueInt)); } /** * Initializes this password generator. * * @param serverContext A handle to the server context for the server in * which this extension is running. * @param config The general configuration for this password * generator. * @param parser The argument parser which has been initialized from * the configuration for this password generator. * * @throws LDAPException If a problem occurs while initializing this * password generator. */ @Override() public void initializePasswordGenerator( final DirectoryServerContext serverContext, final PasswordGeneratorConfig config, final ArgumentParser parser) throws LDAPException { serverContext.debugInfo("Beginning password generator initialization"); this.serverContext = serverContext; // The work we need to do is the same for the initial configuration as for // a configuration change, so we'll just call the same method in both cases. applyConfig(parser); } /** * Indicates whether the configuration contained in the provided argument * parser represents a valid configuration for this extension. * * @param config The general configuration for this password * generator. * @param parser The argument parser which has been initialized * with the proposed configuration. * @param unacceptableReasons A list that can be updated with reasons that * the proposed configuration is not acceptable. * * @return {@code true} if the proposed configuration is acceptable, or * {@code false} if not. */ @Override() public boolean isConfigurationAcceptable(final PasswordGeneratorConfig config, final ArgumentParser parser, final List<String> unacceptableReasons) { boolean acceptable = true; // The argument parser will handle most of the validation. The only // additional validation to perform is to ensure that the proposed character // set is not an empty string. final StringArgument charsetArg = (StringArgument) parser.getNamedArgument(ARG_NAME_CHARSET); if ((charsetArg != null) && charsetArg.isPresent()) { final String valueStr = charsetArg.getValue(); if (valueStr.length() == 0) { unacceptableReasons.add( "The password character set must not be empty."); acceptable = false; } } return acceptable; } /** * Attempts to apply the configuration contained in the provided argument * parser. * * @param config The general configuration for this password * generator. * @param parser The argument parser which has been * initialized with the new configuration. * @param adminActionsRequired A list that can be updated with information * about any administrative actions that may be * required before one or more of the * configuration changes will be applied. * @param messages A list that can be updated with information * about the result of applying the new * configuration. * * @return A result code that provides information about the result of * attempting to apply the configuration change. */ @Override() public ResultCode applyConfiguration(final PasswordGeneratorConfig config, final ArgumentParser parser, final List<String> adminActionsRequired, final List<String> messages) { // The work we need to do is the same for the initial configuration as for // a configuration change, so we'll just call the same method in both cases. applyConfig(parser); return ResultCode.SUCCESS; } /** * Applies the configuration contained in the provided argument parser. * * @param parser The argument parser with the configuration to apply. */ private void applyConfig(final ArgumentParser parser) { char[] newCharSet = DEFAULT_CHARSET.toCharArray(); int newLength = DEFAULT_LENGTH; final StringArgument charsetArg = (StringArgument) parser.getNamedArgument(ARG_NAME_CHARSET); if ((charsetArg != null) && charsetArg.isPresent()) { final String valueStr = charsetArg.getValue(); newCharSet = valueStr.toCharArray(); } final IntegerArgument lengthArg = (IntegerArgument) parser.getNamedArgument(ARG_NAME_LENGTH); if ((lengthArg != null) && lengthArg.isPresent()) { newLength = lengthArg.getValue(); } passwordChars = newCharSet; passwordLength = newLength; serverContext.debugInfo("Set the password character set to " + new String(passwordChars)); serverContext.debugInfo("Set the password length set to " + passwordLength); } /** * Performs any cleanup which may be necessary when this password generator is * to be taken out of service. */ @Override() public void finalizePasswordGenerator() { // No finalization is required. } /** * Generates a password for the provided user. * * @param userEntry The entry of the user for whom to generate the * password. * * @return The generated password. * * @throws LDAPException If a problem occurs while attempting to generate a * password for the user. */ @Override() public ByteString generatePassword(final Entry userEntry) throws LDAPException { // Create local copies for the variables to protect against configuration // changes while generating a password. final char[] chars = passwordChars; final int length = passwordLength; // Create a new random number generator, since they aren't threadsafe. // We could use synchronization or thread-locals, but this is good enough. final Random random = new Random(); final StringBuilder buffer = new StringBuilder(length); for (int i=0; i < length; i++) { buffer.append(chars[random.nextInt(chars.length)]); } return new ASN1OctetString(buffer.toString()); } /** * Retrieves a map containing examples of configurations that may be used for * this extension. The map key should be a list of sample arguments, and the * corresponding value should be a description of the behavior that will be * exhibited by the extension when used with that configuration. * * @return A map containing examples of configurations that may be used for * this extension. It may be {@code null} or empty if there should * not be any example argument sets. */ @Override() public Map<List<String>,String> getExamplesArgumentSets() { final LinkedHashMap<List<String>,String> exampleMap = new LinkedHashMap<List<String>,String>(1); exampleMap.put( Arrays.asList(ARG_NAME_CHARSET + "=abcdefghijklmnopqrstuvwxyz", ARG_NAME_LENGTH + "=10"), "Generate passwords with 10 lowercase ASCII alphabetic letters"); return exampleMap; } }