/* * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). You may * not use this file except in compliance with the License. A copy of the * License is located at * * http://aws.amazon.com/apache2.0/ * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.amazonaws.events.es; import com.amazonaws.DefaultRequest; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.Signer; import com.amazonaws.http.HttpMethodName; import org.apache.http.Header; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import static org.apache.http.protocol.HttpCoreContext.HTTP_TARGET_HOST; /** * An HttpRequestInterceptor class that signs requests using the AWS Signer * and AWSCredentialsProvider. */ public class AWSSignerInterceptor implements HttpRequestInterceptor { /** * The service that we're connecting to. Technically not necessary. * Could be used by a future Signer, though. */ private final String service; /** * The particular signer implementation. */ private final Signer signer; /** * The source of AWS credentials for signing. */ private final AWSCredentialsProvider awsCredentialsProvider; /** * * @param service service that we're connecting to * @param signer particular signer implementation * @param awsCredentialsProvider source of AWS credentials for signing */ public AWSSignerInterceptor(final String service, final Signer signer, final AWSCredentialsProvider awsCredentialsProvider) { this.service = service; this.signer = signer; this.awsCredentialsProvider = awsCredentialsProvider; } /** * {@inheritDoc} */ @Override public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { URIBuilder uriBuilder; try { uriBuilder = new URIBuilder(request.getRequestLine().getUri()); } catch (URISyntaxException e) { throw new IOException("Invalid URI" , e); } // Copy Apache HttpRequest to AWS DefaultRequest DefaultRequest signableRequest = new DefaultRequest<>(service); HttpHost host = (HttpHost) context.getAttribute(HTTP_TARGET_HOST); if (host != null) { signableRequest.setEndpoint(URI.create(host.toURI())); } final HttpMethodName httpMethod = HttpMethodName.fromValue(request.getRequestLine().getMethod()); signableRequest.setHttpMethod(httpMethod); try { signableRequest.setResourcePath(uriBuilder.build().getRawPath()); } catch (URISyntaxException e) { throw new IOException("Invalid URI" , e); } if (request instanceof HttpEntityEnclosingRequest) { HttpEntityEnclosingRequest httpEntityEnclosingRequest = (HttpEntityEnclosingRequest) request; if (httpEntityEnclosingRequest.getEntity() != null) { signableRequest.setContent(httpEntityEnclosingRequest.getEntity().getContent()); } } signableRequest.setParameters(nvpToMapParams(uriBuilder.getQueryParams())); signableRequest.setHeaders(headerArrayToMap(request.getAllHeaders())); // Sign it signer.sign(signableRequest, awsCredentialsProvider.getCredentials()); // Now copy everything back request.setHeaders(mapToHeaderArray(signableRequest.getHeaders())); if (request instanceof HttpEntityEnclosingRequest) { HttpEntityEnclosingRequest httpEntityEnclosingRequest = (HttpEntityEnclosingRequest) request; if (httpEntityEnclosingRequest.getEntity() != null) { BasicHttpEntity basicHttpEntity = new BasicHttpEntity(); basicHttpEntity.setContent(signableRequest.getContent()); httpEntityEnclosingRequest.setEntity(basicHttpEntity); } } } /** * * @param params list of HTTP query params as NameValuePairs * @return a multimap of HTTP query params */ private static Map> nvpToMapParams(final List params) { Map> parameterMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (NameValuePair nvp : params) { List argsList = parameterMap.computeIfAbsent(nvp.getName(), k -> new ArrayList<>()); argsList.add(nvp.getValue()); } return parameterMap; } /** * @param mapParams a multimap of HTTP query params * @return list of HTTP query params as NameValuePairs */ private static List mapToNvpParams(final Map> mapParams) { List nameValuePairs = new ArrayList<>(); for (Map.Entry> paramEntry : mapParams.entrySet()) { for (String value : paramEntry.getValue()) { nameValuePairs.add(new BasicNameValuePair(paramEntry.getKey(), value)); } } return nameValuePairs; } /** * @param headers modeled Header objects * @return a Map of header entries */ private static Map headerArrayToMap(final Header[] headers) { Map headersMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (Header header : headers) { if (!skipHeader(header)) { headersMap.put(header.getName(), header.getValue()); } } return headersMap; } /** * @param header header line to check * @return true if the given header should be excluded when signing */ private static boolean skipHeader(final Header header) { return ("content-length".equalsIgnoreCase(header.getName()) && "0".equals(header.getValue())) // Strip Content-Length: 0 || "host".equalsIgnoreCase(header.getName()); // Host comes from endpoint } /** * @param mapHeaders Map of header entries * @return modeled Header objects */ private static Header[] mapToHeaderArray(final Map mapHeaders) { Header[] headers = new Header[mapHeaders.size()]; int i = 0; for (Map.Entry headerEntry : mapHeaders.entrySet()) { headers[i++] = new BasicHeader(headerEntry.getKey(), headerEntry.getValue()); } return headers; } }