diff --git a/README.md b/README.md index f96b443..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.3.1' + 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 - klass return instance; } - public OAuthBaseClient(Context c, final BaseApi apiInstance, String consumerUrl, final String consumerKey, final 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; tokenClient = new OAuthTokenClient(apiInstance, consumerKey, - consumerSecret, callbackUrl, new OAuthTokenClient.OAuthTokenHandler() { + consumerSecret, callbackUrl, scope, new OAuthTokenClient.OAuthTokenHandler() { // Store request token and launch the authorization URL in the browser @Override @@ -60,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(); } } @@ -86,8 +89,7 @@ public void onReceivedAccessToken(Token accessToken, String oAuthVersion) { editor.commit(); } else if (oAuthVersion == OAUTH2_VERSION) { OAuth2AccessToken oAuth2AccessToken = (OAuth2AccessToken) accessToken; - - //TODO(rhu) - create client for OAuth2 cases + instantiateClient(consumerKey, consumerSecret, oAuth2AccessToken); tokenClient.setAccessToken(accessToken); editor.putString(OAuthConstants.TOKEN, oAuth2AccessToken.getAccessToken()); editor.putString(OAuthConstants.SCOPE, oAuth2AccessToken.getScope()); @@ -122,8 +124,10 @@ public void instantiateClient(String consumerKey, String consumerSecret, Token t 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); } } @@ -162,10 +166,15 @@ 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 diff --git a/library/src/main/java/com/codepath/oauth/OAuthTokenClient.java b/library/src/main/java/com/codepath/oauth/OAuthTokenClient.java index b92c578..d76079d 100755 --- a/library/src/main/java/com/codepath/oauth/OAuthTokenClient.java +++ b/library/src/main/java/com/codepath/oauth/OAuthTokenClient.java @@ -30,15 +30,24 @@ public class OAuthTokenClient { // Requires the apiClass, consumerKey, consumerSecret and callbackUrl along with the TokenHandler public OAuthTokenClient(BaseApi apiInstance, String consumerKey, String consumerSecret, String callbackUrl, - OAuthTokenHandler handler) { + String scope, OAuthTokenHandler handler) { this.apiInstance = apiInstance; this.handler = handler; if (callbackUrl == null) { callbackUrl = OAuthConstants.OUT_OF_BAND; }; - this.service = new ServiceBuilder() + 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 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 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; + } + } + +}