From 8a4447856372049846aa8419bb201ceda5164c8c Mon Sep 17 00:00:00 2001 From: Roger Hu Date: Wed, 31 Jul 2019 23:32:15 -0700 Subject: [PATCH] first pass at this library --- .../codepath/oauth/OAuthAsyncHttpClient.java | 176 --------------- .../com/codepath/oauth/OAuthBaseClient.java | 200 ------------------ .../codepath/oauth/OAuthLoginActivity.java | 64 ------ .../codepath/oauth/OAuthLoginFragment.java | 40 ---- .../codepath/oauth/ScribeRequestAdapter.java | 174 --------------- build.gradle | 1 + {app => library}/build.gradle | 16 +- {app => library}/libs/codepath-utils.jar | Bin {app => library}/src/main/AndroidManifest.xml | 0 .../java/com/codepath/oauth/OAuth1Client.java | 177 ++++++++++++++++ .../codepath/oauth/OAuth1ClientProvider.java | 8 + .../codepath/oauth/OAuthAsyncHttpClient.java | 25 +++ .../codepath/oauth/OAuthLoginActivity.java | 34 +-- .../codepath/oauth/OAuthLoginFragment.java | 47 ++++ {app => library}/src/main/res/.gitkeep | 0 {app => library}/src/main/res/layout/.gitkeep | 0 settings.gradle | 2 +- 17 files changed, 279 insertions(+), 685 deletions(-) delete mode 100755 app/src/main/java/com/codepath/oauth/OAuthAsyncHttpClient.java delete mode 100755 app/src/main/java/com/codepath/oauth/OAuthBaseClient.java delete mode 100755 app/src/main/java/com/codepath/oauth/OAuthLoginActivity.java delete mode 100755 app/src/main/java/com/codepath/oauth/OAuthLoginFragment.java delete mode 100755 app/src/main/java/com/codepath/oauth/ScribeRequestAdapter.java rename {app => library}/build.gradle (89%) rename {app => library}/libs/codepath-utils.jar (100%) rename {app => library}/src/main/AndroidManifest.xml (100%) create mode 100644 library/src/main/java/com/codepath/oauth/OAuth1Client.java create mode 100644 library/src/main/java/com/codepath/oauth/OAuth1ClientProvider.java create mode 100755 library/src/main/java/com/codepath/oauth/OAuthAsyncHttpClient.java rename app/src/main/java/com/codepath/oauth/OAuthLoginActionBarActivity.java => library/src/main/java/com/codepath/oauth/OAuthLoginActivity.java (54%) create mode 100755 library/src/main/java/com/codepath/oauth/OAuthLoginFragment.java rename {app => library}/src/main/res/.gitkeep (100%) rename {app => library}/src/main/res/layout/.gitkeep (100%) diff --git a/app/src/main/java/com/codepath/oauth/OAuthAsyncHttpClient.java b/app/src/main/java/com/codepath/oauth/OAuthAsyncHttpClient.java deleted file mode 100755 index 8b4a2b5..0000000 --- a/app/src/main/java/com/codepath/oauth/OAuthAsyncHttpClient.java +++ /dev/null @@ -1,176 +0,0 @@ -package com.codepath.oauth; - -import android.content.Context; -import android.net.Uri; - -import com.codepath.utils.AsyncSimpleTask; -import com.github.scribejava.core.builder.ServiceBuilder; -import com.github.scribejava.core.builder.api.BaseApi; -import com.github.scribejava.core.exceptions.OAuthException; -import com.github.scribejava.core.model.OAuth1RequestToken; -import com.github.scribejava.core.model.OAuthConstants; -import com.github.scribejava.core.model.Token; -import com.github.scribejava.core.oauth.OAuth10aService; -import com.github.scribejava.core.oauth.OAuth20Service; -import com.github.scribejava.core.oauth.OAuthService; -import com.loopj.android.http.AsyncHttpClient; -import com.loopj.android.http.RequestHandle; -import com.loopj.android.http.ResponseHandlerInterface; - -/* - * OAuthAsyncHttpClient is responsible for managing the request and access token exchanges and then - * signing all requests with the OAuth signature after access token has been retrieved and stored. - * The client is based on AsyncHttpClient for async http requests and uses Scribe to manage the OAuth authentication. - */ -public class OAuthAsyncHttpClient extends AsyncHttpClient { - - private BaseApi apiInstance; - private OAuthTokenHandler handler; - private Token accessToken; - private OAuthService service; - - // Requires the apiClass, consumerKey, consumerSecret and callbackUrl along with the TokenHandler - public OAuthAsyncHttpClient(BaseApi apiInstance, String consumerKey, String consumerSecret, String callbackUrl, - OAuthTokenHandler handler) { - this.apiInstance = apiInstance; - this.handler = handler; - if (callbackUrl == null) { callbackUrl = OAuthConstants.OUT_OF_BAND; }; - this.service = new ServiceBuilder() - .apiKey(consumerKey) - .apiSecret(consumerSecret).callback(callbackUrl) - .build(apiInstance); - } - - // Get a request token and the authorization url - // Once fetched, fire the onReceivedRequestToken for the request token handler - // Works for both OAuth1.0a and OAuth2 - public void fetchRequestToken() { - new AsyncSimpleTask(new AsyncSimpleTask.AsyncSimpleTaskHandler() { - String authorizeUrl = null; - Exception e = null; - Token requestToken; - - public void doInBackground() { - try { - if (service.getVersion() == "1.0") { - OAuth10aService oAuth10aService = (OAuth10aService) service; - requestToken = oAuth10aService.getRequestToken(); - authorizeUrl = oAuth10aService.getAuthorizationUrl((OAuth1RequestToken) requestToken); - } else if (service.getVersion() == "2.0") { - OAuth20Service oAuth20Service = (OAuth20Service) service; - authorizeUrl = oAuth20Service.getAuthorizationUrl(null); - } - } catch (Exception e) { - this.e = e; - } - } - - public void onPostExecute() { - if (e != null) { - handler.onFailure(e); - } else { - handler.onReceivedRequestToken(requestToken, authorizeUrl, service.getVersion()); - } - } - }); - } - - // Get the access token by exchanging the requestToken to the defined URL - // Once receiving the access token, fires the onReceivedAccessToken method on the handler - public void fetchAccessToken(final Token requestToken, final Uri uri) { - - new AsyncSimpleTask(new AsyncSimpleTask.AsyncSimpleTaskHandler() { - Exception e = null; - - public void doInBackground() { - // Fetch the verifier code from redirect url parameters - Uri authorizedUri = uri; - - try { - if (service.getVersion() == "1.0") { - // Use verifier token to fetch access token - - if (authorizedUri.getQuery().contains(OAuthConstants.VERIFIER)) { - String oauth_verifier = authorizedUri.getQueryParameter(OAuthConstants.VERIFIER); - OAuth1RequestToken oAuth1RequestToken = (OAuth1RequestToken) requestToken; - OAuth10aService oAuth10aService = (OAuth10aService) service; - accessToken = oAuth10aService.getAccessToken(oAuth1RequestToken, oauth_verifier); - } - else { // verifier was null - throw new OAuthException("No verifier code was returned with uri '" + uri + "' " + - "and access token cannot be retrieved"); - } - } else if (service.getVersion() == "2.0") { - if (authorizedUri.getQuery().contains(OAuthConstants.CODE)) { - String code = authorizedUri.getQueryParameter(OAuthConstants.CODE); - OAuth20Service oAuth20Service = (OAuth20Service) service; - accessToken = oAuth20Service.getAccessToken(code); - } - else { // verifier was null - throw new OAuthException("No code was returned with uri '" + uri + "' " + - "and access token cannot be retrieved"); - } - } - } catch (Exception e) { - this.e = e; - } - } - - public void onPostExecute() { - if (e != null) { - handler.onFailure(e); - } else { - setAccessToken(accessToken); - handler.onReceivedAccessToken(accessToken, service.getVersion()); - } - } - }); - } - - // Set the access token used for signing requests - public void setAccessToken(Token accessToken) { - if (accessToken == null) { - this.accessToken = null; - } else { - this.accessToken = accessToken; - } - } - - public Token getAccessToken() { - return this.accessToken; - } - - // Send scribe signed request based on the async http client to construct a signed request - // Accepts an HttpEntity which has the underlying entity for the request params - - @Override - protected RequestHandle sendRequest( - cz.msebera.android.httpclient.impl.client.DefaultHttpClient client, - cz.msebera.android.httpclient.protocol.HttpContext httpContext, - cz.msebera.android.httpclient.client.methods.HttpUriRequest uriRequest, - String contentType, ResponseHandlerInterface responseHandler, - Context context) { - - if (this.service != null && accessToken != null) { - try { - ScribeRequestAdapter adapter = new ScribeRequestAdapter(uriRequest); - this.service.signRequest(accessToken, adapter); - return super.sendRequest(client, httpContext, uriRequest, contentType, responseHandler, context); - } catch (Exception e) { - e.printStackTrace(); - } - } else if (accessToken == null) { - throw new OAuthException("Cannot send unauthenticated requests for " + apiInstance.getClass().getSimpleName() + " client. Please attach an access token!"); - } else { // service is null - throw new OAuthException("Cannot send unauthenticated requests for undefined service. Please specify a valid api service!"); - } - return null; // Hopefully never reaches here - } - - // Defines the interface handler for different token handlers - public interface OAuthTokenHandler { - public void onReceivedRequestToken(Token requestToken, String authorizeUrl, String oAuthVersion); - public void onReceivedAccessToken(Token accessToken, String oAuthVersion); - public void onFailure(Exception e); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/codepath/oauth/OAuthBaseClient.java b/app/src/main/java/com/codepath/oauth/OAuthBaseClient.java deleted file mode 100755 index 1db4680..0000000 --- a/app/src/main/java/com/codepath/oauth/OAuthBaseClient.java +++ /dev/null @@ -1,200 +0,0 @@ -package com.codepath.oauth; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; - -import com.github.scribejava.core.builder.api.BaseApi; -import com.github.scribejava.core.model.OAuth1AccessToken; -import com.github.scribejava.core.model.OAuth1RequestToken; -import com.github.scribejava.core.model.OAuth2AccessToken; -import com.github.scribejava.core.model.OAuthConstants; -import com.github.scribejava.core.model.Token; - -import java.util.HashMap; - -public abstract class OAuthBaseClient { - protected String baseUrl; - protected Context context; - protected OAuthAsyncHttpClient client; - protected SharedPreferences prefs; - protected SharedPreferences.Editor editor; - protected OAuthAccessHandler accessHandler; - protected String callbackUrl; - protected int requestIntentFlags = -1; - - private static final String OAUTH1_REQUEST_TOKEN = "request_token"; - private static final String OAUTH1_REQUEST_TOKEN_SECRET = "request_token_secret"; - private static final String OAUTH1_VERSION = "1.0"; - private static final String OAUTH2_VERSION = "2.0"; - - protected static HashMap, OAuthBaseClient> instances = - new HashMap, OAuthBaseClient>(); - - public static OAuthBaseClient getInstance(Class klass, Context context) { - OAuthBaseClient instance = instances.get(klass); - if (instance == null) { - try { - instance = (OAuthBaseClient) klass.getConstructor(Context.class).newInstance(context); - instances.put(klass, instance); - } catch (Exception e) { - e.printStackTrace(); - } - } - return instance; - } - - public OAuthBaseClient(Context c, BaseApi apiInstance, String consumerUrl, String consumerKey, String consumerSecret, String callbackUrl) { - this.baseUrl = consumerUrl; - this.callbackUrl = callbackUrl; - client = new OAuthAsyncHttpClient(apiInstance, consumerKey, - consumerSecret, callbackUrl, new OAuthAsyncHttpClient.OAuthTokenHandler() { - - // Store request token and launch the authorization URL in the browser - @Override - public void onReceivedRequestToken(Token requestToken, String authorizeUrl, String oAuthVersion) { - if (requestToken != null) { - if (oAuthVersion == OAUTH1_VERSION) { // store for OAuth1.0a - OAuth1RequestToken oAuth1RequestToken = (OAuth1RequestToken) requestToken; - editor.putString(OAUTH1_REQUEST_TOKEN, oAuth1RequestToken.getToken()); - editor.putString(OAUTH1_REQUEST_TOKEN_SECRET, oAuth1RequestToken.getTokenSecret()); - editor.commit(); - } - } - // Launch the authorization URL in the browser - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(authorizeUrl)); - if (requestIntentFlags != -1) { - intent.setFlags(requestIntentFlags); - } - OAuthBaseClient.this.context.startActivity(intent); - } - - // Store the access token in preferences, set the token in the client and fire the success callback - @Override - public void onReceivedAccessToken(Token accessToken, String oAuthVersion) { - - if (oAuthVersion == OAUTH1_VERSION) { - OAuth1AccessToken oAuth1AccessToken = (OAuth1AccessToken) accessToken; - - client.setAccessToken(accessToken); - editor.putString(OAuthConstants.TOKEN, oAuth1AccessToken.getToken()); - editor.putString(OAuthConstants.TOKEN_SECRET, oAuth1AccessToken.getTokenSecret()); - editor.putInt(OAuthConstants.VERSION, 1); - editor.commit(); - } else if (oAuthVersion == OAUTH2_VERSION) { - OAuth2AccessToken oAuth2AccessToken = (OAuth2AccessToken) accessToken; - client.setAccessToken(accessToken); - editor.putString(OAuthConstants.TOKEN, oAuth2AccessToken.getAccessToken()); - editor.putString(OAuthConstants.SCOPE, oAuth2AccessToken.getScope()); - editor.putString(OAuthConstants.REFRESH_TOKEN, oAuth2AccessToken.getRefreshToken()); - editor.putInt(OAuthConstants.VERSION, 2); - editor.commit(); - - } - accessHandler.onLoginSuccess(); - } - - @Override - public void onFailure(Exception e) { - accessHandler.onLoginFailure(e); - } - - }); - - this.context = c; - // Store preferences namespaced by the class and consumer key used - this.prefs = this.context.getSharedPreferences("OAuth_" + apiInstance.getClass().getSimpleName() + "_" + consumerKey, 0); - this.editor = this.prefs.edit(); - // Set access token in the client if already stored in preferences - if (this.checkAccessToken() != null) { - client.setAccessToken(this.checkAccessToken()); - } - } - - // Fetches a request token and retrieve and authorization url - // Should open a browser in onReceivedRequestToken once the url has been received - public void connect() { - client.fetchRequestToken(); - } - - // Retrieves access token given authorization url - public void authorize(Uri uri, OAuthAccessHandler handler) { - this.accessHandler = handler; - if (checkAccessToken() == null && uri != null) { - // TODO: check UriServiceCallback with intent:// scheme - client.fetchAccessToken(getOAuth1RequestToken(), uri); - - } else if (checkAccessToken() != null) { // already have access token - this.accessHandler.onLoginSuccess(); - } - } - - // Return access token if the token exists in preferences - public Token checkAccessToken() { - int oAuthVersion = prefs.getInt(OAuthConstants.VERSION, 0); - - if (oAuthVersion == 1 && prefs.contains(OAuthConstants.TOKEN) && prefs.contains(OAuthConstants.TOKEN_SECRET)) { - return new OAuth1AccessToken(prefs.getString(OAuthConstants.TOKEN, ""), - prefs.getString(OAuthConstants.TOKEN_SECRET, "")); - } else if (oAuthVersion == 2 && prefs.contains(OAuthConstants.TOKEN)) { - return new OAuth2AccessToken(prefs.getString(OAuthConstants.TOKEN, "")); - } - return null; - } - - protected OAuthAsyncHttpClient getClient() { - return client; - } - - // Returns the request token stored during the request token phase - protected OAuth1RequestToken getOAuth1RequestToken() { - return new OAuth1RequestToken(prefs.getString(OAUTH1_REQUEST_TOKEN, ""), - prefs.getString(OAUTH1_REQUEST_TOKEN_SECRET, "")); - } - - // Assigns the base url for the API - protected void setBaseUrl(String url) { - this.baseUrl = url; - } - - // Returns the full ApiUrl - protected String getApiUrl(String path) { - return this.baseUrl + "/" + path; - } - - // Removes the access tokens (for signing out) - public void clearAccessToken() { - client.setAccessToken(null); - editor.remove(OAuthConstants.TOKEN); - editor.remove(OAuthConstants.TOKEN_SECRET); - editor.remove(OAuthConstants.REFRESH_TOKEN); - editor.remove(OAuthConstants.SCOPE); - editor.commit(); - } - - // Returns true if the client is authenticated; false otherwise. - public boolean isAuthenticated() { - return client.getAccessToken() != null; - } - - // Sets the flags used when launching browser to authenticate through OAuth - public void setRequestIntentFlags(int flags) { - this.requestIntentFlags = flags; - } - - // Defines the handler events for the OAuth flow - public static interface OAuthAccessHandler { - public void onLoginSuccess(); - - public void onLoginFailure(Exception e); - } - - public void enableProxy() { - client.setProxy(System.getProperty("http.proxyHost"), Integer.parseInt(System.getProperty("http.proxyPort"))); - } - - public void addHeader(String headerName, String headerValue) { - client.addHeader(headerName, headerValue); - } -} diff --git a/app/src/main/java/com/codepath/oauth/OAuthLoginActivity.java b/app/src/main/java/com/codepath/oauth/OAuthLoginActivity.java deleted file mode 100755 index 53d4a8a..0000000 --- a/app/src/main/java/com/codepath/oauth/OAuthLoginActivity.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.codepath.oauth; - -import android.content.Intent; -import android.net.Uri; -import android.support.v4.app.FragmentActivity; - -import com.codepath.utils.GenericsUtil; - -//This is the FragmentActivity supportv4 version of LoginActivity -public abstract class OAuthLoginActivity extends FragmentActivity - implements OAuthBaseClient.OAuthAccessHandler { - - private T client; - - // Use this to properly assign the new intent with callback code - // for activities with a "singleTask" launch mode - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - } - - // Extract the uri data and call authorize to retrieve access token - // This is why after the browser redirects to the app, authentication is completed - @SuppressWarnings("unchecked") - @Override - protected void onResume() { - super.onResume(); - Class clientClass = getClientClass(); - // Extracts the authenticated url data after the user - // authorizes the OAuth app in the browser - Uri uri = getIntent().getData(); - - try { - client = (T) OAuthBaseClient.getInstance(clientClass, this); - client.authorize(uri, this); // fetch access token (if needed) - } catch (Exception e) { - e.printStackTrace(); - } - } - - public T getClient() { - return client; - } - - @SuppressWarnings("unchecked") - private Class getClientClass() { - return (Class) GenericsUtil.getTypeArguments(OAuthLoginActivity.class, this.getClass()).get(0); - } -} - -/* - * 1) Subclass OAuthBaseClient like TwitterClient - * 2) Subclass OAuthLoginActivity - * 3) Invoke .login - * 4) Optionally override - * a) onLoginSuccess - * b) onLoginFailure(Exception e) - * 5) In other activities that need the client - * a) c = TwitterClient.getSharedClient() -* b) c.getTimeline(...) - * 6) Modify AndroidManifest.xml to add an IntentFilter w/ the callback URL - * defined in the OAuthBaseClient. - */ diff --git a/app/src/main/java/com/codepath/oauth/OAuthLoginFragment.java b/app/src/main/java/com/codepath/oauth/OAuthLoginFragment.java deleted file mode 100755 index 10645d2..0000000 --- a/app/src/main/java/com/codepath/oauth/OAuthLoginFragment.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.codepath.oauth; - -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.Fragment; - -import com.codepath.utils.GenericsUtil; - -public abstract class OAuthLoginFragment extends Fragment implements - OAuthBaseClient.OAuthAccessHandler { - - private T client; - - @SuppressWarnings("unchecked") - @Override - public void onActivityCreated(Bundle saved) { - super.onActivityCreated(saved); - - // Fetch the uri that was passed in (which exists if this is being returned from authorization flow) - Uri uri = getActivity().getIntent().getData(); - // Fetch the client class this fragment is responsible for. - Class clientClass = getClientClass(); - - try { - client = (T) OAuthBaseClient.getInstance(clientClass, getActivity()); - client.authorize(uri, this); // fetch access token (if not stored) - } catch (Exception e) { - e.printStackTrace(); - } - } - - public T getClient() { - return client; - } - - @SuppressWarnings("unchecked") - private Class getClientClass() { - return (Class) GenericsUtil.getTypeArguments(OAuthLoginFragment.class, this.getClass()).get(0); - } -} diff --git a/app/src/main/java/com/codepath/oauth/ScribeRequestAdapter.java b/app/src/main/java/com/codepath/oauth/ScribeRequestAdapter.java deleted file mode 100755 index 12f9aa8..0000000 --- a/app/src/main/java/com/codepath/oauth/ScribeRequestAdapter.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.codepath.oauth; - -import android.net.Uri; - -import com.github.scribejava.core.model.OAuthConstants; -import com.github.scribejava.core.model.OAuthRequest; -import com.github.scribejava.core.model.ParameterList; -import com.github.scribejava.core.model.Verb; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import cz.msebera.android.httpclient.NameValuePair; -import cz.msebera.android.httpclient.client.methods.HttpEntityEnclosingRequestBase; -import cz.msebera.android.httpclient.client.methods.HttpRequestBase; -import cz.msebera.android.httpclient.client.methods.HttpUriRequest; -import cz.msebera.android.httpclient.client.utils.URLEncodedUtils; - -/* - * Implements the scribe-java Request interface allowing - * AsyncHttpClient requests to be signed with Scribe. - */ -public class ScribeRequestAdapter extends OAuthRequest { - private cz.msebera.android.httpclient.client.methods.HttpUriRequest httpUriRequest; - private HashMap oauthParameters; - - public ScribeRequestAdapter( - cz.msebera.android.httpclient.client.methods.HttpUriRequest httpUriRequest) { - - super(getMethod(httpUriRequest.getMethod()), httpUriRequest.getURI().toString()); - - this.httpUriRequest = httpUriRequest; - this.oauthParameters = new HashMap(); - } - - public static Verb getMethod(String method) { - switch (method) { - case "GET": - return Verb.GET; - case "POST": - return Verb.POST; - case "DELETE": - return Verb.DELETE; - case "PUT": - return Verb.PUT; - case "PATCH": - return Verb.PATCH; - case "OPTIONS": - return Verb.OPTIONS; - } - throw new IllegalStateException(method); - } - // Adds OAuth parameter with associated value - @Override - public void addOAuthParameter(String key, String value) { - this.oauthParameters.put(key, value); - } - - // Returns OAuth parameters - @Override - public Map getOauthParameters() { - return this.oauthParameters; - } - - // Adds header entry with associated value - @Override - public void addHeader(String key, String value) { - this.httpUriRequest.addHeader(key, value); - } - - // Add query string parameters to the HTTP Request. - @Override - public void addQuerystringParameter(String key, String value) { - // Workaround since some OAuth2 require "access_token" and others "oauth_token" - if (key.equals(OAuthConstants.ACCESS_TOKEN)) { addQuerystringParameter(OAuthConstants.TOKEN, value); } - // Workaround, convert URI to Uri, build on the URL to add the new query parameter and then update the HTTP Request - Uri updatedUri = Uri.parse(httpUriRequest.getURI().toString()).buildUpon().appendQueryParameter(key, value).build(); - ((HttpRequestBase) httpUriRequest).setURI(URI.create(updatedUri.toString())); - } - - // Returns query strings embedded in the URL - @Override - public ParameterList getQueryStringParams() { - try { - return parseQueryParams(); - } catch (UnsupportedEncodingException e) { - return new ParameterList(); - } - } - - // Returns params parsed from the entity body - @Override - public ParameterList getBodyParams() { - if (getVerb() == Verb.GET || getVerb() == Verb.DELETE) { return new ParameterList(); } - else { return parseEntityParams(); } - } - - // Returns the full URL with query strings - @Override - public String getCompleteUrl() { - return getHttpRequest().getURI().toString(); - } - - // Returns the base URL without query strings or host - @Override - public String getSanitizedUrl() { - return getCompleteUrl().replaceAll("\\?.*", "").replace("\\:\\d{4}", ""); - } - - // Returns Verb enum for the request method (i.e Verb.GET) - @Override - public Verb getVerb() { - return Verb.valueOf(getHttpRequest().getMethod()); - } - - // Returns simple string representation of the request - // i.e @Request(GET http://foo.com/bar) - @Override - public String toString() - { - return String.format("@Request(%s %s)", getVerb(), getCompleteUrl()); - } - - // Returns the underlying HTTP request - protected HttpUriRequest getHttpRequest() { - return this.httpUriRequest; - } - - // Parses and returns the entity provided as a ParameterList - private ParameterList parseEntityParams() { - cz.msebera.android.httpclient.HttpEntity entity = null; - List parameters = null; - try{ - entity = ((HttpEntityEnclosingRequestBase) httpUriRequest).getEntity(); - parameters = new ArrayList( URLEncodedUtils.parse(entity)); - } catch (Exception e) { - return new ParameterList(); - } - - ParameterList list = new ParameterList(); - for (NameValuePair pair : parameters) { - list.add(pair.getName(), pair.getValue()); - } - return list; - } - - // Returns the ParameterList of query parameters parsed from the URL string - private ParameterList parseQueryParams() throws UnsupportedEncodingException { - ParameterList params = new ParameterList(); - String queryString = URI.create(getCompleteUrl()).getQuery(); - if (queryString == null) { return params; } - for (String param : queryString.split("&")) { - String pair[] = param.split("="); - String key = URLDecoder.decode(pair[0], "UTF-8"); - String value = ""; - if (pair.length > 1) { - value = URLDecoder.decode(pair[1], "UTF-8"); - } - params.add(new String(key), new String(value)); - } - return params; - } - - @Override - public String getRealm() { - return null; - } - -} diff --git a/build.gradle b/build.gradle index 9d73a49..cd09089 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ allprojects { repositories { google() jcenter() + maven { url "https://jitpack.io" } } } /* diff --git a/app/build.gradle b/library/build.gradle similarity index 89% rename from app/build.gradle rename to library/build.gradle index 9744b4b..dbfa69e 100644 --- a/app/build.gradle +++ b/library/build.gradle @@ -7,8 +7,8 @@ apply plugin: 'com.jfrog.bintray' ext { GROUP = 'com.codepath.libraries' - BASE_VERSION = "1.4" - VERSION_NAME = "1.4.0" + BASE_VERSION = "1.5" + VERSION_NAME = "1.5.0" POM_PACKAGING = "aar" POM_DESCRIPTION = "CodePath OAuth Handler" @@ -37,7 +37,7 @@ android { defaultConfig { versionCode 1 versionName VERSION_NAME - minSdkVersion 14 + minSdkVersion 21 targetSdkVersion 28 } @@ -114,11 +114,15 @@ ext { } dependencies { - api "com.android.support:appcompat-v7:${supportVersion}" - api "com.android.support:support-v4:${supportVersion}" - api 'com.loopj.android:android-async-http:1.4.9' + api "androidx.appcompat:appcompat:1.0.2" implementation files('libs/codepath-utils.jar') api 'com.github.scribejava:scribejava-apis:4.1.1' + api 'com.github.scribejava:scribejava-httpclient-okhttp:4.1.1' + implementation 'com.github.codepath:CPAsyncHttpClient:78ad2d305d' + implementation 'se.akerfeldt:okhttp-signpost:1.1.0' + implementation 'oauth.signpost:signpost-core:1.2.1.2' + + } /*task jar(type: Jar) { diff --git a/app/libs/codepath-utils.jar b/library/libs/codepath-utils.jar similarity index 100% rename from app/libs/codepath-utils.jar rename to library/libs/codepath-utils.jar diff --git a/app/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml similarity index 100% rename from app/src/main/AndroidManifest.xml rename to library/src/main/AndroidManifest.xml diff --git a/library/src/main/java/com/codepath/oauth/OAuth1Client.java b/library/src/main/java/com/codepath/oauth/OAuth1Client.java new file mode 100644 index 0000000..a405782 --- /dev/null +++ b/library/src/main/java/com/codepath/oauth/OAuth1Client.java @@ -0,0 +1,177 @@ +package com.codepath.oauth; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.util.Log; + +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.builder.api.DefaultApi10a; +import com.github.scribejava.core.model.OAuth1AccessToken; +import com.github.scribejava.core.model.OAuth1RequestToken; +import com.github.scribejava.core.model.OAuthAsyncRequestCallback; +import com.github.scribejava.core.model.OAuthConstants; +import com.github.scribejava.core.oauth.OAuth10aService; +import com.github.scribejava.httpclient.okhttp.OkHttpHttpClientConfig; + +public class OAuth1Client { + protected String baseUrl; + protected Context context; + protected SharedPreferences prefs; + protected SharedPreferences.Editor editor; + protected OAuthAccessHandler accessHandler; + protected String callbackUrl; + protected int requestIntentFlags = -1; + + private static final String OAUTH1_REQUEST_TOKEN = "request_token"; + private static final String OAUTH1_REQUEST_TOKEN_SECRET = "request_token_secret"; + + OAuth10aService service; + + public static String DEBUG = "debug"; + + public OAuth1Client(Context c, DefaultApi10a apiInstance, String consumerUrl, String consumerKey, String consumerSecret, String callbackUrl) { + this.baseUrl = consumerUrl; + this.callbackUrl = callbackUrl; + + if (callbackUrl == null) { callbackUrl = OAuthConstants.OUT_OF_BAND; }; + service = new ServiceBuilder() + .apiKey(consumerKey) + .apiSecret(consumerSecret).callback(callbackUrl) + .httpClientConfig(OkHttpHttpClientConfig.defaultConfig()) + .debug() + .build(apiInstance); + + this.context = c; + // Store preferences namespaced by the class and consumer key used + this.prefs = this.context.getSharedPreferences("OAuth_" + apiInstance.getClass().getSimpleName() + "_" + consumerKey, 0); + this.editor = this.prefs.edit(); + } + + // Fetches a request token and retrieve and authorization url + // Should open a browser in onReceivedRequestToken once the url has been received + public void connect() { + service.getRequestTokenAsync(new OAuthAsyncRequestCallback() { + @Override + public void onCompleted(OAuth1RequestToken responseToken) { + if (responseToken != null) { + // put in vault + editor.putString(OAUTH1_REQUEST_TOKEN, responseToken.getToken()); + editor.putString(OAUTH1_REQUEST_TOKEN_SECRET, responseToken.getTokenSecret()); + editor.commit(); + } + + String authorizeUrl = service.getAuthorizationUrl(responseToken); + // Launch the authorization URL in the browser + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(authorizeUrl)); + if (requestIntentFlags != -1) { + intent.setFlags(requestIntentFlags); + } + OAuth1Client.this.context.startActivity(intent); + } + + @Override + public void onThrowable(Throwable e) { + Log.e(DEBUG, "Error trying to connect" + e.toString()); + e.printStackTrace(); + + } + }); + } + + // Retrieves access token given authorization url + public void authorize(Uri authorizedUri, final OAuthAccessHandler handler) { + if (checkAccessToken() == null && authorizedUri != null) { + // TODO: check UriServiceCallback with intent:// scheme + if (authorizedUri.getQuery().contains(OAuthConstants.VERIFIER)) { + String oauth_verifier = authorizedUri.getQueryParameter(OAuthConstants.VERIFIER); + + service.getAccessTokenAsync(getOAuth1RequestToken(), oauth_verifier, + new OAuthAsyncRequestCallback() { + + @Override + public void onCompleted(OAuth1AccessToken oAuth1AccessToken) { + if (oAuth1AccessToken != null) { + editor.putInt(OAuthConstants.VERSION, 1); + editor.putString(OAuthConstants.TOKEN, + oAuth1AccessToken.getToken()); + editor.putString(OAuthConstants.TOKEN_SECRET, + oAuth1AccessToken.getTokenSecret()); + editor.commit(); + + handler.onLoginSuccess(); + + } + } + + @Override + public void onThrowable(Throwable e) { + Log.d(DEBUG, "Exception" + e.toString()); + e.printStackTrace(); + handler.onLoginFailure(new Exception()); + } + + }); + } + } else { + handler.onLoginSuccess(); + } + } + + // Return access token if the token exists in preferences + public OAuth1AccessToken checkAccessToken() { + int oAuthVersion = prefs.getInt(OAuthConstants.VERSION, 0); + + if (oAuthVersion == 1 && prefs.contains(OAuthConstants.TOKEN) && prefs.contains(OAuthConstants.TOKEN_SECRET)) { + return new OAuth1AccessToken(prefs.getString(OAuthConstants.TOKEN, ""), + prefs.getString(OAuthConstants.TOKEN_SECRET, "")); + } + return null; + } + + // Returns the request token stored during the request token phase + protected OAuth1RequestToken getOAuth1RequestToken() { + return new OAuth1RequestToken(prefs.getString(OAUTH1_REQUEST_TOKEN, ""), + prefs.getString(OAUTH1_REQUEST_TOKEN_SECRET, "")); + } + + // Assigns the base url for the API + protected void setBaseUrl(String url) { + this.baseUrl = url; + } + + // Returns the full ApiUrl + protected String getApiUrl(String path) { + return this.baseUrl + "/" + path; + } + + // Removes the access tokens (for signing out) + public void clearAccessToken() { + //client.setAccessToken(null); + editor.remove(OAuthConstants.TOKEN); + editor.remove(OAuthConstants.TOKEN_SECRET); + editor.remove(OAuthConstants.REFRESH_TOKEN); + editor.remove(OAuthConstants.SCOPE); + editor.commit(); + } + + // Returns true if the client is authenticated; false otherwise. + public boolean isAuthenticated() { + //return client.getAccessToken() != null; + return getOAuth1RequestToken() != null; + } + + // Sets the flags used when launching browser to authenticate through OAuth + public void setRequestIntentFlags(int flags) { + this.requestIntentFlags = flags; + } + + // Defines the handler events for the OAuth flow + public static interface OAuthAccessHandler { + public void onLoginSuccess(); + + public void onLoginFailure(Exception e); + } + +} diff --git a/library/src/main/java/com/codepath/oauth/OAuth1ClientProvider.java b/library/src/main/java/com/codepath/oauth/OAuth1ClientProvider.java new file mode 100644 index 0000000..6fe5212 --- /dev/null +++ b/library/src/main/java/com/codepath/oauth/OAuth1ClientProvider.java @@ -0,0 +1,8 @@ +package com.codepath.oauth; + +import android.content.Context; + +public abstract interface OAuth1ClientProvider { + + public OAuth1Client getClient(Context context); +} diff --git a/library/src/main/java/com/codepath/oauth/OAuthAsyncHttpClient.java b/library/src/main/java/com/codepath/oauth/OAuthAsyncHttpClient.java new file mode 100755 index 0000000..03a4b3f --- /dev/null +++ b/library/src/main/java/com/codepath/oauth/OAuthAsyncHttpClient.java @@ -0,0 +1,25 @@ +package com.codepath.oauth; + +import com.codepath.asynchttpclient.AsyncHttpClient; + +import okhttp3.OkHttpClient; +import se.akerfeldt.okhttp.signpost.OkHttpOAuthConsumer; +import se.akerfeldt.okhttp.signpost.SigningInterceptor; + +public class OAuthAsyncHttpClient extends AsyncHttpClient { + + protected OAuthAsyncHttpClient(OkHttpClient httpClient) { + super(httpClient); + } + + public static OAuthAsyncHttpClient create(String consumerKey, String consumerSecret, String tokenKey, + String tokenSecret) { + OkHttpOAuthConsumer consumer = new OkHttpOAuthConsumer(consumerKey, consumerSecret); + consumer.setTokenWithSecret(tokenKey, tokenSecret); + OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(new SigningInterceptor(consumer)).build(); + + OAuthAsyncHttpClient asyncHttpClient = new OAuthAsyncHttpClient(httpClient); + return asyncHttpClient; + } + +} diff --git a/app/src/main/java/com/codepath/oauth/OAuthLoginActionBarActivity.java b/library/src/main/java/com/codepath/oauth/OAuthLoginActivity.java similarity index 54% rename from app/src/main/java/com/codepath/oauth/OAuthLoginActionBarActivity.java rename to library/src/main/java/com/codepath/oauth/OAuthLoginActivity.java index beea737..33d5af6 100755 --- a/app/src/main/java/com/codepath/oauth/OAuthLoginActionBarActivity.java +++ b/library/src/main/java/com/codepath/oauth/OAuthLoginActivity.java @@ -1,18 +1,14 @@ package com.codepath.oauth; -import com.codepath.utils.GenericsUtil; - import android.content.Intent; import android.net.Uri; -import android.support.v7.app.AppCompatActivity; -// This is the ActionBarActivity supportv7 version of LoginActivity -public abstract class OAuthLoginActionBarActivity extends +import androidx.appcompat.app.AppCompatActivity; + +public abstract class OAuthLoginActivity extends AppCompatActivity - implements OAuthBaseClient.OAuthAccessHandler { + implements OAuth1Client.OAuthAccessHandler, OAuth1ClientProvider { - private T client; - // Use this to properly assign the new intent with callback code // for activities with a "singleTask" launch mode @Override @@ -27,33 +23,23 @@ protected void onNewIntent(Intent intent) { @Override protected void onResume() { super.onResume(); - Class clientClass = getClientClass(); - // Extracts the authenticated url data after the user + // Extracts the authenticated url data after the user // authorizes the OAuth app in the browser Uri uri = getIntent().getData(); try { - client = (T) OAuthBaseClient.getInstance(clientClass, this); - client.authorize(uri, this); // fetch access token (if needed) + OAuth1Client client = getClient(this); + client.authorize(uri, this); // fetch access token (if needed) } catch (Exception e) { e.printStackTrace(); } } - - public T getClient() { - return client; - } - - @SuppressWarnings("unchecked") - private Class getClientClass() { - return (Class) GenericsUtil.getTypeArguments(OAuthLoginActionBarActivity.class, this.getClass()).get(0); - } } /* - * 1) Subclass OAuthBaseClient like TwitterClient - * 2) Subclass OAuthLoginActivity - * 3) Invoke .login + * 1) Instantiate an OAuth client through OAuthAsyncHttpClient + * 2) Subclass OAuthLoginActivity and implement getClient() + * 3) Invoke .connect() * 4) Optionally override * a) onLoginSuccess * b) onLoginFailure(Exception e) diff --git a/library/src/main/java/com/codepath/oauth/OAuthLoginFragment.java b/library/src/main/java/com/codepath/oauth/OAuthLoginFragment.java new file mode 100755 index 0000000..a1985c4 --- /dev/null +++ b/library/src/main/java/com/codepath/oauth/OAuthLoginFragment.java @@ -0,0 +1,47 @@ +package com.codepath.oauth; + +public abstract class OAuthLoginFragment { +} + + +// +//import android.net.Uri; +//import android.os.Bundle; +//import android.support.v4.app.Fragment; +// +//import androidx.fragment.app.Fragment; +// +//import com.codepath.utils.GenericsUtil; +// +//public abstract class OAuthLoginFragment extends Fragment implements +// OAuthBaseClient.OAuthAccessHandler { +// +// private T client; +// +// @SuppressWarnings("unchecked") +// @Override +// public void onActivityCreated(Bundle saved) { +// super.onActivityCreated(saved); +// +// // Fetch the uri that was passed in (which exists if this is being returned from authorization flow) +// Uri uri = getActivity().getIntent().getData(); +// // Fetch the client class this fragment is responsible for. +// Class clientClass = getClientClass(); +// +// try { +// client = (T) OAuthBaseClient.getInstance(clientClass, getActivity()); +// client.authorize(uri, this); // fetch access token (if not stored) +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } +// +// public T getClient() { +// return client; +// } +// +// @SuppressWarnings("unchecked") +// private Class getClientClass() { +// return (Class) GenericsUtil.getTypeArguments(OAuthLoginFragment.class, this.getClass()).get(0); +// } +//} diff --git a/app/src/main/res/.gitkeep b/library/src/main/res/.gitkeep similarity index 100% rename from app/src/main/res/.gitkeep rename to library/src/main/res/.gitkeep diff --git a/app/src/main/res/layout/.gitkeep b/library/src/main/res/layout/.gitkeep similarity index 100% rename from app/src/main/res/layout/.gitkeep rename to library/src/main/res/layout/.gitkeep diff --git a/settings.gradle b/settings.gradle index e7b4def..d8f14a1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app' +include ':library'