From a5e8825882a986bd25455a56e2eab2778fbdf75e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 12 Mar 2015 20:46:50 +0100 Subject: finish implementing twitter resource --- .../keychain/pgp/linked/LinkedCookieResource.java | 2 +- .../keychain/pgp/linked/LinkedIdentity.java | 2 - .../pgp/linked/resources/TwitterResource.java | 229 +++++++++++++++------ .../keychain/ui/adapter/LinkedIdsAdapter.java | 1 - .../ui/linked/LinkedIdCreateFinalFragment.java | 14 +- .../linked/LinkedIdCreateTwitterStep1Fragment.java | 14 +- .../linked/LinkedIdCreateTwitterStep2Fragment.java | 31 +-- .../linked/LinkedIdCreateTwitterStep3Fragment.java | 29 +-- .../src/main/res/layout/linked_id_item.xml | 2 +- 9 files changed, 215 insertions(+), 109 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedCookieResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedCookieResource.java index 84b79920a..c92624f65 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedCookieResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedCookieResource.java @@ -107,7 +107,7 @@ public abstract class LinkedCookieResource extends LinkedResource { String candidateFp = match.group(1).toLowerCase(); try { - int nonceCandidate = Integer.parseInt(match.group(2).toLowerCase(), 16); + int nonceCandidate = (int) Long.parseLong(match.group(2).toLowerCase(), 16); if (nonce != nonceCandidate) { log.add(LogType.MSG_LV_NONCE_ERROR, indent); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedIdentity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedIdentity.java index ff5995329..d529a2a36 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedIdentity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/LinkedIdentity.java @@ -3,7 +3,6 @@ package org.sufficientlysecure.keychain.pgp.linked; import org.spongycastle.bcpg.UserAttributeSubpacket; import org.spongycastle.util.Strings; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.util.Log; @@ -13,7 +12,6 @@ import java.nio.ByteBuffer; import java.util.Arrays; import android.content.Context; -import android.content.Intent; import android.support.annotation.DrawableRes; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/TwitterResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/TwitterResource.java index 3553ce740..d00fa7ece 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/TwitterResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/linked/resources/TwitterResource.java @@ -1,22 +1,25 @@ package org.sufficientlysecure.keychain.pgp.linked.resources; import android.content.Context; +import android.content.Intent; +import android.net.Uri; import android.support.annotation.DrawableRes; -import android.util.Base64; +import android.util.Log; import com.textuality.keybase.lib.JWalk; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.BasicHttpParams; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource; @@ -27,14 +30,47 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URI; -import java.net.URLEncoder; import java.util.HashMap; +import java.util.HashSet; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class TwitterResource extends LinkedCookieResource { - TwitterResource(Set flags, HashMap params, URI uri) { + final String mHandle; + final String mTweetId; + + TwitterResource(Set flags, HashMap params, + URI uri, String handle, String tweetId) { super(flags, params, uri); + + mHandle = handle; + mTweetId = tweetId; + } + + public static TwitterResource create(URI uri) { + return create(new HashSet(), new HashMap(), uri); + } + + public static TwitterResource create(Set flags, HashMap params, URI uri) { + + // no params or flags + if (!flags.isEmpty() || !params.isEmpty()) { + return null; + } + + Pattern p = Pattern.compile("https://twitter.com/([a-zA-Z0-9_]+)/status/([0-9]+)"); + Matcher match = p.matcher(uri.toString()); + if (!match.matches()) { + return null; + } + String handle = match.group(1); + String tweetId = match.group(2); + + return new TwitterResource(flags, params, uri, handle, tweetId); + } public static String generateText (Context context, byte[] fingerprint, int nonce) { @@ -42,21 +78,127 @@ public class TwitterResource extends LinkedCookieResource { return LinkedCookieResource.generate(context, fingerprint, nonce); } - private String getTwitterStream(String screenName) { - String results = null; + @Override + protected String fetchResource(OperationLog log, int indent) { + + String authToken = getAuthToken(); + + if (authToken == null) { + return null; + } + + HttpGet httpGet = + new HttpGet("https://api.twitter.com/1.1/statuses/show.json" + + "?id=" + mTweetId + + "&include_entities=false"); + + // construct a normal HTTPS request and include an Authorization + // header with the value of Bearer <> + httpGet.setHeader("Authorization", "Bearer " + authToken); + httpGet.setHeader("Content-Type", "application/json"); - // Step 1: Encode consumer key and secret try { - // URL encode the consumer key and secret - String urlApiKey = URLEncoder.encode("6IhPnWbYxASAoAzH2QaUtHD0J", "UTF-8"); - String urlApiSecret = URLEncoder.encode("L0GnuiOnapWbSBbQtLIqtpeS5BTtvh06dmoMoKQfHQS8UwHuWm", "UTF-8"); + String response = getResponseBody(httpGet); + JSONObject obj = new JSONObject(response); + + if (!obj.has("text")) { + return null; + } + + JSONObject user = obj.getJSONObject("user"); + if (!mHandle.equalsIgnoreCase(user.getString("screen_name"))) { + return null; + } - // Concatenate the encoded consumer key, a colon character, and the - // encoded consumer secret - String combined = urlApiKey + ":" + urlApiSecret; + // update the results with the body of the response + return obj.getString("text"); + } catch (JSONException e) { + Log.e(Constants.TAG, "json error parsing stream", e); + return null; + } + + } + + @Override + public @DrawableRes int getDisplayIcon() { + return R.drawable.twitter; + } - // Base64 encode the string - String base64Encoded = Base64.encodeToString(combined.getBytes(), Base64.NO_WRAP); + @Override + public String getDisplayTitle(Context context) { + return "Twitter"; + } + + @Override + public String getDisplayComment(Context context) { + return "@" + mHandle; + } + + @Override + public boolean isViewable() { + return true; + } + + @Override + public Intent getViewIntent() { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(mSubUri.toString())); + return intent; + } + + public static TwitterResource searchInTwitterStream(String screenName, String needle) { + + String authToken = getAuthToken(); + + if (authToken == null) { + return null; + } + + HttpGet httpGet = + new HttpGet("https://api.twitter.com/1.1/statuses/user_timeline.json" + + "?screen_name=" + screenName + + "&count=15" + + "&include_rts=false" + + "&trim_user=true" + + "&exclude_replies=true"); + + // construct a normal HTTPS request and include an Authorization + // header with the value of Bearer <> + httpGet.setHeader("Authorization", "Bearer " + authToken); + httpGet.setHeader("Content-Type", "application/json"); + + try { + String response = getResponseBody(httpGet); + JSONArray array = new JSONArray(response); + + for (int i = 0; i < array.length(); i++) { + JSONObject obj = array.getJSONObject(i); + String tweet = obj.getString("text"); + if (tweet.contains(needle)) { + String id = obj.getString("id_str"); + URI uri = URI.create("https://twitter.com/" + screenName + "/status/" + id); + return create(uri); + } + } + + // update the results with the body of the response + return null; + } catch (JSONException e) { + Log.e(Constants.TAG, "json error parsing stream", e); + return null; + } + } + + private static String authToken; + + private static String getAuthToken() { + if (authToken != null) { + return authToken; + } + try { + String base64Encoded = + "NkloUG5XYll4QVNBb0F6SDJRYVV0SEQwSjpMMEdudWlPbmFwV2JTQ" + + "mJRdExJcXRwZVM1QlR0dmgwNmRtb01vS1FmSFFTOFV3SHVXbQ=="; // Step 2: Obtain a bearer token HttpPost httpPost = new HttpPost("https://api.twitter.com/oauth2/token"); @@ -64,28 +206,21 @@ public class TwitterResource extends LinkedCookieResource { httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); httpPost.setEntity(new StringEntity("grant_type=client_credentials")); JSONObject rawAuthorization = new JSONObject(getResponseBody(httpPost)); - String auth = JWalk.getString(rawAuthorization, "access_token"); // Applications should verify that the value associated with the // token_type key of the returned object is bearer - if (auth != null && JWalk.getString(rawAuthorization, "token_type").equals("bearer")) { - - // Step 3: Authenticate API requests with bearer token - HttpGet httpGet = - new HttpGet("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=" + screenName); - - // construct a normal HTTPS request and include an Authorization - // header with the value of Bearer <> - httpGet.setHeader("Authorization", "Bearer " + auth); - httpGet.setHeader("Content-Type", "application/json"); - // update the results with the body of the response - results = getResponseBody(httpGet); + if (!"bearer".equals(JWalk.getString(rawAuthorization, "token_type"))) { + return null; } - } catch (UnsupportedEncodingException ex) { - } catch (JSONException ex) { - } catch (IllegalStateException ex1) { + + authToken = JWalk.getString(rawAuthorization, "access_token"); + return authToken; + + } catch (UnsupportedEncodingException | JSONException | IllegalStateException ex) { + Log.e(Constants.TAG, "auth token fetching error", ex); + return null; } - return results; + } private static String getResponseBody(HttpRequestBase request) { @@ -104,41 +239,17 @@ public class TwitterResource extends LinkedCookieResource { BufferedReader bReader = new BufferedReader( new InputStreamReader(inputStream, "UTF-8"), 8); - String line = null; + String line; while ((line = bReader.readLine()) != null) { sb.append(line); } } else { sb.append(reason); } - } catch (UnsupportedEncodingException ex) { - } catch (ClientProtocolException ex1) { - } catch (IOException ex2) { + } catch (IOException e) { + Log.e(Constants.TAG, "http request error", e); } return sb.toString(); } - @Override - protected String fetchResource(OperationLog log, int indent) { - return getTwitterStream("Valodim"); - } - - @Override - public @DrawableRes int getDisplayIcon() { - return R.drawable.twitter; - } - - @Override - public String getDisplayTitle(Context context) { - return "twitter"; - } - - @Override - public String getDisplayComment(Context context) { - return null; - } - - public static LinkedCookieResource create(Set flags, HashMap params, URI subUri) { - return null; - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java index c5bb58c3d..577fb1765 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java @@ -45,7 +45,6 @@ import org.sufficientlysecure.keychain.util.FilterCursorWrapper; import java.io.IOException; import java.util.WeakHashMap; - public class LinkedIdsAdapter extends UserAttributesAdapter { private final boolean mShowCertification; protected LayoutInflater mInflater; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java index ef76bc9d2..b8d36e0a1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java @@ -33,7 +33,7 @@ public abstract class LinkedIdCreateFinalFragment extends Fragment { public static final String ARG_NONCE = "nonce"; protected static final int REQUEST_CODE_PASSPHRASE = 0x00007008; - private LinkedIdWizard mLinkedIdWizard; + protected LinkedIdWizard mLinkedIdWizard; private ImageView mVerifyImage; private View mVerifyProgress; @@ -113,16 +113,19 @@ public abstract class LinkedIdCreateFinalFragment extends Fragment { } } - private void proofVerify() { + protected void proofVerify() { setVerifyProgress(true, null); - final LinkedCookieResource resource = getResource(); - new AsyncTask() { @Override protected LinkedVerifyResult doInBackground(Void... params) { - return resource.verify(mLinkedIdWizard.mFingerprint, mResourceNonce); + LinkedCookieResource resource = getResource(); + LinkedVerifyResult result = resource.verify(mLinkedIdWizard.mFingerprint, mResourceNonce); + if (result.success()) { + mVerifiedResource = resource; + } + return result; } @Override @@ -130,7 +133,6 @@ public abstract class LinkedIdCreateFinalFragment extends Fragment { super.onPostExecute(result); if (result.success()) { setVerifyProgress(false, true); - mVerifiedResource = resource; } else { setVerifyProgress(false, false); // on error, show error message diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java index e966fd71f..86579a132 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java @@ -75,7 +75,7 @@ public class LinkedIdCreateTwitterStep1Fragment extends Fragment { @Override protected Boolean doInBackground(Void... params) { - return true; // checkHandle(handle); + return true; // return checkHandle(handle); } @Override @@ -83,21 +83,21 @@ public class LinkedIdCreateTwitterStep1Fragment extends Fragment { super.onPostExecute(result); if (result == null) { - Notify.showNotify(getActivity(), "Connection error while checking username!", Notify.Style.ERROR); + Notify.showNotify(getActivity(), + "Connection error while checking username!", Notify.Style.ERROR); return; } if (!result) { - Notify.showNotify(getActivity(), "This handle does not exist on Twitter!", Notify.Style.ERROR); + Notify.showNotify(getActivity(), + "This handle does not exist on Twitter!", Notify.Style.ERROR); return; } int proofNonce = RawLinkedIdentity.generateNonce(); - String proofText = TwitterResource.generateText(getActivity(), - mLinkedIdWizard.mFingerprint, proofNonce); LinkedIdCreateTwitterStep2Fragment frag = - LinkedIdCreateTwitterStep2Fragment.newInstance(handle, proofNonce, proofText); + LinkedIdCreateTwitterStep2Fragment.newInstance(handle, proofNonce); mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); } @@ -114,7 +114,7 @@ public class LinkedIdCreateTwitterStep1Fragment extends Fragment { }); mEditHandle = (EditText) view.findViewById(R.id.linked_create_twitter_handle); - mEditHandle.setText("Valodim"); + mEditHandle.setText("v_debug"); return view; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java index 837b84d40..e2fa6d5f6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java @@ -45,36 +45,46 @@ public class LinkedIdCreateTwitterStep2Fragment extends Fragment { TextView mVerifyStatus, mEditTweetTextLen; String mResourceHandle; - String mResourceNonce, mResourceString; + int mResourceNonce; + String mResourceString; String mCookiePreview; /** * Creates new instance of this fragment */ public static LinkedIdCreateTwitterStep2Fragment newInstance - (String handle, int proofNonce, String proofText) { + (String handle, int proofNonce) { LinkedIdCreateTwitterStep2Fragment frag = new LinkedIdCreateTwitterStep2Fragment(); Bundle args = new Bundle(); args.putString(HANDLE, handle); args.putInt(NONCE, proofNonce); - args.putString(TEXT, proofText); frag.setArguments(args); return frag; } + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mLinkedIdWizard = (LinkedIdWizard) getActivity(); + + mResourceHandle = getArguments().getString(HANDLE); + mResourceNonce = getArguments().getInt(NONCE); + + mResourceString = TwitterResource.generateText(getActivity(), + mLinkedIdWizard.mFingerprint, mResourceNonce); + + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.linked_create_twitter_fragment_step2, container, false); mCookiePreview = TwitterResource.generatePreview(); - mResourceHandle = getArguments().getString(HANDLE); - mResourceNonce = getArguments().getString(NONCE); - mResourceString = getArguments().getString(TEXT); - view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -142,11 +152,4 @@ public class LinkedIdCreateTwitterStep2Fragment extends Fragment { return view; } - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - mLinkedIdWizard = (LinkedIdWizard) getActivity(); - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep3Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep3Fragment.java index 0c317004b..cfa8d8d23 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep3Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep3Fragment.java @@ -33,6 +33,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.linked.LinkedCookieResource; +import org.sufficientlysecure.keychain.pgp.linked.resources.TwitterResource; import org.sufficientlysecure.keychain.ui.util.Notify; import java.util.List; @@ -45,15 +46,16 @@ public class LinkedIdCreateTwitterStep3Fragment extends LinkedIdCreateFinalFragm String mResourceHandle, mCustom, mFullString; String mResourceString; + private int mNonce; public static LinkedIdCreateTwitterStep3Fragment newInstance - (String handle, String proofNonce, String proofText, String customText) { + (String handle, int proofNonce, String proofText, String customText) { LinkedIdCreateTwitterStep3Fragment frag = new LinkedIdCreateTwitterStep3Fragment(); Bundle args = new Bundle(); args.putString(ARG_HANDLE, handle); - args.putString(ARG_NONCE, proofNonce); + args.putInt(ARG_NONCE, proofNonce); args.putString(ARG_TEXT, proofText); args.putString(ARG_CUSTOM, customText); frag.setArguments(args); @@ -65,9 +67,11 @@ public class LinkedIdCreateTwitterStep3Fragment extends LinkedIdCreateFinalFragm public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mResourceHandle = getArguments().getString(ARG_HANDLE); - mResourceString = getArguments().getString(ARG_TEXT); - mCustom = getArguments().getString(ARG_CUSTOM); + Bundle args = getArguments(); + mResourceHandle = args.getString(ARG_HANDLE); + mResourceString = args.getString(ARG_TEXT); + mCustom = args.getString(ARG_CUSTOM); + mNonce = args.getInt(ARG_NONCE); mFullString = mCustom.isEmpty() ? mResourceString : (mCustom + " " + mResourceString); @@ -94,23 +98,12 @@ public class LinkedIdCreateTwitterStep3Fragment extends LinkedIdCreateFinalFragm } }); - view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - - // AffirmationCreateHttpsStep2Fragment frag = - // AffirmationCreateHttpsStep2Fragment.newInstance(); - - // mAffirmationWizard.loadFragment(null, frag, AffirmationWizard.FRAG_ACTION_TO_RIGHT); - } - }); - return view; } @Override LinkedCookieResource getResource() { - return null; + return TwitterResource.searchInTwitterStream(mResourceHandle, mFullString); } @Override @@ -137,7 +130,7 @@ public class LinkedIdCreateTwitterStep3Fragment extends LinkedIdCreateFinalFragm PackageManager.MATCH_DEFAULT_ONLY); boolean resolved = false; - for(ResolveInfo resolveInfo : resolvedInfoList){ + for(ResolveInfo resolveInfo : resolvedInfoList) { if(resolveInfo.activityInfo.packageName.startsWith("com.twitter.android")) { tweetIntent.setClassName( resolveInfo.activityInfo.packageName, diff --git a/OpenKeychain/src/main/res/layout/linked_id_item.xml b/OpenKeychain/src/main/res/layout/linked_id_item.xml index 312dee6ba..5fc5e78a8 100644 --- a/OpenKeychain/src/main/res/layout/linked_id_item.xml +++ b/OpenKeychain/src/main/res/layout/linked_id_item.xml @@ -8,7 +8,7 @@