package io.scal.secureshareui.lib; import timber.log.Timber; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Environment; import android.preference.PreferenceManager; import android.util.Log; import android.view.View; import android.widget.TextView; import com.squareup.okhttp.OkHttpClient; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import ch.boye.httpclientandroidlib.Header; import ch.boye.httpclientandroidlib.HttpHost; import ch.boye.httpclientandroidlib.HttpResponse; import ch.boye.httpclientandroidlib.NameValuePair; import ch.boye.httpclientandroidlib.client.HttpClient; import ch.boye.httpclientandroidlib.client.entity.UrlEncodedFormEntity; import ch.boye.httpclientandroidlib.client.methods.HttpGet; import ch.boye.httpclientandroidlib.client.methods.HttpPost; import ch.boye.httpclientandroidlib.conn.params.ConnRoutePNames; import ch.boye.httpclientandroidlib.entity.StringEntity; import ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder; import ch.boye.httpclientandroidlib.impl.client.HttpClients; import ch.boye.httpclientandroidlib.message.BasicNameValuePair; import info.guardianproject.netcipher.client.StrongHttpsClient; import info.guardianproject.netcipher.proxy.OrbotHelper; import io.scal.secureshareuilibrary.R; import retrofit.RestAdapter; import retrofit.client.OkClient; import retrofit.client.Response; import retrofit.mime.TypedOutput; /** * Created by mnbogner on 1/30/15. */ public class SMWrapper { private final Context mContext; private final String mClientId; private final String mClientSecret; private String mToken; StrongHttpsClient mClient = null; boolean proxySet = false; //HttpClient mClient = null; private String AUTHORIZE_URL; // previously hard coded to "https://storymaker.org/api/oauth2/access_token" private String UPLOAD_URL; // previously hard coded to "https://storymaker.org/api/story/" private static final String CLIENT_ID = "client_id"; private static final String CLIENT_SECRET = "client_secret"; private static final String USERNAME = "username"; private static final String PASSWORD = "password"; private static final String GRANT_TYPE = "grant_type"; private static final String CONTENT = "content"; private static final String TITLE = "title"; private static final String DESCRIPTION = "description"; private static final String PUBLISH_DATE = "publish_date"; // public SMWrapper(String clientId, String clientSecret, String token) { public SMWrapper(Context context) { mContext = context; mClientId = context.getString(R.string.sm_key); // FIXME obfuscate these, and when you do generate new keys mClientSecret = context.getString(R.string.sm_secret); String url = getUrl(); if (!url.endsWith("/")) { url = url + "/"; } AUTHORIZE_URL = url + "api/oauth2/access_token"; UPLOAD_URL = url + "api/story/"; } private synchronized StrongHttpsClient getHttpClientInstance() { //private synchronized HttpClient getHttpClientInstance() { if ( mClient == null) { try { mClient = new StrongHttpsClient(mContext, R.raw.debiancacerts, null); //mClient = HttpClients.createDefault(); } catch (Exception e) { Log.e("NetCipher","error init'd stronghttpsclient",e); } } return mClient; } /** * * @param username * @param password * @return token, if null it was unsuccessful * @throws IOException */ public String login(String username, String password) throws IOException { if (username == null && password == null) { throw new IllegalArgumentException("username and password are null"); } else if (username == null) { throw new IllegalArgumentException("username is null"); } else if (password == null) { throw new IllegalArgumentException("password is null"); } //Timber.d(mClientId); //Timber.d(mClientSecret); //Timber.d("password"); //Timber.d(username); //Timber.d(password); StrongHttpsClient client = getHttpClientInstance(); //HttpClient client = getHttpClientInstance(); // check for tor SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext); boolean useTor = settings.getBoolean("pusetor", false); /* if (useTor) { OrbotHelper oh = new OrbotHelper(mContext); if ((!oh.isOrbotInstalled()) || (!oh.isOrbotRunning())) { Timber.e("TOR SELECTED BUT ORBOT IS INACTIVE (ABORTING)"); return null; } else { Timber.d("TOR SELECTED, HOST " + mContext.getString(R.string.sm_tor_host) + ", PORT " + mContext.getString(R.string.sm_tor_port) + " (SETTING PROXY)"); String host = mContext.getString(R.string.sm_tor_host); int port = Integer.parseInt(mContext.getString(R.string.sm_tor_port)); //client.useProxy(true, "http", host, port); HttpHost proxy = new HttpHost(host, port, "http"); client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); proxySet = true; } } else { if (proxySet) { Timber.d("TOR NOT SELECTED (CLEARING PROXY)"); client.getParams().removeParameter(ConnRoutePNames.DEFAULT_PROXY); proxySet = false; } else { Timber.d("TOR NOT SELECTED"); } } */ /* HttpPost post = new HttpPost(AUTHORIZE_URL); List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair(CLIENT_ID, mClientId)); params.add(new BasicNameValuePair(CLIENT_SECRET, mClientSecret)); params.add(new BasicNameValuePair(GRANT_TYPE, "password")); params.add(new BasicNameValuePair(USERNAME, username)); params.add(new BasicNameValuePair(PASSWORD, password)); //params.add(new BasicNameValuePair("redirect_uri", "http://localhost/callback")); post.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); HttpClient client = HttpClientBuilder.create().build(); HttpPost post = new HttpPost(AUTHORIZE_URL + "?" + CLIENT_ID + "=" + mClientId + "&" + CLIENT_SECRET + "=" + mClientSecret + "&" + GRANT_TYPE + "=" + "password" + "&" + USERNAME + "=" + username + "&" + PASSWORD + "=" + password); List<NameValuePair> urlParameters = new ArrayList<NameValuePair>(); urlParameters.add(new BasicNameValuePair(CLIENT_ID, mClientId)); urlParameters.add(new BasicNameValuePair(CLIENT_SECRET, mClientSecret)); urlParameters.add(new BasicNameValuePair(GRANT_TYPE, "password")); urlParameters.add(new BasicNameValuePair(USERNAME, username)); urlParameters.add(new BasicNameValuePair(PASSWORD, password)); post.setEntity(new UrlEncodedFormEntity(urlParameters)); HttpResponse response = null; */ // TRY NEW RETROFIT STUFF RestAdapter.Builder builder = new RestAdapter.Builder() .setLogLevel(RestAdapter.LogLevel.BASIC) .setEndpoint(getUrl()); // check for tor if (useTor) { if ((!OrbotHelper.isOrbotInstalled(mContext)) || (!OrbotHelper.isOrbotRunning(mContext))) { Timber.e("TOR SELECTED BUT ORBOT IS INACTIVE (ABORTING)"); throw new IOException("tor selected but orbot inactive"); } else { // get tor parameters String torHost = mContext.getString(R.string.sm_tor_host); String torPort = mContext.getString(R.string.sm_tor_port); Timber.d("TOR SELECTED, HOST " + torHost + ", PORT " + torPort + " (BUILDING CUSTOM CLIENT)"); // build a client with a proxy OkHttpClient httpClient = new OkHttpClient(); SocketAddress torSocket = new InetSocketAddress(torHost, Integer.parseInt(torPort)); Proxy torProxy = new Proxy(Proxy.Type.HTTP, torSocket); httpClient.setProxy(torProxy); // create retrofit wrapper class OkClient retrofitClient = new OkClient(httpClient); // add to builder builder.setClient(retrofitClient); } } /* RestAdapter restAdapter = new RestAdapter.Builder() .setLogLevel(RestAdapter.LogLevel.FULL) .setEndpoint(getUrl()) .build(); */ RestAdapter restAdapter = builder.build(); LoginInterface loginService = restAdapter.create(LoginInterface.class); try { Response rResponse = loginService.getAccessToken(mClientId, mClientSecret, "password", username, password); /* try { response = client.execute(post); } catch (Exception e) { Timber.e(e.getMessage()); e.printStackTrace(); } */ //Timber.d("RESPONSE CODE: " + response.getStatusLine().getStatusCode()); Timber.d("RESPONSE CODE: " + rResponse.getStatus()); BufferedReader rd = new BufferedReader( //new InputStreamReader(response.getEntity().getContent()) new InputStreamReader(rResponse.getBody().in()) ); StringBuffer result = new StringBuffer(); String line = ""; while ((line = rd.readLine()) != null) { result.append(line); } //Header[] postHeaders = response.getAllHeaders(); List<retrofit.client.Header> postHeaders = rResponse.getHeaders(); //for (int i = 0; i < postHeaders.length; i++) { for (int i = 0; i < postHeaders.size(); i++) { //Timber.d("FOUND HEADER: " + postHeaders[i].getName() + ": " + postHeaders[i].getValue()); Timber.d("FOUND HEADER: " + postHeaders.get(i).getName() + ": " + postHeaders.get(i).getValue()); } Timber.d("RESPONSE: " + result.toString()); // need to attempt to deal with cloudflare captcha challenge over tor if ((useTor) && result.toString().contains("chk_captcha")) { Timber.e("ENCOUNTERED CAPTCHA CHALLENGE PAGE (TOR IP ADDRESSES MAY BE CONSIDERED SUSPICIOUS)"); throw new CaptchaException(); } try { JSONObject json = new JSONObject(result.toString()); mToken = json.getString("access_token"); //Timber.d("TOKEN: " + mToken); } catch (JSONException je) { Timber.e("FAILED TO PARSE RESPONSE: " + je.getMessage()); throw new IOException("unexpected response received"); } } catch (retrofit.RetrofitError re) { Timber.e("FAILED TO CONNECT: " + re.getMessage()); throw new IOException("no response received"); } return mToken; } // mimeType and file are nullable // link this with upload method public String post (String user, String title, String body, String embed, String[] catstrings, String medium, String mediaService, String mediaGuid, String mimeType, File file) throws IOException { //HttpResponse postResponse = upload(user, title, catstrings, body, embed, mToken); Response postResponse = upload(user, title, catstrings, body, embed, mToken); // catch null (probably caused by retrofit handling of 404) if(postResponse == null) { Timber.e("PUBLICATION FAILED"); return "0" + ":" + "Publishing to StoryMaker failed."; } //Timber.d("RESPONSE CODE: " + postResponse.getStatusLine().getStatusCode()); Timber.d("RESPONSE CODE: " + postResponse.getStatus()); BufferedReader rd = new BufferedReader( //new InputStreamReader(postResponse.getEntity().getContent()) new InputStreamReader(postResponse.getBody().in()) ); StringBuffer result = new StringBuffer(); String line = ""; while ((line = rd.readLine()) != null) { result.append(line); } //Header[] postHeaders = postResponse.getAllHeaders(); List<retrofit.client.Header> postHeaders = postResponse.getHeaders(); //for (int i = 0; i < postHeaders.length; i++) { for (int i = 0; i < postHeaders.size(); i++) { //Timber.d("FOUND HEADER: " + postHeaders[i].getName() + ": " + postHeaders[i].getValue()); Timber.d("FOUND HEADER: " + postHeaders.get(i).getName() + ": " + postHeaders.get(i).getValue()); } Timber.d("RESPONSE: " + result.toString()); // check for tor SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext); boolean useTor = settings.getBoolean("pusetor", false); // need to attempt to deal with cloudflare captcha challenge over tor if ((useTor) && result.toString().contains("chk_captcha")) { Timber.e("ENCOUNTERED CAPTCHA CHALLENGE PAGE (TOR IP ADDRESSES MAY BE CONSIDERED SUSPICIOUS)"); return postResponse.getStatus() + ":" + "Publishing to StoryMaker failed. Try restarting TOR"; } // catch other failures if((postResponse.getStatus() < 200) || (postResponse.getStatus() > 299)) { Timber.e("PUBLICATION FAILED"); return postResponse.getStatus() + ":" + "Publishing to StoryMaker failed."; } return null; // FIXME need to parse post id out of response (response currently appears to be the json object representing the post and has no id) } // NEW/TEMP // DOWNLOAD AVAILABE INDEX FOR CURRENT USER AND SAVE TO TARGET FILE // RETURN TRUE IF SUCCESSFUL // CAN'T SAVE TO FILE, CONVERSION REQUIRED AND DON'T WANT CLASS DEPENDENT ON LIGER public JSONArray index(int version) { // check for tor SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext); boolean useTor = settings.getBoolean("pusetor", false); // TRY NEW RETROFIT STUFF RestAdapter.Builder builder = new RestAdapter.Builder() .setLogLevel(RestAdapter.LogLevel.BASIC) .setEndpoint(getUrl()); // check for tor if (useTor) { if ((!OrbotHelper.isOrbotInstalled(mContext)) || (!OrbotHelper.isOrbotRunning(mContext))) { Timber.e("INDEX", "TOR SELECTED BUT ORBOT IS INACTIVE (ABORTING)"); return null; } else { // get tor parameters String torHost = mContext.getString(R.string.sm_tor_host); String torPort = mContext.getString(R.string.sm_tor_port); Timber.d("TOR SELECTED, HOST " + torHost + ", PORT " + torPort + " (BUILDING CUSTOM CLIENT)"); // build a client with a proxy OkHttpClient httpClient = new OkHttpClient(); SocketAddress torSocket = new InetSocketAddress(torHost, Integer.parseInt(torPort)); Proxy torProxy = new Proxy(Proxy.Type.HTTP, torSocket); httpClient.setProxy(torProxy); // create retrofit wrapper class OkClient retrofitClient = new OkClient(httpClient); // add to builder builder.setClient(retrofitClient); } } /* RestAdapter restAdapter = new RestAdapter.Builder() .setLogLevel(RestAdapter.LogLevel.FULL) .setEndpoint(getUrl()) .build(); */ RestAdapter restAdapter = builder.build(); IndexInterface indexService = restAdapter.create(IndexInterface.class); try { Response rResponse = indexService.getIndex(version, "Bearer " + mToken); Timber.d("RESPONSE CODE: " + rResponse.getStatus()); BufferedReader rd = new BufferedReader( new InputStreamReader(rResponse.getBody().in()) ); StringBuffer result = new StringBuffer(); String line = ""; while ((line = rd.readLine()) != null) { result.append(line); } List<retrofit.client.Header> postHeaders = rResponse.getHeaders(); for (int i = 0; i < postHeaders.size(); i++) { Timber.d("FOUND HEADER: " + postHeaders.get(i).getName() + ": " + postHeaders.get(i).getValue()); } Timber.d("RESPONSE: " + result.toString()); // response should be a collection of json objects to convert to index items try { JSONArray jArray = new JSONArray(result.toString()); return jArray; } catch (JSONException je) { Timber.e("FAILED TO PARSE RESPONSE: " + je.getMessage()); return null; } } catch (retrofit.RetrofitError re) { Timber.e("FAILED TO CONNECT: " + re.getMessage()); return null; } catch (IOException ioe) { Timber.e("FAILED TO READ RESPONSE: " + ioe.getMessage()); return null; } } //public HttpResponse upload(String user, String title, String[] catstrings, String body, String embed, String credentials) throws IOException { public Response upload(String user, String title, String[] catstrings, String body, String embed, String credentials) throws IOException { Date publishDate = new Date(); //Timber.d(mClientId); //Timber.d(mClientSecret); //Timber.d(title); //Timber.d(desc); //Timber.d(body); //Timber.d(publishDate.toString()); //Timber.d(user + "_public"); //Timber.d(credentials); StrongHttpsClient client = getHttpClientInstance(); //HttpClient client = getHttpClientInstance(); // check for tor SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext); boolean useTor = settings.getBoolean("pusetor", false); /* if (useTor) { OrbotHelper oh = new OrbotHelper(mContext); if ((!oh.isOrbotInstalled()) || (!oh.isOrbotRunning())) { Timber.e("TOR SELECTED BUT ORBOT IS INACTIVE (ABORTING)"); return null; } else { Timber.e("TOR SELECTED, HOST " + mContext.getString(R.string.sm_tor_host) + ", PORT " + mContext.getString(R.string.sm_tor_port) + " (SETTING PROXY)"); String host = mContext.getString(R.string.sm_tor_host); int port = Integer.parseInt(mContext.getString(R.string.sm_tor_port)); //client.useProxy(true, "http", host, port); HttpHost proxy = new HttpHost(host, port, "http"); client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); proxySet = true; } } else { if (proxySet) { Timber.d("TOR NOT SELECTED (CLEARING PROXY)"); client.getParams().removeParameter(ConnRoutePNames.DEFAULT_PROXY); proxySet = false; } else { Timber.d("TOR NOT SELECTED"); } } */ HttpPost post = new HttpPost(UPLOAD_URL); post.setHeader("Authorization", "Bearer " + credentials); /* List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair(CLIENT_ID, mClientId)); params.add(new BasicNameValuePair(CLIENT_SECRET, mClientSecret)); params.add(new BasicNameValuePair(TITLE, title)); //params.add(new BasicNameValuePair(DESCRIPTION, desc)); <- CURRENTLY NULL params.add(new BasicNameValuePair(CONTENT, body)); params.add(new BasicNameValuePair(PUBLISH_DATE, publishDate.toString())); //new params.add(new BasicNameValuePair("group_ids", user + "_public")); //params.add(new BasicNameValuePair("redirect_uri", "http://localhost/callback")); post.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); */ String catString = ""; for (int i = 0; i < catstrings.length; i++) { catString = catString + "\"" + catstrings[i] + "\""; if (i < catstrings.length - 1) { catString = catString + ", "; } } // build json string to post String jsonString = "{" + "\"title\": " + "\"" + title + "\", " + "\"description\": " + "\"" + "\", " + "\"content\": " + "\"" + body + "\", " + "\"keywords_string\": " + "\"" + "\", " + "\"categories\": " + "[" + "], " + // leave categories blank for now "\"related_posts\": " + "[" + "], " + "\"groups\": " + "[{\"name\": " + "\"" + user + "_public\"}], " + "\"media_shortcodes\": " + "[{\"shortcode\": " + "\"" + embed + "\"}]" + "}"; /* { "title": "test1: post to protected ", "description": "", "content": "<iframe width=\"760\" height=\"430\" src=\"https://www.youtube.com/embed/-Gp56HzYUlw\" frameborder=\"0\" allowfullscreen></iframe>", "keywords_string": "", "categories": [], "related_posts": [], "groups": [{"name":"test1_protected"}], "media_shortcodes": [{"shortcode":"foo2"},{"shortcode":"bar2"}] } */ Timber.d("JSON: " + jsonString); StringEntity jsonEntity = new StringEntity(jsonString, "UTF-8"); jsonEntity.setContentType("application/json"); post.setEntity(jsonEntity); // TRY NEW RETROFIT STUFF RestAdapter.Builder builder = new RestAdapter.Builder() .setLogLevel(RestAdapter.LogLevel.BASIC) .setEndpoint(getUrl()); // check for tor if (useTor) { if ((!OrbotHelper.isOrbotInstalled(mContext)) || (!OrbotHelper.isOrbotRunning(mContext))) { Timber.e("TOR SELECTED BUT ORBOT IS INACTIVE (ABORTING)"); return null; } else { // get tor parameters String torHost = mContext.getString(R.string.sm_tor_host); String torPort = mContext.getString(R.string.sm_tor_port); Timber.d("TOR SELECTED, HOST " + torHost + ", PORT " + torPort + " (BUILDING CUSTOM CLIENT)"); // build a client with a proxy OkHttpClient httpClient = new OkHttpClient(); SocketAddress torSocket = new InetSocketAddress(torHost, Integer.parseInt(torPort)); Proxy torProxy = new Proxy(Proxy.Type.HTTP, torSocket); httpClient.setProxy(torProxy); // create retrofit wrapper class OkClient retrofitClient = new OkClient(httpClient); // add to builder builder.setClient(retrofitClient); } } /* RestAdapter restAdapter = new RestAdapter.Builder() .setLogLevel(RestAdapter.LogLevel.FULL) .setEndpoint(getUrl()) .build(); */ RestAdapter restAdapter = builder.build(); PostInterface postService = restAdapter.create(PostInterface.class); EntityWrapper jsonEntityWrapper = new EntityWrapper(jsonEntity); try { Response rResponse = postService.postContent("Bearer " + credentials, jsonEntityWrapper); //return client.execute(post); return rResponse; } catch (retrofit.RetrofitError re) { Timber.e("FAILED TO CONNECT: " + re.getMessage()); return null; } } // need to implement these methods public String getPostUrl (String postId) { return null; } public Object getPost (String postId) { return null; // wordpress class returned a "Page"? } public List<Object> getRecentPosts (int num) { return null; // wordpress class returned a "Page"? } public List<Object> getComments (Object page) { return null; // wordpress class took a "Page" and returned a "Comment"? } public String addMedia (String mimeType, File file) { return null; // wordpress class returned the url of a "MediaObject"? } // FIXME this could be more robust and needs a test private String getUrl() { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext); String url = settings.getString("pserver", Constants.DEFAULT_SERVER_URL); // Because of the peculiarities of using cloudflare ssl certs and therefore only being allowed // sub domains one deep, we need to do a bit of massaging here to figure out the right api domain // if our domain is https://storymaker.org we want https://api.storymaker.org // if our domain is https://demo.storymaker.org we want: https://api-demo.storymaker.org // we want to just use api. not api-www since we have a real api. cert that goes outside of cloudflare if (url.equals(Constants.DEFAULT_SERVER_URL_WWW)) { url = Constants.DEFAULT_SERVER_URL; } String prefix = "api-"; if (url.equals(Constants.DEFAULT_SERVER_URL)) { prefix = "api."; } String[] splits = url.split("://"); return splits[0] + "://" + prefix + splits[1]; } }