/*
* Copyright (C) 2011 Citrix Systems, Inc. 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.
* 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 com.cloud.stack;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
/**
* CloudStackCommand wraps command properties that are being sent to CloudStack
*
* @author Kelven Yang
*/
public class CloudStackCommand {
Map<String, String> _params = new HashMap<String, String>();
public CloudStackCommand(String cmdName) {
this(cmdName, "json");
}
public CloudStackCommand(String cmdName, String responseType) {
_params.put("command", cmdName);
if(responseType != null)
_params.put("response", responseType);
}
public CloudStackCommand setParam(String paramName, String paramValue) {
assert(paramName != null);
assert(paramValue != null);
_params.put(paramName, paramValue);
return this;
}
public String signCommand(String apiKey, String secretKey) throws SignatureException {
assert(_params.get("command") != null);
List<String> paramNames = new ArrayList<String>();
for(String paramName : _params.keySet())
paramNames.add(paramName);
paramNames.add("apikey");
Collections.sort(paramNames);
StringBuffer sb = new StringBuffer();
for(String name : paramNames) {
String value;
if("apikey".equals(name))
value = apiKey;
else
value = _params.get(name);
assert(value != null);
value = urlSafe(value);
if(sb.length() == 0) {
sb.append(name).append("=").append(value);
} else {
sb.append("&").append(name).append("=").append(value);
}
}
String signature = calculateRFC2104HMAC(sb.toString().toLowerCase(), secretKey);
return composeQueryString(apiKey, signature);
}
private String composeQueryString(String apiKey, String signature) {
StringBuffer sb = new StringBuffer();
String name;
String value;
// treat command specially (although not really necessary )
name = "command";
value = _params.get(name);
if(value != null) {
value = urlSafe(value);
sb.append(name).append("=").append(value);
}
for(Map.Entry<String, String> entry : _params.entrySet()) {
name = entry.getKey();
if(!"command".equals(name)) {
value = urlSafe(entry.getValue());
if(sb.length() == 0)
sb.append(name).append("=").append(value);
else
sb.append("&").append(name).append("=").append(value);
}
}
sb.append("&apikey=").append(urlSafe(apiKey));
sb.append("&signature=").append(urlSafe(signature));
return sb.toString();
}
private String calculateRFC2104HMAC( String signIt, String secretKey ) throws SignatureException {
String result = null;
try {
SecretKeySpec key = new SecretKeySpec( secretKey.getBytes(), "HmacSHA1" );
Mac hmacSha1 = Mac.getInstance( "HmacSHA1" );
hmacSha1.init( key );
byte [] rawHmac = hmacSha1.doFinal( signIt.getBytes());
result = new String( Base64.encodeBase64( rawHmac ));
} catch( Exception e ) {
throw new SignatureException( "Failed to generate keyed HMAC on soap request: " + e.getMessage());
}
return result.trim();
}
private String urlSafe(String value) {
try {
if (value != null)
return URLEncoder.encode(value, "UTF-8").replaceAll("\\+", "%20");
else
return null;
} catch (UnsupportedEncodingException e) {
assert(false);
}
return value;
}
}