001/* 002 * Copyright 2015-2024 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.server.resources; 019 020import com.unboundid.scim2.common.GenericScimResource; 021import com.unboundid.scim2.common.ScimResource; 022import com.unboundid.scim2.common.annotations.NotNull; 023import com.unboundid.scim2.common.annotations.Nullable; 024import com.unboundid.scim2.common.filters.Filter; 025import com.unboundid.scim2.common.messages.ListResponse; 026import com.unboundid.scim2.common.types.ResourceTypeResource; 027import com.unboundid.scim2.common.exceptions.ForbiddenException; 028import com.unboundid.scim2.common.exceptions.ResourceNotFoundException; 029import com.unboundid.scim2.common.exceptions.ScimException; 030import com.unboundid.scim2.server.annotations.ResourceType; 031import com.unboundid.scim2.server.utils.ResourcePreparer; 032import com.unboundid.scim2.server.utils.ResourceTypeDefinition; 033import com.unboundid.scim2.server.utils.SchemaAwareFilterEvaluator; 034 035import jakarta.ws.rs.GET; 036import jakarta.ws.rs.Path; 037import jakarta.ws.rs.PathParam; 038import jakarta.ws.rs.Produces; 039import jakarta.ws.rs.QueryParam; 040import jakarta.ws.rs.core.Application; 041import jakarta.ws.rs.core.Context; 042import jakarta.ws.rs.core.MediaType; 043import jakarta.ws.rs.core.UriInfo; 044import java.util.ArrayList; 045import java.util.Collection; 046import java.util.HashSet; 047import java.util.Set; 048 049import static com.unboundid.scim2.common.utils.ApiConstants.*; 050 051/** 052 * An abstract JAX-RS resource class for servicing the Resource Types 053 * endpoint. 054 */ 055@ResourceType( 056 description = "SCIM 2.0 Resource Type", 057 name = "ResourceType", 058 schema = ResourceTypeResource.class, 059 discoverable = false) 060@Path("ResourceTypes") 061public class ResourceTypesEndpoint 062{ 063 @NotNull 064 private static final ResourceTypeDefinition RESOURCE_TYPE_DEFINITION = 065 ResourceTypeDefinition.fromJaxRsResource( 066 ResourceTypesEndpoint.class); 067 068 @NotNull 069 @Context 070 private Application application; 071 072 /** 073 * Service SCIM request to retrieve all resource types defined at the 074 * service provider using GET. 075 * 076 * @param filterString The filter string used to request a subset of 077 * resources. Will throw 403 Forbidden if specified. 078 * @param uriInfo UriInfo of the request. 079 * @return All resource types in a ListResponse container. 080 * @throws ScimException If an error occurs. 081 */ 082 @GET 083 @Produces({MEDIA_TYPE_SCIM, MediaType.APPLICATION_JSON}) 084 @NotNull 085 public ListResponse<GenericScimResource> search( 086 @Nullable @QueryParam(QUERY_PARAMETER_FILTER) final String filterString, 087 @NotNull @Context final UriInfo uriInfo) 088 throws ScimException 089 { 090 if(filterString != null) 091 { 092 throw new ForbiddenException("Filtering not allowed"); 093 } 094 095 // https://tools.ietf.org/html/draft-ietf-scim-api-19#section-4 says 096 // query params should be ignored for discovery endpoints so we can't use 097 // SimpleSearchResults. 098 ResourcePreparer<GenericScimResource> preparer = 099 new ResourcePreparer<GenericScimResource>( 100 RESOURCE_TYPE_DEFINITION, uriInfo); 101 Collection<ResourceTypeResource> resourceTypes = getResourceTypes(); 102 Collection<GenericScimResource> preparedResources = 103 new ArrayList<GenericScimResource>(resourceTypes.size()); 104 for(ResourceTypeResource resourceType : resourceTypes) 105 { 106 GenericScimResource preparedResource = 107 resourceType.asGenericScimResource(); 108 preparer.setResourceTypeAndLocation(preparedResource); 109 preparedResources.add(preparedResource); 110 } 111 return new ListResponse<GenericScimResource>(preparedResources); 112 } 113 114 /** 115 * Service SCIM request to retrieve a resource type by ID. 116 * 117 * @param id The ID of the resource type to retrieve. 118 * @param uriInfo UriInfo of the request. 119 * @return The retrieved resource type. 120 * @throws ScimException If an error occurs. 121 */ 122 @Path("{id}") 123 @GET 124 @Produces({MEDIA_TYPE_SCIM, MediaType.APPLICATION_JSON}) 125 @NotNull 126 public ScimResource get(@NotNull @PathParam("id") final String id, 127 @NotNull @Context final UriInfo uriInfo) 128 throws ScimException 129 { 130 Filter filter = Filter.or(Filter.eq("id", id), Filter.eq("name", id)); 131 SchemaAwareFilterEvaluator filterEvaluator = 132 new SchemaAwareFilterEvaluator(RESOURCE_TYPE_DEFINITION); 133 ResourcePreparer<GenericScimResource> resourcePreparer = 134 new ResourcePreparer<GenericScimResource>( 135 RESOURCE_TYPE_DEFINITION, uriInfo); 136 for(ResourceTypeResource resourceType : getResourceTypes()) 137 { 138 GenericScimResource resource = resourceType.asGenericScimResource(); 139 if(filter.visit(filterEvaluator, resource.getObjectNode())) 140 { 141 resourcePreparer.setResourceTypeAndLocation(resource); 142 return resource; 143 } 144 } 145 146 throw new ResourceNotFoundException( 147 "No resource type defined with ID or name " + id); 148 } 149 150 /** 151 * Retrieve all resource types defined at the service provider. The default 152 * implementation will generate ResourceType definitions from all JAX-RS 153 * resource classes with the ResourceType annotation. 154 * 155 * @return All resource types defined at the service provider. 156 * @throws ScimException If an error occurs. 157 */ 158 @NotNull 159 public Collection<ResourceTypeResource> getResourceTypes() 160 throws ScimException 161 { 162 Set<ResourceTypeResource> resourceTypes = 163 new HashSet<ResourceTypeResource>(); 164 for(Class<?> resourceClass : application.getClasses()) 165 { 166 ResourceTypeDefinition resourceTypeDefinition = 167 ResourceTypeDefinition.fromJaxRsResource(resourceClass); 168 if(resourceTypeDefinition != null && 169 resourceTypeDefinition.isDiscoverable()) 170 { 171 resourceTypes.add(resourceTypeDefinition.toScimResource()); 172 } 173 } 174 175 for(Object resourceInstance : application.getSingletons()) 176 { 177 ResourceTypeDefinition resourceTypeDefinition = 178 ResourceTypeDefinition.fromJaxRsResource(resourceInstance.getClass()); 179 if(resourceTypeDefinition != null && 180 resourceTypeDefinition.isDiscoverable()) 181 { 182 resourceTypes.add(resourceTypeDefinition.toScimResource()); 183 } 184 } 185 186 return resourceTypes; 187 } 188}