/* * 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://www.apache.org/licenses/LICENSE-2.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.neptune.auth; import com.amazonaws.DefaultRequest; import com.amazonaws.SignableRequest; import com.amazonaws.auth.AWS4Signer; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.BasicSessionCredentials; import com.amazonaws.http.HttpMethodName; import com.amazonaws.util.SdkHttpUtils; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.amazonaws.auth.internal.SignerConstants.AUTHORIZATION; import static com.amazonaws.auth.internal.SignerConstants.HOST; import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_DATE; /** * Base implementation of {@link NeptuneSigV4Signer} interface. *
* This implementation uses the internal AWS SDK signer to sign requests. The constructor * requires the region name for which to sign as well as an {@link AWSCredentialsProvider} * providing access to the credentials used for signing the request. The service name used * within the signing process is hardcoded to be "neptune-db", which is the official name * of the Amazon Neptune service. *
* The implementation uses the following approach for signing the request: *
* Note that the signable request internally, during the signing process, adds a "Host" header. * This may lead to problems if the original request has a host header with a name in different * capitalization (e.g. "host"), leading to duplicate host headers and the signing process to fail. * Hence, when using the API you need to make sure that there is either no host header in your * original request or the host header uses the exact string "Host" as the header name. The easiest * solution, if you have control over the native HTTP request, is to just leave out the host * header when translating and create one when signing (the host header value will be part of * the struct returned from the signing process). * * @param nativeRequest the native HTTP request * @return the {@link SignableRequest} * @throws NeptuneSigV4SignerException in case something goes wrong during translation */ protected abstract SignableRequest> toSignableRequest(final T nativeRequest) throws NeptuneSigV4SignerException; /** * Attach the signature provided in the signature object to the nativeRequest. * More precisely, the signature contains two headers, X-AMZ-DATE and an Authorization * header, which need to be attached to the native HTTP request as HTTP headers or query string depending on the * type of signature requested - header/pre-signed url. * * @param nativeRequest the native HTTP request * @param signature the signature information to attach * @throws NeptuneSigV4SignerException in case something goes wrong during signing of the native request */ protected abstract void attachSignature(final T nativeRequest, final NeptuneSigV4Signature signature) throws NeptuneSigV4SignerException; /** * Main logics to sign the request. The scheme is to convert the request into a * signable request using toSignableRequest, then sign it using the AWS SDK, and * finally attach the signature headers to the original request using attachSignature. *
* Note that toSignableRequest and attachSignature are abstract classes in * this base class, they require dedicated implementations depending on the type of * the native HTTP request. * * @param request the request to be signed * @throws NeptuneSigV4SignerException in case something goes wrong during signing */ @Override public void signRequest(final T request) throws NeptuneSigV4SignerException { try { // 1. Convert the Apache Http request into an AWS SDK signable request // => to be implemented in subclass final SignableRequest> awsSignableRequest = toSignableRequest(request); // 2. Sign the AWS SDK signable request (which internally adds some HTTP headers) // => generic, using the AWS SDK signer final AWSCredentials credentials = awsCredentialsProvider.getCredentials(); aws4Signer.sign(awsSignableRequest, credentials); // extract session token if temporary credentials are provided String sessionToken = ""; if ((credentials instanceof BasicSessionCredentials)) { sessionToken = ((BasicSessionCredentials) credentials).getSessionToken(); } final NeptuneSigV4Signature signature = new NeptuneSigV4Signature( awsSignableRequest.getHeaders().get(HOST), awsSignableRequest.getHeaders().get(X_AMZ_DATE), awsSignableRequest.getHeaders().get(AUTHORIZATION), sessionToken); // 3. Copy over the Signature V4 headers to the original request // => to be implemented in subclass attachSignature(request, signature); } catch (final Throwable t) { throw new NeptuneSigV4SignerException(t); } } /** * Helper method to create an AWS SDK {@link SignableRequest} based on HTTP information. * None of the information passed in here must be null. Can (yet must not) be used by * implementing classes. *
* Also note that the resulting request will not yet be actually signed; this is really
* only a helper to convert the relevant information from the original HTTP request into
* the AWS SDK's internal format that will be used for computing the signature in a later
* step, see the signRequest method for details.
*
* @param httpMethodName name of the HTTP method (e.g. "GET", "POST", ...)
* @param httpEndpointUri URI of the endpoint to which the HTTP request is sent. E.g. http://[host]:port/
* @param resourcePath the resource path of the request. /resource/id is the path in http://[host]:port/resource/id
* @param httpHeaders the headers, defined as a mapping from keys (header name) to values (header values)
* @param httpParameters the parameters, defined as a mapping from keys (parameter names) to a list of values
* @param httpContent the content carried by the HTTP request; use an empty InputStream for GET requests
*
* @return the resulting AWS SDK signable request
* @throws NeptuneSigV4SignerException in case something goes wrong signing the request
*/
protected SignableRequest> convertToSignableRequest(
final String httpMethodName,
final URI httpEndpointUri,
final String resourcePath,
final Map