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