diff --git a/.travis.yml b/.travis.yml
index cb97c9a..607a3b0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,14 +6,10 @@ android:
components:
- tools
- platform-tools
- - build-tools-27.0.3
- - android-27
- - extra-android-m2repository
- - extra-google-m2repository
- licenses:
- - android-sdk-license-.+
+ - build-tools-28.0.3
+ - android-28
before_install:
- - yes | sdkmanager "platforms;android-27"
+ - yes | sdkmanager "platforms;android-28"
script:
- "./gradlew build check --daemon"
after_failure: "cat $TRAVIS_BUILD_DIR/app/build/outputs/lint-results.xml"
diff --git a/README.md b/README.md
index 3884220..9a5b758 100755
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ approach that keeps the details of the OAuth process abstracted from the end-use
This library leverages a few key libraries underneath to power the functionality:
* [scribe-java](https://github.com/scribejava/scribejava) - Simple OAuth library for handling the authentication flow.
- * [Android Async HTTP](https://github.com/loopj/android-async-http) - Simple asynchronous HTTP requests with JSON parsing.
+ * [Android Async HTTP](https://github.com/codepath/asynchttpclient) - Simple asynchronous HTTP requests with JSON parsing.
## Installation
@@ -24,7 +24,7 @@ Next, add this line to your `app/build.gradle` file:
```gradle
dependencies {
- compile 'com.codepath.libraries:android-oauth-handler:1.2.5'
+ compile 'com.codepath.libraries:android-oauth-handler:2.1.3'
}
```
@@ -51,12 +51,12 @@ public class TwitterClient extends OAuthBaseClient {
public TwitterClient(Context context) {
super(context, REST_API_INSTANCE, REST_URL,
- REST_CONSUMER_KEY, REST_CONSUMER_SECRET, REST_CALLBACK_URL);
+ REST_CONSUMER_KEY, REST_CONSUMER_SECRET, null, REST_CALLBACK_URL);
}
// ENDPOINTS BELOW
- public void getHomeTimeline(int page, AsyncHttpResponseHandler handler) {
+ public void getHomeTimeline(int page, JsonHttpResponseHandler handler) {
String apiUrl = getApiUrl("statuses/home_timeline.json");
RequestParams params = new RequestParams();
params.put("page", String.valueOf(page));
@@ -169,9 +169,9 @@ with a `JsonHttpResponseHandler` handler:
// SomeActivity.java
RestClient client = RestClientApp.getRestClient();
client.getHomeTimeline(1, new JsonHttpResponseHandler() {
- public void onSuccess(int statusCode, Header[] headers, JSONArray json) {
+ public void onSuccess(int statusCode, Headers headers, JSON json) {
// Response is automatically parsed into a JSONArray
- // json.getJSONObject(0).getLong("id");
+ // json.jsonArray.getJSONObject(0).getLong("id");
}
});
```
@@ -180,27 +180,18 @@ Based on the JSON response (array or object), you need to declare the expected t
```java
RestClient client = RestClientApp.getRestClient();
-client.get("http://www.google.com", new AsyncHttpResponseHandler() {
+client.get("http://www.google.com", new JsonHttpResponseHandler() {
@Override
- public void onSuccess(int statusCode, Header[] headers, String response) {
+ public void onSuccess(int statusCode, Headers headers, String response) {
System.out.println(response);
}
});
```
-Check out [Android Async HTTP Docs](http://loopj.com/android-async-http/) for more request creation details.
+Check out [Android Async HTTP Docs](https://github.com/codepath/asynchttpclient) for more request creation details.
## Extra Functionality
-### Adding Request Headers
-
-In certain cases, requests will require a particular custom header to be passed through the client. In this case, you can add custom headers to the client that will be added to all requests with:
-
-```java
-RestClient client = RestApplication.getRestClient();
-// Specify the header to append to the request
-client.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1)");
-```
### Access Authorization
@@ -226,40 +217,32 @@ This can be helpful in cases where you must add a flag such as when encountering
You can log out by clearing the access token at any time through the client object:
-You can log out by clearing the access token at any time through the client object:
-
```java
RestClient client = RestApplication.getRestClient();
client.clearAccessToken();
```
-### Enabling a Proxy
+### Debugging
-In order to [troubleshoot API calls](http://guides.codepath.com/android/Troubleshooting-API-calls) using a method such as Charles Proxy, you'll want to enable proxy support with:
+In order to [troubleshoot API calls](http://guides.codepath.com/android/Troubleshooting-API-calls), you can take advantage of the Stetho library:
+Next, initialize Stetho inside your Application object:
```java
-RestClient client = RestApplication.getRestClient();
-client.enableProxy();
+public class MyApplication extends Application {
+ public void onCreate() {
+ super.onCreate();
+ Stetho.initializeWithDefaults(this);
+ }
+}
```
-Proxies are useful for monitoring the network traffic but require a custom SSL certificate to be added to your emulator or device. Because Android API 24 and above now require [explicit control](https://developer.android.com/training/articles/security-config.html) on custom SSL certificates that are used in apps, you will need to allow for added certs to be added by specifying `res/xml/network_security_config.xml` in your app:
-
+Edit the manifest.xml file in your project. To let the Android operating system know that you have a custom Application class, add an attribute called `android:name` to the manifest’s application tag and set the value to the name of your custom Application class.
```xml
-
-
-
-
-
-
-
-
-
+
```
-Inside your AndroidManifest.xml file, make sure to include this `networkSecurityConfig` parameter:
-
-```xml
- 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 8ec92f8..4055c94 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,28 +1,18 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
- jcenter()
google()
+ mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.0.1'
- classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
+ classpath 'com.android.tools.build:gradle:4.0.1'
}
}
-plugins {
- id "com.jfrog.bintray" version "1.6"
-}
-
-bintray {
- publications = []
- configurations = []
-}
-
allprojects {
repositories {
- jcenter()
google()
+ jcenter()
}
}
/*
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..199d16e
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d19e467..dfb6cb5 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sat Mar 31 23:33:37 PDT 2018
+#Sun Aug 11 22:16:42 PDT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
diff --git a/library/build.gradle b/library/build.gradle
new file mode 100644
index 0000000..c25a507
--- /dev/null
+++ b/library/build.gradle
@@ -0,0 +1,144 @@
+apply plugin: 'com.android.library'
+apply plugin: 'maven-publish'
+apply plugin: 'signing'
+
+ext {
+
+ GROUP = 'com.codepath.libraries'
+ BASE_VERSION = "2.3"
+ VERSION_NAME = "2.3.0"
+ POM_PACKAGING = "aar"
+ POM_DESCRIPTION = "CodePath OAuth Handler"
+
+ POM_ARTIFACT_ID = "android-oauth-handler"
+ POM_NAME = "CodePath OAuth Handler"
+ POM_URL = "https://github.com/codepath/android-oauth-handler/"
+ POM_SCM_URL = "https://github.com/codepath/android-oauth-handler/"
+ POM_SCM_CONNECTION = "scm:git:https://github.com/codepath/android-oauth-handler.git"
+ POM_SCM_DEV_CONNECTION = "scm:git:git@github.com:codepath/android-oauth-handler.git"
+
+ POM_LICENCE_NAME = "The Apache Software License, Version 2.0"
+ POM_LICENCE_URL = "http://www.apache.org/licenses/LICENSE-2.0.txt"
+ POM_LICENCE_DIST = "repo"
+
+ POM_DEVELOPER_ID = "codepath"
+ POM_DEVELOPER_NAME = "CodePath, Inc."
+}
+
+version = VERSION_NAME
+group = GROUP
+archivesBaseName = POM_ARTIFACT_ID
+
+android {
+ compileSdkVersion 30
+
+ defaultConfig {
+ versionCode 1
+ versionName VERSION_NAME
+ minSdkVersion 21
+ targetSdkVersion 30
+ }
+
+ // Related to https://github.com/scribejava/scribejava/issues/480
+ // Scribe expects Java 7 or this custom Apache library
+ lintOptions {
+ lintConfig rootProject.file('gradle/lint.xml')
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+}
+
+afterEvaluate {
+ publishing {
+ publications {
+ // Creates a Maven publication called "release".
+ release(MavenPublication) {
+ // Applies the component for the release build variant.
+ from components.release
+
+ // You can then customize attributes of the publication as shown below.
+ groupId = GROUP
+ artifactId = POM_ARTIFACT_ID
+ version = VERSION_NAME
+
+ pom {
+ name = POM_NAME
+ url = POM_URL
+ description = POM_DESCRIPTION
+ licenses {
+ license {
+ name = POM_LICENCE_NAME
+ url = POM_LICENCE_URL
+ }
+ }
+ developers {
+ developer {
+ id = POM_DEVELOPER_ID
+ name = POM_DEVELOPER_NAME
+ }
+ }
+ scm {
+ url = POM_SCM_URL
+ }
+ }
+ }
+ }
+ repositories {
+ maven {
+ name = "Sonatype"
+ credentials {
+ username = System.getenv('NEXUS_USERNAME')
+ password = System.getenv('NEXUS_PASSWORD')
+ }
+ def releasesRepoUrl = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/'
+ def snapshotsRepoUrl = 'https://s01.oss.sonatype.org/content/repositories/snapshots/'
+ url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
+ setUrl(url)
+ }
+ }
+ }
+}
+
+signing {
+ // gpg on MacOS is the same as gpg2
+ // ln -s /usr/local/bin/gpg /usr/local/bin/gpg2
+ // Make sure to populate the variables in gradle.properties
+ // signing.gnupg.keyName=XXX
+ // signing.gnupg.passpharse
+ useGpgCmd()
+ sign(publishing.publications)
+}
+
+task sourcesJar(type: Jar) {
+ from android.sourceSets.main.java.srcDirs
+ archiveClassifier.set("sources")
+}
+
+artifacts {
+ archives sourcesJar
+}
+
+ext {
+ supportVersion = '28.0.0'
+}
+
+dependencies {
+ api "androidx.appcompat:appcompat:1.3.0"
+ api 'com.codepath.libraries:asynchttpclient:2.1.1'
+ api 'com.github.scribejava:scribejava-apis:4.1.1'
+ api 'com.github.scribejava:scribejava-httpclient-okhttp:4.1.1'
+ implementation 'se.akerfeldt:okhttp-signpost:1.1.0'
+ implementation 'oauth.signpost:signpost-core:1.2.1.2'
+ implementation 'com.facebook.stetho:stetho:1.5.1'
+ implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
+ implementation "com.squareup.okhttp3:logging-interceptor:4.7.2"
+
+}
+
+task jar(type: Jar) {
+ from android.sourceSets.main.java.srcDirs
+}
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/OAuthAsyncHttpClient.java b/library/src/main/java/com/codepath/oauth/OAuthAsyncHttpClient.java
new file mode 100644
index 0000000..b9dd7a4
--- /dev/null
+++ b/library/src/main/java/com/codepath/oauth/OAuthAsyncHttpClient.java
@@ -0,0 +1,69 @@
+package com.codepath.oauth;
+
+import com.codepath.asynchttpclient.AsyncHttpClient;
+import com.facebook.stetho.okhttp3.StethoInterceptor;
+import com.github.scribejava.core.model.OAuth1AccessToken;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.logging.HttpLoggingInterceptor;
+import se.akerfeldt.okhttp.signpost.OkHttpOAuthConsumer;
+import se.akerfeldt.okhttp.signpost.SigningInterceptor;
+
+public class OAuthAsyncHttpClient extends AsyncHttpClient {
+
+ protected OAuthAsyncHttpClient(OkHttpClient httpClient) {
+ super(httpClient);
+ }
+
+ private static String BEARER = "Bearer";
+
+ public static HttpLoggingInterceptor createLogger() {
+ HttpLoggingInterceptor logger = new HttpLoggingInterceptor();
+ logger.level(HttpLoggingInterceptor.Level.HEADERS);
+ return logger;
+ }
+
+ public static OAuthAsyncHttpClient create(String consumerKey, String consumerSecret, OAuth1AccessToken token) {
+ OkHttpOAuthConsumer consumer = new OkHttpOAuthConsumer(consumerKey, consumerSecret);
+ HttpLoggingInterceptor logging = createLogger();
+
+ consumer.setTokenWithSecret(token.getToken(), token.getTokenSecret());
+ OkHttpClient httpClient = new OkHttpClient.Builder()
+ .addInterceptor(logging)
+ .addNetworkInterceptor(new StethoInterceptor())
+ .addInterceptor(new SigningInterceptor(consumer)).build();
+
+ OAuthAsyncHttpClient asyncHttpClient = new OAuthAsyncHttpClient(httpClient);
+ return asyncHttpClient;
+ }
+
+ public static OAuthAsyncHttpClient create(final OAuth2AccessToken token) {
+ final String bearer = String.format("%s %s", BEARER, token.getAccessToken());
+
+ HttpLoggingInterceptor logging = createLogger();
+
+ OkHttpClient httpClient = new OkHttpClient.Builder()
+ .addInterceptor(logging)
+ .addNetworkInterceptor(new StethoInterceptor())
+ .addInterceptor(new Interceptor() {
+ @NotNull
+ @Override
+ public Response intercept(@NotNull Chain chain) throws IOException {
+ Request originalRequest = chain.request();
+ Request authedRequest = originalRequest.newBuilder().header("Authorization", bearer).build();
+ return chain.proceed(authedRequest);
+ }
+ }).build();
+
+ OAuthAsyncHttpClient asyncHttpClient = new OAuthAsyncHttpClient(httpClient);
+ return asyncHttpClient;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codepath/oauth/OAuthBaseClient.java b/library/src/main/java/com/codepath/oauth/OAuthBaseClient.java
similarity index 74%
rename from app/src/main/java/com/codepath/oauth/OAuthBaseClient.java
rename to library/src/main/java/com/codepath/oauth/OAuthBaseClient.java
index 1db4680..038a76e 100755
--- a/app/src/main/java/com/codepath/oauth/OAuthBaseClient.java
+++ b/library/src/main/java/com/codepath/oauth/OAuthBaseClient.java
@@ -5,6 +5,8 @@
import android.content.SharedPreferences;
import android.net.Uri;
+import androidx.annotation.Nullable;
+
import com.github.scribejava.core.builder.api.BaseApi;
import com.github.scribejava.core.model.OAuth1AccessToken;
import com.github.scribejava.core.model.OAuth1RequestToken;
@@ -17,6 +19,7 @@
public abstract class OAuthBaseClient {
protected String baseUrl;
protected Context context;
+ protected OAuthTokenClient tokenClient;
protected OAuthAsyncHttpClient client;
protected SharedPreferences prefs;
protected SharedPreferences.Editor editor;
@@ -45,11 +48,11 @@ public static OAuthBaseClient getInstance(Class extends OAuthBaseClient> klass
return instance;
}
- public OAuthBaseClient(Context c, BaseApi apiInstance, String consumerUrl, String consumerKey, String consumerSecret, String callbackUrl) {
+ public OAuthBaseClient(Context c, final BaseApi apiInstance, String consumerUrl, final String consumerKey, final String consumerSecret, @Nullable String scope, String callbackUrl) {
this.baseUrl = consumerUrl;
this.callbackUrl = callbackUrl;
- client = new OAuthAsyncHttpClient(apiInstance, consumerKey,
- consumerSecret, callbackUrl, new OAuthAsyncHttpClient.OAuthTokenHandler() {
+ tokenClient = new OAuthTokenClient(apiInstance, consumerKey,
+ consumerSecret, callbackUrl, scope, new OAuthTokenClient.OAuthTokenHandler() {
// Store request token and launch the authorization URL in the browser
@Override
@@ -59,6 +62,7 @@ public void onReceivedRequestToken(Token requestToken, String authorizeUrl, Stri
OAuth1RequestToken oAuth1RequestToken = (OAuth1RequestToken) requestToken;
editor.putString(OAUTH1_REQUEST_TOKEN, oAuth1RequestToken.getToken());
editor.putString(OAUTH1_REQUEST_TOKEN_SECRET, oAuth1RequestToken.getTokenSecret());
+ editor.putInt(OAuthConstants.VERSION, 1);
editor.commit();
}
}
@@ -70,21 +74,23 @@ public void onReceivedRequestToken(Token requestToken, String authorizeUrl, Stri
OAuthBaseClient.this.context.startActivity(intent);
}
- // Store the access token in preferences, set the token in the client and fire the success callback
+ // Store the access token in preferences, set the token in the tokenClient and fire the success callback
@Override
public void onReceivedAccessToken(Token accessToken, String oAuthVersion) {
if (oAuthVersion == OAUTH1_VERSION) {
OAuth1AccessToken oAuth1AccessToken = (OAuth1AccessToken) accessToken;
- client.setAccessToken(accessToken);
+ tokenClient.setAccessToken(accessToken);
+ instantiateClient(consumerKey, consumerSecret, oAuth1AccessToken);
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);
+ instantiateClient(consumerKey, consumerSecret, oAuth2AccessToken);
+ tokenClient.setAccessToken(accessToken);
editor.putString(OAuthConstants.TOKEN, oAuth2AccessToken.getAccessToken());
editor.putString(OAuthConstants.SCOPE, oAuth2AccessToken.getScope());
editor.putString(OAuthConstants.REFRESH_TOKEN, oAuth2AccessToken.getRefreshToken());
@@ -106,16 +112,29 @@ public void onFailure(Exception e) {
// 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());
+ // Set access token in the tokenClient if already stored in preferences
+ Token accessToken = this.checkAccessToken();
+ if (accessToken != null) {
+ tokenClient.setAccessToken(accessToken);
+ instantiateClient(consumerKey, consumerSecret, accessToken);
}
}
+ public void instantiateClient(String consumerKey, String consumerSecret, Token token) {
+
+ if (token instanceof OAuth1AccessToken) {
+ client = OAuthAsyncHttpClient.create(consumerKey, consumerSecret, (OAuth1AccessToken)(token));
+ } else if (token instanceof OAuth2AccessToken){
+ client = OAuthAsyncHttpClient.create((OAuth2AccessToken) token);
+ } else {
+ throw new IllegalStateException("unrecognized token type" + token);
+ }
+
+ }
// 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();
+ tokenClient.fetchRequestToken();
}
// Retrieves access token given authorization url
@@ -123,7 +142,7 @@ 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);
+ tokenClient.fetchAccessToken(getOAuth1RequestToken(), uri);
} else if (checkAccessToken() != null) { // already have access token
this.accessHandler.onLoginSuccess();
@@ -143,14 +162,19 @@ public Token checkAccessToken() {
return null;
}
- protected OAuthAsyncHttpClient getClient() {
- return client;
+ protected OAuthTokenClient getTokenClient() {
+ return tokenClient;
}
- // 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, ""));
+ // Returns the request token stored during the request token phase (OAuth1 only)
+ protected @Nullable Token getOAuth1RequestToken() {
+ int oAuthVersion = prefs.getInt(OAuthConstants.VERSION, 0);
+
+ if (oAuthVersion == 1) {
+ return new OAuth1RequestToken(prefs.getString(OAUTH1_REQUEST_TOKEN, ""),
+ prefs.getString(OAUTH1_REQUEST_TOKEN_SECRET, ""));
+ }
+ return null;
}
// Assigns the base url for the API
@@ -165,7 +189,7 @@ protected String getApiUrl(String path) {
// Removes the access tokens (for signing out)
public void clearAccessToken() {
- client.setAccessToken(null);
+ tokenClient.setAccessToken(null);
editor.remove(OAuthConstants.TOKEN);
editor.remove(OAuthConstants.TOKEN_SECRET);
editor.remove(OAuthConstants.REFRESH_TOKEN);
@@ -173,9 +197,9 @@ public void clearAccessToken() {
editor.commit();
}
- // Returns true if the client is authenticated; false otherwise.
+ // Returns true if the tokenClient is authenticated; false otherwise.
public boolean isAuthenticated() {
- return client.getAccessToken() != null;
+ return tokenClient.getAccessToken() != null;
}
// Sets the flags used when launching browser to authenticate through OAuth
@@ -189,12 +213,4 @@ public static interface OAuthAccessHandler {
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/OAuthLoginActionBarActivity.java b/library/src/main/java/com/codepath/oauth/OAuthLoginActionBarActivity.java
similarity index 97%
rename from app/src/main/java/com/codepath/oauth/OAuthLoginActionBarActivity.java
rename to library/src/main/java/com/codepath/oauth/OAuthLoginActionBarActivity.java
index beea737..def658f 100755
--- a/app/src/main/java/com/codepath/oauth/OAuthLoginActionBarActivity.java
+++ b/library/src/main/java/com/codepath/oauth/OAuthLoginActionBarActivity.java
@@ -1,10 +1,12 @@
package com.codepath.oauth;
-import com.codepath.utils.GenericsUtil;
-
import android.content.Intent;
import android.net.Uri;
-import android.support.v7.app.AppCompatActivity;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.codepath.utils.GenericsUtil;
+
// This is the ActionBarActivity supportv7 version of LoginActivity
public abstract class OAuthLoginActionBarActivity extends
diff --git a/app/src/main/java/com/codepath/oauth/OAuthLoginActivity.java b/library/src/main/java/com/codepath/oauth/OAuthLoginActivity.java
similarity index 97%
rename from app/src/main/java/com/codepath/oauth/OAuthLoginActivity.java
rename to library/src/main/java/com/codepath/oauth/OAuthLoginActivity.java
index 53d4a8a..64fa354 100755
--- a/app/src/main/java/com/codepath/oauth/OAuthLoginActivity.java
+++ b/library/src/main/java/com/codepath/oauth/OAuthLoginActivity.java
@@ -2,7 +2,8 @@
import android.content.Intent;
import android.net.Uri;
-import android.support.v4.app.FragmentActivity;
+
+import androidx.fragment.app.FragmentActivity;
import com.codepath.utils.GenericsUtil;
diff --git a/app/src/main/java/com/codepath/oauth/OAuthLoginFragment.java b/library/src/main/java/com/codepath/oauth/OAuthLoginFragment.java
similarity index 96%
rename from app/src/main/java/com/codepath/oauth/OAuthLoginFragment.java
rename to library/src/main/java/com/codepath/oauth/OAuthLoginFragment.java
index 10645d2..9fe5c72 100755
--- a/app/src/main/java/com/codepath/oauth/OAuthLoginFragment.java
+++ b/library/src/main/java/com/codepath/oauth/OAuthLoginFragment.java
@@ -2,7 +2,8 @@
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
+
+import androidx.fragment.app.Fragment;
import com.codepath.utils.GenericsUtil;
diff --git a/library/src/main/java/com/codepath/oauth/OAuthTokenClient.java b/library/src/main/java/com/codepath/oauth/OAuthTokenClient.java
new file mode 100755
index 0000000..d76079d
--- /dev/null
+++ b/library/src/main/java/com/codepath/oauth/OAuthTokenClient.java
@@ -0,0 +1,158 @@
+package com.codepath.oauth;
+
+import android.net.Uri;
+
+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.OAuth1AccessToken;
+import com.github.scribejava.core.model.OAuth1RequestToken;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.model.OAuthAsyncRequestCallback;
+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.github.scribejava.httpclient.okhttp.OkHttpHttpClientConfig;
+
+/*
+ * OAuthTokenClient 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 OAuthTokenClient {
+
+ private BaseApi apiInstance;
+ private OAuthTokenHandler handler;
+ private Token accessToken;
+ private OAuthService service;
+
+ // Requires the apiClass, consumerKey, consumerSecret and callbackUrl along with the TokenHandler
+ public OAuthTokenClient(BaseApi apiInstance, String consumerKey, String consumerSecret, String callbackUrl,
+ String scope, OAuthTokenHandler handler) {
+ this.apiInstance = apiInstance;
+ this.handler = handler;
+ if (callbackUrl == null) { callbackUrl = OAuthConstants.OUT_OF_BAND; };
+ if(scope == null) {
+ this.service = new ServiceBuilder()
+ .apiKey(consumerKey)
+ .apiSecret(consumerSecret).callback(callbackUrl)
+ .httpClientConfig(OkHttpHttpClientConfig.defaultConfig())
+ .build(apiInstance);
+ } else {
+ this.service = new ServiceBuilder()
+ .apiKey(consumerKey)
+ .apiSecret(consumerSecret).callback(callbackUrl)
+ .httpClientConfig(OkHttpHttpClientConfig.defaultConfig())
+ .scope(scope) // OAuth2 requires scope
+ .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() {
+ if (service.getVersion() == "1.0") {
+ final OAuth10aService oAuth10aService = (OAuth10aService) service;
+ oAuth10aService.getRequestTokenAsync(new OAuthAsyncRequestCallback() {
+ @Override
+ public void onCompleted(OAuth1RequestToken requestToken) {
+ String authorizeUrl = oAuth10aService.getAuthorizationUrl((OAuth1RequestToken) requestToken);
+ handler.onReceivedRequestToken(requestToken, authorizeUrl, service.getVersion());
+
+ }
+
+ @Override
+ public void onThrowable(Throwable t) {
+ handler.onFailure(new Exception(t.getMessage()));
+ }
+ });
+ }
+ if (service.getVersion() == "2.0") {
+ OAuth20Service oAuth20Service = (OAuth20Service) service;
+ String authorizeUrl = oAuth20Service.getAuthorizationUrl(null);
+ handler.onReceivedRequestToken(null, 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) {
+
+ Uri authorizedUri = uri;
+
+ 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;
+
+ oAuth10aService.getAccessTokenAsync(oAuth1RequestToken, oauth_verifier,
+ new OAuthAsyncRequestCallback() {
+
+ @Override
+ public void onCompleted(OAuth1AccessToken oAuth1AccessToken) {
+ setAccessToken(oAuth1AccessToken);
+ handler.onReceivedAccessToken(oAuth1AccessToken, service.getVersion());
+ }
+
+ @Override
+ public void onThrowable(Throwable e) {
+ handler.onFailure(new OAuthException(e.getMessage()));
+ }
+ });
+
+ }
+ 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;
+ oAuth20Service.getAccessToken(code, new OAuthAsyncRequestCallback() {
+ @Override
+ public void onCompleted(OAuth2AccessToken accessToken) {
+ setAccessToken(accessToken);
+ handler.onReceivedAccessToken(accessToken, service.getVersion());
+
+ }
+
+ @Override
+ public void onThrowable(Throwable t) {
+
+ }
+ });
+ }
+ else { // verifier was null
+ handler.onFailure(new OAuthException("No code was returned with uri '" + uri + "' " +
+ "and access token cannot be retrieved"));
+ }
+ }
+ }
+
+ // 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;
+ }
+
+ // 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/library/src/main/java/com/codepath/utils/GenericsUtil.java b/library/src/main/java/com/codepath/utils/GenericsUtil.java
new file mode 100644
index 0000000..66298f9
--- /dev/null
+++ b/library/src/main/java/com/codepath/utils/GenericsUtil.java
@@ -0,0 +1,80 @@
+package com.codepath.utils;
+
+import java.lang.reflect.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+@SuppressWarnings("rawtypes")
+public class GenericsUtil {
+ public static List> getTypeArguments(Class baseClass,
+ Class extends T> childClass) {
+ Map resolvedTypes = new HashMap();
+ Type type = childClass;
+ // start walking up the inheritance hierarchy until we hit baseClass
+ while (!getClass(type).equals(baseClass)) {
+ if (type instanceof Class) {
+ type = ((Class) type).getGenericSuperclass();
+ } else {
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ assert parameterizedType != null;
+ Class> rawType = (Class) parameterizedType.getRawType();
+
+ Type[] actualTypeArguments = parameterizedType
+ .getActualTypeArguments();
+ TypeVariable>[] typeParameters = rawType.getTypeParameters();
+ for (int i = 0; i < actualTypeArguments.length; i++) {
+ resolvedTypes
+ .put(typeParameters[i], actualTypeArguments[i]);
+ }
+
+ if (!rawType.equals(baseClass)) {
+ type = rawType.getGenericSuperclass();
+ }
+ }
+ }
+
+ // finally, for each actual type argument provided to baseClass,
+ // determine (if possible)
+ // the raw class for that type argument.
+ Type[] actualTypeArguments;
+ if (type instanceof Class) {
+ actualTypeArguments = ((Class) type).getTypeParameters();
+ } else {
+ assert !(type == null);
+ actualTypeArguments = ((ParameterizedType) type)
+ .getActualTypeArguments();
+ }
+ List> typeArgumentsAsClasses = new ArrayList>();
+ // resolve types by chasing down type variables.
+ for (Type baseType : actualTypeArguments) {
+ while (resolvedTypes.containsKey(baseType)) {
+ baseType = resolvedTypes.get(baseType);
+ }
+ typeArgumentsAsClasses.add(getClass(baseType));
+ }
+ return typeArgumentsAsClasses;
+ }
+
+ private static Class> getClass(Type type) {
+ if (type instanceof Class) {
+ return (Class) type;
+ } else if (type instanceof ParameterizedType) {
+ return getClass(((ParameterizedType) type).getRawType());
+ } else if (type instanceof GenericArrayType) {
+ Type componentType = ((GenericArrayType) type)
+ .getGenericComponentType();
+ Class> componentClass = getClass(componentType);
+ if (componentClass != null) {
+ return Array.newInstance(componentClass, 0).getClass();
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+}
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'