/* * 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-2018 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; } }