private HttpUriRequest newHcRequest(HttpRequest request) throws IOException {
    URI uri = request.getUri().toJavaUri();
    HttpUriRequest httpUriRequest = HttpMethod.valueOf(request.getMethod()).newMessage(uri);

    for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
      httpUriRequest.addHeader(entry.getKey(), StringUtils.join(entry.getValue(), ','));
    }
    return httpUriRequest;
  }
 /**
  * @see
  *     org.apache.shindig.gadgets.http.BasicHttpFetcher#fetch(org.apache.shindig.gadgets.http.HttpRequest)
  */
 @Override
 public HttpResponse fetch(HttpRequest request) throws GadgetException {
   // Add authentication information if necessary
   if (request.getOAuthArguments() == null
       && request.getOAuth2Arguments() == null
       && isAuthEndpoint(request)) {
     this.authenticationProvider.provideAuthentication(request);
   }
   return super.fetch(request);
 }
 @Override
 public boolean equals(Object obj) {
   if (obj == this) {
     return true;
   }
   if (!(obj instanceof HttpRequest)) {
     return false;
   }
   HttpRequest req = (HttpRequest) obj;
   return super.equals(obj)
       && req.getCacheTtl() == getCacheTtl()
       && req.getIgnoreCache() == getIgnoreCache();
 }
 public boolean rewrite(HttpRequest request, HttpResponse resp, MutableContent content) {
   // Content fetched through the proxy can stipulate that it must be sanitized.
   if (request.isSanitizationRequested()) {
     ContentRewriterFeature rewriterFeature =
         rewriterFeatureFactory.createRewriteAllFeature(request.getCacheTtl());
     if (StringUtils.isEmpty(request.getRewriteMimeType())) {
       logger.log(
           Level.WARNING, "Request to sanitize without content type for " + request.getUri());
       content.setContent("");
       return true;
     } else if (request.getRewriteMimeType().equalsIgnoreCase("text/css")) {
       return rewriteProxiedCss(request, resp, content, rewriterFeature);
     } else if (request.getRewriteMimeType().toLowerCase().startsWith("image/")) {
       return rewriteProxiedImage(request, resp, content);
     } else {
       logger.log(
           Level.WARNING,
           "Request to sanitize unknown content type "
               + request.getRewriteMimeType()
               + " for "
               + request.getUri());
       content.setContent("");
       return true;
     }
   } else {
     // No Op
     return false;
   }
 }
