package com.gorillalogic.monkeyconsole.editors.utils; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.nio.charset.Charset; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONException; import org.json.JSONObject; import com.gorillalogic.cloud.ideversion.CloudConstants; import com.gorillalogic.cloud.ideversion.FileUtils; import com.gorillalogic.cloud.ideversion.HttpUtils; import com.gorillalogic.monkeyconsole.plugin.FoneMonkeyPlugin; import com.gorillalogic.monkeyconsole.preferences.PreferenceConstants; public class CloudServices { static String learnMoreUrl = "http://www.gorillalogic.com/cloud/overview"; public static String registerUrl = "https://www.gorillalogic.com/cloud-register"; static String acceptTermsUrl = "https://www.gorillalogic.com/cloudmonkey/license-agreement"; static String logEventsUrl = "https://cloud.gorillalogic.com" + CloudConstants.LOG_EVENT; static String userToken = ""; protected static String getControllerProtocol() { String protocol = FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CONTROLLER_PROTOCOL); if (protocol == null || protocol.length()==0) { protocol = "https"; } return protocol; } protected static String getControllerPort(String protocol) { if (protocol.equals("http")) { String s=FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CONTROLLER_PORT); if (s!=null && s.length()>0) { return s; } else { return "80"; } } else { String s = FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CONTROLLER_SSL_PORT); if (s!=null && s.length()>0) { return s; } else { return "443"; } } } protected static String getControllerUrlRoot() { String protocol = CloudServices.getControllerProtocol(); return protocol + "://" + CloudServices.getControllerHost() + ":" + CloudServices.getControllerPort(protocol); } public static String getToken() throws CloudServiceException { if (userToken == null || userToken.length() == 0 || checkTokenValidityAgainstServer(userToken)) { userToken = login( FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CLOUDUSR), FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CLOUDPASS)); } return userToken; } public static String getTokenAndUsername() throws CloudServiceException { return "\"token\":\"" + getToken() + "\",\"username\":\"" + FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CLOUDUSR) + "\""; } public static JSONObject getDeviceTypes() throws CloudServiceException { String errorMessage = "CloudMonkey Appliance at " + CloudServices.getControllerHost() + " could not be reached."; try { String url = CloudServices.getControllerUrlRoot() + CloudConstants.INFO_PARAMS; JSONObject jo = new JSONObject(HttpUtils.get(url)); // log("getDeviceTypes() elapsed=" + (System.currentTimeMillis() - start) + "ms"); return jo; } catch (JSONException e) { throw new CloudServiceException(errorMessage, e); } catch (IOException e) { throw new CloudServiceException(errorMessage, e); } } // Ping is an alias for getDeviceTypes(). This simply hits a valid CMA endpoint to establish // availability. public static void ping() throws CloudServiceException { getDeviceTypes(); } /** * Log an event with the requested tag in a separate thread * * @param tag * the tag for the event. should be a legal event tag, i.e. expected server-side. * should never be null */ public static void logEventAsync(String tag) { logEventAsync(tag, null); } /** * Log an event with the requested tag and data in a separate thread * * @param tag * the tag for the event. should be a legal event tag, i.e. expected server-side. * should never be null * @param data * optional JSON-encoded data providing additional context for the event (e.g. jobId * or user) */ public static void logEventAsync(String tag, String data) { logEventAsync(tag, data, null); } /** * Log an event with the requested tag, data, and timestamp in a separate thread * * @param tag * the tag for the event. should be a legal event tag, i.e. expected server-side. * should never be null * @param data * optional JSON-encoded data providing additional context for the event (e.g. jobId * or user) * @param timestamp * timestamp for the event; if null, one will be generated */ public static void logEventAsync(String tag, String data, Date timestamp) { final String ftag = tag; final String fdata = data; final Date ftimestamp = timestamp; new Thread(new Runnable() { public void run() { try { logEvent(ftag, fdata, ftimestamp); } catch (Exception e) { // log("exception caught in logEventAsync(), ignoring. Stack trace for diagnostic purposes:", e); } } }).start(); } public static JSONObject logEvent(String tag) throws CloudServiceException { return logEvent(tag, null); } public static JSONObject logEvent(String tag, String data) throws CloudServiceException { return logEvent(tag, data, null); } /** * Log an event to the server with the given tag, data, and timestamp * * @param tag * the tag for the event. should be a legal event tag, i.e. expected server-side. * should never be null * @param data * optional JSON-encoded data providing additional context for the event (e.g. jobId * or user) * @param timestamp * timestamp for the event; if null, one will be generated * * @return the response from the server */ public static JSONObject logEvent(String tag, String data, Date timestamp) throws CloudServiceException { boolean eventConsent = false; if (FoneMonkeyPlugin.getDefault().getPreferenceStore() .contains(PreferenceConstants.P_LOGEVENTCONSENT)) { eventConsent = FoneMonkeyPlugin.getDefault().getPreferenceStore() .getBoolean(PreferenceConstants.P_LOGEVENTCONSENT); } if (!eventConsent) { return new JSONObject(); } return logEventInternal(tag, data, timestamp); } private static JSONObject logEventInternal(String tag, String data, Date timestamp) throws CloudServiceException { try { if (data == null || data.length() == 0) { data = "{}"; } if (timestamp == null) { timestamp = new Date(); } JSONObject mainObject = new JSONObject(); JSONObject dataWrapper = new JSONObject(); dataWrapper.put("id", -1); dataWrapper.put("tag", tag); SimpleDateFormat formatter; formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); dataWrapper.put("timestamp", formatter.format(timestamp)); dataWrapper.put("data", data); mainObject.put("data", dataWrapper); mainObject.put("message", "logEvent"); String json = HttpUtils.post(CloudServices.logEventsUrl, mainObject.toString()); JSONObject jo = new JSONObject(json); // log("logEvent() elapsed=" + (System.currentTimeMillis() - start) + "ms"); return jo; } catch (IOException ex) { //log("error caught in logEvent()", ex); return new JSONObject(); } catch (JSONException ex) { log("error caught in logEvent()", ex); return new JSONObject(); } catch (Exception e) { log("error caught in logEvent()", e); return new JSONObject(); } } /** * OptOut the current user from Event Reporting */ public static void optOutAsync() { new Thread(new Runnable() { public void run() { try { optOut(); } catch (Exception e) { log("exception caught in optOutAsync(), ignoring. Stack trace for diagnostic purposes:", e); } } }).start(); } public static void optOut() throws CloudServiceException { logEventInternal( "MONKEYTALK_IDE_LOGEVENT_OPT_OUT", "username=" + FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CLOUDUSR), new Date()); } /** * OptIn the current user from Event Reporting */ public static void optInAsync() { new Thread(new Runnable() { public void run() { try { optIn(); } catch (Exception e) { log("exception caught in OptInAsync(), ignoring. Stack trace for diagnostic purposes:", e); } } }).start(); } public static void optIn() throws CloudServiceException { logEventInternal( "MONKEYTALK_IDE_LOGEVENT_OPT_IN", "username=" + FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CLOUDUSR), new Date()); } public static boolean checkTokenValidityAgainstServer(String token) throws CloudServiceException { return true; } public static String login(String username, String password) throws CloudServiceException { String url = CloudServices.getControllerUrlRoot() + CloudConstants.USER_LOGIN; String token = ""; String json = null; try { json = HttpUtils.post(url, "{ \"username\": \"" + username + "\", \"password\": \"" + password + "\" }"); if (null == json) { throw new CloudServiceException( "CloudMonkey authorization service is down for maintenance"); } JSONObject jo = new JSONObject(json); if (jo.has("message") && jo.getString("message").equalsIgnoreCase("error")) { String errorMessage = jo.getString("message"); if (jo.has("data") && jo.getString("data").length() > 0) { errorMessage = jo.getString("data"); } throw new CloudServiceException(errorMessage); } if (jo.has("data")) { token = jo.getJSONObject("data").getString("token"); } } catch (IOException e) { // log("error caught in login()", e); } catch (JSONException e) { log("non-JSON response to login: \"" + json + "\"", e); } return token; } public static JSONObject submitJob(String script, File proj, File apk, String jobName, String thinktime, String timeout, List<String> params, String jobType) throws CloudServiceException { String url = CloudServices.getControllerUrlRoot() + CloudConstants.JOB_LAUNCH; try { long start = System.currentTimeMillis(); String ret = sendFormPost(url, apk, proj, script, thinktime, timeout, params, jobType); log("submitJob() elapsed=" + (System.currentTimeMillis() - start) + "ms"); return new JSONObject(ret); } catch (Exception ex) { log("POST failed in submitJob(), throwing exception - stack trace here just in case:", ex); throw new CloudServiceException("POST failed", ex); } } private static String sendFormPost(String url, File apk, File proj, String script, String thinktime, String timeout, List<String> params, String jobType) throws IOException { HttpClient client = getHttpClientForFormPost(); HttpUtils.setupProxy(client); try { HttpPost post = new HttpPost(url); MultipartEntity multipart = new MultipartEntity(); multipart.addPart( "username", new StringBody(FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CLOUDUSR), Charset.forName("UTF-8"))); multipart.addPart( "password", new StringBody(FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CLOUDPASS), Charset.forName("UTF-8"))); if (thinktime != null && thinktime.length() > 0) multipart.addPart("thinktime", new StringBody(thinktime, Charset.forName("UTF-8"))); if (timeout != null && timeout.length() > 0) multipart.addPart("timeout", new StringBody(timeout, Charset.forName("UTF-8"))); multipart.addPart("script", new StringBody(script, Charset.forName("UTF-8"))); multipart.addPart("type", new StringBody(jobType, Charset.forName("UTF-8"))); if (apk != null) { multipart.addPart("binary", new FileBody(apk)); } if (proj != null) { multipart.addPart("project", new FileBody(proj)); } for (String param : params) { String encodedParam = java.net.URLEncoder.encode(param, "UTF-8"); multipart.addPart(encodedParam, new StringBody("on", Charset.forName("UTF-8"))); } post.setEntity(multipart); HttpResponse resp = client.execute(post); HttpEntity out = resp.getEntity(); InputStream in = out.getContent(); String responseAsString=FileUtils.readStream(in);; return responseAsString; } catch (Exception ex) { throw new IOException("POST failed", ex); } finally { try { client.getConnectionManager().shutdown(); } catch (Exception ex) { // ignore } } } private static HttpClient getHttpClientForFormPost() { HttpClient base = new DefaultHttpClient(); if (CloudServices.getControllerProtocol().toLowerCase().equals("http")) { return base; } else { // some weird SSL stuff, I guess for the file uploads or multi-part? SSLContext ctx = null; try { ctx = SSLContext.getInstance("TLS"); } catch (NoSuchAlgorithmException e) { log("exception in SSL setup for sendFormPost():", e); } X509TrustManager tm = new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException { } @Override public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException { } }; try { ctx.init(null, new TrustManager[] { tm }, null); } catch (KeyManagementException e) { log("exception in SSL setup for sendFormPost():", e); } SSLSocketFactory ssf = new SSLSocketFactory(ctx); ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); ClientConnectionManager ccm = base.getConnectionManager(); SchemeRegistry sr = ccm.getSchemeRegistry(); sr.register(new Scheme("https", ssf, 443)); HttpClient client = new DefaultHttpClient(ccm, base.getParams()); return client; } } public static JSONObject getJobHistory() throws CloudServiceException { try { String url = CloudServices.getControllerUrlRoot() + CloudConstants.USER_HISTORY; String json = HttpUtils.post( url, "{\"token\":\"" + getToken() + "\",\"username\":\"" + FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CLOUDUSR) + "\"}"); if (json == null) return null; JSONObject ret = new JSONObject(json); // log("getJobHistory() elapsed=" + (System.currentTimeMillis() - start) + "ms"); return ret; } catch (IOException e) { return new JSONObject(); } catch (JSONException e) { return new JSONObject(); } } public static JSONObject getJobStatus(String jobid) throws CloudServiceException { String url = CloudServices.getControllerUrlRoot() + CloudConstants.JOB_STATUS; try { String json = HttpUtils.post(url, "{\"message\":\"jobRequest\",\"data\":{" + getTokenAndUsername() + ",\"id\":" + jobid + "}}"); JSONObject jo = new JSONObject(json); // log("getJobStatus() elapsed=" + (System.currentTimeMillis() - start) + "ms"); return jo; } catch (IOException ex) { log("exception in getJobStatus():", ex); return new JSONObject(); } catch (JSONException ex) { log("non-JSON response in getJobStatus():", ex); return new JSONObject(); } } public static JSONObject getJobResults(String jobid) throws CloudServiceException { String url = CloudServices.getControllerUrlRoot() + CloudConstants.JOB_RESULTS; try { String json = HttpUtils.post(url, "{\"message\":\"jobRequest\",\"data\":{" + getTokenAndUsername() + ",\"id\":" + jobid + "}}"); JSONObject jo = new JSONObject(json); // log("getJobResults() elapsed=" + (System.currentTimeMillis() - start) + "ms"); return jo; } catch (IOException ex) { log("exception in getJobResults():", ex); return new JSONObject(); } catch (JSONException ex) { log("non-JSON response in getJobResults():", ex); return new JSONObject(); } } public static String readStream(InputStream in) throws IOException { StringBuilder sb = new StringBuilder(); String line = null; BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); while ((line = reader.readLine()) != null) { sb.append(line).append('\n'); } return (sb.length() > 0 ? sb.substring(0, sb.length() - 1) : ""); } protected static PrintStream logStream = System.out; protected static void log(String s) { logStream.println(new Date() + " CloudServices: " + s); } protected static void log(String s, Exception e) { logStream.println(new Date() + " CloudServices: " + s); e.printStackTrace(logStream); } public static String getControllerHost() { String controllerHostPref = FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CONTROLLER_HOST); if (controllerHostPref != null && controllerHostPref.length() > 0) { return controllerHostPref; } return CloudConstants.DEFAULT_CONTROLLER_HOST; } public static int getControllerPort() { String controllerPortPref = FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CONTROLLER_PORT); if (controllerPortPref != null && controllerPortPref.length() > 0) { int portPref = -1; try { portPref = Integer.parseInt(controllerPortPref); } catch (NumberFormatException nfe) { portPref = -1; } if (portPref != -1) { return portPref; } } return CloudConstants.DEFAULT_CONTROLLER_PUBLIC_PORT; } public static int getControllerSslPort() { String controllerPortPref = FoneMonkeyPlugin.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_CONTROLLER_SSL_PORT); if (controllerPortPref != null && controllerPortPref.length() > 0) { int portPref = -1; try { portPref = Integer.parseInt(controllerPortPref); } catch (NumberFormatException nfe) { portPref = -1; } if (portPref != -1) { return portPref; } } return CloudConstants.DEFAULT_CONTROLLER_SSL_PORT; } }