001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * docs/licenses/cddl.txt
011 * or http://www.opensource.org/licenses/cddl1.php.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * docs/licenses/cddl.txt.  If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 *      Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 *      Copyright 2021 Ping Identity Corporation
026 */
027package com.unboundid.directory.sdk.scim2.api;
028
029import com.unboundid.directory.sdk.broker.internal.BrokerExtension;
030import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider;
031import com.unboundid.directory.sdk.common.internal.Reconfigurable;
032import com.unboundid.directory.sdk.common.internal.UnboundIDExtension;
033import com.unboundid.directory.sdk.scim2.config.SCIMSubResourceTypeHandlerConfig;
034import com.unboundid.directory.sdk.scim2.types.SCIMCreateRequest;
035import com.unboundid.directory.sdk.scim2.types.SCIMDeleteRequest;
036import com.unboundid.directory.sdk.scim2.types.SCIMModifyRequest;
037import com.unboundid.directory.sdk.scim2.types.SCIMReplaceRequest;
038import com.unboundid.directory.sdk.scim2.types.SCIMRetrieveRequest;
039import com.unboundid.directory.sdk.scim2.types.SCIMSearchRequest;
040import com.unboundid.directory.sdk.scim2.types.SCIMServerContext;
041import com.unboundid.ldap.sdk.ResultCode;
042import com.unboundid.scim2.common.ScimResource;
043import com.unboundid.scim2.common.exceptions.NotImplementedException;
044import com.unboundid.scim2.common.exceptions.ScimException;
045import com.unboundid.scim2.common.types.SchemaResource;
046import com.unboundid.util.Extensible;
047import com.unboundid.util.args.ArgumentException;
048import com.unboundid.util.args.ArgumentParser;
049
050import java.util.Collections;
051import java.util.List;
052import java.util.Map;
053
054/**
055 * This class defines an API that may be implemented by PingAuthorize Server
056 * extensions that need to extend the server's standard SCIM 2 capabilities.
057 * <p>
058 * A SCIM sub-resource type is a child type of a SCIM resource type. Where a
059 * SCIM resource type strictly provides a view of a data store record (such
060 * as an LDAP directory server entry), a SCIM sub-resource type may expose
061 * arbitrary capabilities or data associated with a parent SCIM resource type.
062 * <p>
063 * A SCIM sub-resource type REST API endpoint is accessed as a child path of
064 * the parent SCIM resource. For example, a SCIM Sub-Resource Type Handler that
065 * provides an account state management API for a "User" SCIM resource type
066 * might be accessed via the path <i>/scim/v2/Users/{resourceId}/account</i>.
067 *
068 * <h2>One-to-one vs. one-to-many relationships</h2>
069 *
070 * A SCIM sub-resource type may have a <i>one-to-one</i> relationship with its
071 * parent resource type, meaning that only one sub-resource exists per parent
072 * SCIM resource, or it may have a <i>one-to-many</i> relationship, meaning
073 * that multiple sub-resources may exist parent SCIM resource.
074 *
075 * Each resource belonging to a one-to-many SCIM sub-resource type should have
076 * a unique identifier, just as each resource of a SCIM resource type has a
077 * unique identifier. For example, a SCIM sub-resource representing a user's
078 * pet cat might have the path
079 * <i>/scim/v2/Users/{resourceId}/Cats/{subresourceId}</i>, where
080 * "<i>{subresourceId}</i>" is the cat resource's unique identifier.
081 *
082 * <h2>Configuring SCIM Sub-Resource Type Handlers</h2>
083 *
084 * In order to configure a SCIM Sub-Resource Type Handler created using this
085 * API, use a command like:
086 * <pre>
087 *      dsconfig create-scim-sub-resource-type-handler \
088 *           --type-name "<i>{parent-resource-type}</i> \
089 *           ---handler-name "<i>{name}</i>" \
090 *           --type third-party \
091 *           --set enabled:true \
092 *           --set "endpoint:<i>{endpoint}</i>" \
093 *           --set "extension-class:<i>{class-name}</i>" \
094 *           --set "extension-argument:<i>{name=value}</i>"
095 * </pre>
096 * where "<i>{parent-resource-type}</i>" is the name of the parent SCIM
097 * Resource Type (such as "Users"), "<i>{name}</i>" is the name to use for the
098 * SCIM Sub-Resource Type Handler instance, "<i>{endpoint}</i>" is the HTTP
099 * addressable endpoint of the SCIM sub-resource type relative to the base
100 * resource URL, "<i>{class-name}</i>" is the fully-qualified name of the Java
101 * class that extends
102 * {@code com.unboundid.directory.sdk.scim2.api.SCIMSubResourceTypeHandler},
103 * and "<i>{name=value}</i>" represents name-value pairs for any arguments to
104 * provide to the SCIM Sub-Resource Type Handler. If multiple arguments should
105 * be provided to the extension, then the
106 * "<code>--set extension-argument:<i>{name=value}</i></code>" option should be
107 * provided multiple times.
108 */
109@Extensible()
110@BrokerExtension()
111public abstract class SCIMSubResourceTypeHandler
112    implements UnboundIDExtension,
113    Reconfigurable<SCIMSubResourceTypeHandlerConfig>, ExampleUsageProvider
114{
115  /**
116   * {@inheritDoc}
117   */
118  @Override
119  public abstract String getExtensionName();
120
121  /**
122   * {@inheritDoc}
123   */
124  @Override
125  public abstract String[] getExtensionDescription();
126
127  /**
128   * {@inheritDoc}
129   */
130  @Override
131  public void defineConfigArguments(final ArgumentParser parser)
132      throws ArgumentException
133  {
134    // No arguments will be allowed by default.
135  }
136
137  /**
138   * {@inheritDoc}
139   */
140  @Override
141  public Map<List<String>, String> getExamplesArgumentSets()
142  {
143    // No example arguments will be provided by default.
144    return null;
145  }
146
147  /**
148   * {@inheritDoc}
149   */
150  @Override
151  public boolean isConfigurationAcceptable(
152      final SCIMSubResourceTypeHandlerConfig config,
153      final ArgumentParser parser,
154
155      final List<String> unacceptableReasons)
156  {
157    // No extended validation will be performed by default.
158    return true;
159  }
160
161  /**
162   * {@inheritDoc}
163   */
164  @Override
165  public ResultCode applyConfiguration(
166      final SCIMSubResourceTypeHandlerConfig config,
167      final ArgumentParser parser,
168      final List<String> adminActionsRequired,
169      final List<String> messages)
170  {
171    // By default, no configuration changes will be applied. If there are any
172    // arguments, then add an admin action message indicating that the extension
173    // needs to be restarted for any changes to take effect.
174    if (!parser.getNamedArguments().isEmpty())
175    {
176      adminActionsRequired.add(
177          "No configuration change has actually been applied. The new " +
178              "configuration will not take effect until this SCIM " +
179              "Sub-Resource Type Handler is disabled and re-enabled or until " +
180              "the server is restarted.");
181    }
182
183    return ResultCode.SUCCESS;
184  }
185
186  /**
187   * Initializes this SCIM Sub-Resource Type Handler.
188   *
189   * @param serverContext  A handle to the server context for the server in
190   *                       which this extension is running.
191   * @param config         The configuration for this SCIM Sub-Resource Type
192   *                       Handler.
193   * @param parser         The argument parser which has been initialized from
194   *                       the configuration for this SCIM Sub-Resource Type
195   *                       Handler.
196   * @throws Exception     If a problem occurs while initializing this SCIM
197   *                       Sub-Resource Type Handler.
198   */
199  public abstract void initializeHandler(
200      final SCIMServerContext serverContext,
201      final SCIMSubResourceTypeHandlerConfig config,
202      final ArgumentParser parser) throws Exception;
203
204  /**
205   * Performs any cleanup that might be necessary when this SCIM Sub-Resource
206   * Type Handler is taken out of service.
207   */
208  public void finalizeHandler()
209  {
210    // Do nothing by default.
211  }
212
213  /**
214   * Gets the SCIM Sub-Resource Type's core schema.
215   *
216   * @return The core schema for the SCIM Sub-Resource Type.
217   *         This must not return {@code null}.
218   */
219  public abstract SchemaResource getCoreSchema();
220
221  /**
222   * Gets the SCIM Sub-Resource Type's schema extensions, if any.
223   * <p>
224   * By default, this method will return an empty map, indicating that no
225   * schema extensions are supported.
226   *
227   * @return A map of schema extensions, where each key is a SchemaResource and
228   *         the value is a boolean indicating whether the schema extension is
229   *         required.
230   */
231  public Map<SchemaResource, Boolean> getSchemaExtensions()
232  {
233    return Collections.emptyMap();
234  }
235
236  /**
237   * Indicates whether the SCIM Sub-Resource Type implemented by this handler
238   * is supports one and only one sub-resource per parent resource or supports
239   * multiple sub-resources per parent resource.
240   *
241   * @return True if this SCIM Sub-Resource Type Handler supports multiple
242   *         sub-resources per parent resource, or false if it supports a
243   *         single sub-resource per parent resource.
244   */
245  public abstract boolean supportsOneToMany();
246
247  /**
248   * Handles SCIM create requests.
249   * <p>
250   * By default, this method will throw a {@link NotImplementedException} if
251   * it is not overridden.
252   *
253   * @param request         A SCIM create request.
254   * @return                The created sub-resource.
255   * @throws ScimException  If an error occurs.
256   */
257  public ScimResource create(final SCIMCreateRequest request)
258      throws ScimException
259  {
260    throw new NotImplementedException(notImplementedMessage("Create"));
261  }
262
263  /**
264   * Handles SCIM retrieve requests.
265   * <p>
266   * By default, this method will throw a {@link NotImplementedException} if
267   * it is not overridden.
268   *
269   * @param request         A SCIM retrieve request.
270   * @return                The retrieved sub-resource.
271   * @throws ScimException  If an error occurs.
272   */
273  public ScimResource retrieve(final SCIMRetrieveRequest request)
274      throws ScimException
275  {
276    throw new NotImplementedException(notImplementedMessage("Retrieve"));
277  }
278
279  /**
280   * Handles SCIM modify requests.
281   * <p>
282   * By default, this method will throw a {@link NotImplementedException} if
283   * it is not overridden.
284   *
285   * @param request         A SCIM modify request.
286   * @return                The modified sub-resource.
287   * @throws ScimException  If an error occurs.
288   */
289  public ScimResource modify(final SCIMModifyRequest request)
290      throws ScimException
291  {
292    throw new NotImplementedException(notImplementedMessage("Modify"));
293  }
294
295  /**
296   * Handles SCIM replace requests.
297   * <p>
298   * By default, this method will throw a {@link NotImplementedException} if
299   * it is not overridden.
300   *
301   * @param request         A SCIM replace request.
302   * @return                The replaced sub-resource.
303   * @throws ScimException  If an error occurs.
304   */
305  public ScimResource replace(final SCIMReplaceRequest request)
306      throws ScimException
307  {
308    throw new NotImplementedException(notImplementedMessage("Replace"));
309  }
310
311  /**
312   * Handles SCIM delete requests.
313   * <p>
314   * By default, this method will throw a {@link NotImplementedException} if
315   * it is not overridden.
316   *
317   * @param request         A SCIM delete request.
318   * @throws ScimException  If an error occurs.
319   */
320  public void delete(final SCIMDeleteRequest request)
321      throws ScimException
322  {
323    throw new NotImplementedException(notImplementedMessage("Delete"));
324  }
325
326  /**
327   * Handles SCIM search requests.
328   * <p>
329   * By default, this method will throw a {@link NotImplementedException} if
330   * it is not overridden.
331   *
332   * @param request         A SCIM search request.
333   * @throws ScimException  If an error occurs.
334   */
335  public void search(final SCIMSearchRequest request)
336      throws ScimException
337  {
338    throw new NotImplementedException(notImplementedMessage("Search"));
339  }
340
341  /**
342   * Returns a human-readable message to be used when throwing a
343   * {@link NotImplementedException}.
344   *
345   * @param operationType  The SCIM operation type, such as "Retrieve".
346   *
347   * @return               A human-readable message to be used when throwing a
348   *                       {@link NotImplementedException}.
349   */
350  private String notImplementedMessage(final String operationType)
351  {
352    return String.format("%s operations are not supported by the '%s' " +
353            "sub-resource", operationType, getExtensionName());
354  }
355}