示例#5
0
  @Override
  public void fetch(HttpServletRequest request, HttpServletResponse response)
      throws IOException, GadgetException {
    if (request.getHeader("If-Modified-Since") != null) {
      response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
      return;
    }

    String host = request.getHeader("Host");
    if (!lockedDomainService.isSafeForOpenProxy(host)) {
      // Force embedded images and the like to their own domain to avoid XSS
      // in gadget domains.
      String msg =
          "Embed request for url "
              + getParameter(request, URL_PARAM, "")
              + " made to wrong domain "
              + host;
      logger.info(msg);
      throw new GadgetException(GadgetException.Code.INVALID_PARAMETER, msg);
    }

    HttpRequest rcr = buildHttpRequest(request);
    HttpResponse results = fetcher.fetch(rcr);
    if (contentRewriterRegistry != null) {
      results = contentRewriterRegistry.rewriteHttpResponse(rcr, results);
    }

    setResponseHeaders(request, response, results);

    for (Map.Entry<String, List<String>> entry : results.getHeaders().entrySet()) {
      String name = entry.getKey();
      if (!DISALLOWED_RESPONSE_HEADERS.contains(name.toLowerCase())) {
        for (String value : entry.getValue()) {
          response.addHeader(name, value);
        }
      }
    }

    if (rcr.getRewriteMimeType() != null) {
      response.setContentType(rcr.getRewriteMimeType());
    }

    if (results.getHttpStatusCode() != HttpResponse.SC_OK) {
      response.sendError(results.getHttpStatusCode());
    }

    IOUtils.copy(results.getResponse(), response.getOutputStream());
  }
 public static String getMimeType(HttpRequest request, HttpResponseBuilder original) {
   String mimeType = request.getRewriteMimeType();
   if (mimeType == null) {
     mimeType = original.getHeader("Content-Type");
   }
   return mimeType != null ? mimeType.toLowerCase() : null;
 }
  private HttpClient newHttpClient(String sslProtocol, HttpRequest request) {
    HttpParams params = new BasicHttpParams();
    HttpConnectionParams.setConnectionTimeout(params, httpTimeoutsProvider.getConnectionTimeout());
    HttpConnectionParams.setSoTimeout(params, httpTimeoutsProvider.getSocketTimeout());

    // AG-1059: Need to follow redirects to ensure that if the host app is setup with Apache and
    // mod_jk we can still
    // fetch internal gadgets.
    if (request.getFollowRedirects()) {
      params.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, true);
      params.setIntParameter(ClientPNames.MAX_REDIRECTS, 3);
    }
    params.setParameter(
        ClientPNames.DEFAULT_HEADERS,
        ImmutableSet.of(new BasicHeader("Accept-Encoding", "gzip, deflate")));
    DefaultHttpClient client = new DefaultHttpClient(params);
    // AG-1044: Need to use the JVM's default proxy configuration for requests.
    ProxySelectorRoutePlanner routePlanner =
        new ProxySelectorRoutePlanner(
            client.getConnectionManager().getSchemeRegistry(), ProxySelector.getDefault());
    client.setRoutePlanner(routePlanner);

    client.addResponseInterceptor(new GzipDeflatingInterceptor());
    client
        .getConnectionManager()
        .getSchemeRegistry()
        .register(new Scheme("https", new CustomSSLSocketFactory(sslProtocol), 443));
    return client;
  }
 /**
  * @param request the outbound http request
  * @return true if the request should be authenticated
  */
 private boolean isAuthEndpoint(HttpRequest request) {
   Uri uri = request.getUri();
   String path = uri.getPath();
   for (String authEndpoint : authEndpoints) {
     if (path.startsWith(authEndpoint)) {
       return true;
     }
   }
   return false;
 }
 private HttpResponse handleEchoUrl(HttpRequest request) throws Exception {
   String query = request.getUri().getQuery();
   if (query.contains("add_oauth_token")) {
     query = query + "&oauth_token=abc";
   }
   return new HttpResponseBuilder()
       .setHttpStatusCode(HttpResponse.SC_OK)
       .setResponseString(query)
       .create();
 }
