├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle ├── lint.xml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── codepath │ │ ├── oauth │ │ ├── OAuthAsyncHttpClient.java │ │ ├── OAuthBaseClient.java │ │ ├── OAuthLoginActionBarActivity.java │ │ ├── OAuthLoginActivity.java │ │ ├── OAuthLoginFragment.java │ │ └── OAuthTokenClient.java │ │ └── utils │ │ └── GenericsUtil.java │ └── res │ ├── .gitkeep │ └── layout │ └── .gitkeep └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Source: https://gist.github.com/nesquena/5617544/raw/53710b374e7df3302df43b552488d876040ada3d/.gitignore 2 | 3 | # built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # files for the dex VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # generated files 14 | bin/ 15 | gen/ 16 | 17 | # Local configuration file (sdk path, etc) 18 | local.properties 19 | 20 | # Eclipse project files 21 | .classpath 22 | .project 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Intellij project files 28 | *.iml 29 | *.ipr 30 | *.iws 31 | .idea/ 32 | 33 | *.pydevproject 34 | .project 35 | .metadata 36 | bin/** 37 | tmp/** 38 | tmp/**/* 39 | *.tmp 40 | *.bak 41 | *.swp 42 | *~.nib 43 | local.properties 44 | .classpath 45 | .settings/ 46 | .loadpath 47 | 48 | # External tool builders 49 | .externalToolBuilders/ 50 | 51 | # Locally stored "Eclipse launch configurations" 52 | *.launch 53 | 54 | # CDT-specific 55 | .cproject 56 | 57 | # PDT-specific 58 | .buildpath 59 | 60 | # Android Studio project files 61 | *.iml 62 | .gradle 63 | .idea 64 | build 65 | import-summary.txt 66 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: 3 | - oraclejdk8 4 | sudo: false 5 | android: 6 | components: 7 | - tools 8 | - platform-tools 9 | - build-tools-28.0.3 10 | - android-28 11 | before_install: 12 | - yes | sdkmanager "platforms;android-28" 13 | script: 14 | - "./gradlew build check --daemon" 15 | after_failure: "cat $TRAVIS_BUILD_DIR/app/build/outputs/lint-results.xml" 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 CodePath 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodePath OAuth Handler [![Build Status](https://travis-ci.org/codepath/android-oauth-handler.svg?branch=master)](https://travis-ci.org/codepath/android-oauth-handler) 2 | 3 | This library is an Android library for managing OAuth requests with an extremely easy 4 | approach that keeps the details of the OAuth process abstracted from the end-user developer. 5 | 6 | This library leverages a few key libraries underneath to power the functionality: 7 | 8 | * [scribe-java](https://github.com/scribejava/scribejava) - Simple OAuth library for handling the authentication flow. 9 | * [Android Async HTTP](https://github.com/codepath/asynchttpclient) - Simple asynchronous HTTP requests with JSON parsing. 10 | 11 | ## Installation 12 | 13 | Inside your project's root `build.gradle`, make sure the jCenter repository is added: 14 | 15 | ```gradle 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | } 20 | } 21 | ``` 22 | 23 | Next, add this line to your `app/build.gradle` file: 24 | 25 | ```gradle 26 | dependencies { 27 | compile 'com.codepath.libraries:android-oauth-handler:2.1.3' 28 | } 29 | ``` 30 | 31 | If you want an easier way to get setup with this library, try downloading the 32 | [android-rest-client-template](https://github.com/thecodepath/android-rest-client-template/archive/master.zip) 33 | instead and using that as the template for your project. 34 | 35 | ## Getting Started 36 | 37 | This library is very simple to use and simply requires you to create an Activity that is used for authenticating with OAuth and ultimately give your application access to an authenticated API. 38 | 39 | ### Creating a REST Client 40 | 41 | The first step is to create a REST Client that will be used to access the authenticated APIs 42 | within your application. A REST Client is defined in the structure below: 43 | 44 | ```java 45 | public class TwitterClient extends OAuthBaseClient { 46 | public static final BaseApi REST_API_INSTANCE = TwitterApi.instance(); 47 | public static final String REST_URL = "https://api.twitter.com/1.1"; 48 | public static final String REST_CONSUMER_KEY = "SOME_KEY_HERE"; 49 | public static final String REST_CONSUMER_SECRET = "SOME_SECRET_HERE"; 50 | public static final String REST_CALLBACK_URL = "oauth://arbitraryname.com"; 51 | 52 | public TwitterClient(Context context) { 53 | super(context, REST_API_INSTANCE, REST_URL, 54 | REST_CONSUMER_KEY, REST_CONSUMER_SECRET, null, REST_CALLBACK_URL); 55 | } 56 | 57 | // ENDPOINTS BELOW 58 | 59 | public void getHomeTimeline(int page, JsonHttpResponseHandler handler) { 60 | String apiUrl = getApiUrl("statuses/home_timeline.json"); 61 | RequestParams params = new RequestParams(); 62 | params.put("page", String.valueOf(page)); 63 | client.get(apiUrl, params, handler); 64 | } 65 | } 66 | ``` 67 | 68 | Configure the `REST_API_INSTANCE`, `REST_URL`, `REST_CONSUMER_KEY`, `REST_CONSUMER_SECRET` based on the values needed to connect to your particular API. The `REST_URL` should be the base URL used for connecting to the API (i.e `https://api.twitter.com`). The `REST_API_INSTANCE` should be the instance defining the service you wish to connect to. Check out the [full list of services](https://github.com/scribejava/scribejava/tree/master/scribejava-apis/src/main/java/com/github/scribejava/apis) you can select (i.e `FlickrApi.instance()`). 69 | 70 | Make sure that the project's `AndroidManifest.xml` has the appropriate `intent-filter` tags that correspond 71 | with the `REST_CALLBACK_URL` defined in the client: 72 | 73 | ```xml 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 85 | 86 | 87 | ``` 88 | 89 | If the manifest does not have a matching `intent-filter` then the OAuth flow will not work. 90 | 91 | #### Customizations for Twitter 92 | 93 | The above instructions cover most OAuth integrations. When using this template to build a Twitter integration, you will need to make a few changes. 94 | 95 | In `AndroidManifest.xml`, use the OAuth scheme name `x-oauthflow-twitter`: 96 | 97 | ```xml 98 | 99 | 100 | 104 | 105 | 106 | ``` 107 | 108 | In `TwitterClient.java`, the value for `REST_CALLBACK_URL` must also use the `x-oauthflow-twitter` protocol. 109 | 110 | ```java 111 | public static final String REST_CALLBACK_URL = "x-oauthflow-twitter://arbitraryname.com"; 112 | ``` 113 | 114 | Note that the `arbitraryname.com` value can be any string. If you leave the setting unchecked for callback locking in your Twitter developer settings, then you can use any placeholder value. The callback host value in `AndroidManifest.xml` must correspond to the part after the `://` in `REST_CALLBACK_URL`, but it need not match the setting in your Twitter developer settings page. 115 | 116 | ### Creating a LoginActivity 117 | 118 | The next step to add support for authenticating with a service is to create a `LoginActivity` which is responsible for the task: 119 | 120 | ```java 121 | public class LoginActivity extends OAuthLoginActivity { 122 | // This fires once the user is authenticated, or fires immediately 123 | // if the user is already authenticated. 124 | 125 | @Override 126 | protected void onCreate(Bundle savedInstanceState) { 127 | super.onCreate(savedInstanceState); 128 | setContentView(R.layout.activity_login); 129 | } 130 | 131 | @Override 132 | public void onLoginSuccess() { 133 | Intent i = new Intent(this, PhotosActivity.class); 134 | startActivity(i); 135 | } 136 | 137 | // Fires if the authentication process fails for any reason. 138 | @Override 139 | public void onLoginFailure(Exception e) { 140 | e.printStackTrace(); 141 | } 142 | 143 | // Method to be called to begin the authentication process 144 | // assuming user is not authenticated. 145 | // Typically used as an event listener for a button for the user to press. 146 | public void loginToRest(View view) { 147 | getClient().connect(); 148 | } 149 | } 150 | ``` 151 | 152 | A few notes for your `LoginActivity`: 153 | 154 | * Your activity must extend from `OAuthLoginActivity` 155 | * Your activity must implement `onLoginSuccess` and `onLoginFailure` 156 | * The `onLoginSuccess` should launch an "authenticated" activity. 157 | * The activity should have a button or other view a user can press to trigger authentication 158 | * Authentication is initiated by invoking `getClient().connect()` within the LoginActivity. 159 | 160 | In more advanced cases where you want to authenticate **multiple services from a single activity**, check out the related 161 | [guide for using OAuthLoginFragment](https://github.com/thecodepath/android-oauth-handler/wiki/Advanced-Use-with-OAuthLoginFragment). 162 | 163 | ### Using the REST Client 164 | 165 | These endpoint methods will automatically execute asynchronous requests signed with the authenticated access token anywhere your application. To use JSON endpoints, simply invoke the method 166 | with a `JsonHttpResponseHandler` handler: 167 | 168 | ```java 169 | // SomeActivity.java 170 | RestClient client = RestClientApp.getRestClient(); 171 | client.getHomeTimeline(1, new JsonHttpResponseHandler() { 172 | public void onSuccess(int statusCode, Headers headers, JSON json) { 173 | // Response is automatically parsed into a JSONArray 174 | // json.jsonArray.getJSONObject(0).getLong("id"); 175 | } 176 | }); 177 | ``` 178 | 179 | Based on the JSON response (array or object), you need to declare the expected type inside the `onSuccess` signature i.e `public void onSuccess(int statusCode, Header[] headers, JSONObject json)`. If the endpoint does not return JSON, then you can use the `AsyncHttpResponseHandler`: 180 | 181 | ```java 182 | RestClient client = RestClientApp.getRestClient(); 183 | client.get("http://www.google.com", new JsonHttpResponseHandler() { 184 | @Override 185 | public void onSuccess(int statusCode, Headers headers, String response) { 186 | System.out.println(response); 187 | } 188 | }); 189 | ``` 190 | 191 | Check out [Android Async HTTP Docs](https://github.com/codepath/asynchttpclient) for more request creation details. 192 | 193 | ## Extra Functionality 194 | 195 | 196 | ### Access Authorization 197 | 198 | Once the request token has been received, an access token is granted by redirecting to the device's browser to allow the user to grant permission on the API provider's web address. The browser is opened using an implicit intent with no intent flags specified: 199 | 200 | ```java 201 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(authorizeUrl)); 202 | ``` 203 | 204 | However, specifying [intent flags](http://guides.codepath.com/android/Navigation-and-Task-Stacks#configuring-intent-flags) to alter that behavior can be added to the message using the following: 205 | 206 | ```java 207 | RestClient client = RestApplication.getRestClient(); 208 | // Specify the intent flags as desired 209 | client.setRequestIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 210 | // Trigger authorization 211 | client.connect(); 212 | ``` 213 | 214 | This can be helpful in cases where you must add a flag such as when encountering the `android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?` error. 215 | 216 | ### Logging Out 217 | 218 | You can log out by clearing the access token at any time through the client object: 219 | 220 | ```java 221 | RestClient client = RestApplication.getRestClient(); 222 | client.clearAccessToken(); 223 | ``` 224 | 225 | ### Debugging 226 | 227 | In order to [troubleshoot API calls](http://guides.codepath.com/android/Troubleshooting-API-calls), you can take advantage of the Stetho library: 228 | 229 | Next, initialize Stetho inside your Application object: 230 | ```java 231 | public class MyApplication extends Application { 232 | public void onCreate() { 233 | super.onCreate(); 234 | Stetho.initializeWithDefaults(this); 235 | } 236 | } 237 | ``` 238 | 239 | 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. 240 | ```xml 241 | 246 | ``` 247 | 248 | You can then use `chrome://inspect`, pick the app currently running, and click on the Network tab to view. See [this guide](https://github.com/codepath/android_guides/wiki/Debugging-with-Stetho) for more context. 249 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:4.0.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | /* 19 | allprojects { thisproject -> 20 | bintray { 21 | if (thisproject == rootProject) { 22 | configurations = [] 23 | } else { 24 | configurations = ['archives'] 25 | } 26 | } 27 | }*/ -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | -------------------------------------------------------------------------------- /gradle/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codepath/android-oauth-handler/c6a1c4cc12a9c7597d28b702193a6698dd5a0d72/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Aug 11 22:16:42 PDT 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'maven-publish' 3 | apply plugin: 'signing' 4 | 5 | ext { 6 | 7 | GROUP = 'com.codepath.libraries' 8 | BASE_VERSION = "2.3" 9 | VERSION_NAME = "2.3.0" 10 | POM_PACKAGING = "aar" 11 | POM_DESCRIPTION = "CodePath OAuth Handler" 12 | 13 | POM_ARTIFACT_ID = "android-oauth-handler" 14 | POM_NAME = "CodePath OAuth Handler" 15 | POM_URL = "https://github.com/codepath/android-oauth-handler/" 16 | POM_SCM_URL = "https://github.com/codepath/android-oauth-handler/" 17 | POM_SCM_CONNECTION = "scm:git:https://github.com/codepath/android-oauth-handler.git" 18 | POM_SCM_DEV_CONNECTION = "scm:git:git@github.com:codepath/android-oauth-handler.git" 19 | 20 | POM_LICENCE_NAME = "The Apache Software License, Version 2.0" 21 | POM_LICENCE_URL = "http://www.apache.org/licenses/LICENSE-2.0.txt" 22 | POM_LICENCE_DIST = "repo" 23 | 24 | POM_DEVELOPER_ID = "codepath" 25 | POM_DEVELOPER_NAME = "CodePath, Inc." 26 | } 27 | 28 | version = VERSION_NAME 29 | group = GROUP 30 | archivesBaseName = POM_ARTIFACT_ID 31 | 32 | android { 33 | compileSdkVersion 30 34 | 35 | defaultConfig { 36 | versionCode 1 37 | versionName VERSION_NAME 38 | minSdkVersion 21 39 | targetSdkVersion 30 40 | } 41 | 42 | // Related to https://github.com/scribejava/scribejava/issues/480 43 | // Scribe expects Java 7 or this custom Apache library 44 | lintOptions { 45 | lintConfig rootProject.file('gradle/lint.xml') 46 | } 47 | buildTypes { 48 | release { 49 | minifyEnabled false 50 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 51 | } 52 | } 53 | } 54 | 55 | afterEvaluate { 56 | publishing { 57 | publications { 58 | // Creates a Maven publication called "release". 59 | release(MavenPublication) { 60 | // Applies the component for the release build variant. 61 | from components.release 62 | 63 | // You can then customize attributes of the publication as shown below. 64 | groupId = GROUP 65 | artifactId = POM_ARTIFACT_ID 66 | version = VERSION_NAME 67 | 68 | pom { 69 | name = POM_NAME 70 | url = POM_URL 71 | description = POM_DESCRIPTION 72 | licenses { 73 | license { 74 | name = POM_LICENCE_NAME 75 | url = POM_LICENCE_URL 76 | } 77 | } 78 | developers { 79 | developer { 80 | id = POM_DEVELOPER_ID 81 | name = POM_DEVELOPER_NAME 82 | } 83 | } 84 | scm { 85 | url = POM_SCM_URL 86 | } 87 | } 88 | } 89 | } 90 | repositories { 91 | maven { 92 | name = "Sonatype" 93 | credentials { 94 | username = System.getenv('NEXUS_USERNAME') 95 | password = System.getenv('NEXUS_PASSWORD') 96 | } 97 | def releasesRepoUrl = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/' 98 | def snapshotsRepoUrl = 'https://s01.oss.sonatype.org/content/repositories/snapshots/' 99 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 100 | setUrl(url) 101 | } 102 | } 103 | } 104 | } 105 | 106 | signing { 107 | // gpg on MacOS is the same as gpg2 108 | // ln -s /usr/local/bin/gpg /usr/local/bin/gpg2 109 | // Make sure to populate the variables in gradle.properties 110 | // signing.gnupg.keyName=XXX 111 | // signing.gnupg.passpharse 112 | useGpgCmd() 113 | sign(publishing.publications) 114 | } 115 | 116 | task sourcesJar(type: Jar) { 117 | from android.sourceSets.main.java.srcDirs 118 | archiveClassifier.set("sources") 119 | } 120 | 121 | artifacts { 122 | archives sourcesJar 123 | } 124 | 125 | ext { 126 | supportVersion = '28.0.0' 127 | } 128 | 129 | dependencies { 130 | api "androidx.appcompat:appcompat:1.3.0" 131 | api 'com.codepath.libraries:asynchttpclient:2.1.1' 132 | api 'com.github.scribejava:scribejava-apis:4.1.1' 133 | api 'com.github.scribejava:scribejava-httpclient-okhttp:4.1.1' 134 | implementation 'se.akerfeldt:okhttp-signpost:1.1.0' 135 | implementation 'oauth.signpost:signpost-core:1.2.1.2' 136 | implementation 'com.facebook.stetho:stetho:1.5.1' 137 | implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1' 138 | implementation "com.squareup.okhttp3:logging-interceptor:4.7.2" 139 | 140 | } 141 | 142 | task jar(type: Jar) { 143 | from android.sourceSets.main.java.srcDirs 144 | } 145 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /library/src/main/java/com/codepath/oauth/OAuthAsyncHttpClient.java: -------------------------------------------------------------------------------- 1 | package com.codepath.oauth; 2 | 3 | import com.codepath.asynchttpclient.AsyncHttpClient; 4 | import com.facebook.stetho.okhttp3.StethoInterceptor; 5 | import com.github.scribejava.core.model.OAuth1AccessToken; 6 | import com.github.scribejava.core.model.OAuth2AccessToken; 7 | 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.io.IOException; 11 | 12 | import okhttp3.Interceptor; 13 | import okhttp3.OkHttpClient; 14 | import okhttp3.Request; 15 | import okhttp3.Response; 16 | import okhttp3.logging.HttpLoggingInterceptor; 17 | import se.akerfeldt.okhttp.signpost.OkHttpOAuthConsumer; 18 | import se.akerfeldt.okhttp.signpost.SigningInterceptor; 19 | 20 | public class OAuthAsyncHttpClient extends AsyncHttpClient { 21 | 22 | protected OAuthAsyncHttpClient(OkHttpClient httpClient) { 23 | super(httpClient); 24 | } 25 | 26 | private static String BEARER = "Bearer"; 27 | 28 | public static HttpLoggingInterceptor createLogger() { 29 | HttpLoggingInterceptor logger = new HttpLoggingInterceptor(); 30 | logger.level(HttpLoggingInterceptor.Level.HEADERS); 31 | return logger; 32 | } 33 | 34 | public static OAuthAsyncHttpClient create(String consumerKey, String consumerSecret, OAuth1AccessToken token) { 35 | OkHttpOAuthConsumer consumer = new OkHttpOAuthConsumer(consumerKey, consumerSecret); 36 | HttpLoggingInterceptor logging = createLogger(); 37 | 38 | consumer.setTokenWithSecret(token.getToken(), token.getTokenSecret()); 39 | OkHttpClient httpClient = new OkHttpClient.Builder() 40 | .addInterceptor(logging) 41 | .addNetworkInterceptor(new StethoInterceptor()) 42 | .addInterceptor(new SigningInterceptor(consumer)).build(); 43 | 44 | OAuthAsyncHttpClient asyncHttpClient = new OAuthAsyncHttpClient(httpClient); 45 | return asyncHttpClient; 46 | } 47 | 48 | public static OAuthAsyncHttpClient create(final OAuth2AccessToken token) { 49 | final String bearer = String.format("%s %s", BEARER, token.getAccessToken()); 50 | 51 | HttpLoggingInterceptor logging = createLogger(); 52 | 53 | OkHttpClient httpClient = new OkHttpClient.Builder() 54 | .addInterceptor(logging) 55 | .addNetworkInterceptor(new StethoInterceptor()) 56 | .addInterceptor(new Interceptor() { 57 | @NotNull 58 | @Override 59 | public Response intercept(@NotNull Chain chain) throws IOException { 60 | Request originalRequest = chain.request(); 61 | Request authedRequest = originalRequest.newBuilder().header("Authorization", bearer).build(); 62 | return chain.proceed(authedRequest); 63 | } 64 | }).build(); 65 | 66 | OAuthAsyncHttpClient asyncHttpClient = new OAuthAsyncHttpClient(httpClient); 67 | return asyncHttpClient; 68 | } 69 | } -------------------------------------------------------------------------------- /library/src/main/java/com/codepath/oauth/OAuthBaseClient.java: -------------------------------------------------------------------------------- 1 | package com.codepath.oauth; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.net.Uri; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | import com.github.scribejava.core.builder.api.BaseApi; 11 | import com.github.scribejava.core.model.OAuth1AccessToken; 12 | import com.github.scribejava.core.model.OAuth1RequestToken; 13 | import com.github.scribejava.core.model.OAuth2AccessToken; 14 | import com.github.scribejava.core.model.OAuthConstants; 15 | import com.github.scribejava.core.model.Token; 16 | 17 | import java.util.HashMap; 18 | 19 | public abstract class OAuthBaseClient { 20 | protected String baseUrl; 21 | protected Context context; 22 | protected OAuthTokenClient tokenClient; 23 | protected OAuthAsyncHttpClient client; 24 | protected SharedPreferences prefs; 25 | protected SharedPreferences.Editor editor; 26 | protected OAuthAccessHandler accessHandler; 27 | protected String callbackUrl; 28 | protected int requestIntentFlags = -1; 29 | 30 | private static final String OAUTH1_REQUEST_TOKEN = "request_token"; 31 | private static final String OAUTH1_REQUEST_TOKEN_SECRET = "request_token_secret"; 32 | private static final String OAUTH1_VERSION = "1.0"; 33 | private static final String OAUTH2_VERSION = "2.0"; 34 | 35 | protected static HashMap, OAuthBaseClient> instances = 36 | new HashMap, OAuthBaseClient>(); 37 | 38 | public static OAuthBaseClient getInstance(Class klass, Context context) { 39 | OAuthBaseClient instance = instances.get(klass); 40 | if (instance == null) { 41 | try { 42 | instance = (OAuthBaseClient) klass.getConstructor(Context.class).newInstance(context); 43 | instances.put(klass, instance); 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | return instance; 49 | } 50 | 51 | public OAuthBaseClient(Context c, final BaseApi apiInstance, String consumerUrl, final String consumerKey, final String consumerSecret, @Nullable String scope, String callbackUrl) { 52 | this.baseUrl = consumerUrl; 53 | this.callbackUrl = callbackUrl; 54 | tokenClient = new OAuthTokenClient(apiInstance, consumerKey, 55 | consumerSecret, callbackUrl, scope, new OAuthTokenClient.OAuthTokenHandler() { 56 | 57 | // Store request token and launch the authorization URL in the browser 58 | @Override 59 | public void onReceivedRequestToken(Token requestToken, String authorizeUrl, String oAuthVersion) { 60 | if (requestToken != null) { 61 | if (oAuthVersion == OAUTH1_VERSION) { // store for OAuth1.0a 62 | OAuth1RequestToken oAuth1RequestToken = (OAuth1RequestToken) requestToken; 63 | editor.putString(OAUTH1_REQUEST_TOKEN, oAuth1RequestToken.getToken()); 64 | editor.putString(OAUTH1_REQUEST_TOKEN_SECRET, oAuth1RequestToken.getTokenSecret()); 65 | editor.putInt(OAuthConstants.VERSION, 1); 66 | editor.commit(); 67 | } 68 | } 69 | // Launch the authorization URL in the browser 70 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(authorizeUrl)); 71 | if (requestIntentFlags != -1) { 72 | intent.setFlags(requestIntentFlags); 73 | } 74 | OAuthBaseClient.this.context.startActivity(intent); 75 | } 76 | 77 | // Store the access token in preferences, set the token in the tokenClient and fire the success callback 78 | @Override 79 | public void onReceivedAccessToken(Token accessToken, String oAuthVersion) { 80 | 81 | if (oAuthVersion == OAUTH1_VERSION) { 82 | OAuth1AccessToken oAuth1AccessToken = (OAuth1AccessToken) accessToken; 83 | 84 | tokenClient.setAccessToken(accessToken); 85 | instantiateClient(consumerKey, consumerSecret, oAuth1AccessToken); 86 | editor.putString(OAuthConstants.TOKEN, oAuth1AccessToken.getToken()); 87 | editor.putString(OAuthConstants.TOKEN_SECRET, oAuth1AccessToken.getTokenSecret()); 88 | editor.putInt(OAuthConstants.VERSION, 1); 89 | editor.commit(); 90 | } else if (oAuthVersion == OAUTH2_VERSION) { 91 | OAuth2AccessToken oAuth2AccessToken = (OAuth2AccessToken) accessToken; 92 | instantiateClient(consumerKey, consumerSecret, oAuth2AccessToken); 93 | tokenClient.setAccessToken(accessToken); 94 | editor.putString(OAuthConstants.TOKEN, oAuth2AccessToken.getAccessToken()); 95 | editor.putString(OAuthConstants.SCOPE, oAuth2AccessToken.getScope()); 96 | editor.putString(OAuthConstants.REFRESH_TOKEN, oAuth2AccessToken.getRefreshToken()); 97 | editor.putInt(OAuthConstants.VERSION, 2); 98 | editor.commit(); 99 | 100 | } 101 | accessHandler.onLoginSuccess(); 102 | } 103 | 104 | @Override 105 | public void onFailure(Exception e) { 106 | accessHandler.onLoginFailure(e); 107 | } 108 | 109 | }); 110 | 111 | this.context = c; 112 | // Store preferences namespaced by the class and consumer key used 113 | this.prefs = this.context.getSharedPreferences("OAuth_" + apiInstance.getClass().getSimpleName() + "_" + consumerKey, 0); 114 | this.editor = this.prefs.edit(); 115 | // Set access token in the tokenClient if already stored in preferences 116 | Token accessToken = this.checkAccessToken(); 117 | if (accessToken != null) { 118 | tokenClient.setAccessToken(accessToken); 119 | instantiateClient(consumerKey, consumerSecret, accessToken); 120 | } 121 | } 122 | 123 | public void instantiateClient(String consumerKey, String consumerSecret, Token token) { 124 | 125 | if (token instanceof OAuth1AccessToken) { 126 | client = OAuthAsyncHttpClient.create(consumerKey, consumerSecret, (OAuth1AccessToken)(token)); 127 | } else if (token instanceof OAuth2AccessToken){ 128 | client = OAuthAsyncHttpClient.create((OAuth2AccessToken) token); 129 | } else { 130 | throw new IllegalStateException("unrecognized token type" + token); 131 | } 132 | 133 | } 134 | // Fetches a request token and retrieve and authorization url 135 | // Should open a browser in onReceivedRequestToken once the url has been received 136 | public void connect() { 137 | tokenClient.fetchRequestToken(); 138 | } 139 | 140 | // Retrieves access token given authorization url 141 | public void authorize(Uri uri, OAuthAccessHandler handler) { 142 | this.accessHandler = handler; 143 | if (checkAccessToken() == null && uri != null) { 144 | // TODO: check UriServiceCallback with intent:// scheme 145 | tokenClient.fetchAccessToken(getOAuth1RequestToken(), uri); 146 | 147 | } else if (checkAccessToken() != null) { // already have access token 148 | this.accessHandler.onLoginSuccess(); 149 | } 150 | } 151 | 152 | // Return access token if the token exists in preferences 153 | public Token checkAccessToken() { 154 | int oAuthVersion = prefs.getInt(OAuthConstants.VERSION, 0); 155 | 156 | if (oAuthVersion == 1 && prefs.contains(OAuthConstants.TOKEN) && prefs.contains(OAuthConstants.TOKEN_SECRET)) { 157 | return new OAuth1AccessToken(prefs.getString(OAuthConstants.TOKEN, ""), 158 | prefs.getString(OAuthConstants.TOKEN_SECRET, "")); 159 | } else if (oAuthVersion == 2 && prefs.contains(OAuthConstants.TOKEN)) { 160 | return new OAuth2AccessToken(prefs.getString(OAuthConstants.TOKEN, "")); 161 | } 162 | return null; 163 | } 164 | 165 | protected OAuthTokenClient getTokenClient() { 166 | return tokenClient; 167 | } 168 | 169 | // Returns the request token stored during the request token phase (OAuth1 only) 170 | protected @Nullable Token getOAuth1RequestToken() { 171 | int oAuthVersion = prefs.getInt(OAuthConstants.VERSION, 0); 172 | 173 | if (oAuthVersion == 1) { 174 | return new OAuth1RequestToken(prefs.getString(OAUTH1_REQUEST_TOKEN, ""), 175 | prefs.getString(OAUTH1_REQUEST_TOKEN_SECRET, "")); 176 | } 177 | return null; 178 | } 179 | 180 | // Assigns the base url for the API 181 | protected void setBaseUrl(String url) { 182 | this.baseUrl = url; 183 | } 184 | 185 | // Returns the full ApiUrl 186 | protected String getApiUrl(String path) { 187 | return this.baseUrl + "/" + path; 188 | } 189 | 190 | // Removes the access tokens (for signing out) 191 | public void clearAccessToken() { 192 | tokenClient.setAccessToken(null); 193 | editor.remove(OAuthConstants.TOKEN); 194 | editor.remove(OAuthConstants.TOKEN_SECRET); 195 | editor.remove(OAuthConstants.REFRESH_TOKEN); 196 | editor.remove(OAuthConstants.SCOPE); 197 | editor.commit(); 198 | } 199 | 200 | // Returns true if the tokenClient is authenticated; false otherwise. 201 | public boolean isAuthenticated() { 202 | return tokenClient.getAccessToken() != null; 203 | } 204 | 205 | // Sets the flags used when launching browser to authenticate through OAuth 206 | public void setRequestIntentFlags(int flags) { 207 | this.requestIntentFlags = flags; 208 | } 209 | 210 | // Defines the handler events for the OAuth flow 211 | public static interface OAuthAccessHandler { 212 | public void onLoginSuccess(); 213 | 214 | public void onLoginFailure(Exception e); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /library/src/main/java/com/codepath/oauth/OAuthLoginActionBarActivity.java: -------------------------------------------------------------------------------- 1 | package com.codepath.oauth; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | 6 | import androidx.appcompat.app.AppCompatActivity; 7 | 8 | import com.codepath.utils.GenericsUtil; 9 | 10 | 11 | // This is the ActionBarActivity supportv7 version of LoginActivity 12 | public abstract class OAuthLoginActionBarActivity extends 13 | AppCompatActivity 14 | implements OAuthBaseClient.OAuthAccessHandler { 15 | 16 | private T client; 17 | 18 | // Use this to properly assign the new intent with callback code 19 | // for activities with a "singleTask" launch mode 20 | @Override 21 | protected void onNewIntent(Intent intent) { 22 | super.onNewIntent(intent); 23 | setIntent(intent); 24 | } 25 | 26 | // Extract the uri data and call authorize to retrieve access token 27 | // This is why after the browser redirects to the app, authentication is completed 28 | @SuppressWarnings("unchecked") 29 | @Override 30 | protected void onResume() { 31 | super.onResume(); 32 | Class clientClass = getClientClass(); 33 | // Extracts the authenticated url data after the user 34 | // authorizes the OAuth app in the browser 35 | Uri uri = getIntent().getData(); 36 | 37 | try { 38 | client = (T) OAuthBaseClient.getInstance(clientClass, this); 39 | client.authorize(uri, this); // fetch access token (if needed) 40 | } catch (Exception e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | 45 | public T getClient() { 46 | return client; 47 | } 48 | 49 | @SuppressWarnings("unchecked") 50 | private Class getClientClass() { 51 | return (Class) GenericsUtil.getTypeArguments(OAuthLoginActionBarActivity.class, this.getClass()).get(0); 52 | } 53 | } 54 | 55 | /* 56 | * 1) Subclass OAuthBaseClient like TwitterClient 57 | * 2) Subclass OAuthLoginActivity 58 | * 3) Invoke .login 59 | * 4) Optionally override 60 | * a) onLoginSuccess 61 | * b) onLoginFailure(Exception e) 62 | * 5) In other activities that need the client 63 | * a) c = TwitterClient.getSharedClient() 64 | * b) c.getTimeline(...) 65 | * 6) Modify AndroidManifest.xml to add an IntentFilter w/ the callback URL 66 | * defined in the OAuthBaseClient. 67 | */ 68 | -------------------------------------------------------------------------------- /library/src/main/java/com/codepath/oauth/OAuthLoginActivity.java: -------------------------------------------------------------------------------- 1 | package com.codepath.oauth; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | 6 | import androidx.fragment.app.FragmentActivity; 7 | 8 | import com.codepath.utils.GenericsUtil; 9 | 10 | //This is the FragmentActivity supportv4 version of LoginActivity 11 | public abstract class OAuthLoginActivity extends FragmentActivity 12 | implements OAuthBaseClient.OAuthAccessHandler { 13 | 14 | private T client; 15 | 16 | // Use this to properly assign the new intent with callback code 17 | // for activities with a "singleTask" launch mode 18 | @Override 19 | protected void onNewIntent(Intent intent) { 20 | super.onNewIntent(intent); 21 | setIntent(intent); 22 | } 23 | 24 | // Extract the uri data and call authorize to retrieve access token 25 | // This is why after the browser redirects to the app, authentication is completed 26 | @SuppressWarnings("unchecked") 27 | @Override 28 | protected void onResume() { 29 | super.onResume(); 30 | Class clientClass = getClientClass(); 31 | // Extracts the authenticated url data after the user 32 | // authorizes the OAuth app in the browser 33 | Uri uri = getIntent().getData(); 34 | 35 | try { 36 | client = (T) OAuthBaseClient.getInstance(clientClass, this); 37 | client.authorize(uri, this); // fetch access token (if needed) 38 | } catch (Exception e) { 39 | e.printStackTrace(); 40 | } 41 | } 42 | 43 | public T getClient() { 44 | return client; 45 | } 46 | 47 | @SuppressWarnings("unchecked") 48 | private Class getClientClass() { 49 | return (Class) GenericsUtil.getTypeArguments(OAuthLoginActivity.class, this.getClass()).get(0); 50 | } 51 | } 52 | 53 | /* 54 | * 1) Subclass OAuthBaseClient like TwitterClient 55 | * 2) Subclass OAuthLoginActivity 56 | * 3) Invoke .login 57 | * 4) Optionally override 58 | * a) onLoginSuccess 59 | * b) onLoginFailure(Exception e) 60 | * 5) In other activities that need the client 61 | * a) c = TwitterClient.getSharedClient() 62 | * b) c.getTimeline(...) 63 | * 6) Modify AndroidManifest.xml to add an IntentFilter w/ the callback URL 64 | * defined in the OAuthBaseClient. 65 | */ 66 | -------------------------------------------------------------------------------- /library/src/main/java/com/codepath/oauth/OAuthLoginFragment.java: -------------------------------------------------------------------------------- 1 | package com.codepath.oauth; 2 | 3 | import android.net.Uri; 4 | import android.os.Bundle; 5 | 6 | import androidx.fragment.app.Fragment; 7 | 8 | import com.codepath.utils.GenericsUtil; 9 | 10 | public abstract class OAuthLoginFragment extends Fragment implements 11 | OAuthBaseClient.OAuthAccessHandler { 12 | 13 | private T client; 14 | 15 | @SuppressWarnings("unchecked") 16 | @Override 17 | public void onActivityCreated(Bundle saved) { 18 | super.onActivityCreated(saved); 19 | 20 | // Fetch the uri that was passed in (which exists if this is being returned from authorization flow) 21 | Uri uri = getActivity().getIntent().getData(); 22 | // Fetch the client class this fragment is responsible for. 23 | Class clientClass = getClientClass(); 24 | 25 | try { 26 | client = (T) OAuthBaseClient.getInstance(clientClass, getActivity()); 27 | client.authorize(uri, this); // fetch access token (if not stored) 28 | } catch (Exception e) { 29 | e.printStackTrace(); 30 | } 31 | } 32 | 33 | public T getClient() { 34 | return client; 35 | } 36 | 37 | @SuppressWarnings("unchecked") 38 | private Class getClientClass() { 39 | return (Class) GenericsUtil.getTypeArguments(OAuthLoginFragment.class, this.getClass()).get(0); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /library/src/main/java/com/codepath/oauth/OAuthTokenClient.java: -------------------------------------------------------------------------------- 1 | package com.codepath.oauth; 2 | 3 | import android.net.Uri; 4 | 5 | import com.github.scribejava.core.builder.ServiceBuilder; 6 | import com.github.scribejava.core.builder.api.BaseApi; 7 | import com.github.scribejava.core.exceptions.OAuthException; 8 | import com.github.scribejava.core.model.OAuth1AccessToken; 9 | import com.github.scribejava.core.model.OAuth1RequestToken; 10 | import com.github.scribejava.core.model.OAuth2AccessToken; 11 | import com.github.scribejava.core.model.OAuthAsyncRequestCallback; 12 | import com.github.scribejava.core.model.OAuthConstants; 13 | import com.github.scribejava.core.model.Token; 14 | import com.github.scribejava.core.oauth.OAuth10aService; 15 | import com.github.scribejava.core.oauth.OAuth20Service; 16 | import com.github.scribejava.core.oauth.OAuthService; 17 | import com.github.scribejava.httpclient.okhttp.OkHttpHttpClientConfig; 18 | 19 | /* 20 | * OAuthTokenClient is responsible for managing the request and access token exchanges and then 21 | * signing all requests with the OAuth signature after access token has been retrieved and stored. 22 | * The client is based on AsyncHttpClient for async http requests and uses Scribe to manage the OAuth authentication. 23 | */ 24 | public class OAuthTokenClient { 25 | 26 | private BaseApi apiInstance; 27 | private OAuthTokenHandler handler; 28 | private Token accessToken; 29 | private OAuthService service; 30 | 31 | // Requires the apiClass, consumerKey, consumerSecret and callbackUrl along with the TokenHandler 32 | public OAuthTokenClient(BaseApi apiInstance, String consumerKey, String consumerSecret, String callbackUrl, 33 | String scope, OAuthTokenHandler handler) { 34 | this.apiInstance = apiInstance; 35 | this.handler = handler; 36 | if (callbackUrl == null) { callbackUrl = OAuthConstants.OUT_OF_BAND; }; 37 | if(scope == null) { 38 | this.service = new ServiceBuilder() 39 | .apiKey(consumerKey) 40 | .apiSecret(consumerSecret).callback(callbackUrl) 41 | .httpClientConfig(OkHttpHttpClientConfig.defaultConfig()) 42 | .build(apiInstance); 43 | } else { 44 | this.service = new ServiceBuilder() 45 | .apiKey(consumerKey) 46 | .apiSecret(consumerSecret).callback(callbackUrl) 47 | .httpClientConfig(OkHttpHttpClientConfig.defaultConfig()) 48 | .scope(scope) // OAuth2 requires scope 49 | .build(apiInstance); 50 | } 51 | } 52 | 53 | // Get a request token and the authorization url 54 | // Once fetched, fire the onReceivedRequestToken for the request token handler 55 | // Works for both OAuth1.0a and OAuth2 56 | public void fetchRequestToken() { 57 | if (service.getVersion() == "1.0") { 58 | final OAuth10aService oAuth10aService = (OAuth10aService) service; 59 | oAuth10aService.getRequestTokenAsync(new OAuthAsyncRequestCallback() { 60 | @Override 61 | public void onCompleted(OAuth1RequestToken requestToken) { 62 | String authorizeUrl = oAuth10aService.getAuthorizationUrl((OAuth1RequestToken) requestToken); 63 | handler.onReceivedRequestToken(requestToken, authorizeUrl, service.getVersion()); 64 | 65 | } 66 | 67 | @Override 68 | public void onThrowable(Throwable t) { 69 | handler.onFailure(new Exception(t.getMessage())); 70 | } 71 | }); 72 | } 73 | if (service.getVersion() == "2.0") { 74 | OAuth20Service oAuth20Service = (OAuth20Service) service; 75 | String authorizeUrl = oAuth20Service.getAuthorizationUrl(null); 76 | handler.onReceivedRequestToken(null, authorizeUrl, service.getVersion()); 77 | } 78 | } 79 | 80 | // Get the access token by exchanging the requestToken to the defined URL 81 | // Once receiving the access token, fires the onReceivedAccessToken method on the handler 82 | public void fetchAccessToken(final Token requestToken, final Uri uri) { 83 | 84 | Uri authorizedUri = uri; 85 | 86 | if (service.getVersion() == "1.0") { 87 | // Use verifier token to fetch access token 88 | 89 | if (authorizedUri.getQuery().contains(OAuthConstants.VERIFIER)) { 90 | String oauth_verifier = authorizedUri.getQueryParameter(OAuthConstants.VERIFIER); 91 | OAuth1RequestToken oAuth1RequestToken = (OAuth1RequestToken) requestToken; 92 | OAuth10aService oAuth10aService = (OAuth10aService) service; 93 | 94 | oAuth10aService.getAccessTokenAsync(oAuth1RequestToken, oauth_verifier, 95 | new OAuthAsyncRequestCallback() { 96 | 97 | @Override 98 | public void onCompleted(OAuth1AccessToken oAuth1AccessToken) { 99 | setAccessToken(oAuth1AccessToken); 100 | handler.onReceivedAccessToken(oAuth1AccessToken, service.getVersion()); 101 | } 102 | 103 | @Override 104 | public void onThrowable(Throwable e) { 105 | handler.onFailure(new OAuthException(e.getMessage())); 106 | } 107 | }); 108 | 109 | } 110 | else { // verifier was null 111 | throw new OAuthException("No verifier code was returned with uri '" + uri + "' " + 112 | "and access token cannot be retrieved"); 113 | } 114 | } else if (service.getVersion() == "2.0") { 115 | if (authorizedUri.getQuery().contains(OAuthConstants.CODE)) { 116 | String code = authorizedUri.getQueryParameter(OAuthConstants.CODE); 117 | OAuth20Service oAuth20Service = (OAuth20Service) service; 118 | oAuth20Service.getAccessToken(code, new OAuthAsyncRequestCallback() { 119 | @Override 120 | public void onCompleted(OAuth2AccessToken accessToken) { 121 | setAccessToken(accessToken); 122 | handler.onReceivedAccessToken(accessToken, service.getVersion()); 123 | 124 | } 125 | 126 | @Override 127 | public void onThrowable(Throwable t) { 128 | 129 | } 130 | }); 131 | } 132 | else { // verifier was null 133 | handler.onFailure(new OAuthException("No code was returned with uri '" + uri + "' " + 134 | "and access token cannot be retrieved")); 135 | } 136 | } 137 | } 138 | 139 | // Set the access token used for signing requests 140 | public void setAccessToken(Token accessToken) { 141 | if (accessToken == null) { 142 | this.accessToken = null; 143 | } else { 144 | this.accessToken = accessToken; 145 | } 146 | } 147 | 148 | public Token getAccessToken() { 149 | return this.accessToken; 150 | } 151 | 152 | // Defines the interface handler for different token handlers 153 | public interface OAuthTokenHandler { 154 | public void onReceivedRequestToken(Token requestToken, String authorizeUrl, String oAuthVersion); 155 | public void onReceivedAccessToken(Token accessToken, String oAuthVersion); 156 | public void onFailure(Exception e); 157 | } 158 | } -------------------------------------------------------------------------------- /library/src/main/java/com/codepath/utils/GenericsUtil.java: -------------------------------------------------------------------------------- 1 | package com.codepath.utils; 2 | 3 | import java.lang.reflect.*; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | 10 | @SuppressWarnings("rawtypes") 11 | public class GenericsUtil { 12 | public static List> getTypeArguments(Class baseClass, 13 | Class childClass) { 14 | Map resolvedTypes = new HashMap(); 15 | Type type = childClass; 16 | // start walking up the inheritance hierarchy until we hit baseClass 17 | while (!getClass(type).equals(baseClass)) { 18 | if (type instanceof Class) { 19 | type = ((Class) type).getGenericSuperclass(); 20 | } else { 21 | ParameterizedType parameterizedType = (ParameterizedType) type; 22 | assert parameterizedType != null; 23 | Class rawType = (Class) parameterizedType.getRawType(); 24 | 25 | Type[] actualTypeArguments = parameterizedType 26 | .getActualTypeArguments(); 27 | TypeVariable[] typeParameters = rawType.getTypeParameters(); 28 | for (int i = 0; i < actualTypeArguments.length; i++) { 29 | resolvedTypes 30 | .put(typeParameters[i], actualTypeArguments[i]); 31 | } 32 | 33 | if (!rawType.equals(baseClass)) { 34 | type = rawType.getGenericSuperclass(); 35 | } 36 | } 37 | } 38 | 39 | // finally, for each actual type argument provided to baseClass, 40 | // determine (if possible) 41 | // the raw class for that type argument. 42 | Type[] actualTypeArguments; 43 | if (type instanceof Class) { 44 | actualTypeArguments = ((Class) type).getTypeParameters(); 45 | } else { 46 | assert !(type == null); 47 | actualTypeArguments = ((ParameterizedType) type) 48 | .getActualTypeArguments(); 49 | } 50 | List> typeArgumentsAsClasses = new ArrayList>(); 51 | // resolve types by chasing down type variables. 52 | for (Type baseType : actualTypeArguments) { 53 | while (resolvedTypes.containsKey(baseType)) { 54 | baseType = resolvedTypes.get(baseType); 55 | } 56 | typeArgumentsAsClasses.add(getClass(baseType)); 57 | } 58 | return typeArgumentsAsClasses; 59 | } 60 | 61 | private static Class getClass(Type type) { 62 | if (type instanceof Class) { 63 | return (Class) type; 64 | } else if (type instanceof ParameterizedType) { 65 | return getClass(((ParameterizedType) type).getRawType()); 66 | } else if (type instanceof GenericArrayType) { 67 | Type componentType = ((GenericArrayType) type) 68 | .getGenericComponentType(); 69 | Class componentClass = getClass(componentType); 70 | if (componentClass != null) { 71 | return Array.newInstance(componentClass, 0).getClass(); 72 | } else { 73 | return null; 74 | } 75 | } else { 76 | return null; 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /library/src/main/res/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codepath/android-oauth-handler/c6a1c4cc12a9c7597d28b702193a6698dd5a0d72/library/src/main/res/.gitkeep -------------------------------------------------------------------------------- /library/src/main/res/layout/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codepath/android-oauth-handler/c6a1c4cc12a9c7597d28b702193a6698dd5a0d72/library/src/main/res/layout/.gitkeep -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':library' 2 | --------------------------------------------------------------------------------