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