/*
* 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 2021-2023 Ping Identity Corporation
*/
package com.unboundid.directory.sdk.examples;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.unboundid.directory.sdk.common.api.PassphraseProvider;
import com.unboundid.directory.sdk.common.config.PassphraseProviderConfig;
import com.unboundid.directory.sdk.common.types.ServerContext;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.NotNull;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.StringArgument;
/**
* This class provides an example passphrase provider implementation that may
* be used to obtain a passphrase from a specified Java property.
* <p>
* The passphrase provider supports the following configuration properties:
* <ul>
* <li>
* property-name -- The name of the Java property that holds the passphrase.
* This property must be defined and must have a non-empty value.
* </li>
* </ul>
*/
public final class ExamplePassphraseProvider
extends PassphraseProvider
{
/**
* The name of the extension argument that specifies the name of the Java
* property from which the passphrase will be read.
*/
private static final String ARG_NAME_PROPERTY_NAME = "property-name";
// The name of the property that holds the passphrase.
private volatile String propertyName;
/**
* Creates a new instance of this passphrase provider. All passphrase
* provider implementations must include a default constructor, but any
* initialization should generally be done in the
* {@code initializePassphraseProvider} method.
*/
public ExamplePassphraseProvider()
{
propertyName = null;
}
/**
* Retrieves a human-readable name for this extension.
*
* @return A human-readable name for this extension.
*/
@NotNull()
@Override()
public String getExtensionName()
{
return "Example Passphrase Provider";
}
/**
* 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.
*/
@NotNull()
@Override()
public String[] getExtensionDescription()
{
return new String[]
{
"An example passphrase provider that can retrieve a passphrase from a " +
"specified Java property that is defined in the JVM. The " +
"property must be defined and non-empty."
};
}
/**
* Updates the provided argument parser to define any configuration arguments
* which may be used by this extension. 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 extension.
*
* @throws ArgumentException If a problem is encountered while updating the
* provided argument parser.
*/
@Override()
public void defineConfigArguments(@NotNull final ArgumentParser parser)
throws ArgumentException
{
final Character shortIdentifier = null;
final String longIdentifier = ARG_NAME_PROPERTY_NAME;
final boolean required = true;
final int maxOccurrences = 1;
final String placeholder = "{propertyName}";
final String description = "The name of the Java property from " +
"which the passphrase will be read.";
parser.addArgument(new StringArgument(shortIdentifier, longIdentifier,
required, maxOccurrences, placeholder, description));
}
/**
* Initializes this passphrase provider.
*
* @param serverContext A handle to the server context for the server in
* which this extension is running.
* @param config The general configuration for this passphrase
* provider.
* @param parser The argument parser which has been initialized from
* the configuration for this passphrase provider.
*
* @throws LDAPException If a problem occurs while initializing this
* passphrase provider.
*/
@Override()
public void initializePassphraseProvider(
@NotNull final ServerContext serverContext,
@NotNull final PassphraseProviderConfig config,
@NotNull final ArgumentParser parser)
throws LDAPException
{
propertyName = validateConfigAndGetPropertyName(parser);
}
/**
* Indicates whether the configuration represented by the provided argument
* parser is acceptable for use by this extension. The parser will have been
* used to parse any configuration available for this extension, and any
* automatic validation will have been performed. This method may be used to
* perform any more complex validation which cannot be performed automatically
* by the argument parser.
*
* @param config The general configuration for this extension.
* @param parser The argument parser that has been used to
* parse the proposed configuration for this
* extension.
* @param unacceptableReasons A list to which messages may be added to
* provide additional information about why the
* provided configuration is not acceptable.
*
* @return {@code true} if the configuration in the provided argument parser
* appears to be acceptable, or {@code false} if not.
*/
@Override()
public boolean isConfigurationAcceptable(
@NotNull final PassphraseProviderConfig config,
@NotNull final ArgumentParser parser,
@NotNull final List<String> unacceptableReasons)
{
try
{
validateConfigAndGetPropertyName(parser);
return true;
}
catch (final LDAPException e)
{
config.getServerContext().debugCaught(e);
unacceptableReasons.add(e.getMessage());
return false;
}
}
/**
* Attempts to apply the configuration from the provided argument parser to
* this extension.
*
* @param config The general configuration for this extension.
* @param parser The argument parser that has been used to
* parse the new configuration for this
* extension.
* @param adminActionsRequired A list to which messages may be added to
* provide additional information about any
* additional administrative actions that may
* be required to apply some of the
* configuration changes.
* @param messages A list to which messages may be added to
* provide additional information about the
* processing performed by this method.
*
* @return A result code providing information about the result of applying
* the configuration change. A result of {@code SUCCESS} should be
* used to indicate that all processing completed successfully. Any
* other result will indicate that a problem occurred during
* processing.
*/
@NotNull()
@Override()
public ResultCode applyConfiguration(
@NotNull final PassphraseProviderConfig config,
@NotNull final ArgumentParser parser,
@NotNull final List<String> adminActionsRequired,
@NotNull final List<String> messages)
{
try
{
propertyName = validateConfigAndGetPropertyName(parser);
return ResultCode.SUCCESS;
}
catch (final LDAPException e)
{
config.getServerContext().debugCaught(e);
messages.add(e.getMessage());
return e.getResultCode();
}
}
/**
* Validates the configuration and retrieves the name of the property that
* will hold the passphrase.
*
* @param parser The argument parser that holds the configuration to
* validate.
*
* @return The name of the property that will hold the passphrase. It will
* not be {@code null}.
*
* @throws LDAPException If the configuration is not valid.
*/
@NotNull()
private static String validateConfigAndGetPropertyName(
@NotNull final ArgumentParser parser)
throws LDAPException
{
// Make sure that the property-name argument was specified.
final StringArgument propertyNameArg =
parser.getStringArgument(ARG_NAME_PROPERTY_NAME);
if ((propertyNameArg == null) || (! propertyNameArg.isPresent()))
{
throw new LDAPException(ResultCode.PARAM_ERROR,
"The required " + ARG_NAME_PROPERTY_NAME +
" argument was not provided.");
}
// Make sure that the specified property name is not empty.
final String propertyName = propertyNameArg.getValue();
if ((propertyName == null) || propertyName.isEmpty())
{
throw new LDAPException(ResultCode.PARAM_ERROR,
"The required " + ARG_NAME_PROPERTY_NAME +
" argument has an empty value.");
}
// Make sure that we can get the passphrase from the specified property.
getPassphraseFromProperty(propertyName);
return propertyName;
}
/**
* Performs any cleanup which may be necessary when this passphrase provider
* is to be taken out of service.
*/
@Override()
public void finalizePassphraseProvider()
{
// No special finalization is required.
}
/**
* Retrieves the passphrase.
*
* @param allowCached Indicates whether to allow the server to use a cached
* version of the passphrase.
*
* @return The passphrase that should be used. It must not be {@code null}
* or empty.
*
* @throws LDAPException If the passphrase cannot be retrieved.
*/
@Override()
@NotNull()
public char[] getPassphrase(final boolean allowCached)
throws LDAPException
{
return getPassphraseFromProperty(propertyName);
}
/**
* Retrieves the passphrase from the specified system property.
*
* @param propertyName The name of the system property from which the
* passphrase should be retrieved. It must not be
* {@code null}.
*
* @return The passphrase read from the specified system property.
*
* @throws LDAPException If the specified system property does not have a
* valid passphrase.
*/
@NotNull()
private static char[] getPassphraseFromProperty(
@NotNull final String propertyName)
throws LDAPException
{
if (propertyName == null)
{
// This should never happen in a properly initialized passphrase provider.
throw new LDAPException(ResultCode.OTHER,
"Unable to retrieve the passphrase from an uninitialized " +
"passphrase provider.");
}
final String propertyValue = System.getProperty(propertyName);
if (propertyValue == null)
{
throw new LDAPException(ResultCode.OTHER,
"Unable to retrieve a passphrase from system property '" +
propertyName + "' because that property is not defined in " +
"the JVM in which the server is running.");
}
if (propertyValue.isEmpty())
{
throw new LDAPException(ResultCode.OTHER,
"Unable to retrieve a passphrase from system property '" +
propertyName + "' because that property has an empty value.");
}
return propertyValue.toCharArray();
}
/**
* 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.
*/
@NotNull()
@Override()
public Map<List<String>,String> getExamplesArgumentSets()
{
return StaticUtils.mapOf(
Collections.singletonList(
"property-name=secret-passphrase"),
"Retrieves the passphrase from a Java system property named " +
"'secret-passphrase'.");
}
}