示例#10
0
  /** Generate a remote content request based on the parameters sent from the client. */
  private HttpRequest buildHttpRequest(HttpServletRequest request) throws GadgetException {
    Uri url = validateUrl(request.getParameter(URL_PARAM));

    HttpRequest req = new HttpRequest(url);

    req.setContainer(getContainer(request));
    if (request.getParameter(GADGET_PARAM) != null) {
      req.setGadget(Uri.parse(request.getParameter(GADGET_PARAM)));
    }

    // Allow the rewriter to use an externally forced mime type. This is needed
    // allows proper rewriting of <script src="x"/> where x is returned with
    // a content type like text/html which unfortunately happens all too often
    req.setRewriteMimeType(request.getParameter(REWRITE_MIME_TYPE_PARAM));

    req.setIgnoreCache(getIgnoreCache(request));

    // If the proxy request specifies a refresh param then we want to force the min TTL for
    // the retrieved entry in the cache regardless of the headers on the content when it
    // is fetched from the original source.
    if (request.getParameter(REFRESH_PARAM) != null) {
      try {
        req.setCacheTtl(Integer.parseInt(request.getParameter(REFRESH_PARAM)));
      } catch (NumberFormatException nfe) {
        // Ignore
      }
    }

    return req;
  }
 /**
  * We don't actually rewrite the image we just ensure that it is in fact a valid and known image
  * type.
  */
 private boolean rewriteProxiedImage(
     HttpRequest request, HttpResponse resp, MutableContent content) {
   boolean imageIsSafe = false;
   try {
     String contentType = resp.getHeader("Content-Type");
     if (contentType == null || contentType.toLowerCase().startsWith("image/")) {
       // Unspecified or unknown image mime type.
       try {
         ImageFormat imageFormat =
             Sanselan.guessFormat(
                 new ByteSourceInputStream(resp.getResponse(), request.getUri().getPath()));
         if (imageFormat == ImageFormat.IMAGE_FORMAT_UNKNOWN) {
           logger.log(
               Level.INFO, "Unable to sanitize unknown image type " + request.getUri().toString());
           return true;
         }
         imageIsSafe = true;
         // Return false to indicate that no rewriting occurred
         return false;
       } catch (IOException ioe) {
         throw new RuntimeException(ioe);
       } catch (ImageReadException ire) {
         // Unable to read the image so its not safe
         logger.log(
             Level.INFO,
             "Unable to detect image type for "
                 + request.getUri().toString()
                 + " for sanitized content",
             ire);
         return true;
       }
     } else {
       return true;
     }
   } finally {
     if (!imageIsSafe) {
       content.setContent("");
     }
   }
 }
  @Test
  public void testFetch_withToken() throws Exception {

    Uri uri = Uri.parse("http://host?p=1&st=sometoken");

    // We should get the request untouched.
    HttpRequest request = createMock(HttpRequest.class);
    expect(request.getUri()).andReturn(uri);
    replay(request);

    HttpResponse response = new HttpResponse();

    HttpFetcher fetcher = createMock(HttpFetcher.class);
    expect(fetcher.fetch(request)).andReturn(response);
    replay(fetcher);

    FakeUserHttpFetcher fakeFetcher;
    fakeFetcher = new FakeUserHttpFetcher(config, fetcher, crypter);
    fakeFetcher.fetch(request);

    verify(request);
    verify(fetcher);
  }
  @Test
  public void testFetch_noToken() throws Exception {

    Uri uri = Uri.parse("http://host?p=1");

    HttpRequest request = createMock(HttpRequest.class);
    expect(request.getUri()).andReturn(uri);
    expect(request.setUri(Uri.parse("http://host?p=1&st=default%3Anull"))).andReturn(request);
    replay(request);

    HttpResponse response = new HttpResponse();

    HttpFetcher fetcher = createMock(HttpFetcher.class);
    expect(fetcher.fetch(request)).andReturn(response);
    replay(fetcher);

    FakeUserHttpFetcher fakeFetcher;
    fakeFetcher = new FakeUserHttpFetcher(config, fetcher, crypter);
    fakeFetcher.fetch(request);

    verify(request);
    verify(fetcher);
  }
 public HttpResponse fetch(HttpRequest request) throws GadgetException {
   if (returnNull) {
     return null;
   }
   if (gadgetException != null) {
     throw gadgetException;
   }
   if (runtimeException != null) {
     throw runtimeException;
   }
   if (request.getFollowRedirects()) {
     throw new RuntimeException("Not supposed to follow OAuth redirects");
   }
   String url = request.getUri().toString();
   try {
     if (url.startsWith(REQUEST_TOKEN_URL)) {
       ++requestTokenCount;
       return handleRequestTokenUrl(request);
     } else if (url.startsWith(ACCESS_TOKEN_URL)) {
       ++accessTokenCount;
       return handleAccessTokenUrl(request);
     } else if (url.startsWith(RESOURCE_URL)) {
       ++resourceAccessCount;
       return handleResourceUrl(request);
     } else if (url.startsWith(NOT_FOUND_URL)) {
       return handleNotFoundUrl(request);
     } else if (url.startsWith(ERROR_400)) {
       return handleError400Url(request);
     } else if (url.startsWith(ECHO_URL)) {
       return handleEchoUrl(request);
     }
   } catch (Exception e) {
     throw new RuntimeException("Problem with request for URL " + url, e);
   }
   throw new RuntimeException("Unexpected request for " + url);
 }
 /** {@inheritDoc} */
 public HttpResponse fetch(HttpRequest request) {
   if (!whitelist.allows(request.getUri().toJavaUri())) {
     log.warn(
         "A request to "
             + request.getUri()
             + " has been denied.  To allow requests to this URL add the application URL to your whitelist (http://confluence.atlassian.com/x/KQfCDQ ).");
     return new HttpResponseBuilder()
         .setHttpStatusCode(HttpResponse.SC_FORBIDDEN)
         .setHeader("Content-Type", "text/plain")
         .setResponseString(
             "Requests to "
                 + request.getUri()
                 + " are not allowed.  See your administrator about configuring a whitelist entry for this destination (http://confluence.atlassian.com/x/KQfCDQ ).")
         .create();
   }
   HttpCacheKey cacheKey = new HttpCacheKey(request);
   HttpResponse response = cache.getResponse(cacheKey, request);
   if (response != null) {
     return response;
   }
   try {
     HttpUriRequest hcRequest = newHcRequest(request);
     if (request.getPostBodyLength() > 0) {
       ((HttpEntityEnclosingRequest) hcRequest)
           .setEntity(new InputStreamEntity(request.getPostBody(), request.getPostBodyLength()));
     }
     org.apache.http.HttpResponse hcResponse;
     try {
       // first try with TLS, the most common SSL protocol
       hcResponse = newHttpClient("TLSv1", request).execute(hcRequest);
     } catch (SSLException e) {
       log.debug(
           "SSL Exception establishing connection with TLSv1 protocol. Falling back to SSLv3.", e);
       // if TLS failed, try with SSLv3
       hcResponse = newHttpClient("SSLv3", request).execute(hcRequest);
     }
     response = makeResponse(hcResponse);
     return cache.addResponse(cacheKey, request, response);
   } catch (IOException e) {
     if (e instanceof java.net.SocketTimeoutException || e instanceof java.net.SocketException) {
       return HttpResponse.timeout();
     } else {
       log.error("Unable to retrieve response", e);
     }
     return HttpResponse.error();
   }
 }
  /** Sanitize a CSS file. */
  private boolean rewriteProxiedCss(
      HttpRequest request,
      HttpResponse response,
      MutableContent content,
      ContentRewriterFeature rewriterFeature) {
    String sanitized = "";
    try {
      String contentType = response.getHeader("Content-Type");
      if (contentType == null || contentType.toLowerCase().startsWith("text/")) {
        SanitizingProxyingLinkRewriter cssImportRewriter =
            sanitizingProxyingLinkRewriterFactory.create(
                request.getGadget(),
                rewriterFeature,
                request.getContainer(),
                "text/css",
                false,
                request.getIgnoreCache());
        SanitizingProxyingLinkRewriter cssImageRewriter =
            sanitizingProxyingLinkRewriterFactory.create(
                request.getGadget(),
                rewriterFeature,
                request.getContainer(),
                "image/*",
                false,
                request.getIgnoreCache());
        sanitized =
            cssSanitizer.sanitize(
                content.getContent(), request.getUri(), cssImportRewriter, cssImageRewriter);
      }

      return true;
    } finally {
      // Set sanitized content in finally to ensure it is always cleared in
      // the case of errors
      content.setContent(sanitized);
    }
  }
