001/*
002 * Copyright 2015-2020 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.providers;
019
020import com.fasterxml.jackson.core.JsonParser;
021import com.fasterxml.jackson.databind.ObjectReader;
022import com.unboundid.scim2.common.messages.SearchRequest;
023import com.unboundid.scim2.common.utils.JsonUtils;
024import com.unboundid.scim2.common.utils.StaticUtils;
025import com.unboundid.scim2.server.utils.ServerUtils;
026
027import javax.annotation.Priority;
028import javax.ws.rs.BadRequestException;
029import javax.ws.rs.HttpMethod;
030import javax.ws.rs.NotSupportedException;
031import javax.ws.rs.Priorities;
032import javax.ws.rs.container.ContainerRequestContext;
033import javax.ws.rs.container.ContainerRequestFilter;
034import javax.ws.rs.container.PreMatching;
035import javax.ws.rs.core.MediaType;
036import javax.ws.rs.core.NoContentException;
037import javax.ws.rs.core.PathSegment;
038import javax.ws.rs.core.UriBuilder;
039import javax.ws.rs.ext.Provider;
040import java.io.IOException;
041import java.util.List;
042
043import static com.unboundid.scim2.common.utils.ApiConstants.*;
044
045/**
046 * A ContainerRequestFilter implementation to convert a search request using
047 * HTTP POST combine with the "{@code .search}" path extension to a regular search
048 * using HTTP GET.
049 */
050@Provider
051@PreMatching
052@Priority(Priorities.ENTITY_CODER)
053public class DotSearchFilter implements ContainerRequestFilter
054{
055  /**
056   * {@inheritDoc}
057   */
058  public void filter(final ContainerRequestContext requestContext)
059      throws IOException
060  {
061    if(requestContext.getMethod().equals(HttpMethod.POST) &&
062        requestContext.getUriInfo().getPath().endsWith(
063            SEARCH_WITH_POST_PATH_EXTENSION))
064
065    {
066      if(requestContext.getMediaType() == null ||
067          !(requestContext.getMediaType().isCompatible(
068              ServerUtils.MEDIA_TYPE_SCIM_TYPE) ||
069              requestContext.getMediaType().isCompatible(
070                  MediaType.APPLICATION_JSON_TYPE)))
071      {
072        throw new NotSupportedException();
073      }
074
075      ObjectReader reader =
076          JsonUtils.getObjectReader().forType(SearchRequest.class);
077      JsonParser p = reader.getFactory().createParser(
078          requestContext.getEntityStream());
079      if(p.nextToken() == null)
080      {
081        throw new BadRequestException(
082            new NoContentException("Empty Entity"));
083      }
084      SearchRequest searchRequest = reader.readValue(p);
085      UriBuilder builder = requestContext.getUriInfo().getBaseUriBuilder();
086      List<PathSegment> pathSegments =
087          requestContext.getUriInfo().getPathSegments();
088      for(int i = 0; i < pathSegments.size() - 1; i ++)
089      {
090        builder.path(pathSegments.get(i).getPath());
091      }
092      if(searchRequest.getAttributes() != null)
093      {
094        builder.queryParam(QUERY_PARAMETER_ATTRIBUTES,
095            encodeTemplateNames(StaticUtils.collectionToString(
096                searchRequest.getAttributes(), ",")));
097      }
098      if(searchRequest.getExcludedAttributes() != null)
099      {
100        builder.queryParam(QUERY_PARAMETER_EXCLUDED_ATTRIBUTES,
101            encodeTemplateNames(StaticUtils.collectionToString(
102                searchRequest.getExcludedAttributes(), ",")));
103      }
104      if(searchRequest.getFilter() != null)
105      {
106        builder.queryParam(QUERY_PARAMETER_FILTER,
107            encodeTemplateNames(searchRequest.getFilter()));
108      }
109      if(searchRequest.getSortBy() != null)
110      {
111        builder.queryParam(QUERY_PARAMETER_SORT_BY,
112            encodeTemplateNames(searchRequest.getSortBy()));
113      }
114      if(searchRequest.getSortOrder() != null)
115      {
116        builder.queryParam(QUERY_PARAMETER_SORT_ORDER,
117            encodeTemplateNames(searchRequest.getSortOrder().getName()));
118      }
119      if(searchRequest.getStartIndex() != null)
120      {
121        builder.queryParam(QUERY_PARAMETER_PAGE_START_INDEX,
122            searchRequest.getStartIndex());
123      }
124      if(searchRequest.getCount() != null)
125      {
126        builder.queryParam(QUERY_PARAMETER_PAGE_SIZE,
127            searchRequest.getCount());
128      }
129      requestContext.setRequestUri(builder.build());
130      requestContext.setMethod(HttpMethod.GET);
131    }
132  }
133
134  /**
135   * Encodes a string with template parameters names present, specifically the
136   * characters '{' and '}' will be percent-encoded.
137   *
138   * @param s the string with zero or more template parameters names
139   * @return the string with encoded template parameters names.
140   */
141  static String encodeTemplateNames(final String s)
142  {
143    String s1 = s;
144    int i = s1.indexOf('{');
145    if (i != -1)
146    {
147      s1 = s1.replace("{", "%7B");
148    }
149    i = s1.indexOf('}');
150    if (i != -1)
151    {
152      s1 = s1.replace("}", "%7D");
153    }
154
155    return s1;
156  }
157
158}