blob: 9fecf69ca327dc915639db9047899d9beccb64be [file] [log] [blame]
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License 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 org.jclouds.fujitsu.fgcp.filters;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.UnrecoverableKeyException;
import java.util.Calendar;
import java.util.Locale;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import org.jclouds.Constants;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.date.TimeStamp;
import org.jclouds.encryption.internal.Base64;
import org.jclouds.fujitsu.fgcp.reference.RequestParameters;
import org.jclouds.http.HttpException;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.internal.SignatureWire;
import org.jclouds.http.utils.Queries;
import org.jclouds.logging.Logger;
import org.jclouds.rest.RequestSigner;
import org.jclouds.rest.annotations.ApiVersion;
import org.jclouds.rest.annotations.Credential;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Multimap;
/**
* Generates and signs the access key id and adds the mandatory http header and
* request parameters to the request.
*
* @author Dies Koper
*/
@Singleton
public class RequestAuthenticator implements HttpRequestFilter, RequestSigner {
@Resource
@Named(Constants.LOGGER_SIGNATURE)
private Logger signatureLog = Logger.NULL;
final Provider<Calendar> calendarProvider;
final Signature signer;
final Provider<UriBuilder> builder;
final String apiVersion;
public String signatureVersion = "1.0";
public String signatureMethod = "SHA1withRSA";
private HttpUtils utils;
@Inject
public RequestAuthenticator(@TimeStamp Provider<Calendar> calendarProvider,
Provider<KeyStore> keyStoreProvider,
@Credential String keyPassword, Provider<UriBuilder> builder,
HttpUtils utils, SignatureWire signatureWire,
@ApiVersion String apiVersion) throws NoSuchAlgorithmException,
InvalidKeyException, KeyStoreException, UnrecoverableKeyException {
this.calendarProvider = checkNotNull(calendarProvider);
this.builder = checkNotNull(builder);
this.utils = checkNotNull(utils, "utils");
this.apiVersion = checkNotNull(apiVersion, "apiVersion");
signer = Signature.getInstance(signatureMethod);
KeyStore keyStore = checkNotNull(keyStoreProvider).get();
String alias = keyStore.aliases().nextElement(); // there should be only
// one private key
PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias,
keyPassword.toCharArray());
signer.initSign(privateKey);
}
public HttpRequest filter(HttpRequest request) throws HttpException {
checkNotNull(request, "request must be present");
utils.logRequest(signatureLog, request, ">>");
// create accesskeyid
String accessKeyId = createStringToSign(request);
String signature = sign(accessKeyId);
// leaving this in for now for reference in case I need it for multipart
// POSTs
// add parameters. Note that in case of a GET, url escaping is required
/*
* Multimap<String, String> decodedParams = null; if
* (HttpMethod.GET.equals(request.getMethod())) { decodedParams =
* ModifyRequest.parseQueryToMap(request.getEndpoint().getRawQuery());
*
* } else if (HttpMethod.POST.equals(request.getMethod())) {
* decodedParams =
* ModifyRequest.parseQueryToMap(request.getPayload().getRawContent()
* .toString()); }
*
* checkNotNull(decodedParams, "no query params found");
* System.out.println("filter: request params before: " +
* decodedParams.toString()); addAuthenticationParams(decodedParams,
* accessKeyId, signature); addLocaleParam(decodedParams);
* System.out.println("filter: request params after : " +
* decodedParams.toString()); if (signatureWire.enabled())
* signatureWire.output(decodedParams);
*
*
* request = setPayload(request, decodedParams);
*/
// only "en" and "ja" are allowed
String lang = Locale.JAPANESE.getLanguage().equals(
Locale.getDefault().getLanguage()) ? Locale.JAPANESE
.getLanguage() : Locale.ENGLISH.getLanguage();
if (HttpMethod.GET.equals(request.getMethod())) {
Multimap<String, String> decodedParams = Queries
.parseQueryToMap(request.getEndpoint().getRawQuery());
if (!decodedParams.containsKey(RequestParameters.VERSION)) {
decodedParams.put(RequestParameters.VERSION, apiVersion);
}
decodedParams.put(RequestParameters.LOCALE, lang);
decodedParams.put(RequestParameters.ACCESS_KEY_ID, accessKeyId);
decodedParams.put(RequestParameters.SIGNATURE, signature);
request = request.toBuilder().replaceQueryParams(decodedParams)
.build();
} else {
String payload = request.getPayload().getRawContent().toString();
payload = createXmlElementWithValue(payload,
RequestParameters.VERSION, apiVersion);
payload = createXmlElementWithValue(payload,
RequestParameters.LOCALE, lang);
payload = createXmlElementWithValue(payload,
RequestParameters.ACCESS_KEY_ID, accessKeyId);
payload = createXmlElementWithValue(payload,
RequestParameters.SIGNATURE, signature);
// ensure there are no other query params left
request.setPayload(payload);
request.getPayload().getContentMetadata()
.setContentType(MediaType.TEXT_XML);
}
// may need to do this elsewhere (see ConvertToGaeRequest)
HttpRequest filteredRequest = request.toBuilder()
.replaceHeader(HttpHeaders.USER_AGENT, "OViSS-API-CLIENT")
.build();
utils.logRequest(signatureLog, filteredRequest, ">>->");
return filteredRequest;
}
String createXmlElementWithValue(String payload, String tag, String value) {
String startTag = String.format("<%s>", tag);
String endTag = String.format("</%s>", tag);
return payload.replace(startTag + endTag, startTag + value + endTag);
}
/*
* HttpRequest setPayload(HttpRequest request, Multimap<String, String>
* decodedParams) {
* request.setPayload(ModifyRequest.makeQueryLine(decodedParams, null)); //
* request.getPayload().getContentMetadata().setContentType(
* "application/x-www-form-urlencoded"); return request; }
*/
@VisibleForTesting
public String sign(String stringToSign) {
String signed;
try {
signer.update(stringToSign.getBytes("UTF-8"));
signed = Base64.encodeBytes(signer.sign()).replace("\n", "\r\n");
// signed = CryptoStreams.base64(signer.sign());
} catch (Exception e) {
throw new HttpException("error signing request", e);
}
// if (signatureWire.enabled())
// signatureWire.input(Strings2.toInputStream(signed));
return signed;
}
@VisibleForTesting
public String generateAccessKeyId() {
Calendar cal = calendarProvider.get();
String timezone = cal.getTimeZone().getDisplayName(Locale.ENGLISH);
String expires = String.valueOf(cal.getTime().getTime());
String signatureData = String.format("%s&%s&%s&%s", timezone, expires,
signatureVersion, signatureMethod);
try {
String accessKeyId = Base64.encodeBytes(signatureData.getBytes("UTF-8"));
return accessKeyId.replace("\n", "\r\n");
// return CryptoStreams.base64(signatureData.getBytes("UTF-8")).;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // should never happen as
// signatureData contains only ASCII
}
}
@Override
public String createStringToSign(HttpRequest input) {
return generateAccessKeyId();
}
}