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}