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.SchemaResource; 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; 044 045import java.util.ArrayList; 046import java.util.Collection; 047import java.util.HashSet; 048import java.util.Set; 049 050import static com.unboundid.scim2.common.utils.ApiConstants.*; 051 052/** 053 * An abstract JAX-RS resource class for servicing the Schemas 054 * endpoint. 055 */ 056@ResourceType( 057 description = "SCIM 2.0 Schema", 058 name = "Schema", 059 schema = SchemaResource.class, 060 discoverable = false) 061@Path("Schemas") 062public class SchemasEndpoint 063{ 064 @NotNull 065 private static final ResourceTypeDefinition RESOURCE_TYPE_DEFINITION = 066 ResourceTypeDefinition.fromJaxRsResource( 067 SchemasEndpoint.class); 068 069 @Nullable 070 @Context 071 private Application application; 072 073 /** 074 * Service SCIM request to retrieve all schemas defined at the 075 * service provider using GET. 076 * 077 * @param filterString The filter string used to request a subset of 078 * resources. Will throw 403 Forbidden if specified. 079 * @param uriInfo UriInfo of the request. 080 * @return All schemas in a ListResponse container. 081 * @throws ScimException If an error occurs. 082 */ 083 @NotNull 084 @GET 085 @Produces({MEDIA_TYPE_SCIM, MediaType.APPLICATION_JSON}) 086 public ListResponse<GenericScimResource> search( 087 @Nullable @QueryParam(QUERY_PARAMETER_FILTER) final String filterString, 088 @NotNull @Context final UriInfo uriInfo) 089 throws ScimException 090 { 091 if(filterString != null) 092 { 093 throw new ForbiddenException("Filtering not allowed"); 094 } 095 096 // https://tools.ietf.org/html/draft-ietf-scim-api-19#section-4 says 097 // query params should be ignored for discovery endpoints so we can't use 098 // SimpleSearchResults. 099 ResourcePreparer<GenericScimResource> preparer = 100 new ResourcePreparer<GenericScimResource>( 101 RESOURCE_TYPE_DEFINITION, uriInfo); 102 Collection<SchemaResource> schemas = getSchemas(); 103 Collection<GenericScimResource> preparedResources = 104 new ArrayList<GenericScimResource>(schemas.size()); 105 for(SchemaResource schema : schemas) 106 { 107 GenericScimResource preparedResource = schema.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 schema by ID. 116 * 117 * @param id The ID of the schema to retrieve. 118 * @param uriInfo UriInfo of the request. 119 * @return The retrieved schema. 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 (SchemaResource schema : getSchemas()) 137 { 138 GenericScimResource resource = schema.asGenericScimResource(); 139 if (filter.visit(filterEvaluator, resource.getObjectNode())) 140 { 141 resourcePreparer.setResourceTypeAndLocation(resource); 142 return resource; 143 } 144 } 145 146 throw new ResourceNotFoundException("No schema defined with ID " + id); 147 } 148 149 /** 150 * Retrieve all schemas defined at the service provider. The default 151 * implementation will generate Schemas definitions based on the ResourceType 152 * of all JAX-RS resource classes with the ResourceType annotation. 153 * 154 * @return All schemas defined at the service provider. 155 * @throws ScimException If an error occurs. 156 */ 157 @NotNull 158 public Collection<SchemaResource> getSchemas() throws ScimException 159 { 160 Set<SchemaResource> schemas = 161 new HashSet<SchemaResource>(); 162 for(Class<?> resourceClass : application.getClasses()) 163 { 164 ResourceTypeDefinition resourceTypeDefinition = 165 ResourceTypeDefinition.fromJaxRsResource(resourceClass); 166 if(resourceTypeDefinition != null && 167 resourceTypeDefinition.isDiscoverable()) 168 { 169 if(resourceTypeDefinition.getCoreSchema() != null) 170 { 171 schemas.add(resourceTypeDefinition.getCoreSchema()); 172 } 173 for(SchemaResource schemaExtension : 174 resourceTypeDefinition.getSchemaExtensions().keySet()) 175 { 176 schemas.add(schemaExtension); 177 } 178 } 179 } 180 for(Object resourceInstance : application.getSingletons()) 181 { 182 ResourceTypeDefinition resourceTypeDefinition = 183 ResourceTypeDefinition.fromJaxRsResource(resourceInstance.getClass()); 184 if(resourceTypeDefinition != null && 185 resourceTypeDefinition.isDiscoverable()) 186 { 187 if(resourceTypeDefinition.getCoreSchema() != null) 188 { 189 schemas.add(resourceTypeDefinition.getCoreSchema()); 190 } 191 for(SchemaResource schemaExtension : 192 resourceTypeDefinition.getSchemaExtensions().keySet()) 193 { 194 schemas.add(schemaExtension); 195 } 196 } 197 } 198 199 return schemas; 200 } 201}