package com.pkm;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.FileReader;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
/**
* Created by pritom on 2/01/2018.
*/
public class XeroPrivateApiUtils {
private static final String NONCE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final String ORGANIZATION_URL = "https://api.xero.com/api.xro/2.0/Organisation";
private String sbs;
private String path;
private String method;
private Map<Object, Object> configs = new HashMap<>();
private Map<Object, Object> params = new HashMap<>();
private Map<Object, Object> sign = new HashMap<>();
private String KEY_FILE = null;
private String CONSUMER_KEY = null;
private String CONSUMER_SECRET = null;
public static void main(String[] args) throws Exception {
XeroPrivateApiUtils xero = new XeroPrivateApiUtils();
HttpJavaClient.Response response = xero.execute("GET", XeroPrivateApiUtils.ORGANIZATION_URL, new HashMap(), new HashMap());
System.out.println(response.toString());
}
private XeroPrivateApiUtils() {
this.KEY_FILE = "private.key";
this.CONSUMER_KEY = "IKAEAI8BA..........1MAOR9XBEHU";
this.CONSUMER_SECRET = "STXPEDM..........0FWMPLIDGB6DS";
putToMap(this.configs, "consumer_key", CONSUMER_KEY);
putToMap(this.configs, "consumer_secret", CONSUMER_SECRET);
putToMap(this.configs, "core_version", "2.0");
putToMap(this.configs, "payroll_version", "1.0");
putToMap(this.configs, "file_version", "1.0");
putToMap(this.configs, "application_type", "Private");
}
HttpJavaClient.Response execute(
String method, String path,
Map<Object, Object> configs,
Map<Object, Object> params
) throws Exception {
this.method = method;
this.path = path;
this.params = params;
putToMap(this.configs, configs);
this.buildParameters();
this.sign();
Map<Object, Object> headers = new HashMap<>();
putToMap(headers, "Accept", "application/json");
if (this.method.equalsIgnoreCase("get")) {
return HttpJavaClient.doGet((String) this.sign.get("signed_url"), headers);
}
return null;
}
private void sign() throws Exception {
putToMap(this.params, "oauth_signature", this.generateSignature());
putToMap(this.sign, "parameters", this.params);
putToMap(this.sign, "signature", escape((String) this.params.get("oauth_signature")));
putToMap(this.sign, "signed_url", this.path + "?" + normalizeParameters(true));
putToMap(this.sign, "sbs", this.sbs);
}
private String generateSignature() throws Exception {
switch ((String) this.params.get("oauth_signature_method")) {
case "RSA-SHA1":
this.sbs = escape(this.method.toUpperCase());
this.sbs += "&" + escape(this.path);
this.sbs += "&" + escape(this.normalizeParameters(false));
RSAPrivateKey privateKey = getPrivateKey(KEY_FILE);
Signature sign = Signature.getInstance("SHA1withRSA");
sign.initSign(privateKey);
sign.update(this.sbs.getBytes("UTF-8"));
return encodeBase64(sign.sign());
case "HMAC-SHA1":
String secret = this.configs.get("consumer_secret") + "&";
if (this.configs.get("access_token_secret") != null) {
secret = secret + this.configs.get("access_token_secret");
}
this.sbs = escape(this.method.toUpperCase());
this.sbs += "&" + escape(this.path);
this.sbs += "&" + escape(this.normalizeParameters(false));
return hmacSha(secret, this.sbs);
default:
throw new Exception("Undefined signature method");
}
}
private static String hmacSha(String key, String value) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(keySpec);
byte[] result = mac.doFinal(value.getBytes());
return encodeBase64(result);
}
public static String encodeBase64(String o) {
BASE64Encoder base64Encoder = new BASE64Encoder();
return base64Encoder.encodeBuffer(o.getBytes()).replace("\r\n", "").replace("\n", "");
}
private static String encodeBase64(byte[] o) {
BASE64Encoder base64Encoder = new BASE64Encoder();
return base64Encoder.encodeBuffer(o).replace("\r\n", "").replace("\n", "");
}
private static RSAPrivateKey getPrivateKey(String fileName) throws Exception {
byte[] encoded = decodeBase64(getKey(fileName));
KeyFactory kf = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
return (RSAPrivateKey) kf.generatePrivate(keySpec);
}
private static byte[] decodeBase64(String o) throws Exception {
BASE64Decoder decoder = new BASE64Decoder();
return decoder.decodeBuffer(o);
}
private String normalizeParameters(Boolean filter) throws Exception {
String normalized = "";
for (Object o : this.params.entrySet()) {
Map.Entry me2 = (Map.Entry) o;
String key = me2.getKey().toString();
Object value = me2.getValue();
Boolean allow = !(key.equalsIgnoreCase("xml") && filter) && !key.endsWith("_secret");
if (allow) {
normalized += escape(key) + "=" + escape(value.toString()) + "&";
}
}
return normalized.length() > 0 ? normalized.substring(0, normalized.length() - 1) : normalized;
}
private static String escape(String context) throws Exception {
context = rawUrlEncode(context);
context = stringReplace("+", "%20", context);
context = stringReplace("!", "%21", context);
context = stringReplace("*", "%2A", context);
context = stringReplace("\'", "%27", context);
context = stringReplace("(", "%28", context);
context = stringReplace(")", "%29", context);
return context;
}
private static String rawUrlEncode(String context) throws Exception {
return URLEncoder.encode(context, "UTF-8");
}
private static String rawUrlDecode(String context) throws Exception {
return URLDecoder.decode(context, "UTF-8");
}
private static String stringReplace(String search, String fill, String context) {
return context.replace(search, fill);
}
private void buildParameters() {
putToMap(this.params, "oauth_nonce", shuffle(5));
putToMap(this.params, "oauth_token", CONSUMER_KEY);
putToMap(this.params, "oauth_version", "1.0");
putToMap(this.params, "oauth_timestamp", "" + ((System.currentTimeMillis()) / 1000));
putToMap(this.params, "oauth_consumer_key", CONSUMER_KEY);
putToMap(this.params, "oauth_signature_method", "RSA-SHA1");
this.params = new TreeMap(this.params); //Sorted map by key
}
private static String shuffle(Integer length) {
List<Character> characters = new ArrayList<Character>();
for(char c : NONCE.toCharArray()){
characters.add(c);
}
StringBuilder output = new StringBuilder(length);
while(length != 0) {
int randPicker = (int) (Math.random() * characters.size());
output.append(characters.get(randPicker));
length --;
}
return output.toString();
}
private static String getKey(String filename) throws Exception {
String key = "";
BufferedReader br = new BufferedReader(new FileReader(filename));
String line;
while ((line = br.readLine()) != null) {
if (!line.startsWith("-")) {
key += line + "\n";
}
}
br.close();
return key;
}
private static void putToMap(Map<Object, Object> map, Map fromMap) {
for (Object key : fromMap.keySet().toArray()) {
putToMap(map, key.toString(), fromMap.get(key));
}
}
private static void putToMap(Map<Object, Object> map, Object key, Object value) {
map.put(key, value);
}
private static void println(Object o) {
System.out.println("" + o);
}
private static void exception() throws Exception {
throw new Exception();
}
}