/*
* 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 2016-2020 Ping Identity Corporation
*/
package com.unboundid.directory.sdk.examples;
import com.unboundid.directory.sdk.broker.api.StoreAdapterPlugin;
import com.unboundid.directory.sdk.broker.config.StoreAdapterPluginConfig;
import com.unboundid.directory.sdk.broker.types.BrokerContext;
import com.unboundid.directory.sdk.broker.types.StorePreRetrieveRequestContext;
import com.unboundid.directory.sdk.broker.types.StorePreSearchRequestContext;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.scim2.common.exceptions.ScimException;
import com.unboundid.scim2.common.filters.Filter;
import com.unboundid.util.args.Argument;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.StringArgument;
import javax.servlet.http.HttpServletRequest;
/**
* This example provides an implementation of a Store Adapter Plugin
* which updates the SCIM filter on each request to add an additional
* filter component referencing a value from a HTTP request header.
*/
public class ExampleStoreAdapterPlugin
extends StoreAdapterPlugin
{
/**
* The name of a header which provides a value for the additional filter
* component.
*/
private String headerName;
/**
* The name of a native Store Adapter attribute to use in the additional
* filter component.
*/
private String attrName;
/**
* The name of the argument that will be used to specify the header name.
*/
private static final String ARG_NAME_HEADER_NAME = "header-name";
/**
* The name of the argument that will be used to specify the header name.
*/
private static final String ARG_NAME_ATTR_NAME = "attr-name";
/**
* Handle to the ServerContext object.
*/
private BrokerContext serverContext;
/**
* {@inheritDoc}
*/
@Override
public String getExtensionName()
{
return "Example Store Adapter Plugin";
}
/**
* {@inheritDoc}
*/
@Override
public String[] getExtensionDescription()
{
return new String[]
{
"This example provides an implementation of a Store Adapter Plugin " +
"which updates the SCIM filter on each request to add an " +
"additional filter component referencing a value from a HTTP request " +
"header."
};
}
/**
* {@inheritDoc}
*/
@Override
public void defineConfigArguments(final ArgumentParser parser)
throws ArgumentException
{
final Argument headerNameArg = new StringArgument(
null,
ARG_NAME_HEADER_NAME,
true,
1,
null,
"The name of the HTTP header providing a filter value");
final Argument attrNameArg = new StringArgument(
null,
ARG_NAME_ATTR_NAME,
true,
1,
null,
"The name of the native Store Adapter attribute to filter on");
parser.addArgument(headerNameArg);
parser.addArgument(attrNameArg);
}
/**
* Initializes this store adapter plugin. Any initialization should be
* performed here. This method should generally store the
* {@link BrokerContext} in a class member so that it can be used elsewhere
* in the implementation.
*
* @param serverContext A handle to the server context for the server in
* which this extension is running. Extensions should
* typically store this in a class member.
* @param config The general configuration for this object.
* @param parser The argument parser which has been initialized from
* the configuration for this store adapter plugin.
* @throws LDAPException If a problem occurs while initializing this store
* adapter plugin.
*/
@Override
public void initializeStoreAdapterPlugin(
final BrokerContext serverContext,
final StoreAdapterPluginConfig config,
final ArgumentParser parser)
throws LDAPException
{
this.serverContext = serverContext;
final StringArgument headerNameArg =
(StringArgument) parser.getNamedArgument(ARG_NAME_HEADER_NAME);
final StringArgument attrNameArg =
(StringArgument) parser.getNamedArgument(ARG_NAME_ATTR_NAME);
headerName = headerNameArg.getValue();
attrName = attrNameArg.getValue();
}
/**
* This method is called before searching for entries in the native data
* store.
*
* @param context The store adapter request context.
* @throws ScimException If thrown, the operation will be aborted and the
* corresponding SCIM ErrorResult will be returned to
* the client.
*/
@Override
public void preSearch(final StorePreSearchRequestContext context)
throws ScimException
{
// Get the HTTP request.
final HttpServletRequest request = context.getHttpServletRequest();
// Get the current request filter.
final Filter filter = context.getSCIMFilter();
// Create a new filter for the request.
Filter modifiedFilter = updateFilter(request, filter);
// Set the new filter on the request.
context.setSCIMFilter(modifiedFilter);
}
/**
* This method is called before an entry is retrieved from the native data
* store in the following cases:
* <UL>
* <LI>
* To fetch an entry for a SCIM retrieve operation.
* </LI>
* <LI>
* To fetch an entry to be updated during a SCIM update operation.
* </LI>
* <LI>
* To fetch an entry from a secondary store adapter for each primary store
* adapter entry obtained during a SCIM search.
* </LI>
* <LI>
* To fetch an entry from the primary store adapter for each secondary
* store adapter entry obtained when a SCIM search filter is processed
* by a secondary store adapter.
* </LI>
* </UL>
*
* @param context The store adapter request context.
* @throws ScimException If thrown, the operation will be aborted and a
* corresponding SCIM ErrorResponse will be returned to
* the client.
*/
@Override
public void preRetrieve(final StorePreRetrieveRequestContext context)
throws ScimException
{
// Get the HTTP request.
final HttpServletRequest request = context.getHttpServletRequest();
// Get the current request filter.
final Filter filter = context.getSCIMFilter();
// Create a new filter for the request.
Filter modifiedFilter = updateFilter(request, filter);
// Set the new filter on the request.
context.setSCIMFilter(modifiedFilter);
}
/**
* Modify the provided SCIM filter to add an additional filter component
* referencing a value from a HTTP request header.
* @param request The HTTP request.
* @param filter The SCIM filter to be modified.
* @return The modified filter.
* @throws ScimException If the provided filter cannot be parsed.
*/
private Filter updateFilter(final HttpServletRequest request,
final Filter filter)
throws ScimException
{
if (request == null)
{
// No servlet request. Return unchanged filter.
return filter;
}
// Get the header value.
final String filterValue =
getTrimmedValue(request.getHeader(headerName));
if (filterValue == null)
{
// No header value. Return unchanged filter.
return filter;
}
// Create an equality-match filter component.
final Filter additionalFilter = Filter.eq(attrName, filterValue);
// Create a new filter for the request.
Filter modifiedFilter;
if (filter == null)
{
modifiedFilter = additionalFilter;
}
else
{
modifiedFilter = Filter.and(filter, additionalFilter);
}
return modifiedFilter;
}
/**
* Removes leading and trailing space from the provided value, and returns
* {@code null} if the resulting value is empty. Returns null if the
* provided value is null.
*
* @param value The value to be trimmed of leading and trailing space.
* @return The trimmed value, or {@code null} if the provided value is null
* or the trimmed value is empty.
*/
private String getTrimmedValue(final String value)
{
if (value == null)
{
return null;
}
final String trimmedValue = value.trim();
if (trimmedValue.isEmpty())
{
return null;
}
return trimmedValue;
}
}