/* * 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 * * * Portions Copyright 2022-2024 Ping Identity Corporation */ package com.unboundid.directory.sdk.examples; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.unboundid.directory.sdk.sync.api.SCIM2AttributeMapping; import com.unboundid.directory.sdk.sync.config.SCIM2AttributeMappingConfig; import com.unboundid.directory.sdk.sync.types.SyncServerContext; import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.util.args.ArgumentException; import com.unboundid.util.args.ArgumentParser; import com.unboundid.util.args.StringArgument; import com.unboundid.util.json.JSONArray; import com.unboundid.util.json.JSONField; import com.unboundid.util.json.JSONObject; import com.unboundid.util.json.JSONString; import com.unboundid.util.json.JSONValue; /** * This class provides a simple implementation of a SCIM 2.0 attribute mapping * for use by the Synchronization Server's SCIMv2 sync destination. It takes * an LDAP attribute whose value is a comma-delimited string and converts it to * a SCIM attribute whose value is an array of those strings. This extension * takes the following configuration argument: * <UL> * <LI> * ldap-attribute -- The name of the LDAP attribute whose value is the * comma-delimited string to be converted into SCIM array values. This is * a required argument. * </LI> * </UL> */ public final class ExampleSCIM2AttributeMapping extends SCIM2AttributeMapping { /** * The name of the argument used to specify the source LDAP attribute. */ public static final String ARGUMENT_NAME_LDAP_ATTRIBUTE = "ldap-attribute"; // The server context for this extension. private SyncServerContext serverContext = null; // The name of the source LDAP attribute. private String ldapAttributeName = null; // The name of the destination SCIM attribute. private String scimAttributeName = null; /** * Creates a new instance of this SCIM2 attribute mapping. All attribute * mapping implementations must include a default constructor, but any * initialization should generally be performed in the * {@code initializeAttributeMapping} method. */ public ExampleSCIM2AttributeMapping() { // No implementation required. } /** * Retrieves a human-readable name for this extension. * * @return A human-readable name for this extension. */ @Override() public String getExtensionName() { return "Example SCIM2 Attribute Mapping"; } /** * Retrieves a human-readable description for this extension. Each element * of the array that is returned will be considered a separate paragraph in * generated documentation. * * @return A human-readable description for this extension, or {@code null} * or an empty array if no description should be available. */ @Override() public String[] getExtensionDescription() { return new String[] { "This SCIM2 attribute mapping serves as an example that may be used " + "to demonstrate the process for creating a third-party SCIM2 " + "attribute mapping. It converts an LDAP attribute whose value " + "is a comma-delimited string and converts it to a SCIM attribute " + "whose value is an array of those strings." }; } /** * Retrieves a map containing examples of configurations that may be used for * this extension. The map key should be a list of sample arguments, and the * corresponding value should be a description of the behavior that will be * exhibited by the extension when used with that configuration. * * @return A map containing examples of configurations that may be used for * this extension. It may be {@code null} or empty if there should * not be any example argument sets. */ @Override() public Map<List<String>,String> getExamplesArgumentSets() { final Map<List<String>,String> examples = new LinkedHashMap<>(); final List<String> exampleArgumentList = Collections.singletonList("ldap-attribute=sourceAttributeName"); final String description = "Configures the example SCIM2 attribute " + "mapping so that it will convert values of the " + "'sourceAttributeName' source attribute from a comma-delimited list " + "to a SCIM array."; examples.put(exampleArgumentList, description); return examples; } /** * Updates the provided argument parser to define any configuration arguments * which may be used by this extension. 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 extension. * * @throws ArgumentException If a problem is encountered while updating the * provided argument parser. */ @Override() public void defineConfigArguments(final ArgumentParser parser) throws ArgumentException { final Character shortIdentifier = null; final String longIdentifier = ARGUMENT_NAME_LDAP_ATTRIBUTE; final boolean isRequired = true; final int maxOccurrences = 1; final String valuePlaceholder = "{attributeName}"; final String description = "The name of the source LDAP attribute whose " + "value should be converted from a comma-delimited list to a SCIM " + "array."; final StringArgument ldapAttributeArg = new StringArgument(shortIdentifier, longIdentifier, isRequired, maxOccurrences, valuePlaceholder, description); parser.addArgument(ldapAttributeArg); } /** * Performs any necessary initialization for this SCIM2 attribute mapping. * * @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 SCIM2 attribute mapping. * * @throws LDAPException If a problem occurs while initializing this SCIM2 * attribute mapping. */ public void initializeAttributeMapping( final SyncServerContext serverContext, final SCIM2AttributeMappingConfig config, final ArgumentParser parser) throws LDAPException { // The SCIM attribute name is provided in the config object. scimAttributeName = config.getSCIM2AttributeName(); // Get the LDAP attribute name from the argument parser. StringArgument ldapAttributeArgument = parser.getStringArgument(ARGUMENT_NAME_LDAP_ATTRIBUTE); if ((ldapAttributeArgument == null) || (! ldapAttributeArgument.isPresent())) { // This should never happen, as the argument was declared to be required. throw new LDAPException(ResultCode.PARAM_ERROR, "The required '" + ARGUMENT_NAME_LDAP_ATTRIBUTE + "' argument was not provided."); } ldapAttributeName = ldapAttributeArgument.getValue(); } /** * Indicates whether the configuration represented by the provided argument * parser is acceptable for use by this extension. The parser will have been * used to parse any configuration available for this extension, and any * automatic validation will have been performed. This method may be used to * perform any more complex validation which cannot be performed automatically * by the argument parser. * * @param config The general configuration for this extension. * @param parser The argument parser that has been used to * parse the proposed configuration for this * extension. * @param unacceptableReasons A list to which messages may be added to * provide additional information about why the * provided configuration is not acceptable. * * @return {@code true} if the configuration in the provided argument parser * appears to be acceptable, or {@code false} if not. */ @Override() public boolean isConfigurationAcceptable( final SCIM2AttributeMappingConfig config, final ArgumentParser parser, final List<String> unacceptableReasons) { // No extended validation will be performed by default. The argument // parser should automatically do all the necessary validation. return true; } /** * Attempts to apply the configuration from the provided argument parser to * this extension. * * @param config The general configuration for this extension. * @param parser The argument parser that has been used to * parse the new configuration for this * extension. * @param adminActionsRequired A list to which messages may be added to * provide additional information about any * additional administrative actions that may * be required to apply some of the * configuration changes. * @param messages A list to which messages may be added to * provide additional information about the * processing performed by this method. * * @return A result code providing information about the result of applying * the configuration change. A result of {@code SUCCESS} should be * used to indicate that all processing completed successfully. Any * other result will indicate that a problem occurred during * processing. */ @Override() public ResultCode applyConfiguration( final SCIM2AttributeMappingConfig config, final ArgumentParser parser, final List<String> adminActionsRequired, final List<String> messages) { // The SCIM attribute name is provided in the config object. scimAttributeName = config.getSCIM2AttributeName(); // Get the LDAP attribute name from the argument parser. StringArgument ldapAttributeArgument = parser.getStringArgument(ARGUMENT_NAME_LDAP_ATTRIBUTE); if ((ldapAttributeArgument != null) && ldapAttributeArgument.isPresent()) { ldapAttributeName = ldapAttributeArgument.getValue(); } return ResultCode.SUCCESS; } /** * Performs any necessary cleanup work when this attribute mapping it taken * out of service. */ @Override() public void finalizeAttributeMapping() { // No special cleanup is required for this attribute mapping implementation. } /** * Indicates whether this SCIM2 attribute mapping targets a single-valued * SCIM 2.0 attribute. * * @return {@code true} if this SCIM2 attribute mapping targets a * single-valued SCIM 2.0 attribute, or {@code false} if it targets * an attribute that may have multiple values or is defined as * multivalued in the schema. */ @Override() public boolean isSingleValued() { // This attribute will always return values as a SCIM array, so it's not // single-valued. return false; } /** * Constructs the SCIM2 representation of an attribute from the information * in the provided mapped LDAP representation of an entry as created by a * sync class. * * @param mappedLDAPEntry The mapped LDAP representation of an entry as * created by a sync class. It must not be * {@code null}. * * @return A JSON field that represents the SCIM 2.0 representation of the * attribute. It may be {@code null} if the provided entry does not * contain enough information to create the SCIM2 attribute (e.g., * if the associated LDAP attribute does not exist in the entry). * * @throws LDAPException If a problem occurs while attempting to create the * SCIM 2.0 representation of the attribute from the * provided LDAP entry. */ @Override() public JSONField createSCIM2Representation(final Entry mappedLDAPEntry) throws LDAPException { // Get the source attribute from the mapped LDAP entry. If there isn't // one, then return null to indicate that no corresponding SCIM attribute is // needed. final Attribute sourceAttribute = mappedLDAPEntry.getAttribute(ldapAttributeName); if (sourceAttribute == null) { return null; } // Break the source attribute value into the SCIM array of strings. final List<JSONString> arrayValues = new ArrayList<>(); for (final String sourceAttributeValue : sourceAttribute.getValues()) { final String[] commaDelimitedListItems = sourceAttributeValue.split(","); for (final String item : commaDelimitedListItems) { arrayValues.add(new JSONString(item)); } } return new JSONField(scimAttributeName, new JSONArray(arrayValues)); } /** * Updates the provided LDAP entry with information from the given JSON object * containing the SCIM 2.0 representation of the entry. * * @param scim2EntryID The identifier for the SCIM2 entry. * @param scim2Entry A JSON object containing the SCIM2 * representation of the entry. * @param updatableLDAPEntry An LDAP entry that may be updated with content * obtained from the SCIM2 representation of the * entry. * * @throws LDAPException If a problem occurs while attempting to create the * LDAP representation of the attribute from the * provided JSON object. */ @Override() public void createLDAPRepresentation(final String scim2EntryID, final JSONObject scim2Entry, final Entry updatableLDAPEntry) throws LDAPException { // See if the SCIM entry has the destination attribute. If not, then we // don't need to do anything. final List<JSONValue> scimArrayValues = scim2Entry.getFieldAsArray(scimAttributeName); if ((scimArrayValues == null) || scimArrayValues.isEmpty()) { return; } // Create a string that is simply a concatenation of the array values. final StringBuilder ldapValueBuffer = new StringBuilder(); final Iterator<JSONValue> valueIterator = scimArrayValues.iterator(); while (valueIterator.hasNext()) { final JSONValue scimArrayValue = valueIterator.next(); if (scimArrayValue instanceof JSONString) { ldapValueBuffer.append(((JSONString) scimArrayValue).stringValue()); } else { // The SCIM array had an element that isn't a string. This is not // expected and will be treated as an error. throw new LDAPException(ResultCode.PARAM_ERROR, "SCIM attribute '" + scimAttributeName + "' in entry '" + scim2EntryID + "' has a non-string array value."); } if (valueIterator.hasNext()) { ldapValueBuffer.append(','); } } updatableLDAPEntry.setAttribute(ldapAttributeName, ldapValueBuffer.toString()); } }