001/* 002 * Copyright 2011-2013 UnboundID Corp. 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.scim.wink; 019 020import com.unboundid.scim.data.BaseResource; 021import com.unboundid.scim.marshal.Unmarshaller; 022import com.unboundid.scim.marshal.json.JsonUnmarshaller; 023import com.unboundid.scim.marshal.xml.XmlUnmarshaller; 024import com.unboundid.scim.schema.ResourceDescriptor; 025import com.unboundid.scim.sdk.AttributePath; 026import com.unboundid.scim.sdk.Debug; 027import com.unboundid.scim.sdk.DeleteResourceRequest; 028import com.unboundid.scim.sdk.GetResourceRequest; 029import com.unboundid.scim.sdk.GetResourcesRequest; 030import com.unboundid.scim.sdk.OAuthToken; 031import com.unboundid.scim.sdk.OAuthTokenHandler; 032import com.unboundid.scim.sdk.OAuthTokenStatus; 033import com.unboundid.scim.sdk.PageParameters; 034import com.unboundid.scim.sdk.PatchResourceRequest; 035import com.unboundid.scim.sdk.PostResourceRequest; 036import com.unboundid.scim.sdk.PutResourceRequest; 037import com.unboundid.scim.sdk.ResourceNotFoundException; 038import com.unboundid.scim.sdk.ResourceSchemaBackend; 039import com.unboundid.scim.sdk.Resources; 040import com.unboundid.scim.sdk.SCIMBackend; 041import com.unboundid.scim.sdk.SCIMException; 042import com.unboundid.scim.sdk.SCIMFilter; 043import com.unboundid.scim.sdk.SCIMQueryAttributes; 044import com.unboundid.scim.sdk.SCIMRequest; 045import com.unboundid.scim.sdk.SortParameters; 046import com.unboundid.scim.sdk.UnauthorizedException; 047 048import javax.ws.rs.core.HttpHeaders; 049import javax.ws.rs.core.MediaType; 050import javax.ws.rs.core.Response; 051import java.io.InputStream; 052import java.net.URI; 053import java.util.List; 054import java.util.concurrent.atomic.AtomicReference; 055 056import static com.unboundid.scim.sdk.SCIMConstants. 057 HEADER_NAME_ACCESS_CONTROL_ALLOW_CREDENTIALS; 058import static com.unboundid.scim.sdk.SCIMConstants. 059 HEADER_NAME_ACCESS_CONTROL_ALLOW_ORIGIN; 060import static com.unboundid.scim.sdk.SCIMConstants.QUERY_PARAMETER_ATTRIBUTES; 061import static com.unboundid.scim.sdk.SCIMConstants.RESOURCE_ENDPOINT_SCHEMAS; 062 063 064/** 065 * This class is an abstract Wink resource implementation for 066 * SCIM operations on a SCIM endpoint. 067 */ 068public abstract class AbstractSCIMResource extends AbstractStaticResource 069{ 070 private final SCIMApplication application; 071 072 /** 073 * The OAuth 2.0 bearer token handler. This may be null. 074 */ 075 private final OAuthTokenHandler tokenHandler; 076 077 private final ResourceSchemaBackend resourceSchemaBackend; 078 079 /** 080 * Create a new AbstractSCIMResource for CRUD operations. 081 * 082 * @param application The SCIM JAX-RS application associated with this 083 * resource. 084 * @param tokenHandler The token handler to use for OAuth 085 * authentication. 086 */ 087 public AbstractSCIMResource(final SCIMApplication application, 088 final OAuthTokenHandler tokenHandler) 089 { 090 this.application = application; 091 this.tokenHandler = tokenHandler; 092 this.resourceSchemaBackend = new ResourceSchemaBackend(application); 093 } 094 095 096 097 /** 098 * Process a GET operation. 099 * 100 * @param requestContext The request context. 101 * @param endpoint The endpoint requested. 102 * @param userID The user ID requested. 103 * 104 * @return The response to the operation. 105 */ 106 Response getUser(final RequestContext requestContext, 107 final String endpoint, final String userID) 108 { 109 SCIMBackend backend; 110 ResourceDescriptor resourceDescriptor = null; 111 Response.ResponseBuilder responseBuilder; 112 try { 113 backend = getBackend(endpoint); 114 resourceDescriptor = backend.getResourceDescriptor(endpoint); 115 if(resourceDescriptor == null) 116 { 117 throw new ResourceNotFoundException( 118 endpoint + " is not a valid resource endpoint"); 119 } 120 String authID = requestContext.getAuthID(); 121 if(authID == null && tokenHandler == null) { 122 throw new UnauthorizedException("Invalid credentials"); 123 } 124 final String attributes = 125 requestContext.getUriInfo().getQueryParameters().getFirst( 126 QUERY_PARAMETER_ATTRIBUTES); 127 final SCIMQueryAttributes queryAttributes = 128 new SCIMQueryAttributes(resourceDescriptor, attributes); 129 130 // Process the request. 131 GetResourceRequest getResourceRequest = 132 new GetResourceRequest(requestContext.getUriInfo().getBaseUri(), 133 authID, resourceDescriptor, userID, queryAttributes, 134 requestContext.getRequest()); 135 136 if (authID == null) 137 { 138 AtomicReference<String> authIDRef = new AtomicReference<String>(); 139 Response response = validateOAuthToken(requestContext, 140 getResourceRequest, authIDRef, tokenHandler); 141 if (response != null) 142 { 143 application.getStatsForResource(resourceDescriptor.getName()). 144 incrementStat("get-" + response.getStatus()); 145 return response; 146 } 147 else 148 { 149 authID = authIDRef.get(); 150 getResourceRequest = 151 new GetResourceRequest(requestContext.getUriInfo().getBaseUri(), 152 authID, resourceDescriptor, userID, queryAttributes, 153 requestContext.getRequest()); 154 } 155 } 156 157 BaseResource resource = 158 backend.getResource(getResourceRequest); 159 // Build the response. 160 responseBuilder = Response.status(Response.Status.OK); 161 setResponseEntity(responseBuilder, requestContext.getProduceMediaType(), 162 resource); 163 URI location = resource.getMeta().getLocation(); 164 if(location != null) 165 { 166 responseBuilder.location(location); 167 } 168 169 if (requestContext.getOrigin() != null) 170 { 171 responseBuilder.header(HEADER_NAME_ACCESS_CONTROL_ALLOW_ORIGIN, 172 requestContext.getOrigin()); 173 } 174 responseBuilder.header(HEADER_NAME_ACCESS_CONTROL_ALLOW_CREDENTIALS, 175 Boolean.TRUE.toString()); 176 177 application.getStatsForResource(resourceDescriptor.getName()). 178 incrementStat(ResourceStats.GET_OK); 179 if(requestContext.getProduceMediaType() == 180 MediaType.APPLICATION_JSON_TYPE) 181 { 182 application.getStatsForResource(resourceDescriptor.getName()). 183 incrementStat(ResourceStats.GET_RESPONSE_JSON); 184 } 185 else if(requestContext.getProduceMediaType() == 186 MediaType.APPLICATION_XML_TYPE) 187 { 188 application.getStatsForResource(resourceDescriptor.getName()). 189 incrementStat(ResourceStats.GET_RESPONSE_XML); 190 } 191 } catch (SCIMException e) { 192 // Build the response. 193 responseBuilder = Response.status(e.getStatusCode()); 194 setResponseEntity(responseBuilder, requestContext.getProduceMediaType(), 195 e); 196 if(resourceDescriptor != null) 197 { 198 application.getStatsForResource(resourceDescriptor.getName()). 199 incrementStat("get-" + e.getStatusCode()); 200 } 201 } 202 203 return responseBuilder.build(); 204 } 205 206 207 208 /** 209 * Process a GET operation. 210 * 211 * @param requestContext The request context. 212 * @param endpoint The endpoint requested. 213 * @param filterString The filter query parameter, or {@code null}. 214 * @param baseID The SCIM resource ID of the search base entry, 215 * or {@code null}. 216 * @param searchScope The LDAP search scope to use, or {@code null}. 217 * @param sortBy The sortBy query parameter, or {@code null}. 218 * @param sortOrder The sortOrder query parameter, or {@code null}. 219 * @param pageStartIndex The startIndex query parameter, or {@code null}. 220 * @param pageSize The count query parameter, or {@code null}. 221 * 222 * @return The response to the operation. 223 */ 224 protected Response getUsers(final RequestContext requestContext, 225 final String endpoint, 226 final String filterString, 227 final String baseID, 228 final String searchScope, 229 final String sortBy, 230 final String sortOrder, 231 final String pageStartIndex, 232 final String pageSize) 233 { 234 SCIMBackend backend; 235 ResourceDescriptor resourceDescriptor = null; 236 Response.ResponseBuilder responseBuilder; 237 try 238 { 239 backend = getBackend(endpoint); 240 resourceDescriptor = backend.getResourceDescriptor(endpoint); 241 if(resourceDescriptor == null) 242 { 243 throw new ResourceNotFoundException( 244 endpoint + " is not a valid resource endpoint"); 245 } 246 String authID = requestContext.getAuthID(); 247 if(authID == null && tokenHandler == null) { 248 throw new UnauthorizedException("Invalid credentials"); 249 } 250 final String attributes = 251 requestContext.getUriInfo().getQueryParameters().getFirst( 252 QUERY_PARAMETER_ATTRIBUTES); 253 final SCIMQueryAttributes queryAttributes = 254 new SCIMQueryAttributes(resourceDescriptor, attributes); 255 256 // Parse the filter parameters. 257 final SCIMFilter filter; 258 if (filterString != null && !filterString.isEmpty()) 259 { 260 if(resourceDescriptor.getSchema().equalsIgnoreCase( 261 "urn:unboundid:schemas:scim:ldap:1.0")) 262 { 263 filter = SCIMFilter.parse( 264 filterString, resourceDescriptor.getSchema()); 265 } 266 else 267 { 268 filter = SCIMFilter.parse(filterString); 269 } 270 } 271 else 272 { 273 filter = null; 274 } 275 276 // Parse the sort parameters. 277 final SortParameters sortParameters; 278 if (sortBy != null && !sortBy.isEmpty()) 279 { 280 sortParameters = 281 new SortParameters(AttributePath.parse(sortBy, 282 resourceDescriptor.getSchema()), sortOrder); 283 } 284 else 285 { 286 sortParameters = null; 287 } 288 289 // Parse the pagination parameters. 290 int startIndex = -1; 291 int count = -1; 292 if (pageStartIndex != null && !pageStartIndex.isEmpty()) 293 { 294 try 295 { 296 startIndex = Integer.parseInt(pageStartIndex); 297 } 298 catch (NumberFormatException e) 299 { 300 Debug.debugException(e); 301 throw SCIMException.createException( 302 400, "The pagination startIndex value '" + pageStartIndex + 303 "' is not parsable"); 304 } 305 306 if (startIndex <= 0) 307 { 308 throw SCIMException.createException( 309 400, "The pagination startIndex value '" + pageStartIndex + 310 "' is invalid because it is not greater than zero"); 311 } 312 } 313 if (pageSize != null && !pageSize.isEmpty()) 314 { 315 try 316 { 317 count = Integer.parseInt(pageSize); 318 } 319 catch (NumberFormatException e) 320 { 321 Debug.debugException(e); 322 throw SCIMException.createException( 323 400, "The pagination count value '" + pageSize + 324 "' is not parsable"); 325 } 326 327 if (count <= 0) 328 { 329 throw SCIMException.createException( 330 400, "The pagination count value '" + pageSize + 331 "' is invalid because it is not greater than zero"); 332 } 333 } 334 335 final PageParameters pageParameters; 336 if (startIndex >= 0 && count >= 0) 337 { 338 pageParameters = new PageParameters(startIndex, count); 339 } 340 else if (startIndex >= 0) 341 { 342 pageParameters = new PageParameters(startIndex, 0); 343 } 344 else if (count >= 0) 345 { 346 pageParameters = new PageParameters(1, count); 347 } 348 else 349 { 350 pageParameters = null; 351 } 352 353 // Process the request. 354 GetResourcesRequest getResourcesRequest = 355 new GetResourcesRequest(requestContext.getUriInfo().getBaseUri(), 356 authID, resourceDescriptor, filter, baseID, searchScope, 357 sortParameters, pageParameters, queryAttributes, 358 requestContext.getRequest()); 359 360 if (authID == null) 361 { 362 AtomicReference<String> authIDRef = new AtomicReference<String>(); 363 Response response = validateOAuthToken(requestContext, 364 getResourcesRequest, authIDRef, tokenHandler); 365 if (response != null) 366 { 367 application.getStatsForResource(resourceDescriptor.getName()). 368 incrementStat("query-" + response.getStatus()); 369 return response; 370 } 371 else 372 { 373 authID = authIDRef.get(); 374 getResourcesRequest = 375 new GetResourcesRequest(requestContext.getUriInfo().getBaseUri(), 376 authID, resourceDescriptor, filter, baseID, searchScope, 377 sortParameters, pageParameters, queryAttributes, 378 requestContext.getRequest()); 379 } 380 } 381 382 final Resources resources = backend.getResources(getResourcesRequest); 383 384 // Build the response. 385 responseBuilder = 386 Response.status(Response.Status.OK); 387 setResponseEntity(responseBuilder, requestContext.getProduceMediaType(), 388 resources); 389 390 if (requestContext.getOrigin() != null) 391 { 392 responseBuilder.header(HEADER_NAME_ACCESS_CONTROL_ALLOW_ORIGIN, 393 requestContext.getOrigin()); 394 } 395 responseBuilder.header(HEADER_NAME_ACCESS_CONTROL_ALLOW_CREDENTIALS, 396 Boolean.TRUE.toString()); 397 398 application.getStatsForResource(resourceDescriptor.getName()). 399 incrementStat(ResourceStats.QUERY_OK); 400 if(requestContext.getProduceMediaType() == 401 MediaType.APPLICATION_JSON_TYPE) 402 { 403 application.getStatsForResource(resourceDescriptor.getName()). 404 incrementStat(ResourceStats.QUERY_RESPONSE_JSON); 405 } 406 else if(requestContext.getProduceMediaType() == 407 MediaType.APPLICATION_XML_TYPE) 408 { 409 application.getStatsForResource(resourceDescriptor.getName()). 410 incrementStat(ResourceStats.QUERY_RESPONSE_XML); 411 } 412 } 413 catch(SCIMException e) 414 { 415 responseBuilder = 416 Response.status(e.getStatusCode()); 417 setResponseEntity(responseBuilder, requestContext.getProduceMediaType(), 418 e); 419 if(resourceDescriptor != null) 420 { 421 application.getStatsForResource(resourceDescriptor.getName()). 422 incrementStat("query-" + e.getStatusCode()); 423 } 424 } 425 426 return responseBuilder.build(); 427 } 428 429 430 431 /** 432 * Process a POST operation. 433 * 434 * @param requestContext The request context. 435 * @param endpoint The endpoint requested. 436 * @param inputStream The content to be consumed. 437 * 438 * @return The response to the operation. 439 */ 440 Response postUser(final RequestContext requestContext, 441 final String endpoint, 442 final InputStream inputStream) 443 { 444 SCIMBackend backend; 445 ResourceDescriptor resourceDescriptor = null; 446 Response.ResponseBuilder responseBuilder; 447 try 448 { 449 backend = getBackend(endpoint); 450 resourceDescriptor = backend.getResourceDescriptor(endpoint); 451 if(resourceDescriptor == null) 452 { 453 throw new ResourceNotFoundException( 454 endpoint + " is not a valid resource endpoint"); 455 } 456 final Unmarshaller unmarshaller; 457 if (requestContext.getConsumeMediaType().equals( 458 MediaType.APPLICATION_JSON_TYPE)) 459 { 460 unmarshaller = new JsonUnmarshaller(); 461 application.getStatsForResource(resourceDescriptor.getName()). 462 incrementStat(ResourceStats.POST_CONTENT_JSON); 463 } 464 else 465 { 466 unmarshaller = new XmlUnmarshaller(); 467 application.getStatsForResource(resourceDescriptor.getName()). 468 incrementStat(ResourceStats.POST_CONTENT_XML); 469 } 470 String authID = requestContext.getAuthID(); 471 if(authID == null && tokenHandler == null) 472 { 473 throw new UnauthorizedException("Invalid credentials"); 474 } 475 476 // Parse the resource. 477 final BaseResource postedResource = unmarshaller.unmarshal( 478 inputStream, resourceDescriptor, BaseResource.BASE_RESOURCE_FACTORY); 479 480 final String attributes = 481 requestContext.getUriInfo().getQueryParameters().getFirst( 482 QUERY_PARAMETER_ATTRIBUTES); 483 final SCIMQueryAttributes queryAttributes = 484 new SCIMQueryAttributes(resourceDescriptor, attributes); 485 486 // Process the request. 487 PostResourceRequest postResourceRequest = 488 new PostResourceRequest(requestContext.getUriInfo().getBaseUri(), 489 authID, resourceDescriptor, postedResource.getScimObject(), 490 queryAttributes, requestContext.getRequest()); 491 492 if (authID == null) 493 { 494 AtomicReference<String> authIDRef = new AtomicReference<String>(); 495 Response response = validateOAuthToken(requestContext, 496 postResourceRequest, authIDRef, tokenHandler); 497 if (response != null) 498 { 499 application.getStatsForResource(resourceDescriptor.getName()). 500 incrementStat("post-" + response.getStatus()); 501 return response; 502 } 503 else 504 { 505 authID = authIDRef.get(); 506 postResourceRequest = 507 new PostResourceRequest(requestContext.getUriInfo().getBaseUri(), 508 authID, resourceDescriptor, postedResource.getScimObject(), 509 queryAttributes, requestContext.getRequest()); 510 } 511 } 512 513 final BaseResource resource = backend.postResource(postResourceRequest); 514 // Build the response. 515 responseBuilder = Response.status(Response.Status.CREATED); 516 setResponseEntity(responseBuilder, requestContext.getProduceMediaType(), 517 resource); 518 responseBuilder.location(resource.getMeta().getLocation()); 519 application.getStatsForResource(resourceDescriptor.getName()). 520 incrementStat(ResourceStats.POST_OK); 521 if(requestContext.getProduceMediaType() == 522 MediaType.APPLICATION_JSON_TYPE) 523 { 524 application.getStatsForResource(resourceDescriptor.getName()). 525 incrementStat(ResourceStats.POST_RESPONSE_JSON); 526 } 527 else if(requestContext.getProduceMediaType() == 528 MediaType.APPLICATION_XML_TYPE) 529 { 530 application.getStatsForResource(resourceDescriptor.getName()). 531 incrementStat(ResourceStats.POST_RESPONSE_XML); 532 } 533 } catch (SCIMException e) { 534 Debug.debugException(e); 535 // Build the response. 536 responseBuilder = Response.status(e.getStatusCode()); 537 setResponseEntity(responseBuilder, requestContext.getProduceMediaType(), 538 e); 539 if(resourceDescriptor != null) 540 { 541 application.getStatsForResource(resourceDescriptor.getName()). 542 incrementStat("post-" + e.getStatusCode()); 543 } 544 } 545 546 return responseBuilder.build(); 547 } 548 549 550 551 /** 552 * Process a PUT operation. 553 * 554 * @param requestContext The request context. 555 * @param endpoint The endpoint requested. 556 * @param userID The target user ID. 557 * @param inputStream The content to be consumed. 558 * 559 * @return The response to the operation. 560 */ 561 Response putUser(final RequestContext requestContext, 562 final String endpoint, 563 final String userID, 564 final InputStream inputStream) 565 { 566 SCIMBackend backend; 567 ResourceDescriptor resourceDescriptor = null; 568 Response.ResponseBuilder responseBuilder; 569 try { 570 backend = getBackend(endpoint); 571 resourceDescriptor = backend.getResourceDescriptor(endpoint); 572 if(resourceDescriptor == null) 573 { 574 throw new ResourceNotFoundException( 575 endpoint + " is not a valid resource endpoint"); 576 } 577 final Unmarshaller unmarshaller; 578 if (requestContext.getConsumeMediaType().equals( 579 MediaType.APPLICATION_JSON_TYPE)) 580 { 581 unmarshaller = new JsonUnmarshaller(); 582 application.getStatsForResource(resourceDescriptor.getName()). 583 incrementStat(ResourceStats.PUT_CONTENT_JSON); 584 } 585 else 586 { 587 unmarshaller = new XmlUnmarshaller(); 588 application.getStatsForResource(resourceDescriptor.getName()). 589 incrementStat(ResourceStats.PUT_CONTENT_XML); 590 } 591 String authID = requestContext.getAuthID(); 592 if(authID == null && tokenHandler == null) 593 { 594 throw new UnauthorizedException("Invalid credentials"); 595 } 596 597 // Parse the resource. 598 final BaseResource puttedResource = unmarshaller.unmarshal( 599 inputStream, resourceDescriptor, BaseResource.BASE_RESOURCE_FACTORY); 600 601 final String attributes = 602 requestContext.getUriInfo().getQueryParameters().getFirst( 603 QUERY_PARAMETER_ATTRIBUTES); 604 final SCIMQueryAttributes queryAttributes = 605 new SCIMQueryAttributes(resourceDescriptor, attributes); 606 607 // Process the request. 608 PutResourceRequest putResourceRequest = 609 new PutResourceRequest(requestContext.getUriInfo().getBaseUri(), 610 authID, resourceDescriptor, userID, 611 puttedResource.getScimObject(), queryAttributes, 612 requestContext.getRequest()); 613 614 if (authID == null) 615 { 616 AtomicReference<String> authIDRef = new AtomicReference<String>(); 617 Response response = validateOAuthToken(requestContext, 618 putResourceRequest, authIDRef, tokenHandler); 619 if (response != null) 620 { 621 application.getStatsForResource(resourceDescriptor.getName()). 622 incrementStat("put-" + response.getStatus()); 623 return response; 624 } 625 else 626 { 627 authID = authIDRef.get(); 628 putResourceRequest = 629 new PutResourceRequest(requestContext.getUriInfo().getBaseUri(), 630 authID, resourceDescriptor, userID, 631 puttedResource.getScimObject(), queryAttributes, 632 requestContext.getRequest()); 633 } 634 } 635 636 final BaseResource scimResponse = backend.putResource(putResourceRequest); 637 // Build the response. 638 responseBuilder = Response.status(Response.Status.OK); 639 setResponseEntity(responseBuilder, requestContext.getProduceMediaType(), 640 scimResponse); 641 responseBuilder.location(scimResponse.getMeta().getLocation()); 642 application.getStatsForResource(resourceDescriptor.getName()). 643 incrementStat(ResourceStats.PUT_OK); 644 if(requestContext.getProduceMediaType() == 645 MediaType.APPLICATION_JSON_TYPE) 646 { 647 application.getStatsForResource(resourceDescriptor.getName()). 648 incrementStat(ResourceStats.PUT_RESPONSE_JSON); 649 } 650 else if(requestContext.getProduceMediaType() == 651 MediaType.APPLICATION_XML_TYPE) 652 { 653 application.getStatsForResource(resourceDescriptor.getName()). 654 incrementStat(ResourceStats.PUT_RESPONSE_XML); 655 } 656 } catch (SCIMException e) { 657 // Build the response. 658 responseBuilder = Response.status(e.getStatusCode()); 659 setResponseEntity(responseBuilder, requestContext.getProduceMediaType(), 660 e); 661 if(resourceDescriptor != null) 662 { 663 application.getStatsForResource(resourceDescriptor.getName()). 664 incrementStat("put-" + e.getStatusCode()); 665 } 666 } 667 668 return responseBuilder.build(); 669 } 670 671 672 673 /** 674 * Process a PATCH operation. 675 * 676 * @param requestContext The request context. 677 * @param endpoint The endpoint requested. 678 * @param userID The target user ID. 679 * @param inputStream The content to be consumed. 680 * 681 * @return The response to the operation. 682 */ 683 Response patchUser(final RequestContext requestContext, 684 final String endpoint, 685 final String userID, 686 final InputStream inputStream) 687 { 688 SCIMBackend backend; 689 ResourceDescriptor resourceDescriptor = null; 690 Response.ResponseBuilder responseBuilder; 691 try { 692 backend = getBackend(endpoint); 693 resourceDescriptor = backend.getResourceDescriptor(endpoint); 694 if(resourceDescriptor == null) 695 { 696 throw new ResourceNotFoundException( 697 endpoint + " is not a valid resource endpoint"); 698 } 699 String authID = requestContext.getAuthID(); 700 if(authID == null && tokenHandler == null) 701 { 702 throw new UnauthorizedException("Invalid credentials"); 703 } 704 final Unmarshaller unmarshaller; 705 if (requestContext.getConsumeMediaType().equals( 706 MediaType.APPLICATION_JSON_TYPE)) 707 { 708 unmarshaller = new JsonUnmarshaller(); 709 application.getStatsForResource(resourceDescriptor.getName()). 710 incrementStat(ResourceStats.PATCH_CONTENT_JSON); 711 } 712 else 713 { 714 unmarshaller = new XmlUnmarshaller(); 715 application.getStatsForResource(resourceDescriptor.getName()). 716 incrementStat(ResourceStats.PATCH_CONTENT_XML); 717 } 718 // Parse the resource. 719 final BaseResource patchedResource = unmarshaller.unmarshal( 720 inputStream, resourceDescriptor, BaseResource.BASE_RESOURCE_FACTORY); 721 722 final String attributes = 723 requestContext.getUriInfo().getQueryParameters().getFirst( 724 QUERY_PARAMETER_ATTRIBUTES); 725 final SCIMQueryAttributes queryAttributes = 726 new SCIMQueryAttributes(resourceDescriptor, attributes); 727 728 // Process the request. 729 PatchResourceRequest patchResourceRequest = 730 new PatchResourceRequest(requestContext.getUriInfo().getBaseUri(), 731 authID, resourceDescriptor, userID, 732 patchedResource.getScimObject(), queryAttributes, 733 requestContext.getRequest()); 734 735 if (authID == null) 736 { 737 AtomicReference<String> authIDRef = new AtomicReference<String>(); 738 Response response = validateOAuthToken(requestContext, 739 patchResourceRequest, authIDRef, tokenHandler); 740 if (response != null) 741 { 742 application.getStatsForResource(resourceDescriptor.getName()). 743 incrementStat("patch-" + response.getStatus()); 744 return response; 745 } 746 else 747 { 748 authID = authIDRef.get(); 749 patchResourceRequest = 750 new PatchResourceRequest(requestContext.getUriInfo().getBaseUri(), 751 authID, resourceDescriptor, userID, 752 patchedResource.getScimObject(), queryAttributes, 753 requestContext.getRequest()); 754 } 755 } 756 757 final BaseResource scimResponse = 758 backend.patchResource(patchResourceRequest); 759 760 // Build the response. 761 if (!queryAttributes.allAttributesRequested()) 762 { 763 responseBuilder = Response.status(Response.Status.OK); 764 setResponseEntity(responseBuilder, requestContext.getProduceMediaType(), 765 scimResponse); 766 } 767 else 768 { 769 responseBuilder = Response.status(Response.Status.NO_CONTENT); 770 } 771 772 application.getStatsForResource(resourceDescriptor.getName()). 773 incrementStat(ResourceStats.PATCH_OK); 774 if(requestContext.getProduceMediaType() == 775 MediaType.APPLICATION_JSON_TYPE) 776 { 777 application.getStatsForResource(resourceDescriptor.getName()). 778 incrementStat(ResourceStats.PATCH_RESPONSE_JSON); 779 } 780 else if(requestContext.getProduceMediaType() == 781 MediaType.APPLICATION_XML_TYPE) 782 { 783 application.getStatsForResource(resourceDescriptor.getName()). 784 incrementStat(ResourceStats.PATCH_RESPONSE_XML); 785 } 786 } catch (SCIMException e) { 787 // Build the response. 788 responseBuilder = Response.status(e.getStatusCode()); 789 setResponseEntity(responseBuilder, requestContext.getProduceMediaType(), 790 e); 791 if(resourceDescriptor != null) 792 { 793 application.getStatsForResource(resourceDescriptor.getName()). 794 incrementStat("patch-" + e.getStatusCode()); 795 } 796 } 797 798 return responseBuilder.build(); 799 } 800 801 802 803 /** 804 * Process a DELETE operation. 805 * 806 * @param requestContext The request context. 807 * @param endpoint The endpoint requested. 808 * @param userID The target user ID. 809 * 810 * @return The response to the operation. 811 */ 812 Response deleteUser(final RequestContext requestContext, 813 final String endpoint, 814 final String userID) 815 { 816 SCIMBackend backend; 817 ResourceDescriptor resourceDescriptor = null; 818 // Process the request. 819 Response.ResponseBuilder responseBuilder; 820 try { 821 backend = getBackend(endpoint); 822 resourceDescriptor = backend.getResourceDescriptor(endpoint); 823 if(resourceDescriptor == null) 824 { 825 throw new ResourceNotFoundException( 826 endpoint + " is not a valid resource endpoint"); 827 } 828 String authID = requestContext.getAuthID(); 829 if(authID == null && tokenHandler == null) 830 { 831 throw new UnauthorizedException("Invalid credentials"); 832 } 833 DeleteResourceRequest deleteResourceRequest = 834 new DeleteResourceRequest(requestContext.getUriInfo().getBaseUri(), 835 authID, resourceDescriptor, userID, requestContext.getRequest()); 836 837 if (authID == null) 838 { 839 AtomicReference<String> authIDRef = new AtomicReference<String>(); 840 Response response = validateOAuthToken(requestContext, 841 deleteResourceRequest, authIDRef, tokenHandler); 842 if (response != null) 843 { 844 application.getStatsForResource(resourceDescriptor.getName()). 845 incrementStat("delete-" + response.getStatus()); 846 return response; 847 } 848 else 849 { 850 authID = authIDRef.get(); 851 deleteResourceRequest = 852 new DeleteResourceRequest(requestContext.getUriInfo().getBaseUri(), 853 authID, resourceDescriptor, userID, 854 requestContext.getRequest()); 855 } 856 } 857 858 backend.deleteResource(deleteResourceRequest); 859 // Build the response. 860 responseBuilder = Response.status(Response.Status.OK); 861 application.getStatsForResource(resourceDescriptor.getName()). 862 incrementStat(ResourceStats.DELETE_OK); 863 } catch (SCIMException e) { 864 // Build the response. 865 responseBuilder = Response.status(e.getStatusCode()); 866 setResponseEntity(responseBuilder, requestContext.getProduceMediaType(), 867 e); 868 if(resourceDescriptor != null) 869 { 870 application.getStatsForResource(resourceDescriptor.getName()). 871 incrementStat("delete-" + e.getStatusCode()); 872 } 873 } 874 875 return responseBuilder.build(); 876 } 877 878 /** 879 * Handles OAuth bearer token validation. This method should only be called if 880 * 1) the request was not previously authenticated with HTTP Basic Auth, and 881 * 2) we have a OAuthTokenHandler available. 882 * <p> 883 * It will call into the OAuthTokenHandler implementation to validate the 884 * bearer token and then do one of two things: 885 * <ul> 886 * <li>If the token is valid, it will set the output parameter 'authIDRef' 887 * to the DN of the authorization entry and return null.</li> 888 * <li>If the token is invalid, it will construct a Http Response with the 889 * appropriate headers and return it.</li> 890 * </ul> 891 * 892 * @param context The incoming HTTP request context. 893 * @param request The unmarshalled SCIM Request for passing into the 894 * token handler. 895 * @param authIDRef An output parameter to contain the DN of the 896 * authorization entry. 897 * @param tokenHandlerImpl The OAuthTokenHandler to use. 898 * @return {@code null} if the token was successfully validated, 899 * otherwise a Response instance containing the error 900 * information. 901 */ 902 static Response validateOAuthToken(final RequestContext context, 903 final SCIMRequest request, 904 final AtomicReference<String> authIDRef, 905 final OAuthTokenHandler tokenHandlerImpl) 906 { 907 HttpHeaders headers = context.getHeaders(); 908 List<String> headerList = headers.getRequestHeader("Authorization"); 909 910 if (headerList == null || headerList.isEmpty()) 911 { 912 //If the client lacks any authentication information, just return 401 913 Response.ResponseBuilder builder = Response.status(401); 914 builder.header("WWW-Authenticate", "Bearer realm=\"SCIM\""); 915 return builder.build(); 916 917 } 918 else if (headerList.size() > 1) 919 { 920 return invalidRequest("The Authorization header has too many values", 921 context.getProduceMediaType()); 922 } 923 924 String header = headerList.get(0); 925 String[] authorization = header.split(" "); 926 if (authorization.length == 2 && 927 authorization[0].equalsIgnoreCase("Bearer") && 928 authorization[1].length() > 0) 929 { 930 try 931 { 932 OAuthToken token = tokenHandlerImpl.decodeOAuthToken(authorization[1]); 933 934 if (token == null) 935 { 936 return invalidRequest("Could not decode the access token", 937 context.getProduceMediaType()); 938 } 939 940 if (!tokenHandlerImpl.isTokenAuthentic(token)) 941 { 942 return invalidToken("The access token is not authentic", 943 context.getProduceMediaType()); 944 } 945 946 if (!tokenHandlerImpl.isTokenForThisServer(token)) 947 { 948 return invalidToken( 949 "The access token is not intended for this server", 950 context.getProduceMediaType()); 951 } 952 953 if (tokenHandlerImpl.isTokenExpired(token)) 954 { 955 return invalidToken("The access token is expired", 956 context.getProduceMediaType()); 957 } 958 959 OAuthTokenStatus status = 960 tokenHandlerImpl.validateToken(token, request); 961 962 if (status.getErrorCode().equals( 963 OAuthTokenStatus.ErrorCode.INVALID_TOKEN)) 964 { 965 String errorDescription = status.getErrorDescription(); 966 return invalidToken(errorDescription, context.getProduceMediaType()); 967 } 968 else if (status.getErrorCode().equals( 969 OAuthTokenStatus.ErrorCode.INSUFFICIENT_SCOPE)) 970 { 971 String errorDescription = status.getErrorDescription(); 972 String scope = status.getScope(); 973 return insufficientScope(scope, errorDescription, 974 context.getProduceMediaType()); 975 } 976 977 String authID = tokenHandlerImpl.getAuthzDN(token); 978 if (authID == null) 979 { 980 return invalidToken( 981 "The access token did not contain an authorization DN", 982 context.getProduceMediaType()); 983 } 984 else 985 { 986 authIDRef.set(authID); 987 return null; 988 } 989 } 990 catch(Throwable t) 991 { 992 Debug.debugException(t); 993 return invalidRequest(t.getMessage(), context.getProduceMediaType()); 994 } 995 } 996 else if(authorization.length == 2 && 997 authorization[0].equalsIgnoreCase("Basic") && 998 authorization[1].length() > 0) 999 { 1000 //The client tried to do Basic Authentication, and since we made it here, 1001 //it failed. 1002 Response.ResponseBuilder builder = Response.status(401); 1003 builder.header("WWW-Authenticate", "Basic realm=\"SCIM\""); 1004 SCIMException exception = new UnauthorizedException(null); 1005 setResponseEntity(builder, context.getProduceMediaType(), exception); 1006 return builder.build(); 1007 } 1008 else 1009 { 1010 return invalidRequest("The Authorization header was malformed", 1011 context.getProduceMediaType()); 1012 } 1013 } 1014 1015 /** 1016 * Creates an invalid_request Response with the specified error description. 1017 * 1018 * @param errorDescription The description of the validation error. 1019 * @param mediaType The accept-type for SCIMRequest. 1020 * @return a Response instance. 1021 */ 1022 private static Response invalidRequest(final String errorDescription, 1023 final MediaType mediaType) 1024 { 1025 Response.ResponseBuilder builder = Response.status(400); 1026 String authHeaderValue = "Bearer realm=\"SCIM\", error=\"invalid_request\""; 1027 if (errorDescription != null && !errorDescription.isEmpty()) 1028 { 1029 authHeaderValue += ", error_description=\"" + errorDescription + "\""; 1030 } 1031 builder.header("WWW-Authenticate", authHeaderValue); 1032 1033 SCIMException exception = 1034 SCIMException.createException(400, errorDescription); 1035 setResponseEntity(builder, mediaType, exception); 1036 1037 return builder.build(); 1038 } 1039 1040 /** 1041 * Creates an invalid_token Response with the specified error description. 1042 * 1043 * @param errorDescription The description of the validation error. 1044 * @param mediaType The accept-type for SCIMRequest. 1045 * @return a Response instance. 1046 */ 1047 private static Response invalidToken(final String errorDescription, 1048 final MediaType mediaType) 1049 { 1050 Response.ResponseBuilder builder = Response.status(401); 1051 String authHeaderValue = "Bearer realm=\"SCIM\", error=\"invalid_token\""; 1052 if (errorDescription != null && !errorDescription.isEmpty()) 1053 { 1054 authHeaderValue += ", error_description=\"" + errorDescription + "\""; 1055 } 1056 builder.header("WWW-Authenticate", authHeaderValue); 1057 1058 SCIMException exception = 1059 SCIMException.createException(401, errorDescription); 1060 setResponseEntity(builder, mediaType, exception); 1061 1062 return builder.build(); 1063 } 1064 1065 /** 1066 * Creates an insufficient_scope Response with the specified error 1067 * description and scope. 1068 * 1069 * @param errorDescription The description of the validation error. 1070 * @param scope The OAuth scope required to access the target resource. 1071 * @param mediaType The accept-type for SCIMRequest. 1072 * @return a Response instance. 1073 */ 1074 private static Response insufficientScope(final String scope, 1075 final String errorDescription, 1076 final MediaType mediaType) 1077 { 1078 Response.ResponseBuilder builder = Response.status(403); 1079 String authHeaderValue = 1080 "Bearer realm=\"SCIM\", error=\"insufficient_scope\""; 1081 if (errorDescription != null && !errorDescription.isEmpty()) 1082 { 1083 authHeaderValue += ", error_description=\"" + errorDescription + "\""; 1084 } 1085 if (scope != null && !scope.isEmpty()) 1086 { 1087 authHeaderValue += ", scope=\"" + scope + "\""; 1088 } 1089 builder.header("WWW-Authenticate", authHeaderValue); 1090 1091 SCIMException exception = 1092 SCIMException.createException(403, errorDescription); 1093 setResponseEntity(builder, mediaType, exception); 1094 1095 return builder.build(); 1096 } 1097 1098 /** 1099 * Retrieves the backend that should service the provided endpoint. 1100 * 1101 * @param endpoint The endpoint requested. 1102 * @return The backend that should service the provided endpoint. 1103 */ 1104 private SCIMBackend getBackend(final String endpoint) 1105 { 1106 if(endpoint.equals(RESOURCE_ENDPOINT_SCHEMAS)) 1107 { 1108 return resourceSchemaBackend; 1109 } 1110 1111 return application.getBackend(); 1112 } 1113}