EMMA Coverage Report (generated Mon Oct 17 05:41:20 EDT 2011)
[all classes][org.jclouds.azure.storage.filters]

COVERAGE SUMMARY FOR SOURCE FILE [SharedKeyLiteAuthentication.java]

nameclass, %method, %block, %line, %
SharedKeyLiteAuthentication.java100% (1/1)100% (13/13)83%  (320/387)87%  (61.7/71)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class SharedKeyLiteAuthentication100% (1/1)100% (13/13)83%  (320/387)87%  (61.7/71)
appendCanonicalizedHeaders (HttpRequest, StringBuilder): void 100% (1/1)37%  (23/63)45%  (4.1/9)
calculateSignature (String): String 100% (1/1)62%  (10/16)75%  (3/4)
signString (String): String 100% (1/1)65%  (13/20)60%  (3/5)
appendPayloadMetadata (HttpRequest, StringBuilder): void 100% (1/1)77%  (27/35)88%  (2.6/3)
createStringToSign (HttpRequest): String 100% (1/1)86%  (38/44)90%  (9/10)
SharedKeyLiteAuthentication (SignatureWire, String, String, Provider, Crypto,... 100% (1/1)100% (33/33)100% (10/10)
appendCanonicalizedResource (HttpRequest, StringBuilder): void 100% (1/1)100% (12/12)100% (3/3)
appendHttpHeaders (HttpRequest, StringBuilder): void 100% (1/1)100% (30/30)100% (3/3)
appendMethod (HttpRequest, StringBuilder): void 100% (1/1)100% (8/8)100% (2/2)
appendUriPath (HttpRequest, StringBuilder): void 100% (1/1)100% (60/60)100% (11/11)
filter (HttpRequest): HttpRequest 100% (1/1)100% (24/24)100% (5/5)
replaceAuthorizationHeader (HttpRequest, String): HttpRequest 100% (1/1)100% (22/22)100% (1/1)
replaceDateHeader (HttpRequest): HttpRequest 100% (1/1)100% (20/20)100% (5/5)

1/**
2 * Licensed to jclouds, Inc. (jclouds) under one or more
3 * contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  jclouds licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.jclouds.azure.storage.filters;
20 
21import static org.jclouds.util.Patterns.NEWLINE_PATTERN;
22 
23import java.util.Set;
24import java.util.TreeSet;
25 
26import javax.annotation.Resource;
27import javax.inject.Inject;
28import javax.inject.Named;
29import javax.inject.Provider;
30import javax.inject.Singleton;
31import javax.ws.rs.core.HttpHeaders;
32 
33import org.jclouds.Constants;
34import org.jclouds.crypto.Crypto;
35import org.jclouds.crypto.CryptoStreams;
36import org.jclouds.date.TimeStamp;
37import org.jclouds.http.HttpException;
38import org.jclouds.http.HttpRequest;
39import org.jclouds.http.HttpRequestFilter;
40import org.jclouds.http.HttpUtils;
41import org.jclouds.http.internal.SignatureWire;
42import org.jclouds.http.utils.ModifyRequest;
43import org.jclouds.io.InputSuppliers;
44import org.jclouds.logging.Logger;
45import org.jclouds.util.Strings2;
46 
47import com.google.common.annotations.VisibleForTesting;
48import com.google.common.collect.ImmutableMap;
49import com.google.common.collect.ImmutableMap.Builder;
50import com.google.common.collect.Multimaps;
51 
52/**
53 * Signs the Azure Storage request.
54 * 
55 * @see <a href= "http://msdn.microsoft.com/en-us/library/dd179428.aspx" />
56 * @author Adrian Cole
57 * 
58 */
59@Singleton
60public class SharedKeyLiteAuthentication implements HttpRequestFilter {
61   private final String[] firstHeadersToSign = new String[] { HttpHeaders.DATE };
62 
63   private final SignatureWire signatureWire;
64   private final String identity;
65   private final byte[] key;
66   private final Provider<String> timeStampProvider;
67   private final Crypto crypto;
68   private final HttpUtils utils;
69 
70   @Resource
71   @Named(Constants.LOGGER_SIGNATURE)
72   Logger signatureLog = Logger.NULL;
73 
74   @Inject
75   public SharedKeyLiteAuthentication(SignatureWire signatureWire, @Named(Constants.PROPERTY_IDENTITY) String identity,
76         @Named(Constants.PROPERTY_CREDENTIAL) String encodedKey, @TimeStamp Provider<String> timeStampProvider,
77         Crypto crypto, HttpUtils utils) {
78      this.crypto = crypto;
79      this.utils = utils;
80      this.signatureWire = signatureWire;
81      this.identity = identity;
82      this.key = CryptoStreams.base64(encodedKey);
83      this.timeStampProvider = timeStampProvider;
84   }
85 
86   public HttpRequest filter(HttpRequest request) throws HttpException {
87      request = replaceDateHeader(request);
88      String signature = calculateSignature(createStringToSign(request));
89      request = replaceAuthorizationHeader(request, signature);
90      utils.logRequest(signatureLog, request, "<<");
91      return request;
92   }
93 
94   HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) {
95      return ModifyRequest.replaceHeader(request, HttpHeaders.AUTHORIZATION, "SharedKeyLite " + identity + ":"
96            + signature);
97   }
98 
99   HttpRequest replaceDateHeader(HttpRequest request) {
100      Builder<String, String> builder = ImmutableMap.builder();
101      String date = timeStampProvider.get();
102      builder.put(HttpHeaders.DATE, date);
103      request = ModifyRequest.replaceHeaders(request, Multimaps.forMap(builder.build()));
104      return request;
105   }
106 
107   public String createStringToSign(HttpRequest request) {
108      utils.logRequest(signatureLog, request, ">>");
109      StringBuilder buffer = new StringBuilder();
110      // re-sign the request
111      appendMethod(request, buffer);
112      appendPayloadMetadata(request, buffer);
113      appendHttpHeaders(request, buffer);
114      appendCanonicalizedHeaders(request, buffer);
115      appendCanonicalizedResource(request, buffer);
116      if (signatureWire.enabled())
117         signatureWire.output(buffer.toString());
118      return buffer.toString();
119   }
120 
121   private void appendPayloadMetadata(HttpRequest request, StringBuilder buffer) {
122      buffer.append(
123            utils.valueOrEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata()
124                  .getContentMD5())).append("\n");
125      buffer.append(
126            utils.valueOrEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata()
127                  .getContentType())).append("\n");
128   }
129 
130   private String calculateSignature(String toSign) throws HttpException {
131      String signature = signString(toSign);
132      if (signatureWire.enabled())
133         signatureWire.input(Strings2.toInputStream(signature));
134      return signature;
135   }
136 
137   public String signString(String toSign) {
138      String signature;
139      try {
140         signature = CryptoStreams.base64(CryptoStreams.mac(InputSuppliers.of(toSign), crypto.hmacSHA256(key)));
141      } catch (Exception e) {
142         throw new HttpException("error signing request", e);
143      }
144      return signature;
145   }
146 
147   private void appendMethod(HttpRequest request, StringBuilder toSign) {
148      toSign.append(request.getMethod()).append("\n");
149   }
150 
151   private void appendCanonicalizedHeaders(HttpRequest request, StringBuilder toSign) {
152      Set<String> headers = new TreeSet<String>(request.getHeaders().keySet());
153      for (String header : headers) {
154         if (header.startsWith("x-ms-")) {
155            toSign.append(header.toLowerCase()).append(":");
156            for (String value : request.getHeaders().get(header)) {
157               toSign.append(Strings2.replaceAll(value, NEWLINE_PATTERN, "")).append(",");
158            }
159            toSign.deleteCharAt(toSign.lastIndexOf(","));
160            toSign.append("\n");
161         }
162      }
163   }
164 
165   private void appendHttpHeaders(HttpRequest request, StringBuilder toSign) {
166      for (String header : firstHeadersToSign)
167         toSign.append(utils.valueOrEmpty(request.getHeaders().get(header))).append("\n");
168   }
169 
170   @VisibleForTesting
171   void appendCanonicalizedResource(HttpRequest request, StringBuilder toSign) {
172 
173      // 1. Beginning with an empty string (""), append a forward slash (/), followed by the name of
174      // the identity that owns the resource being accessed.
175      toSign.append("/").append(identity);
176      appendUriPath(request, toSign);
177   }
178 
179   @VisibleForTesting
180   void appendUriPath(HttpRequest request, StringBuilder toSign) {
181      // 2. Append the resource's encoded URI path
182      toSign.append(request.getEndpoint().getRawPath());
183 
184      // If the request URI addresses a component of the
185      // resource, append the appropriate query string. The query string should include the question
186      // mark and the comp parameter (for example, ?comp=metadata). No other parameters should be
187      // included on the query string.
188      if (request.getEndpoint().getQuery() != null) {
189         StringBuilder paramsToSign = new StringBuilder("?");
190 
191         String[] params = request.getEndpoint().getQuery().split("&");
192         for (String param : params) {
193            String[] paramNameAndValue = param.split("=");
194 
195            if ("comp".equals(paramNameAndValue[0])) {
196               paramsToSign.append(param);
197            }
198         }
199 
200         if (paramsToSign.length() > 1) {
201            toSign.append(paramsToSign);
202         }
203      }
204   }
205 
206}

[all classes][org.jclouds.azure.storage.filters]
EMMA 2.0.5312 (C) Vladimir Roubtsov