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}