/* * 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 2017-2018 Ping Identity Corporation */ package com.unboundid.directory.sdk.examples; import com.fasterxml.jackson.databind.JsonNode; import com.unboundid.directory.sdk.broker.api.PolicyObligation; import com.unboundid.directory.sdk.broker.config.PolicyObligationConfig; import com.unboundid.directory.sdk.broker.types.BrokerContext; import com.unboundid.directory.sdk.broker.types.NotFulfilledException; import com.unboundid.directory.sdk.broker.types.ObligationContext; import com.unboundid.scim2.common.GenericScimResource; import com.unboundid.scim2.common.exceptions.ScimException; 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 com.unboundid.util.args.IntegerArgument; import java.util.List; /** * Example of a third-party Policy Obligation. This example can be applied * to any policy request with an action of "retrieve", which will include * SCIM GET operations as well as resources returned by SCIM POST, PUT, or * PATCH. The example implements a simple obfuscation scheme for one or more * attributes of the SCIM response, using a configurable obfuscation method. * * This example demonstrates the use of both "obligation arguments" and * extension arguments. Obligation arguments are defined using a JEXL * expression that is computed during the policy evaluation associated with * each SCIM request. The JEXL expression may reference values obtained from * the policy request context for each specific SCIM request. This example * takes a single obligation argument: * <UL> * <LI>attributePaths -- A JEXL expression that returns an array of JSON * paths, each of which selects a value from the SCIM resource that * is to be obfuscated. * </LI> * </UL> * * The following extension arguments are used to configure how obfuscation is * performed: * <UL> * <LI>obfuscateChar -- The character to use to overwrite a portion of the * obfuscated attribute. If not specified defaults to "X". * </LI> * <LI>count -- The number of characters to overwrite in the obfuscated * attribute, starting with the first character. * * </LI> * </UL> * * The following example dsconfig command creates an instance of this * obligation in a hypothetical example policy and rule. It will cause a * user's last name to be obfuscated by replacing the first three characters * with the letter 'Z': * <p> * <code> * dsconfig create-policy-obligation --obligation-name "Example Obligation" * --type third-party * --policy-name "Example Policy" * --rule-name "Example Rule" * --set extension-class:\ * com.unboundid.directory.sdk.examples.ExamplePolicyObligation * --set "obligation-arguments:attributePaths=['name.familyName']" * --set extension-argument:obfuscateChar=Z * --set extension-argument:count=3 * </code> * </p> */ public class ExamplePolicyObligation extends PolicyObligation { /** * The name of the obligation argument that will be used to specify a * list of paths to the attributes and/or sub-attributes to be * obfuscated. */ private static final String ARG_ATTRIBUTE_PATHS = "attributePaths"; /** * The name of the initialization argument that will be used to specify the * obfuscation character. */ private static final String ARG_OBFUSCATE_CHAR = "obfuscateChar"; /** * The name of the initialization argument that will be used to specify the * number of characters to obfuscate. */ private static final String ARG_COUNT = "count"; /** * The character to use for obfuscation. */ private Character obfuscationCharacter; /** * The number of characters to obfuscate. */ private int obfuscationCount; /** * Handle to the ServerContext object. */ private BrokerContext serverContext; /** * Creates a new instance of this policy obligation. All policy * obligation implementations must include a default constructor, * but any initialization should generally be performed in the * {@code initializePolicyObligation} method. */ public ExamplePolicyObligation() { } /** * Retrieves a human-readable name for this extension. * @return extension name */ @Override public String getExtensionName() { return "Example Policy Obligation"; } /** * Retrieves a human-readable description of this extension. * @return text description string */ @Override public String[] getExtensionDescription() { return new String[] { "This Policy Obligation implementation serves as an example that may" + " be used to demonstrate the process for creating a third-party" + " Policy Obligation. It implements a simple obfuscation of" + " a resource attribute. It takes two arguments at start-up:" + " 'obfuscate-char' contains the character to use for obfuscation,"+ " and 'count' contains the number of characters to replace with" + " the obfuscation-char (starting from the first character)." + " The attributes to be obfuscated are computed based on a JEXL" + " expression specified when an instance of this obligation is" + " created within a policy rule." }; } /** * Updates the provided argument parser to define any configuration arguments * which may be used by this policy obligation. 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 provider. * * @throws ArgumentException If a problem is encountered while updating the * provided argument parser. */ @Override public void defineConfigArguments(final ArgumentParser parser) throws ArgumentException { // obfuscate character is optional, default is 'X' final Argument obfuscateCharArg = new StringArgument( null, ARG_OBFUSCATE_CHAR, false, 1, null, "The character to use for obfuscation", "X"); // obfuscation count is optional, default is 4 final Argument obfuscateCountArg = new IntegerArgument( null, ARG_COUNT, false, 1, null, "The number of characters to replace with the obfuscation character", 1, Integer.MAX_VALUE, 4); parser.addArgument(obfuscateCharArg); parser.addArgument(obfuscateCountArg); } /** * Initializes this policy obligation. 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 policy obligation. * @throws Exception If a problem occurs while initializing this store * adapter plugin. */ @Override public void initializePolicyObligation( final BrokerContext serverContext, final PolicyObligationConfig config, final ArgumentParser parser) throws Exception { this.serverContext = serverContext; final StringArgument obfuscateCharArg = parser.getStringArgument(ARG_OBFUSCATE_CHAR); if (obfuscateCharArg.getValue().length() != 1) { throw new Exception( "Value of '" + ARG_OBFUSCATE_CHAR + "' must be a single character."); } this.obfuscationCharacter = obfuscateCharArg.getValue().charAt(0); final IntegerArgument obfuscateCountArg = parser.getIntegerArgument(ARG_COUNT); this.obfuscationCount = obfuscateCountArg.getValue(); } /** * This method is called before a SCIM resource is returned to the calling * client application. * @param context the ObligationContext containing any arguments passed * from policy. This example expects an argument with * name "attributePaths". * @param resource the SCIM resource. * @return the obfuscated resource * @throws NotFulfilledException if the obligation could not be fulfilled. * Throwing this exception will result in an unauthorized * response (401) to the calling client. * @throws ScimException if an internal error occurs trying to fulfill the * obligation. Throwing this exception will result in a * server exception response (500) to the calling client. */ @Override public GenericScimResource onScimRetrieve( final ObligationContext context, final GenericScimResource resource) throws NotFulfilledException, ScimException { List<String> attributePaths = context.getArgumentValue(ARG_ATTRIBUTE_PATHS); for (String attributePath : attributePaths) { JsonNode attributeNode = resource.getValue(attributePath); // if the attribute doesn't exist, there's nothing to do if (attributeNode == null || attributeNode.isNull()) { continue; } // if the attribute is not string-valued, the obligation cannot // be fulfilled. if (!attributeNode.isTextual()) { throw new NotFulfilledException("Attribute " + attributePath + " is not string-valued."); } String valueToObfuscate = attributeNode.textValue(); StringBuilder obfuscated = new StringBuilder(); for (int i = 0; i < valueToObfuscate.length(); i++) { if (i < obfuscationCount) { obfuscated.append(obfuscationCharacter); } else { obfuscated.append(valueToObfuscate.charAt(i)); } } resource.replaceValue(attributePath, obfuscated.toString()); } return resource; } }