示例#17
0
  public HttpResponse fetch(org.apache.shindig.gadgets.http.HttpRequest request)
      throws GadgetException {
    HttpUriRequest httpMethod = null;
    Preconditions.checkNotNull(request);
    final String methodType = request.getMethod();

    final org.apache.http.HttpResponse response;
    final long started = System.currentTimeMillis();

    // Break the request Uri to its components:
    Uri uri = request.getUri();
    if (StringUtils.isEmpty(uri.getAuthority())) {
      throw new GadgetException(
          GadgetException.Code.INVALID_USER_DATA,
          "Missing domain name for request: " + uri,
          HttpServletResponse.SC_BAD_REQUEST);
    }
    if (StringUtils.isEmpty(uri.getScheme())) {
      throw new GadgetException(
          GadgetException.Code.INVALID_USER_DATA,
          "Missing schema for request: " + uri,
          HttpServletResponse.SC_BAD_REQUEST);
    }
    String[] hostparts = StringUtils.splitPreserveAllTokens(uri.getAuthority(), ':');
    int port = -1; // default port
    if (hostparts.length > 2) {
      throw new GadgetException(
          GadgetException.Code.INVALID_USER_DATA,
          "Bad host name in request: " + uri.getAuthority(),
          HttpServletResponse.SC_BAD_REQUEST);
    }
    if (hostparts.length == 2) {
      try {
        port = Integer.parseInt(hostparts[1]);
      } catch (NumberFormatException e) {
        throw new GadgetException(
            GadgetException.Code.INVALID_USER_DATA,
            "Bad port number in request: " + uri.getAuthority(),
            HttpServletResponse.SC_BAD_REQUEST);
      }
    }

    String requestUri = uri.getPath();
    // Treat path as / if set as null.
    if (uri.getPath() == null) {
      requestUri = "/";
    }
    if (uri.getQuery() != null) {
      requestUri += '?' + uri.getQuery();
    }

    // Get the http host to connect to.
    HttpHost host = new HttpHost(hostparts[0], port, uri.getScheme());

    try {
      if ("POST".equals(methodType) || "PUT".equals(methodType)) {
        HttpEntityEnclosingRequestBase enclosingMethod =
            ("POST".equals(methodType)) ? new HttpPost(requestUri) : new HttpPut(requestUri);

        if (request.getPostBodyLength() > 0) {
          enclosingMethod.setEntity(
              new InputStreamEntity(request.getPostBody(), request.getPostBodyLength()));
        }
        httpMethod = enclosingMethod;
      } else if ("GET".equals(methodType)) {
        httpMethod = new HttpGet(requestUri);
      } else if ("HEAD".equals(methodType)) {
        httpMethod = new HttpHead(requestUri);
      } else if ("DELETE".equals(methodType)) {
        httpMethod = new HttpDelete(requestUri);
      }
      for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
        httpMethod.addHeader(entry.getKey(), StringUtils.join(entry.getValue(), ','));
      }

      // Disable following redirects.
      if (!request.getFollowRedirects()) {
        httpMethod.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);
      }

      // HttpClient doesn't handle all cases when breaking url (specifically '_' in domain)
      // So lets pass it the url parsed:
      response = FETCHER.execute(host, httpMethod);

      if (response == null) {
        throw new IOException("Unknown problem with request");
      }

      long now = System.currentTimeMillis();
      if (now - started > slowResponseWarning) {
        slowResponseWarning(request, started, now);
      }

      return makeResponse(response);

    } catch (Exception e) {
      long now = System.currentTimeMillis();

      // Find timeout exceptions, respond accordingly
      if (TIMEOUT_EXCEPTIONS.contains(e.getClass())) {
        LOG.info(
            "Timeout for "
                + request.getUri()
                + " Exception: "
                + e.getClass().getName()
                + " - "
                + e.getMessage()
                + " - "
                + (now - started)
                + "ms");
        return HttpResponse.timeout();
      }

      LOG.log(
          Level.INFO,
          "Got Exception fetching " + request.getUri() + " - " + (now - started) + "ms",
          e);

      // Separate shindig error from external error
      throw new GadgetException(
          GadgetException.Code.INTERNAL_SERVER_ERROR,
          e,
          HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    } finally {
      // cleanup any outstanding resources..
      if (httpMethod != null)
        try {
          httpMethod.abort();
        } catch (UnsupportedOperationException e) {
          // ignore
        }
    }
  }
 public static boolean isHtml(HttpRequest request, HttpResponseBuilder original) {
   String mimeType = getMimeType(request, original);
   return mimeType != null
       && (mimeType.contains("html"))
       && maybeAcceptHtml(request.getParam(UriCommon.Param.HTML_TAG_CONTEXT.getKey()));
 }
  // Loosely based off net.oauth.OAuthServlet, and even more loosely related
  // to the OAuth specification
  private MessageInfo parseMessage(HttpRequest request) {
    MessageInfo info = new MessageInfo();
    info.request = request;
    String method = request.getMethod();
    ParsedUrl parsed = new ParsedUrl(request.getUri().toString());

    List<OAuth.Parameter> params = Lists.newArrayList();
    params.addAll(parsed.getParsedQuery());

    if (!validParamLocations.contains(OAuthParamLocation.URI_QUERY)) {
      // Make sure nothing OAuth related ended up in the query string
      for (OAuth.Parameter p : params) {
        if (p.getKey().contains("oauth_")) {
          throw new RuntimeException("Found unexpected query param " + p.getKey());
        }
      }
    }

    // Parse authorization header
    if (validParamLocations.contains(OAuthParamLocation.AUTH_HEADER)) {
      String aznHeader = request.getHeader("Authorization");
      if (aznHeader != null) {
        info.aznHeader = aznHeader;
        for (OAuth.Parameter p : OAuthMessage.decodeAuthorization(aznHeader)) {
          if (!p.getKey().equalsIgnoreCase("realm")) {
            params.add(p);
          }
        }
      }
    }

    // Parse body
    info.body = request.getPostBodyAsString();
    try {
      info.rawBody = IOUtils.toByteArray(request.getPostBody());
    } catch (IOException e) {
      throw new RuntimeException("Can't read post body bytes", e);
    }
    if (OAuth.isFormEncoded(request.getHeader("Content-Type"))) {
      params.addAll(OAuth.decodeForm(request.getPostBodyAsString()));
      // If we're not configured to pass oauth parameters in the post body, double check
      // that they didn't end up there.
      if (!validParamLocations.contains(OAuthParamLocation.POST_BODY)) {
        if (info.body.contains("oauth_")) {
          throw new RuntimeException("Found unexpected post body data" + info.body);
        }
      }
    }

    // Return the lot
    info.message = new OAuthMessage(method, parsed.getLocation(), params);

    // Check for trusted parameters
    if (checkTrustedParams) {
      if (!"foo".equals(OAuthUtil.getParameter(info.message, "oauth_magic"))) {
        throw new RuntimeException("no oauth_trusted=foo parameter");
      }
      if (!"bar".equals(OAuthUtil.getParameter(info.message, "opensocial_magic"))) {
        throw new RuntimeException("no opensocial_trusted=foo parameter");
      }
      if (!"quux".equals(OAuthUtil.getParameter(info.message, "xoauth_magic"))) {
        throw new RuntimeException("no xoauth_magic=quux parameter");
      }
      trustedParamCount += 3;
    }

    return info;
  }
  private HttpResponse handleResourceUrl(HttpRequest request) throws Exception {
    MessageInfo info = parseMessage(request);
    String consumerId = info.message.getParameter("oauth_consumer_key");
    OAuthConsumer consumer;
    if (CONSUMER_KEY.equals(consumerId)) {
      consumer = oauthConsumer;
    } else if ("signedfetch".equals(consumerId)) {
      consumer = signedFetchConsumer;
    } else if ("container.com".equals(consumerId)) {
      consumer = signedFetchConsumer;
    } else {
      return makeOAuthProblemReport(
          OAuthConstants.PROBLEM_PARAMETER_MISSING,
          "oauth_consumer_key not found",
          HttpResponse.SC_BAD_REQUEST);
    }
    OAuthAccessor accessor = new OAuthAccessor(consumer);
    String responseBody = null;
    if (throttled) {
      return makeOAuthProblemReport(
          OAuthConstants.PROBLEM_CONSUMER_KEY_REFUSED, "exceeded quota", HttpResponse.SC_FORBIDDEN);
    }
    if (unauthorized) {
      return makeOAuthProblemReport(
          OAuthConstants.PROBLEM_PERMISSION_DENIED,
          "user refused access",
          HttpResponse.SC_UNAUTHORIZED);
    }
    if (consumer == oauthConsumer) {
      // for OAuth, check the access token.  We skip this for signed fetch
      String accessToken = info.message.getParameter("oauth_token");
      TokenState state = tokenState.get(accessToken);
      if (state == null) {
        return makeOAuthProblemReport(
            OAuthConstants.PROBLEM_TOKEN_REJECTED,
            "Access token unknown",
            HttpResponse.SC_UNAUTHORIZED);
      }
      // Check the signature
      accessor.accessToken = accessToken;
      accessor.tokenSecret = state.getSecret();
      validateMessage(accessor, info, false);

      if (state.getState() != State.APPROVED) {
        return makeOAuthProblemReport(
            OAuthConstants.PROBLEM_TOKEN_REVOKED,
            "User revoked permissions",
            HttpResponse.SC_UNAUTHORIZED);
      }
      if (sessionExtension) {
        long expiration = state.issued + TOKEN_EXPIRATION_SECONDS * 1000;
        if (expiration < clock.currentTimeMillis()) {
          return makeOAuthProblemReport(
              OAuthConstants.PROBLEM_ACCESS_TOKEN_EXPIRED,
              "token needs to be refreshed",
              HttpResponse.SC_UNAUTHORIZED);
        }
      }
      responseBody = "User data is " + state.getUserData();
    } else {
      // Check the signature
      validateMessage(accessor, info, false);

      // For signed fetch, just echo back the query parameters in the body
      responseBody = request.getUri().getQuery();
    }

    // Send back a response
    HttpResponseBuilder resp =
        new HttpResponseBuilder()
            .setHttpStatusCode(HttpResponse.SC_OK)
            .setResponseString(responseBody);
    if (info.aznHeader != null) {
      resp.setHeader(AUTHZ_ECHO_HEADER, info.aznHeader);
    }
    if (info.body != null) {
      resp.setHeader(BODY_ECHO_HEADER, info.body);
    }
    if (info.rawBody != null) {
      resp.setHeader(RAW_BODY_ECHO_HEADER, new String(Base64.encodeBase64(info.rawBody)));
    }
    return resp.create();
  }