001/* 002 * Copyright 2015-2023 Ping Identity Corporation 003 * 004 * This program is free software; you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License (GPLv2 only) 006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 007 * as published by the Free Software Foundation. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program; if not, see <http://www.gnu.org/licenses>. 016 */ 017 018package com.unboundid.scim2.client; 019 020import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; 021import com.unboundid.scim2.client.requests.CreateRequestBuilder; 022import com.unboundid.scim2.client.requests.DeleteRequestBuilder; 023import com.unboundid.scim2.client.requests.ModifyRequestBuilder; 024import com.unboundid.scim2.client.requests.ReplaceRequestBuilder; 025import com.unboundid.scim2.client.requests.RetrieveRequestBuilder; 026import com.unboundid.scim2.client.requests.SearchRequestBuilder; 027import com.unboundid.scim2.common.ScimResource; 028import com.unboundid.scim2.common.exceptions.ScimException; 029import com.unboundid.scim2.common.messages.ListResponse; 030import com.unboundid.scim2.common.messages.PatchOperation; 031import com.unboundid.scim2.common.messages.PatchRequest; 032import com.unboundid.scim2.common.types.Meta; 033import com.unboundid.scim2.common.types.ResourceTypeResource; 034import com.unboundid.scim2.common.types.SchemaResource; 035import com.unboundid.scim2.common.types.ServiceProviderConfigResource; 036import com.unboundid.scim2.common.utils.JsonUtils; 037 038import javax.ws.rs.client.WebTarget; 039import javax.ws.rs.core.MediaType; 040import java.net.URI; 041 042import static com.unboundid.scim2.common.utils.ApiConstants.MEDIA_TYPE_SCIM; 043import static com.unboundid.scim2.common.utils.ApiConstants.ME_ENDPOINT; 044import static com.unboundid.scim2.common.utils.ApiConstants. 045 RESOURCE_TYPES_ENDPOINT; 046import static com.unboundid.scim2.common.utils.ApiConstants.SCHEMAS_ENDPOINT; 047import static com.unboundid.scim2.common.utils.ApiConstants. 048 SERVICE_PROVIDER_CONFIG_ENDPOINT; 049 050/** 051 * The main entry point to the client API used to access a SCIM 2 service 052 * provider. 053 */ 054public class ScimService implements ScimInterface 055{ 056 /** 057 * The authenticated subject alias. 058 */ 059 public static final URI ME_URI = URI.create(ME_ENDPOINT); 060 061 /** 062 * The SCIM media type. 063 */ 064 public static final MediaType MEDIA_TYPE_SCIM_TYPE = 065 MediaType.valueOf(MEDIA_TYPE_SCIM); 066 067 private final WebTarget baseTarget; 068 private volatile ServiceProviderConfigResource serviceProviderConfig; 069 070 /** 071 * Create a new client instance to the SCIM 2 service provider at the 072 * provided WebTarget. The path of the WebTarget should be the base URI 073 * SCIM 2 service (ie. http://host/scim/v2). 074 * 075 * @param baseTarget The web target for the base URI of the SCIM 2 service 076 * provider. 077 */ 078 public ScimService(final WebTarget baseTarget) 079 { 080 this.baseTarget = baseTarget.register( 081 new JacksonJaxbJsonProvider(JsonUtils.createObjectMapper(), 082 JacksonJaxbJsonProvider.DEFAULT_ANNOTATIONS)); 083 } 084 085 /** 086 * Retrieve the service provider configuration. 087 * 088 * @return the service provider configuration. 089 * @throws ScimException if an error occurs. 090 */ 091 public ServiceProviderConfigResource getServiceProviderConfig() 092 throws ScimException 093 { 094 if(serviceProviderConfig == null) 095 { 096 serviceProviderConfig = retrieve( 097 baseTarget.path(SERVICE_PROVIDER_CONFIG_ENDPOINT).getUri(), 098 ServiceProviderConfigResource.class); 099 } 100 return serviceProviderConfig; 101 } 102 103 /** 104 * Retrieve the resource types supported by the service provider. 105 * 106 * @return The list of resource types supported by the service provider. 107 * @throws ScimException if an error occurs. 108 */ 109 public ListResponse<ResourceTypeResource> getResourceTypes() 110 throws ScimException 111 { 112 return searchRequest(RESOURCE_TYPES_ENDPOINT). 113 invoke(ResourceTypeResource.class); 114 } 115 116 /** 117 * Retrieve a known resource type supported by the service provider. 118 * 119 * @param name The name of the resource type. 120 * @return The resource type with the provided name. 121 * @throws ScimException if an error occurs. 122 */ 123 public ResourceTypeResource getResourceType(final String name) 124 throws ScimException 125 { 126 return retrieve(RESOURCE_TYPES_ENDPOINT, name, ResourceTypeResource.class); 127 } 128 129 /** 130 * Retrieve the schemas supported by the service provider. 131 * 132 * @return The list of schemas supported by the service provider. 133 * @throws ScimException if an error occurs. 134 */ 135 public ListResponse<SchemaResource> getSchemas() 136 throws ScimException 137 { 138 return searchRequest(SCHEMAS_ENDPOINT).invoke(SchemaResource.class); 139 } 140 141 /** 142 * Retrieve a known schema supported by the service provider. 143 * 144 * @param id The schema URN. 145 * @return The resource type with the provided URN. 146 * @throws ScimException if an error occurs. 147 */ 148 public SchemaResource getSchema(final String id) 149 throws ScimException 150 { 151 return retrieve(SCHEMAS_ENDPOINT, id, SchemaResource.class); 152 } 153 154 /** 155 * Create the provided new SCIM resource at the service provider. 156 * 157 * @param endpoint The resource endpoint such as: "{@code Users}" or "Groups" as 158 * defined by the associated resource type. 159 * @param resource The new resource to create. 160 * @param <T> The Java type of the resource. 161 * @return The successfully create SCIM resource. 162 * @throws ScimException if an error occurs. 163 */ 164 public <T extends ScimResource> T create( 165 final String endpoint, final T resource) throws ScimException 166 { 167 return createRequest(endpoint, resource).invoke(); 168 } 169 170 /** 171 * Retrieve a known SCIM resource from the service provider. 172 * 173 * @param endpoint The resource endpoint such as: "{@code Users}" or "{@code Groups}" as 174 * defined by the associated resource type. 175 * @param id The resource identifier (for example the value of the "{@code id}" 176 * attribute). 177 * @param cls The Java class object used to determine the type to return. 178 * @param <T> The Java type of the resource. 179 * @return The successfully retrieved SCIM resource. 180 * @throws ScimException if an error occurs. 181 */ 182 public <T extends ScimResource> T retrieve( 183 final String endpoint, final String id, final Class<T> cls) 184 throws ScimException 185 { 186 return retrieveRequest(endpoint, id).invoke(cls); 187 } 188 189 /** 190 * Retrieve a known SCIM resource from the service provider. 191 * 192 * @param url The URL of the resource to retrieve. 193 * @param cls The Java class object used to determine the type to return. 194 * @param <T> The Java type of the resource. 195 * @return The successfully retrieved SCIM resource. 196 * @throws ScimException if an error occurs. 197 */ 198 public <T extends ScimResource> T retrieve(final URI url, final Class<T> cls) 199 throws ScimException 200 { 201 return retrieveRequest(url).invoke(cls); 202 } 203 204 /** 205 * Retrieve a known SCIM resource from the service provider. If the 206 * service provider supports resource versioning and the resource has not been 207 * modified, the provided resource will be returned. 208 * 209 * @param resource The resource to retrieve. 210 * @param <T> The Java type of the resource. 211 * @return The successfully retrieved SCIM resource. 212 * @throws ScimException if an error occurs. 213 */ 214 public <T extends ScimResource> T retrieve(final T resource) 215 throws ScimException 216 { 217 RetrieveRequestBuilder.Generic<T> builder = retrieveRequest(resource); 218 return builder.invoke(); 219 } 220 221 /** 222 * Modify a SCIM resource by replacing the resource's attributes at the 223 * service provider. If the service provider supports resource versioning, 224 * the resource will only be modified if it has not been modified since it 225 * was retrieved. 226 * 227 * @param resource The previously retrieved and revised resource. 228 * @param <T> The Java type of the resource. 229 * @return The successfully replaced SCIM resource. 230 * @throws ScimException if an error occurs. 231 */ 232 public <T extends ScimResource> T replace( 233 final T resource) throws ScimException 234 { 235 ReplaceRequestBuilder<T> builder = replaceRequest(resource); 236 return builder.invoke(); 237 } 238 239 /** 240 * Delete a SCIM resource at the service provider. 241 * 242 * @param endpoint The resource endpoint such as: "{@code Users}" or "{@code Groups}" as 243 * defined by the associated resource type. 244 * @param id The resource identifier (for example the value of the "{@code id}" 245 * attribute). 246 * @throws ScimException if an error occurs. 247 */ 248 public void delete(final String endpoint, final String id) 249 throws ScimException 250 { 251 deleteRequest(endpoint, id).invoke(); 252 } 253 254 /** 255 * Delete a SCIM resource at the service provider. 256 * 257 * @param url The URL of the resource to delete. 258 * @throws ScimException if an error occurs. 259 */ 260 public void delete(final URI url) throws ScimException 261 { 262 deleteRequest(url).invoke(); 263 } 264 265 /** 266 * Delete a SCIM resource at the service provider. 267 * 268 * @param resource The resource to delete. 269 * @param <T> The Java type of the resource. 270 * @throws ScimException if an error occurs. 271 */ 272 public <T extends ScimResource> void delete(final T resource) 273 throws ScimException 274 { 275 DeleteRequestBuilder builder = deleteRequest(resource); 276 builder.invoke(); 277 } 278 279 /** 280 * Build a request to create the provided new SCIM resource at the service 281 * provider. 282 * 283 * @param endpoint The resource endpoint such as: "{@code Users}" or "{@code Groups}" as 284 * defined by the associated resource type. 285 * @param resource The new resource to create. 286 * @param <T> The Java type of the resource. 287 * @return The request builder that may be used to specify additional request 288 * parameters and to invoke the request. 289 */ 290 public <T extends ScimResource> CreateRequestBuilder<T> createRequest( 291 final String endpoint, final T resource) 292 { 293 return new CreateRequestBuilder<T>(baseTarget.path(endpoint), resource); 294 } 295 296 /** 297 * Build a request to retrieve a known SCIM resource from the service 298 * provider. 299 * 300 * @param endpoint The resource endpoint such as: "{@code Users}" or "{@code Groups}" as 301 * defined by the associated resource type. 302 * @param id The resource identifier (for example the value of the "{@code id}" 303 * attribute). 304 * @return The request builder that may be used to specify additional request 305 * parameters and to invoke the request. 306 */ 307 public RetrieveRequestBuilder.Typed retrieveRequest( 308 final String endpoint, final String id) 309 { 310 return new RetrieveRequestBuilder.Typed(baseTarget.path(endpoint).path(id)); 311 } 312 313 /** 314 * Build a request to retrieve a known SCIM resource from the service 315 * provider. 316 * 317 * @param url The URL of the resource to retrieve. 318 * @return The request builder that may be used to specify additional request 319 * parameters and to invoke the request. 320 */ 321 public RetrieveRequestBuilder.Typed retrieveRequest(final URI url) 322 { 323 return new RetrieveRequestBuilder.Typed(resolveWebTarget(url)); 324 } 325 326 /** 327 * Build a request to retrieve a known SCIM resource from the service 328 * provider. 329 * 330 * @param resource The resource to retrieve. 331 * @param <T> The Java type of the resource. 332 * @return The request builder that may be used to specify additional request 333 * parameters and to invoke the request. 334 */ 335 public <T extends ScimResource> RetrieveRequestBuilder.Generic<T> 336 retrieveRequest(final T resource) 337 { 338 return new RetrieveRequestBuilder.Generic<T>( 339 resolveWebTarget(checkAndGetLocation(resource)), resource); 340 } 341 342 /** 343 * Build a request to query and retrieve resources of a single resource type 344 * from the service provider. 345 * 346 * @param endpoint The resource endpoint such as: "{@code Users}" or "{@code Groups}" as 347 * defined by the associated resource type. 348 * @return The request builder that may be used to specify additional request 349 * parameters and to invoke the request. 350 */ 351 public SearchRequestBuilder searchRequest(final String endpoint) 352 { 353 return new SearchRequestBuilder(baseTarget.path(endpoint)); 354 } 355 356 /** 357 * Build a request to modify a SCIM resource by replacing the resource's 358 * attributes at the service provider. 359 * 360 * @param uri The URL of the resource to modify. 361 * @param resource The resource to replace. 362 * @param <T> The Java type of the resource. 363 * @return The request builder that may be used to specify additional request 364 * parameters and to invoke the request. 365 */ 366 public <T extends ScimResource> ReplaceRequestBuilder<T> replaceRequest( 367 final URI uri, final T resource) 368 { 369 return new ReplaceRequestBuilder<T>(resolveWebTarget(uri), resource); 370 } 371 372 /** 373 * Build a request to modify a SCIM resource by replacing the resource's 374 * attributes at the service provider. 375 * 376 * @param resource The previously retrieved and revised resource. 377 * @param <T> The Java type of the resource. 378 * @return The request builder that may be used to specify additional request 379 * parameters and to invoke the request. 380 */ 381 public <T extends ScimResource> ReplaceRequestBuilder<T> replaceRequest( 382 final T resource) 383 { 384 return new ReplaceRequestBuilder<T>( 385 resolveWebTarget(checkAndGetLocation(resource)), resource); 386 } 387 388 /** 389 * {@inheritDoc} 390 */ 391 @Override 392 public <T extends ScimResource> T modify(final String endpoint, 393 final String id, final PatchRequest patchRequest, final Class<T> clazz) 394 throws ScimException 395 { 396 ModifyRequestBuilder.Typed requestBuilder = new ModifyRequestBuilder.Typed( 397 baseTarget.path(endpoint).path(id)); 398 for(PatchOperation op : patchRequest.getOperations()) 399 { 400 requestBuilder.addOperation(op); 401 } 402 return requestBuilder.invoke(clazz); 403 } 404 405 /** 406 * Modify a SCIM resource by updating one or more attributes using a sequence 407 * of operations to "{@code add}", "{@code remove}", or "{@code replace}" values. The service provider 408 * configuration maybe used to discover service provider support for PATCH. 409 * 410 * @param endpoint The resource endpoint such as: "{@code Users}" or "{@code Groups}" as 411 * defined by the associated resource type. 412 * @param id The resource identifier (for example the value of the "{@code id}" 413 * attribute). 414 * @return The request builder that may be used to specify additional request 415 * parameters and to invoke the request. 416 */ 417 public ModifyRequestBuilder.Typed modifyRequest( 418 final String endpoint, final String id) 419 { 420 return new ModifyRequestBuilder.Typed( 421 baseTarget.path(endpoint).path(id)); 422 } 423 424 425 /** 426 * Modify a SCIM resource by updating one or more attributes using a sequence 427 * of operations to "{@code add}", "{@code remove}", or "{@code replace}" values. The service provider 428 * configuration maybe used to discover service provider support for PATCH. 429 * 430 * @param url The URL of the resource to modify. 431 * @return The request builder that may be used to specify additional request 432 * parameters and to invoke the request. 433 */ 434 public ModifyRequestBuilder.Typed modifyRequest( 435 final URI url) 436 { 437 return new ModifyRequestBuilder.Typed(resolveWebTarget(url)); 438 } 439 440 /** 441 * {@inheritDoc} 442 */ 443 @Override 444 public <T extends ScimResource> T modify( 445 final T resource, final PatchRequest patchRequest) throws ScimException 446 { 447 ModifyRequestBuilder.Generic<T> requestBuilder = 448 new ModifyRequestBuilder.Generic<T>(resolveWebTarget( 449 checkAndGetLocation(resource)), resource); 450 451 for(PatchOperation op : patchRequest.getOperations()) 452 { 453 requestBuilder.addOperation(op); 454 } 455 return requestBuilder.invoke(); 456 } 457 458 /** 459 * Modify a SCIM resource by updating one or more attributes using a sequence 460 * of operations to "{@code add}", "{@code remove}", or "{@code replace}" values. The service provider 461 * configuration maybe used to discover service provider support for PATCH. 462 * 463 * @param resource The resource to modify. 464 * @param <T> The Java type of the resource. 465 * @return The request builder that may be used to specify additional request 466 * parameters and to invoke the request. 467 */ 468 public <T extends ScimResource> ModifyRequestBuilder.Generic<T> modifyRequest( 469 final T resource) 470 { 471 return new ModifyRequestBuilder.Generic<T>( 472 resolveWebTarget(checkAndGetLocation(resource)), resource); 473 } 474 475 /** 476 * Build a request to delete a SCIM resource at the service provider. 477 * 478 * @param endpoint The resource endpoint such as: "{@code Users}" or "{@code Groups}" as 479 * defined by the associated resource type. 480 * @param id The resource identifier (for example the value of the "{@code id}" 481 * attribute). 482 * @return The request builder that may be used to specify additional request 483 * parameters and to invoke the request. 484 * @throws ScimException if an error occurs. 485 */ 486 public DeleteRequestBuilder deleteRequest( 487 final String endpoint, final String id) 488 throws ScimException 489 { 490 return new DeleteRequestBuilder(baseTarget.path(endpoint).path(id)); 491 } 492 493 /** 494 * Build a request to delete a SCIM resource at the service provider. 495 * 496 * @param url The URL of the resource to delete. 497 * @return The request builder that may be used to specify additional request 498 * parameters and to invoke the request. 499 * @throws ScimException if an error occurs. 500 */ 501 public DeleteRequestBuilder deleteRequest(final URI url) throws ScimException 502 { 503 return new DeleteRequestBuilder(resolveWebTarget(url)); 504 } 505 506 /** 507 * Build a request to delete a SCIM resource at the service provider. 508 * 509 * @param resource The resource to delete. 510 * @param <T> The Java type of the resource. 511 * @return The request builder that may be used to specify additional request 512 * parameters and to invoke the request. 513 * @throws ScimException if an error occurs. 514 */ 515 public <T extends ScimResource> DeleteRequestBuilder deleteRequest( 516 final T resource) 517 throws ScimException 518 { 519 return deleteRequest(checkAndGetLocation(resource)); 520 } 521 522 /** 523 * Resolve a URL (relative or absolute) to a web target. 524 * 525 * @param url The URL to resolve. 526 * @return The WebTarget. 527 */ 528 private WebTarget resolveWebTarget(final URI url) 529 { 530 URI relativePath; 531 if(url.isAbsolute()) 532 { 533 relativePath = baseTarget.getUri().relativize(url); 534 if (relativePath.equals(url)) 535 { 536 // The given resource's location is from another service provider 537 throw new IllegalArgumentException("Given resource's location " + 538 url + " is not under this service's " + 539 "base path " + baseTarget.getUri()); 540 } 541 } 542 else 543 { 544 relativePath = url; 545 } 546 547 return baseTarget.path(relativePath.getRawPath()); 548 } 549 550 /** 551 * Get the meta.location attribute value of the SCIM resource. 552 * 553 * @param resource The SCIM resource. 554 * @return The meta.location attribute value. 555 * @throws IllegalArgumentException if the resource does not contain the 556 * meta.location attribute value. 557 */ 558 private URI checkAndGetLocation(final ScimResource resource) 559 throws IllegalArgumentException 560 { 561 Meta meta = resource.getMeta(); 562 if(meta == null || meta.getLocation() == null) 563 { 564 throw new IllegalArgumentException( 565 "Resource URI must be specified by meta.location"); 566 } 567 return meta.getLocation(); 568 } 569 570 /** 571 * {@inheritDoc} 572 */ 573 public <T extends ScimResource> ListResponse<T> search(final String endpoint, 574 final String filter, final Class<T> clazz) throws ScimException 575 { 576 return searchRequest(endpoint).filter(filter).invoke(clazz); 577 } 578}