package com.bazaarvoice.auth.hmac.client; import com.bazaarvoice.auth.hmac.common.RequestConfiguration; import com.bazaarvoice.auth.hmac.common.SignatureGenerator; import com.bazaarvoice.auth.hmac.common.TimeUtils; import com.bazaarvoice.auth.hmac.common.Version; import com.sun.jersey.api.client.ClientHandlerException; import com.sun.jersey.api.client.ClientRequest; import com.sun.jersey.api.client.RequestWriter; import com.sun.jersey.spi.MessageBodyWorkers; import javax.ws.rs.core.UriBuilder; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; /** * Encodes outbound HTTP requests with security credentials so that they can be authenticated * by the receiving server. */ public class RequestEncoder extends RequestWriter { private final String apiKey; private final String secretKey; private final SignatureGenerator signatureGenerator; private final RequestConfiguration requestConfiguration; public RequestEncoder(String apiKey, String secretKey, MessageBodyWorkers messageBodyWorkers, SignatureGenerator signatureGenerator, RequestConfiguration requestConfiguration) { super(messageBodyWorkers); this.apiKey = apiKey; this.secretKey = secretKey; this.signatureGenerator = signatureGenerator; this.requestConfiguration = requestConfiguration; } public void encode(ClientRequest request) { String timestamp = TimeUtils.getCurrentTimestamp(); addApiKey(request); addTimestamp(request, timestamp); addSignature(request, timestamp); addVersion(request, Version.V1); } private void addApiKey(ClientRequest request) { URI uriWithApiKey = UriBuilder.fromUri(request.getURI()) .queryParam(this.requestConfiguration.getApiKeyQueryParamName(), apiKey) .build(); request.setURI(uriWithApiKey); } private void addSignature(ClientRequest request, String timestamp) { String signature = buildSignature(request, timestamp); request.getHeaders().putSingle(this.requestConfiguration.getSignatureHttpHeader(), signature); } private void addTimestamp(ClientRequest request, String timestamp) { request.getHeaders().putSingle(this.requestConfiguration.getTimestampHttpHeader(), timestamp); } private void addVersion(ClientRequest request, Version version) { request.getHeaders().putSingle(this.requestConfiguration.getVersionHttpHeader(), version.toString()); } private String buildSignature(ClientRequest request, String timestamp) { String method = getMethod(request); String path = getPath(request); byte[] content = getContent(request); return signatureGenerator.generate(secretKey, method, timestamp, path, content); } private String getMethod(ClientRequest request) { return request.getMethod(); } private String getPath(ClientRequest request) { // Get the path and any query parameters (e.g. /api/v1/pizza?sort=toppings&apiKey=someKey) return String.format("%s?%s", request.getURI().getPath(), request.getURI().getQuery()); } private byte[] getContent(ClientRequest request) { return getSerializedEntity(request); } /** * Get the serialized representation of the request entity. This is used when generating the client * signature, because this is the representation that the server will receive and use when it generates * the server-side signature to compare to the client-side signature. * * @see com.sun.jersey.client.urlconnection.URLConnectionClientHandler */ private byte[] getSerializedEntity(ClientRequest request) { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { // By using the RequestWriter parent class, we match the behavior of entity writing from // for example, com.sun.jersey.client.urlconnection.URLConnectionClientHandler. writeRequestEntity(request, new RequestEntityWriterListener() { public void onRequestEntitySize(long size) throws IOException { } public OutputStream onGetOutputStream() throws IOException { return outputStream; } }); } catch (IOException e) { throw new ClientHandlerException("Unable to serialize request entity", e); } return outputStream.toByteArray(); } }