/*
* 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
*
*
* Copyright 2010-2014 UnboundID Corp.
*/
package com.unboundid.directory.sdk.examples.groovy;
import java.util.List;
import com.unboundid.directory.sdk.common.operation.SearchRequest;
import com.unboundid.directory.sdk.common.operation.UpdatableAddRequest;
import com.unboundid.directory.sdk.common.operation.UpdatableAddResult;
import com.unboundid.directory.sdk.common.operation.UpdatableCompareRequest;
import com.unboundid.directory.sdk.common.operation.UpdatableCompareResult;
import com.unboundid.directory.sdk.common.operation.UpdatableModifyRequest;
import com.unboundid.directory.sdk.common.operation.UpdatableModifyResult;
import com.unboundid.directory.sdk.common.operation.UpdatableModifyDNRequest;
import com.unboundid.directory.sdk.common.operation.UpdatableModifyDNResult;
import com.unboundid.directory.sdk.common.operation.UpdatableSearchRequest;
import com.unboundid.directory.sdk.common.operation.UpdatableSearchResult;
import com.unboundid.directory.sdk.common.schema.AttributeType;
import com.unboundid.directory.sdk.common.schema.Schema;
import com.unboundid.directory.sdk.common.types.ActiveOperationContext;
import com.unboundid.directory.sdk.common.types.ActiveSearchOperationContext;
import com.unboundid.directory.sdk.common.types.UpdatableEntry;
import com.unboundid.directory.sdk.ds.config.PluginConfig;
import com.unboundid.directory.sdk.ds.scripting.ScriptedPlugin;
import com.unboundid.directory.sdk.ds.types.DirectoryServerContext;
import com.unboundid.directory.sdk.ds.types.PreParsePluginResult;
import com.unboundid.directory.sdk.ds.types.SearchEntryPluginResult;
import com.unboundid.ldap.sdk.Control;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.Modification;
import com.unboundid.ldap.sdk.RDN;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.StringArgument;
/**
* This class provides a simple example of a scripted plugin which will attempt
* to prevent clients from interacting with a specified attribute. Any add,
* compare, modify, modify DN, or search request which references the specified
* attribute will be rejected, and the attribute will be automatically removed
* from any search result entries to be returned. It has one configuration
* argument:
* <UL>
* <LI>attribute -- The name or OID of the attribute to attempt to prevent the
* user from accessing.</LI>
* </UL>
*/
public final class ExampleScriptedPlugin
extends ScriptedPlugin
{
/**
* The name of the argument that will be used to specify the name of the
* attribute to search for the provided identifier.
*/
private static final String ARG_NAME_ATTR = "attribute";
/**
* A pre-parse plugin result that will be returned for requests that should be
* rejected.
*/
private static final PreParsePluginResult REJECT_REQUEST_RESULT =
new PreParsePluginResult(false, // Connection terminated
false, // Continue pre-parse plugin processing
true, // Send response immediately
true); // Skip core processing
// The attribute type definition for the target attribute.
private volatile AttributeType attributeType;
// The server context for the server in which this extension is running.
private DirectoryServerContext serverContext;
/**
* Creates a new instance of this plugin. All plugin implementations must
* include a default constructor, but any initialization should generally be
* done in the {@code initializePlugin} method.
*/
public ExampleScriptedPlugin()
{
// No implementation required.
}
/**
* Updates the provided argument parser to define any configuration arguments
* which may be used by this plugin. 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 plugin.
*
* @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 base DN for users.
Character shortIdentifier = null;
String longIdentifier = ARG_NAME_ATTR;
boolean required = true;
int maxOccurrences = 1;
String placeholder = "{attr}";
String description = "The name or OID of the attribute type to " +
"prevent clients from accessing.";
parser.addArgument(new StringArgument(shortIdentifier, longIdentifier,
required, maxOccurrences, placeholder, description));
}
/**
* Initializes this plugin.
*
* @param serverContext A handle to the server context for the server in
* which this extension is running.
* @param config The general configuration for this plugin.
* @param parser The argument parser which has been initialized from
* the configuration for this plugin.
*
* @throws LDAPException If a problem occurs while initializing this plugin.
*/
@Override()
public void initializePlugin(final DirectoryServerContext serverContext,
final PluginConfig config,
final ArgumentParser parser)
throws LDAPException
{
serverContext.debugInfo("Beginning plugin initialization");
this.serverContext = serverContext;
// Get the attribute type to be excluded.
attributeType = getAttributeType(serverContext, 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 plugin.
* @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 PluginConfig config,
final ArgumentParser parser,
final List<String> unacceptableReasons)
{
boolean acceptable = true;
// Make sure that the requested attribute type is defined in the schema.
try
{
getAttributeType(config.getServerContext(), parser);
}
catch (final LDAPException le)
{
serverContext.debugCaught(le);
unacceptableReasons.add(le.getMessage());
acceptable = false;
}
return acceptable;
}
/**
* Attempts to apply the configuration contained in the provided argument
* parser.
*
* @param config The general configuration for this plugin.
* @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 PluginConfig config,
final ArgumentParser parser,
final List<String> adminActionsRequired,
final List<String> messages)
{
ResultCode rc = ResultCode.SUCCESS;
// Get the attribute type to be excluded.
try
{
attributeType = getAttributeType(config.getServerContext(), parser);
}
catch (final LDAPException le)
{
serverContext.debugCaught(le);
messages.add(le.getMessage());
rc = le.getResultCode();
}
return rc;
}
/**
* Retrieves the definition for the attribute type to retrieve from the server
* schema.
*
* @param serverContext A handle to the server context for the server in
* which this plugin is running.
* @param parser The argument parser with the configuration for this
* plugin.
*
* @return The configured attribute type definition.
*
* @throws LDAPException If the specified attribute type is not defined in
* the server schema, or if a problem is encountered
* while retrieving it.
*/
private static AttributeType getAttributeType(
final DirectoryServerContext serverContext,
final ArgumentParser parser)
throws LDAPException
{
final StringArgument arg =
(StringArgument) parser.getNamedArgument(ARG_NAME_ATTR);
final String attrName = arg.getValue();
final Schema schema = serverContext.getSchema();
final AttributeType at = schema.getAttributeType(attrName, false);
if (at == null)
{
throw new LDAPException(ResultCode.OTHER,
"Attribute type " + attrName +
" is not defined in the server schema.");
}
return at;
}
/**
* Performs any cleanup which may be necessary when this plugin is to be taken
* out of service.
*/
@Override()
public void finalizePlugin()
{
// No finalization is required.
}
/**
* Performs any processing which may be necessary before the server starts
* processing for an add request.
*
* @param operationContext The context for the add operation.
* @param request The add request to be processed. It may be
* altered if desired.
* @param result The result that will be returned to the client if
* the plugin result indicates that processing on
* the operation should be interrupted. It may be
* altered if desired.
*
* @return Information about the result of the plugin processing.
*/
@Override()
public PreParsePluginResult doPreParse(
final ActiveOperationContext operationContext,
final UpdatableAddRequest request,
final UpdatableAddResult result)
{
// Examine the add request and see if the entry includes the target
// attribute type. If so, then reject the request.
final UpdatableEntry addEntry = request.getEntry();
if (addEntry.hasAttribute(attributeType))
{
result.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
result.setDiagnosticMessage(
"Unwilling to allow the client to add an entry containing the " +
attributeType.getNameOrOID() + " attribute type.");
return REJECT_REQUEST_RESULT;
}
else
{
return PreParsePluginResult.SUCCESS;
}
}
/**
* Performs any processing which may be necessary before the server starts
* processing for a compare request.
*
* @param operationContext The context for the compare operation.
* @param request The compare request to be processed. It may be
* altered if desired.
* @param result The result that will be returned to the client if
* the plugin result indicates that processing on
* the operation should be interrupted. It may be
* altered if desired.
*
* @return Information about the result of the plugin processing.
*/
@Override()
public PreParsePluginResult doPreParse(
final ActiveOperationContext operationContext,
final UpdatableCompareRequest request,
final UpdatableCompareResult result)
{
// Examine the compare request and see if uses the target attribute type.
// If so, then reject the request.
final String targetType = request.getAttributeType();
if (attributeType.hasNameOrOID(targetType))
{
result.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
result.setDiagnosticMessage("Unwilling to allow the client to perform " +
"a compare operation targeting the '" + targetType +
" attribute type.");
return REJECT_REQUEST_RESULT;
}
else
{
return PreParsePluginResult.SUCCESS;
}
}
/**
* Performs any processing which may be necessary before the server starts
* processing for a modify request.
*
* @param operationContext The context for the modify operation.
* @param request The modify request to be processed. It may be
* altered if desired.
* @param result The result that will be returned to the client if
* the plugin result indicates that processing on
* the operation should be interrupted. It may be
* altered if desired.
*
* @return Information about the result of the plugin processing.
*/
@Override()
public PreParsePluginResult doPreParse(
final ActiveOperationContext operationContext,
final UpdatableModifyRequest request,
final UpdatableModifyResult result)
{
// Look at the modifications and see if any of them references the target
// attribute type. If so, then reject the request.
final List<Modification> mods = request.getModifications();
for (final Modification m : mods)
{
final String attrName = m.getAttributeName();
if (attributeType.hasNameOrOID(attrName))
{
result.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
result.setDiagnosticMessage("Unwilling to allow the client to " +
"perform a modify operation targeting the '" + attrName +
" attribute type.");
return REJECT_REQUEST_RESULT;
}
}
return PreParsePluginResult.SUCCESS;
}
/**
* Performs any processing which may be necessary before the server starts
* processing for a modify DN request.
*
* @param operationContext The context for the modify DN operation.
* @param request The modify DN request to be processed. It may be
* altered if desired.
* @param result The result that will be returned to the client if
* the plugin result indicates that processing on
* the operation should be interrupted. It may be
* altered if desired.
*
* @return Information about the result of the plugin processing.
*/
@Override()
public PreParsePluginResult doPreParse(
final ActiveOperationContext operationContext,
final UpdatableModifyDNRequest request,
final UpdatableModifyDNResult result)
{
// Look at the request and ensure that the newRDN does not reference the
// target attribute.
final String newRDN = request.getNewRDN();
final RDN parsedNewRDN;
try
{
parsedNewRDN = new RDN(newRDN);
}
catch (final LDAPException le)
{
serverContext.debugCaught(le);
result.setResultCode(ResultCode.INVALID_DN_SYNTAX);
result.setDiagnosticMessage("Unable to parse the provided new RDN: " +
le.getExceptionMessage());
return REJECT_REQUEST_RESULT;
}
for (final String s : parsedNewRDN.getAttributeNames())
{
if (attributeType.hasNameOrOID(s))
{
result.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
result.setDiagnosticMessage("Unwilling to allow the client to " +
"perform a modify DN operation in which the new RDN references " +
"the " + s + " attribute type.");
return REJECT_REQUEST_RESULT;
}
}
return PreParsePluginResult.SUCCESS;
}
/**
* Performs any processing which may be necessary before the server starts
* processing for a search request.
*
* @param operationContext The context for the search operation.
* @param request The search request to be processed. It may be
* altered if desired.
* @param result The result that will be returned to the client if
* the plugin result indicates that processing on
* the operation should be interrupted. It may be
* altered if desired.
*
* @return Information about the result of the plugin processing.
*/
@Override()
public PreParsePluginResult doPreParse(
final ActiveSearchOperationContext operationContext,
final UpdatableSearchRequest request,
final UpdatableSearchResult result)
{
// Look at the filter and ensure that it does not reference the target
// attribute type.
if (filterContainsTargetAttribute(request.getFilter()))
{
result.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
result.setDiagnosticMessage("Unwilling to allow the client to " +
"perform a search which references attribute " +
attributeType.getNameOrOID() + " in the filter.");
return REJECT_REQUEST_RESULT;
}
else
{
return PreParsePluginResult.SUCCESS;
}
}
/**
* Indicates whether the provided search filter references the target
* attribute type.
*
* @param f The filter to examine.
*
* @return {@code true} if the provided filter references the target
* attribute type, or {@code false} if not.
*/
private boolean filterContainsTargetAttribute(final Filter f)
{
switch (f.getFilterType())
{
case Filter.FILTER_TYPE_AND:
case Filter.FILTER_TYPE_OR:
for (final Filter comp : f.getComponents())
{
if (filterContainsTargetAttribute(comp))
{
return true;
}
}
return false;
case Filter.FILTER_TYPE_NOT:
return filterContainsTargetAttribute(f.getNOTComponent());
default:
final String attrName = f.getAttributeName();
return ((attrName != null) && attributeType.hasNameOrOID(attrName));
}
}
/**
* Performs any processing which may be necessary before the server sends a
* search result entry to the client.
*
* @param operationContext The context for the search operation.
* @param request The search request being processed.
* @param result The result that will be returned to the client if
* the plugin result indicates that processing on
* the operation should be interrupted. It may be
* altered if desired.
* @param entry The entry to be returned to the client. It may
* be altered if desired.
* @param controls The set of controls to be included with the
* entry. It may be altered if desired.
*
* @return Information about the result of the plugin processing.
*/
@Override()
public SearchEntryPluginResult doSearchEntry(
final ActiveSearchOperationContext operationContext,
final SearchRequest request, final UpdatableSearchResult result,
final UpdatableEntry entry, final List<Control> controls)
{
// Remove the target attribute from the entry to return.
entry.removeAttribute(attributeType);
return SearchEntryPluginResult.SUCCESS;
}
}
|