├── .gitignore
├── LICENSE
├── README.md
├── RELEASE_NOTES.md
├── auth-manager
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── shiftconnects
│ └── android
│ └── auth
│ ├── AccountAuthenticator.java
│ ├── AccountAuthenticatorService.java
│ ├── AuthTokenCallback.java
│ ├── AuthenticationManager.java
│ ├── AuthenticatorActivity.java
│ ├── model
│ └── OAuthToken.java
│ ├── service
│ └── OAuthTokenService.java
│ └── util
│ ├── AESCrypto.java
│ ├── AuthConstants.java
│ └── Crypto.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── sample
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── shiftconnects
│ │ └── android
│ │ └── auth
│ │ └── example
│ │ ├── ExampleApplication.java
│ │ ├── ExampleAuthenticatedActivity.java
│ │ ├── ExampleAuthenticatorService.java
│ │ ├── ExampleLoginActivity.java
│ │ ├── model
│ │ ├── BitlyOAuthToken.java
│ │ └── ShortenedUrl.java
│ │ ├── service
│ │ ├── BitlyOAuthTokenService.java
│ │ └── BitlyRetrofitService.java
│ │ └── util
│ │ ├── Constants.java
│ │ └── GsonConverter.java
│ └── res
│ ├── layout
│ ├── activity_example.xml
│ └── activity_login.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values
│ ├── dimens.xml
│ ├── strings.xml
│ ├── strings_activity_login.xml
│ └── styles.xml
│ └── xml
│ └── authenticator.xml
├── settings.gradle
└── versions.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | /.idea/libraries
5 | /.idea/copyright
6 | /.idea/scopes
7 | .DS_Store
8 | /build
9 | bintray.properties
10 |
11 | ### Android template
12 | # Built application files
13 | *.apk
14 | *.ap_
15 |
16 | # Files for the Dalvik VM
17 | *.dex
18 |
19 | # Java class files
20 | *.class
21 |
22 | # Generated files
23 | bin/
24 | gen/
25 |
26 | # Gradle files
27 | .gradle/
28 | build/
29 | /*/build/
30 |
31 | # Local configuration file (sdk path, etc)
32 | local.properties
33 |
34 | # Proguard folder generated by Eclipse
35 | proguard/
36 |
37 | # Log Files
38 | *.log
39 |
40 |
41 | ### JetBrains template
42 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
43 |
44 | *.iml
45 |
46 | ## Directory-based project format:
47 | .idea/
48 | # if you remove the above rule, at least ignore the following:
49 |
50 | # User-specific stuff:
51 | # .idea/workspace.xml
52 | # .idea/tasks.xml
53 | # .idea/dictionaries
54 |
55 | # Sensitive or high-churn files:
56 | # .idea/dataSources.ids
57 | # .idea/dataSources.xml
58 | # .idea/sqlDataSources.xml
59 | # .idea/dynamic.xml
60 | # .idea/uiDesigner.xml
61 |
62 | # Gradle:
63 | # .idea/gradle.xml
64 | # .idea/libraries
65 |
66 | # Mongo Explorer plugin:
67 | # .idea/mongoSettings.xml
68 |
69 | ## File-based project format:
70 | *.ipr
71 | *.iws
72 |
73 | ## Plugin-specific files:
74 |
75 | # IntelliJ
76 | out/
77 |
78 | # mpeltonen/sbt-idea plugin
79 | .idea_modules/
80 |
81 | # JIRA plugin
82 | atlassian-ide-plugin.xml
83 |
84 | # Crashlytics plugin (for Android Studio and IntelliJ)
85 | com_crashlytics_export_strings.xml
86 | crashlytics.properties
87 | crashlytics-build.properties
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android Authentication Manager
2 |
3 | This library handles much of the cruft needed in Android to interface with AccountManager. It provides a mechanism for storing a user in your app within AccountManager and automatically refreshing an OAuth2 token when necessary. It currently supports [Resource Owner Password Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.3) and [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4) of RFC 6749.
4 |
5 | ## Setup
6 | * Add the following to your ```build.gradle``` file:
7 | ```gradle
8 | compile('com.shiftconnects.android.auth:auth-manager:1.0.3')
9 | ```
10 | * Implement ```OAuthTokenService``` which will be used to fetch OAuth tokens.
11 | * Implement ```Crypto``` which will be used to encrypt and decrypt the *optional* refresh token. *(if you aren't supporting a refresh token you can just create a stub implementation that returns the same string)*
12 | * Create an instance of ```AuthenticationManager``` which handles authenticating user accounts and storing them within ```AccountManager```.
13 | * Create an instance of ```AccountAuthenticator``` which requires a ```Class``` that will be used for login. The ```Class``` must be an ```Activity``` and must also implement ```AuthenticatorActivity``` and extend ```AccountAuthenticatorActivity``` or contain the same account authenticator code that exists within ```AccountAuthenticatorActivity```. This ```Activity``` will be launched if a call to ```AuthenticationManager.authenticate()``` is made and there is no authenticated account for the account type and auth token type provided.
14 | * Extend ```AccountAuthenticatorService``` and return your instance of ```AccountAuthenticator```. Example:
15 | ```java
16 | public class ExampleAuthenticatorService extends AccountAuthenticatorService {
17 |
18 | @Override protected AccountAuthenticator getAccountAuthenticator() {
19 | return ExampleApplication.ACCOUNT_AUTHENTICATOR;
20 | }
21 | }
22 | ```
23 | * Create an xml file which declares your ```AccountAuthenticator``` and put it in your ```res/xml``` folder. Example:
24 | ```xml
25 |
26 |
29 | ```
30 | * Declare your ```AccountAuthenticatorService``` in your AndroidManifest.xml file. The resource should be the file you just created. Example:
31 | ```xml
32 |
33 |
34 |
35 |
36 |
38 |
39 | ```
40 |
41 | ## Usage
42 |
43 | Typical usage will be creating an "authenticated" ```Activity``` which requires an auth token in order to make a request. For this usage you will want to have your ```Activity``` implement ```AuthenticationManager.Callbacks``` as can be seen in the example activity, ```ExampleAuthenticatedActivity```. Before making a request you will want to initiate a call to ```AuthenticationManager.authenticate()``` passing the account type and auth token type you are looking for. If there is already an authenticated account, you will receive a callback in ```onAuthenticationSuccessful(String authToken)``` with the valid auth token. If not, your login activity class will be launched and the user will be required to login. Upon successful login, your authenticated activity will receive the callback to ```onAuthenticationSuccessful(String authToken)``` with the auth token and you can then make your authenticated request.
44 |
45 | If your authentication server supports refresh tokens, ```AuthenticationManager``` will automatically refresh the expired auth token and return a valid one in the callback.
46 |
47 | When you want to logout your user, make a call to ```AuthenticationManager.logout()``` and a call will be made to ```AuthenticationManager.Callbacks.onAuthenticationInvalidated(String invalidatedAuthToken)``` once the account has been removed and the authentication has been invalidated.
48 |
49 | ## Sample
50 |
51 | There is sample included with this project which will demonstrate how to wire everything up and uses the Resource Owner Password Credentials Grant in order to retrieve OAuth tokens.
52 |
53 | The sample interfaces with the [Bitly](https://bitly.com) api in order to retrieve an OAuth token and then it will use that to shorten a url with the Bitly api.
54 |
55 | In order to test the sample you will need to create an account with Bitly and create an app at their [developer site](http://dev.bitly.com/). Once you have an app you will have a client id and client secret. You will then need to replace the following strings with your client id and client secret in ```ExampleApplication```:
56 |
57 | ```java
58 | private static final String BITLY_CLIENT_ID = "your-bitly-client-id";
59 | private static final String BITLY_CLIENT_SECRET = "your-bitly-client-secret";
60 | ```
61 |
62 | ## Permissions Used
63 |
64 | The following permissions are required and used within this project for obvious reasons:
65 |
66 | ```xml
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | ```
--------------------------------------------------------------------------------
/RELEASE_NOTES.md:
--------------------------------------------------------------------------------
1 | v1.0.3
2 | Debug flag is now false in AccountAuthenticator. Moved ExampleCrypto over to the library as AESCrypto and it now encapsulates the generating and storing of salt and iv.
3 |
4 | v1.0.2
5 | Bumped version to upload to Maven Central.
6 |
7 | v1.0.1
8 | Updated gradle build files for automatic upload to jCenter and Maven Central.
9 |
10 | v1.0.0
11 | This is the initial public release of android-auth-manager.
--------------------------------------------------------------------------------
/auth-manager/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/auth-manager/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 | apply plugin: 'com.jfrog.bintray'
4 |
5 | version = "1.0.3"
6 | def siteUrl = 'https://github.com/shiftconnects/android-auth-manager' // Homepage URL of the library
7 | def gitUrl = 'https://github.com/shiftconnects/android-auth-manager.git' // Git repository URL
8 | def projectDesc = 'Handles much of the cruft needed in Android to interface with AccountManager and provides a mechanism for storing a user in your app within AccountManager and automatically refreshing an OAuth2 token when necessary.'
9 | group = "com.shiftconnects.android.auth"
10 |
11 | Properties properties = new Properties()
12 | properties.load(project.rootProject.file('bintray.properties').newDataInputStream())
13 |
14 | android {
15 | compileSdkVersion 21
16 | buildToolsVersion "21.1.2"
17 |
18 | defaultConfig {
19 | minSdkVersion 16
20 | targetSdkVersion 21
21 | versionCode 1
22 | versionName version
23 | }
24 | buildTypes {
25 | release {
26 | minifyEnabled false
27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
28 | }
29 | }
30 | }
31 |
32 | dependencies {
33 | compile 'com.android.support:support-annotations:21.0.3'
34 | }
35 |
36 | bintray {
37 | user = properties.getProperty("bintray.user")
38 | key = properties.getProperty("bintray.apikey")
39 | def gpgPhrase = properties.getProperty("bintray.gpg.passphrase")
40 |
41 | configurations = ['archives']
42 | pkg {
43 | repo = "maven"
44 | name = group
45 | desc = projectDesc
46 | websiteUrl = siteUrl
47 | vcsUrl = gitUrl
48 | licenses = ["Apache-2.0"]
49 | publish = true
50 |
51 | version {
52 | vcsTag = version
53 | gpg {
54 | sign = true
55 | passphrase = gpgPhrase
56 | }
57 | mavenCentralSync {
58 | sync = true
59 | user = properties.getProperty("oss.userToken")
60 | password = properties.getProperty("oss.userTokenValue")
61 | close = '1'
62 | }
63 | }
64 | }
65 | }
66 |
67 | install {
68 | repositories.mavenInstaller {
69 | // This generates POM.xml with proper parameters
70 | pom {
71 | project {
72 | packaging 'aar'
73 |
74 | // Add your description here
75 | name 'Android Authentication Manager'
76 | description projectDesc
77 | url siteUrl
78 |
79 | // Set your license
80 | licenses {
81 | license {
82 | name 'The Apache Software License, Version 2.0'
83 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
84 | }
85 | }
86 | developers {
87 | developer {
88 | id 'mattkranzler5'
89 | name 'Matt Kranzler'
90 | email 'matt@mattkranzler.org'
91 | }
92 | }
93 | scm {
94 | connection gitUrl
95 | developerConnection gitUrl
96 | url siteUrl
97 |
98 | }
99 | }
100 | }
101 | }
102 | }
103 |
104 | task sourcesJar(type: Jar) {
105 | from android.sourceSets.main.java.srcDirs
106 | classifier = 'sources'
107 | }
108 |
109 | task javadoc(type: Javadoc) {
110 | source = android.sourceSets.main.java.srcDirs
111 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
112 | }
113 |
114 | task javadocJar(type: Jar, dependsOn: javadoc) {
115 | classifier = 'javadoc'
116 | from javadoc.destinationDir
117 | }
118 |
119 | artifacts {
120 | archives javadocJar
121 | archives sourcesJar
122 | }
123 |
--------------------------------------------------------------------------------
/auth-manager/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/mattkranzler/.android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/auth-manager/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/auth-manager/src/main/java/com/shiftconnects/android/auth/AccountAuthenticator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth;
18 |
19 | import android.accounts.AbstractAccountAuthenticator;
20 | import android.accounts.Account;
21 | import android.accounts.AccountAuthenticatorResponse;
22 | import android.accounts.AccountManager;
23 | import android.accounts.NetworkErrorException;
24 | import android.content.Context;
25 | import android.content.Intent;
26 | import android.os.Bundle;
27 | import android.text.TextUtils;
28 | import android.util.Log;
29 |
30 | import static com.shiftconnects.android.auth.util.AuthConstants.DEBUG;
31 | import static com.shiftconnects.android.auth.util.AuthConstants.DEBUG_TAG;
32 |
33 | /**
34 | * This class handles interfacing with {@link android.accounts.AccountManager} to add accounts and retrieve auth tokens
35 | */
36 | public class AccountAuthenticator extends AbstractAccountAuthenticator {
37 |
38 | private static final String TAG = AccountAuthenticator.class.getSimpleName();
39 |
40 | private Context mContext;
41 | private Class mLoginClass;
42 | private AuthenticationManager mAuthenticationManager;
43 |
44 | public AccountAuthenticator(Context context, Class loginClass, AuthenticationManager authenticationManager) {
45 | super(context);
46 | mContext = context;
47 | mLoginClass = loginClass;
48 | mAuthenticationManager = authenticationManager;
49 | }
50 |
51 | @Override
52 | public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
53 | final Intent intent = new Intent(mContext, mLoginClass);
54 | intent.putExtra(AuthenticatorActivity.KEY_ACCOUNT_TYPE, accountType);
55 | intent.putExtra(AuthenticatorActivity.KEY_AUTHTOKEN_TYPE, authTokenType);
56 | intent.putExtra(AuthenticatorActivity.KEY_NEW_ACCOUNT, true);
57 | intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
58 |
59 | final Bundle bundle = new Bundle();
60 | bundle.putParcelable(AccountManager.KEY_INTENT, intent);
61 | return bundle;
62 | }
63 |
64 | @Override
65 | public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
66 | if (DEBUG) {
67 | Log.d(String.format(DEBUG_TAG, TAG), "Getting auth token for account: " + account.name + " of type: " + authTokenType);
68 | }
69 |
70 | final Bundle result = new Bundle();
71 | String authToken = mAuthenticationManager.getAuthToken(account, authTokenType);
72 | if (DEBUG) {
73 | Log.d(String.format(DEBUG_TAG, TAG), "AuthenticationManager returned: " + authToken);
74 | }
75 |
76 | // if we have an auth token we can return it now
77 | if (!TextUtils.isEmpty(authToken)) {
78 | if (DEBUG) {
79 | Log.d(String.format(DEBUG_TAG, TAG), "We have an auth token. Returning " + authToken);
80 | }
81 | result.putString(AuthenticatorActivity.KEY_ACCOUNT_NAME, account.name);
82 | result.putString(AuthenticatorActivity.KEY_ACCOUNT_TYPE, account.type);
83 | result.putString(AuthenticatorActivity.KEY_AUTHTOKEN, authToken);
84 | return result;
85 | }
86 |
87 | if (DEBUG) {
88 | Log.d(String.format(DEBUG_TAG, TAG), "Wasn't able to get an auth token. Requiring user to login.");
89 | }
90 |
91 | // If we get here, then we couldn't access the user's password - so we
92 | // need to re-prompt them for their credentials. We do that by creating
93 | // an intent to display our AuthenticatorActivity.
94 | final Intent intent = new Intent(mContext, mLoginClass);
95 | intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
96 | intent.putExtra(AuthenticatorActivity.KEY_ACCOUNT_TYPE, account.type);
97 | intent.putExtra(AuthenticatorActivity.KEY_AUTHTOKEN_TYPE, authTokenType);
98 | intent.putExtra(AuthenticatorActivity.KEY_ACCOUNT_NAME, account.name);
99 |
100 | result.putParcelable(AccountManager.KEY_INTENT, intent);
101 |
102 | return result;
103 | }
104 |
105 | @Override public String getAuthTokenLabel(String authTokenType) {
106 | return authTokenType;
107 | }
108 |
109 | @Override
110 | public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
111 | return null;
112 | }
113 |
114 | @Override
115 | public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
116 | return null;
117 | }
118 |
119 | @Override
120 | public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
121 | return null;
122 | }
123 |
124 | @Override
125 | public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
126 | return null;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/auth-manager/src/main/java/com/shiftconnects/android/auth/AccountAuthenticatorService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth;
18 |
19 | import android.app.Service;
20 | import android.content.Intent;
21 | import android.os.IBinder;
22 |
23 | /**
24 | * Service used to bind to an {@link AccountAuthenticator}
25 | */
26 | public abstract class AccountAuthenticatorService extends Service {
27 |
28 | @Override public IBinder onBind(Intent intent) {
29 | return getAccountAuthenticator().getIBinder();
30 | }
31 |
32 | protected abstract AccountAuthenticator getAccountAuthenticator();
33 | }
34 |
--------------------------------------------------------------------------------
/auth-manager/src/main/java/com/shiftconnects/android/auth/AuthTokenCallback.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth;
18 |
19 | import android.accounts.Account;
20 | import android.accounts.AccountManager;
21 | import android.accounts.AccountManagerCallback;
22 | import android.accounts.AccountManagerFuture;
23 | import android.accounts.AuthenticatorException;
24 | import android.accounts.OperationCanceledException;
25 | import android.app.Activity;
26 | import android.os.Bundle;
27 | import android.os.Handler;
28 | import android.text.TextUtils;
29 | import android.util.Log;
30 |
31 | import java.io.IOException;
32 |
33 | import static com.shiftconnects.android.auth.util.AuthConstants.DEBUG;
34 | import static com.shiftconnects.android.auth.util.AuthConstants.DEBUG_TAG;
35 |
36 | /**
37 | * Callback required for AccountManager which returns after a call to
38 | * {@link android.accounts.AccountManager#getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)}
39 | */
40 | public class AuthTokenCallback implements AccountManagerCallback {
41 | private static final String TAG = AuthTokenCallback.class.getSimpleName();
42 |
43 | public interface Callbacks {
44 | void onGetAuthTokenCanceled();
45 | void onGetAuthTokenSuccessful(String authToken);
46 | void onGetAuthTokenNetworkError();
47 | void onGetAuthTokenFailed(Exception e);
48 | }
49 |
50 | private AccountManager mAccountManager;
51 | private String mAuthTokenType;
52 | private Callbacks mCallbacks;
53 |
54 | public AuthTokenCallback(AccountManager accountManager, String authTokenType, Callbacks callbacks) {
55 | mAccountManager = accountManager;
56 | mAuthTokenType = authTokenType;
57 | mCallbacks = callbacks;
58 | }
59 |
60 | @Override public void run(AccountManagerFuture future) {
61 | try {
62 | final Bundle result = future.getResult();
63 | String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
64 |
65 | // when adding an account it doesn't automatically get the token so try to get it explicitly
66 | if (TextUtils.isEmpty(authToken)) {
67 | authToken = getAuthTokenFromAccountManager(result.getString(AccountManager.KEY_ACCOUNT_NAME), result.getString(AccountManager.KEY_ACCOUNT_TYPE), mAuthTokenType);
68 | }
69 | if (!TextUtils.isEmpty(authToken)) {
70 | if (DEBUG) {
71 | Log.d(String.format(DEBUG_TAG, TAG), "Successfully retrieved an auth token from AccountManager! " + authToken);
72 | }
73 | mCallbacks.onGetAuthTokenSuccessful(authToken);
74 | } else {
75 | if (DEBUG) {
76 | Log.d(String.format(DEBUG_TAG, TAG), "Received an empty auth token from AccountManager. Authentication failed :(");
77 | }
78 | mCallbacks.onGetAuthTokenFailed(new Exception("Received an empty auth token from AccountManager"));
79 | }
80 | } catch (OperationCanceledException e) {
81 | if (DEBUG) {
82 | Log.d(String.format(DEBUG_TAG, TAG), "Authentication was canceled.");
83 | }
84 | mCallbacks.onGetAuthTokenCanceled();
85 | } catch (IOException e) {
86 | if (DEBUG) {
87 | Log.d(String.format(DEBUG_TAG, TAG), "Encountered a network error while trying to authenticate!");
88 | }
89 | mCallbacks.onGetAuthTokenNetworkError();
90 | } catch (AuthenticatorException e) {
91 | if (DEBUG) {
92 | Log.d(String.format(DEBUG_TAG, TAG), "Encountered a generic exception while trying to authenticate!");
93 | }
94 | mCallbacks.onGetAuthTokenFailed(e);
95 | }
96 | }
97 |
98 | private String getAuthTokenFromAccountManager(String accountName, String accountType, String authTokenType) {
99 | final Account[] accounts = mAccountManager.getAccountsByType(accountType);
100 | Account account = null;
101 | for (Account a : accounts) {
102 | if (TextUtils.equals(accountName, a.name)) {
103 | account = a;
104 | }
105 | }
106 | if (account != null) {
107 | return mAccountManager.peekAuthToken(account, authTokenType);
108 | }
109 | return null;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/auth-manager/src/main/java/com/shiftconnects/android/auth/AuthenticationManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth;
18 |
19 | import android.accounts.Account;
20 | import android.accounts.AccountManager;
21 | import android.accounts.AccountManagerCallback;
22 | import android.accounts.AccountManagerFuture;
23 | import android.app.Activity;
24 | import android.os.Handler;
25 | import android.os.Looper;
26 | import android.support.annotation.NonNull;
27 | import android.support.annotation.Nullable;
28 | import android.text.TextUtils;
29 | import android.util.Log;
30 |
31 | import com.shiftconnects.android.auth.model.OAuthToken;
32 | import com.shiftconnects.android.auth.service.OAuthTokenService;
33 | import com.shiftconnects.android.auth.util.AuthConstants;
34 | import com.shiftconnects.android.auth.util.Crypto;
35 |
36 | import java.util.ArrayList;
37 | import java.util.Date;
38 |
39 | import static com.shiftconnects.android.auth.util.AuthConstants.DEBUG;
40 | import static com.shiftconnects.android.auth.util.AuthConstants.DEBUG_TAG;
41 |
42 | /**
43 | * Manages interfacing the {@link android.accounts.AccountManager} with authentication for the app
44 | */
45 | public class AuthenticationManager implements AuthTokenCallback.Callbacks {
46 | private static final String TAG = AuthenticationManager.class.getSimpleName();
47 |
48 | public interface Callbacks {
49 |
50 | /**
51 | * Authentication was canceled. (The user backed out of the login activity)
52 | */
53 | void onAuthenticationCanceled();
54 |
55 | /**
56 | * Authentication was successful.
57 | * @param authToken - the valid auth token
58 | */
59 | void onAuthenticationSuccessful(String authToken);
60 |
61 | /**
62 | * Authentication failed due to a network related error (the auth server is down or the user
63 | * doesn't have a valid internet connection).
64 | */
65 | void onAuthenticationNetworkError();
66 |
67 | /**
68 | * Authentication failed due to an unknown error.
69 | * @param e - the unknown error
70 | */
71 | void onAuthenticationFailed(Exception e);
72 |
73 | /**
74 | * Authentication was invalidated (most likely due to logout).
75 | * @param invalidatedAuthToken - the auth token that is no longer valid
76 | */
77 | void onAuthenticationInvalidated(String invalidatedAuthToken);
78 | }
79 |
80 | private AccountManager mAccountManager;
81 | private OAuthTokenService mOAuthTokenService;
82 | private Crypto mCrypto;
83 | private String mClientId;
84 | private String mClientSecret;
85 | private ArrayList mCallbacks;
86 |
87 | /**
88 | * Creates a new AuthenticationManager
89 | * @param accountManager - the android {@link android.accounts.AccountManager}
90 | * @param oAuthTokenService - the {@link com.shiftconnects.android.auth.service.OAuthTokenService} to retrieve OAuth tokens
91 | * @param crypto - the {@link com.shiftconnects.android.auth.util.Crypto} to use for encryption/decryption
92 | * @param clientId - the application client id to be used for OAuth
93 | * @param clientSecret - the application client secret to be used for OAuth
94 | */
95 | public AuthenticationManager(AccountManager accountManager, OAuthTokenService oAuthTokenService, Crypto crypto,
96 | String clientId, String clientSecret) {
97 | mAccountManager = accountManager;
98 | mOAuthTokenService = oAuthTokenService;
99 | mCrypto = crypto;
100 | mClientId = clientId;
101 | mClientSecret = clientSecret;
102 | mCallbacks = new ArrayList<>();
103 | }
104 |
105 | public boolean addCallbacks(Callbacks callbacks) {
106 | return mCallbacks.add(callbacks);
107 | }
108 |
109 | public boolean removeCallbacks(Callbacks callbacks) {
110 | return mCallbacks.remove(callbacks);
111 | }
112 |
113 | /**
114 | * Gets the {@link android.accounts.Account} for the provided account name and type
115 | * @param accountName - the {@link android.accounts.Account#name}
116 | * @param accountType - the {@link android.accounts.Account#type}
117 | * @return the {@link android.accounts.Account} if it exists, null if not
118 | */
119 | @Nullable
120 | public Account getAccountByNameForType(@NonNull String accountName, @NonNull String accountType) {
121 | validateAccountName(accountName);
122 | validateAccountType(accountType);
123 |
124 | final Account[] accounts = mAccountManager.getAccountsByType(accountType);
125 | for (Account account : accounts) {
126 | if (TextUtils.equals(accountName, account.name)) {
127 | return account;
128 | }
129 | }
130 | return null;
131 | }
132 |
133 | /**
134 | * Returns a single {@link android.accounts.Account} for the provided {@link android.accounts.Account#type}
135 | * if one exists
136 | * @param accountType - the {@link android.accounts.Account#type}. Must NOT be null or empty
137 | * @return an {@link android.accounts.Account} if one exists, null if none exist
138 | */
139 | @Nullable
140 | public Account getSingleAccountForType(@NonNull String accountType) {
141 | validateAccountType(accountType);
142 |
143 | final Account[] accounts = mAccountManager.getAccountsByType(accountType);
144 | if (accounts.length == 1) {
145 | return accounts[0];
146 | }
147 | return null;
148 | }
149 |
150 | /**
151 | * Gets the auth token from {@link android.accounts.AccountManager}. It will attempt to refresh the auth token
152 | * if a refresh token is available and an existing auth token has expired. This call is synchronous so DO NOT
153 | * call on UI thread
154 | * @param account - the {@link android.accounts.Account} for which to get the auth token. Must NOT be null and
155 | * must have a valid {@link android.accounts.Account#name}
156 | * @param authTokenType - the auth token type
157 | * @return the auth token if available
158 | */
159 | @Nullable
160 | public synchronized String getAuthToken(@NonNull Account account, @NonNull String authTokenType) {
161 | validateAccount(account);
162 | validateAccountName(account.name);
163 | validateAccountType(account.type);
164 | validateAuthTokenType(authTokenType);
165 |
166 | String authToken = mAccountManager.peekAuthToken(account, authTokenType);
167 | if (TextUtils.isEmpty(authToken)) {
168 | if (DEBUG) {
169 | Log.d(TAG, "No auth token is available for the account :(");
170 | }
171 | } else {
172 | String expiration = mAccountManager.getUserData(account, AuthConstants.KEY_TOKEN_EXPIRATION_TIME);
173 | if (!TextUtils.isEmpty(expiration)) {
174 | final long expirationTime = Long.valueOf(expiration);
175 | if (System.currentTimeMillis() > expirationTime) {
176 | if (DEBUG) {
177 | Log.d(TAG, "Auth token has expired. Expiration time [" + new Date(expirationTime) + "]. Attempting to get a new token...");
178 | }
179 | authToken = getNewAuthToken(account, authTokenType);
180 | }
181 | }
182 | }
183 |
184 | return authToken;
185 | }
186 |
187 | private String getNewAuthToken(@NonNull Account account, @NonNull String authTokenType) {
188 | String isClientCredentials = mAccountManager.getUserData(account, AuthConstants.KEY_IS_CLIENT_CREDENTIALS);
189 | if (TextUtils.equals(isClientCredentials, AuthConstants.VALUE_IS_CLIENT_CREDENTIALS)) {
190 | return getNewAuthTokenWithClientCredentials(account, authTokenType);
191 | } else {
192 | return getNewAuthTokenWithRefreshToken(account, authTokenType);
193 | }
194 | }
195 |
196 | private String getNewAuthTokenWithRefreshToken(@NonNull Account account, @NonNull String authTokenType) {
197 | final String encryptedRefreshToken = getEncryptedRefreshToken(account);
198 | if (TextUtils.isEmpty(encryptedRefreshToken)) {
199 | if (DEBUG) {
200 | Log.d(String.format(DEBUG_TAG, TAG), "Failed to refresh auth token due to no refresh token available");
201 | }
202 | authenticationFailed(account, authTokenType);
203 | return null;
204 | }
205 | final String decryptedRefreshToken = decryptRefreshToken(account, encryptedRefreshToken);
206 | if (TextUtils.isEmpty(decryptedRefreshToken)) {
207 | if (DEBUG) {
208 | Log.d(String.format(DEBUG_TAG, TAG), "Failed to refresh token due to a decryption failure");
209 | }
210 | authenticationFailed(account, authTokenType);
211 | return null;
212 | }
213 | final long currentTime = System.currentTimeMillis();
214 | final OAuthToken response = mOAuthTokenService.getTokenWithRefreshToken(mClientId, mClientSecret, decryptedRefreshToken);
215 | if (response == null) {
216 | if (DEBUG) {
217 | Log.d(String.format(DEBUG_TAG, TAG), "Failed to refresh auth token due to an authentication exception");
218 | }
219 | authenticationFailed(account, authTokenType);
220 | return null;
221 | }
222 |
223 | // invalidate the old token
224 | invalidateAuthTokenForAccount(account, authTokenType);
225 |
226 | if (DEBUG) {
227 | Log.d(String.format(DEBUG_TAG, TAG), "Received a new auth token from the oauth service with a refresh token.");
228 | }
229 |
230 | saveAuthentication(account, authTokenType, OAuthTokenService.GrantType.refresh_token, response, currentTime);
231 |
232 | return response.getAuthToken();
233 | }
234 |
235 | private String getNewAuthTokenWithClientCredentials(@NonNull Account account, @NonNull String authTokenType) {
236 | final long currentTime = System.currentTimeMillis();
237 | final OAuthToken response = mOAuthTokenService.getTokenWithClientCredentials(mClientId, mClientSecret);
238 | if (response == null) {
239 | if (DEBUG) {
240 | Log.d(String.format(DEBUG_TAG, TAG), "Failed to refresh auth token due to an authentication exception");
241 | }
242 | authenticationFailed(account, authTokenType);
243 | return null;
244 | }
245 |
246 | // invalidate the old token
247 | invalidateAuthTokenForAccount(account, authTokenType);
248 |
249 | if (DEBUG) {
250 | Log.d(String.format(DEBUG_TAG, TAG), "Received a new auth token from the oauth service with client credentials.");
251 | }
252 | saveAuthentication(account, authTokenType, OAuthTokenService.GrantType.client_credentials, response, currentTime);
253 |
254 | return response.getAuthToken();
255 | }
256 |
257 | private void authenticationFailed(Account account, String authTokenType) {
258 | logout(account, authTokenType);
259 | notifyCallbacksAuthenticationFailed(new Exception("Was unable to refresh auth token"));
260 | }
261 |
262 | public void setUserData(@NonNull Account account, @NonNull String key, String value) {
263 | validateAccount(account);
264 | mAccountManager.setUserData(account, key, value);
265 | }
266 |
267 | public String getUserData(@NonNull Account account, @NonNull String key) {
268 | validateAccount(account);
269 | return mAccountManager.getUserData(account, key);
270 | }
271 |
272 | /**
273 | * Begins the authentication process. If the user has already logged in and has a valid auth token,
274 | * a {@link com.shiftconnects.android.auth.AuthenticationManager.Callbacks#onGetAuthTokenSuccessful(String)} will be called. If
275 | * the user is logged in but has an invalid auth token, an attempt to refresh it will be made and
276 | * upon success a {@link com.shiftconnects.android.auth.AuthenticationManager.Callbacks#onGetAuthTokenSuccessful(String)} will
277 | * be posted. If the user isn't logged in, {@link android.accounts.AccountManager} will handle
278 | * launching the Activity to be used to authenticate the user.
279 | * @param activity - the calling activity. Must NOT be null
280 | * @param accountType - the {@link android.accounts.AccountManager#KEY_ACCOUNT_TYPE}. Must NOT be null or empty
281 | * @param authTokenType - the auth token type. Must NOT be null or empty
282 | */
283 | public void authenticate(@NonNull Activity activity, @NonNull String accountType, @NonNull String authTokenType) {
284 | validateActivity(activity);
285 | validateAccountType(accountType);
286 | validateAuthTokenType(authTokenType);
287 |
288 | // see if we already have a logged in account for this account type
289 | Account account = getSingleAccountForType(accountType);
290 | if (account != null) {
291 | mAccountManager.getAuthToken(
292 | account,
293 | authTokenType,
294 | null,
295 | activity,
296 | new AuthTokenCallback(mAccountManager, authTokenType, this),
297 | null
298 | );
299 | } else {
300 | mAccountManager.addAccount(
301 | accountType,
302 | authTokenType,
303 | null,
304 | null,
305 | activity,
306 | new AuthTokenCallback(mAccountManager, authTokenType, this),
307 | null
308 | );
309 | }
310 | }
311 |
312 | /**
313 | * Logs a user into the app by retrieving an auth token from the oauth service and saving it along with the account in {@link android.accounts.AccountManager}
314 | * @param userName - the userName of the user. Must NOT be null or empty
315 | * @param password - the user's password. Must NOT be null or empty
316 | * @param accountType - the {@link android.accounts.AccountManager#KEY_ACCOUNT_TYPE}. Must NOT be null or empty
317 | * @param authTokenType - the auth token type. Must NOT be null or empty
318 | * @param newAccount - if true, this is a new account
319 | * @return the valid access token upon login
320 | */
321 | @NonNull
322 | public String loginWithUserNamePassword(@NonNull String userName, @NonNull String password, @NonNull String accountType, @NonNull String authTokenType, boolean newAccount) {
323 | validateUserName(userName);
324 | validatePassword(password);
325 | validateAccountType(accountType);
326 | validateAuthTokenType(authTokenType);
327 |
328 | Account account = new Account(userName, accountType);
329 | final long currentTime = System.currentTimeMillis();
330 | OAuthToken response = mOAuthTokenService.getTokenWithPassword(mClientId, mClientSecret, userName, password);
331 | if (newAccount) {
332 |
333 | if (DEBUG) {
334 | Log.d(String.format(DEBUG_TAG, TAG), "Adding new account with username " + userName + " to AccountManager.");
335 | }
336 |
337 | mAccountManager.addAccountExplicitly(account, null, null);
338 | }
339 |
340 | if (DEBUG) {
341 | Log.d(String.format(DEBUG_TAG, TAG), "Login was successful with username and password.");
342 | }
343 |
344 | saveAuthentication(account, authTokenType, OAuthTokenService.GrantType.password, response, currentTime);
345 | return response.getAuthToken();
346 | }
347 |
348 | /**
349 | * Logs a user into the app by retrieving an auth token from the oauth service and saving it along with the account in {@link android.accounts.AccountManager}
350 | * using client credentials
351 | * @param accountName - the account name. Must NOT be null or empty
352 | * @param accountType - the {@link android.accounts.AccountManager#KEY_ACCOUNT_TYPE}. Must NOT be null or empty
353 | * @param authTokenType - the auth token type. Must NOT be null or empty
354 | * @return the valid access token upon login
355 | */
356 | @NonNull
357 | public String loginWithClientCredentials(@NonNull String accountName, @NonNull String accountType, @NonNull String authTokenType) {
358 | validateAccountName(accountName);
359 | validateAccountType(accountType);
360 | validateAuthTokenType(authTokenType);
361 |
362 | Account account = new Account(accountName, accountType);
363 | final long currentTime = System.currentTimeMillis();
364 | OAuthToken response = mOAuthTokenService.getTokenWithClientCredentials(mClientId, mClientSecret);
365 | mAccountManager.addAccountExplicitly(account, null, null);
366 |
367 | if (DEBUG) {
368 | Log.d(String.format(DEBUG_TAG, TAG), "Login was successful with client credentials.");
369 | }
370 |
371 | saveAuthentication(account, authTokenType, OAuthTokenService.GrantType.client_credentials, response, currentTime);
372 | return response.getAuthToken();
373 | }
374 |
375 | /**
376 | * Logs out the account, keeping it on the device
377 | * @param account - the logged in {@link android.accounts.Account}. Must NOT be null
378 | * @param authTokenType - the auth token type. Must NOT be null or empty
379 | */
380 | public void logout(@NonNull final Account account, @NonNull String authTokenType) {
381 | validateAccount(account);
382 | validateAccountName(account.name);
383 | validateAccountType(account.type);
384 | validateAuthTokenType(authTokenType);
385 |
386 | final String authToken = mAccountManager.peekAuthToken(account, authTokenType);
387 | final String accountType = account.type;
388 |
389 | mAccountManager.removeAccount(account, new AccountManagerCallback() {
390 | @Override public void run(AccountManagerFuture future) {
391 | if (!TextUtils.isEmpty(authToken)) {
392 |
393 | if (DEBUG) {
394 | Log.d(String.format(DEBUG_TAG, TAG), "Removing account with name " + account.name + " and type " + accountType
395 | + " from AccountManager and invalidating auth token " + authToken);
396 | }
397 |
398 | notifyCallbacksAuthenticationInvalidated(authToken);
399 | mAccountManager.invalidateAuthToken(accountType, authToken);
400 | }
401 | }
402 | }, new Handler(Looper.getMainLooper()));
403 | }
404 |
405 | private String getEncryptedRefreshToken(Account account) {
406 | return mAccountManager.getUserData(account, AuthConstants.KEY_REFRESH_TOKEN);
407 | }
408 |
409 | private String decryptRefreshToken(Account account, String encryptedRefreshToken) {
410 | String decryptedRefreshToken = null;
411 | try {
412 | decryptedRefreshToken = mCrypto.decrypt(account.name, encryptedRefreshToken);
413 | } catch (Exception e) {
414 | Log.e(String.format(DEBUG_TAG, TAG), "Failed to decrypt the refresh token", e);
415 | }
416 | return decryptedRefreshToken;
417 | }
418 |
419 | private void saveAuthentication(@NonNull Account account, @NonNull String authTokenType, @NonNull OAuthTokenService.GrantType grantType,
420 | @NonNull OAuthToken token, long requestTime) {
421 |
422 | final String authToken = token.getAuthToken();
423 |
424 | // set the auth token in AccountManager
425 | if (DEBUG) {
426 | Log.d(String.format(DEBUG_TAG, TAG), "Setting auth token in AccountManager: " + authToken);
427 | }
428 | mAccountManager.setAuthToken(account, authTokenType, authToken);
429 |
430 | if (token.getExpiresIn() > 0) {
431 | final long expirationTime = requestTime + (token.getExpiresIn() * 1000); // convert to milliseconds
432 | if (DEBUG) {
433 | Log.d(String.format(DEBUG_TAG, TAG), "Auth token expires [" + new Date(expirationTime) + "]");
434 | }
435 | mAccountManager.setUserData(account, AuthConstants.KEY_TOKEN_EXPIRATION_TIME, String.valueOf(expirationTime));
436 | }
437 |
438 | if (grantType == OAuthTokenService.GrantType.client_credentials) {
439 | if (DEBUG) {
440 | Log.d(String.format(DEBUG_TAG, TAG), "Grant type is client_credentials, flagging in account manager.");
441 | }
442 |
443 | // set flag in account manager
444 | mAccountManager.setUserData(account, AuthConstants.KEY_IS_CLIENT_CREDENTIALS, AuthConstants.VALUE_IS_CLIENT_CREDENTIALS);
445 |
446 | } else if (!TextUtils.isEmpty(token.getRefreshToken())) {
447 | if (DEBUG) {
448 | Log.d(String.format(DEBUG_TAG, TAG), "Storing refresh token in account manager.");
449 | }
450 |
451 | // encrypt the refresh token
452 | try {
453 | String encryptedRefreshToken = mCrypto.encrypt(account.name, token.getRefreshToken());
454 |
455 | // add other data to account manager
456 | mAccountManager.setUserData(account, AuthConstants.KEY_REFRESH_TOKEN, encryptedRefreshToken);
457 |
458 | // if we get here due to an encryption failure then we will just not save the refresh token which will require them to login again
459 | } catch (Exception e) {
460 | Log.e(String.format(DEBUG_TAG, TAG), "Unable to save refresh token.", e);
461 | }
462 | }
463 | }
464 |
465 | private void invalidateAuthTokenForAccount(@NonNull Account account, @NonNull String authTokenType) {
466 | mAccountManager.invalidateAuthToken(account.type, mAccountManager.peekAuthToken(account, authTokenType));
467 | }
468 |
469 | //region validations
470 | private void validateAccount(Account account) {
471 | if (account == null) {
472 | throw new IllegalArgumentException("Parameter account cannot be null");
473 | }
474 | }
475 |
476 | private void validateAccountName(String accountName) {
477 | if (TextUtils.isEmpty(accountName)) {
478 | throw new IllegalArgumentException("Parameter accountName cannot be empty");
479 | }
480 | }
481 |
482 | private void validateAccountType(String accountType) {
483 | if (TextUtils.isEmpty(accountType)) {
484 | throw new IllegalArgumentException("Parameter accountType cannot be empty");
485 | }
486 | }
487 |
488 | private void validateAuthTokenType(String authTokenType) {
489 | if (TextUtils.isEmpty(authTokenType)) {
490 | throw new IllegalArgumentException("Parameter authTokenType cannot be null or empty");
491 | }
492 | }
493 |
494 | private void validateActivity(Activity activity) {
495 | if (activity == null) {
496 | throw new IllegalArgumentException("Parameter activity cannot be null");
497 | }
498 | }
499 |
500 | private void validateUserName(String userName) {
501 | if (TextUtils.isEmpty(userName)) {
502 | throw new IllegalArgumentException("Parameter userName cannot be null or empty");
503 | }
504 | }
505 |
506 | private void validatePassword(String password) {
507 | if (TextUtils.isEmpty(password)) {
508 | throw new IllegalArgumentException("Parameter password cannot be null or empty");
509 | }
510 | }
511 |
512 | //endregion
513 |
514 | //region getAuthTokenCallbacks
515 | @Override public void onGetAuthTokenCanceled() {
516 | notifyCallbacksAuthenticationCanceled();
517 | }
518 |
519 | @Override public void onGetAuthTokenSuccessful(String authToken) {
520 | notifyCallbacksAuthenticationSuccessful(authToken);
521 | }
522 |
523 | @Override public void onGetAuthTokenNetworkError() {
524 | notifyCallbacksAuthenticationNetworkError();
525 | }
526 |
527 | @Override public void onGetAuthTokenFailed(Exception e) {
528 | notifyCallbacksAuthenticationFailed(e);
529 | }
530 | //endregion
531 |
532 | //region notify callbacks
533 | private void notifyCallbacksAuthenticationCanceled() {
534 | for (Callbacks callbacks : mCallbacks) {
535 | callbacks.onAuthenticationCanceled();
536 | }
537 | }
538 |
539 | private void notifyCallbacksAuthenticationSuccessful(String authToken) {
540 | for (Callbacks callbacks : mCallbacks) {
541 | callbacks.onAuthenticationSuccessful(authToken);
542 | }
543 | }
544 |
545 | private void notifyCallbacksAuthenticationNetworkError() {
546 | for (Callbacks callbacks : mCallbacks) {
547 | callbacks.onAuthenticationNetworkError();
548 | }
549 | }
550 |
551 | private void notifyCallbacksAuthenticationFailed(Exception e) {
552 | for (Callbacks callbacks : mCallbacks) {
553 | callbacks.onAuthenticationFailed(e);
554 | }
555 | }
556 |
557 | private void notifyCallbacksAuthenticationInvalidated(String invalidatedAuthToken) {
558 | for (Callbacks callbacks : mCallbacks) {
559 | callbacks.onAuthenticationInvalidated(invalidatedAuthToken);
560 | }
561 | }
562 | //endregion
563 | }
564 |
--------------------------------------------------------------------------------
/auth-manager/src/main/java/com/shiftconnects/android/auth/AuthenticatorActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth;
18 |
19 | import android.accounts.AccountManager;
20 |
21 | /**
22 | * Interface that an Activity being used to authenticate must implement
23 | */
24 | public interface AuthenticatorActivity {
25 | public static final String KEY_ACCOUNT_TYPE = AccountManager.KEY_ACCOUNT_TYPE;
26 | public static final String KEY_ACCOUNT_NAME = AccountManager.KEY_ACCOUNT_NAME;
27 | public static final String KEY_AUTHTOKEN = AccountManager.KEY_AUTHTOKEN;
28 | public static final String KEY_AUTHTOKEN_TYPE = "authTokenType";
29 | public static final String KEY_NEW_ACCOUNT = "newAccount";
30 | }
31 |
--------------------------------------------------------------------------------
/auth-manager/src/main/java/com/shiftconnects/android/auth/model/OAuthToken.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.model;
18 |
19 | /**
20 | * Represents a typical oauth token
21 | */
22 | public interface OAuthToken {
23 | String getAuthToken();
24 | String getRefreshToken();
25 | long getExpiresIn();
26 | }
27 |
--------------------------------------------------------------------------------
/auth-manager/src/main/java/com/shiftconnects/android/auth/service/OAuthTokenService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.service;
18 |
19 | import com.shiftconnects.android.auth.model.OAuthToken;
20 |
21 | /**
22 | * Service which retrieves an {@link com.shiftconnects.android.auth.model.OAuthToken}
23 | * with via different grant types.
24 | *
25 | * One use of this interface would be to extend it as a retrofit service to interface to an authorization
26 | * server.
27 | */
28 | @SuppressWarnings("unused")
29 | public interface OAuthTokenService {
30 | T getTokenWithPassword(String clientId, String clientSecret, String userName, String password);
31 | T getTokenWithRefreshToken(String clientId, String clientSecret, String refreshToken);
32 | T getTokenWithClientCredentials(String clientId, String clientSecret);
33 |
34 | public static enum GrantType {
35 | password,
36 | refresh_token,
37 | client_credentials
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/auth-manager/src/main/java/com/shiftconnects/android/auth/util/AESCrypto.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.util;
18 |
19 | import android.content.SharedPreferences;
20 | import android.support.annotation.NonNull;
21 | import android.text.TextUtils;
22 | import android.util.Base64;
23 | import android.util.Log;
24 |
25 | import java.security.NoSuchAlgorithmException;
26 | import java.security.SecureRandom;
27 | import java.security.spec.InvalidKeySpecException;
28 | import java.security.spec.KeySpec;
29 |
30 | import javax.crypto.Cipher;
31 | import javax.crypto.KeyGenerator;
32 | import javax.crypto.NoSuchPaddingException;
33 | import javax.crypto.SecretKey;
34 | import javax.crypto.SecretKeyFactory;
35 | import javax.crypto.spec.IvParameterSpec;
36 | import javax.crypto.spec.PBEKeySpec;
37 | import javax.crypto.spec.SecretKeySpec;
38 |
39 | import static com.shiftconnects.android.auth.util.AuthConstants.DEBUG;
40 | import static com.shiftconnects.android.auth.util.AuthConstants.DEBUG_TAG;
41 |
42 | /**
43 | * Crypto implementation using an AES transformation with a 128-bit key length.
44 | */
45 | public class AESCrypto implements Crypto {
46 |
47 | private static final String TAG = AESCrypto.class.getSimpleName();
48 | private static final String IV = "iv";
49 | private static final String SALT = "salt";
50 |
51 | private static final int ITERATIONS = 1000;
52 | private static final int KEY_LENGTH = 128;
53 | private static final int SALT_LENGTH = 128; // same size as key output
54 |
55 | private SharedPreferences mSharedPrefs;
56 | private byte[] mSalt;
57 | private byte[] mIv;
58 |
59 | /**
60 | * Default constructor
61 | * @param sharedPrefs - {@link SharedPreferences} used to store a generated salt and iv used for
62 | * encryption/decryption
63 | */
64 | public AESCrypto(SharedPreferences sharedPrefs) {
65 | mSharedPrefs = sharedPrefs;
66 | mSalt = generateSalt();
67 | mIv = generateIV();
68 | }
69 |
70 | @NonNull
71 | public String encrypt(@NonNull String password, @NonNull String decryptedString) throws Exception {
72 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
73 | IvParameterSpec ivParams = new IvParameterSpec(mIv);
74 | cipher.init(Cipher.ENCRYPT_MODE, deriveKeyPbkdf2(mSalt, password), ivParams);
75 | byte[] cipherBytes = cipher.doFinal(decryptedString.getBytes("UTF-8"));
76 | return Base64.encodeToString(cipherBytes, Base64.DEFAULT);
77 | }
78 |
79 | @NonNull
80 | public String decrypt(@NonNull String password, @NonNull String encryptedString) throws Exception {
81 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
82 | IvParameterSpec ivParams = new IvParameterSpec(mIv);
83 | cipher.init(Cipher.DECRYPT_MODE, deriveKeyPbkdf2(mSalt, password), ivParams);
84 | byte[] plaintext = cipher.doFinal(Base64.decode(encryptedString, Base64.DEFAULT));
85 | return new String(plaintext , "UTF-8");
86 | }
87 |
88 | private SecretKey deriveKeyPbkdf2(byte[] salt, String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
89 | KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
90 | SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
91 | byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
92 | return new SecretKeySpec(keyBytes, "AES");
93 | }
94 |
95 | private byte[] generateSalt() {
96 | byte[] salt;
97 | String saltString = mSharedPrefs.getString(SALT, null);
98 | if (TextUtils.isEmpty(saltString)) {
99 | try {
100 | if (DEBUG) {
101 | Log.d(String.format(DEBUG_TAG, TAG), "salt is null. Generating one...");
102 | }
103 |
104 | // generate a new salt
105 | SecureRandom secureRandom = new SecureRandom();
106 | KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
107 | keyGenerator.init(SALT_LENGTH, secureRandom);
108 | SecretKey key = keyGenerator.generateKey();
109 | salt = key.getEncoded();
110 |
111 | // encode it
112 | saltString = Base64.encodeToString(salt, Base64.DEFAULT);
113 |
114 | if (DEBUG) {
115 | Log.d(String.format(DEBUG_TAG, TAG), "Newly generated encoded salt: " + saltString);
116 | }
117 |
118 | // save to shared prefs
119 | mSharedPrefs.edit().putString(SALT, saltString).apply();
120 | } catch (NoSuchAlgorithmException e) {
121 | Log.e(String.format(DEBUG_TAG, TAG), "Could not setup salt. This is bad.");
122 | throw new RuntimeException("Unable to setup salt. Cannot run app.", e);
123 | }
124 | } else {
125 | salt = Base64.decode(saltString, Base64.DEFAULT);
126 |
127 | if (DEBUG) {
128 | Log.d(String.format(DEBUG_TAG, TAG), "Salt loaded from disk: " + saltString);
129 | }
130 | }
131 | return salt;
132 | }
133 |
134 | private byte[] generateIV() {
135 | byte[] iv;
136 | String ivString = mSharedPrefs.getString(IV, null);
137 | if (TextUtils.isEmpty(ivString)) {
138 | try {
139 | if (DEBUG) {
140 | Log.d(String.format(DEBUG_TAG, TAG), "Initialization vector is null. Generating one...");
141 | }
142 |
143 | // generate a new iv
144 | SecureRandom secureRandom = new SecureRandom();
145 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
146 | iv = new byte[cipher.getBlockSize()];
147 | secureRandom.nextBytes(iv);
148 |
149 | // encode it
150 | ivString = Base64.encodeToString(iv, Base64.DEFAULT);
151 |
152 | if (DEBUG) {
153 | Log.d(String.format(DEBUG_TAG, TAG), "Newly generated encoded initialization vector: " + ivString);
154 | }
155 |
156 | // save to shared prefs
157 | mSharedPrefs.edit().putString(IV, ivString).apply();
158 | } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
159 | Log.e(String.format(DEBUG_TAG, TAG), "Could not setup IV. This is bad.");
160 | throw new RuntimeException("Unable to setup IV. Cannot run app.", e);
161 | }
162 | } else {
163 | iv = Base64.decode(ivString, Base64.DEFAULT);
164 |
165 | if (DEBUG) {
166 | Log.d(String.format(DEBUG_TAG, TAG), "IV loaded from disk: " + ivString);
167 | }
168 | }
169 | return iv;
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/auth-manager/src/main/java/com/shiftconnects/android/auth/util/AuthConstants.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.util;
18 |
19 | /**
20 | * Constants used with authenticating accounts
21 | */
22 | public class AuthConstants {
23 |
24 | public static final String KEY_REFRESH_TOKEN = "refreshToken";
25 | public static final String KEY_TOKEN_EXPIRATION_TIME = "tokenExpirationTime";
26 | public static final String KEY_IS_CLIENT_CREDENTIALS = "isClientCredentials";
27 | public static final String VALUE_IS_CLIENT_CREDENTIALS = "true";
28 |
29 | public static boolean DEBUG = false;
30 | public static final String DEBUG_TAG = "android-auth-manager [%s]";
31 |
32 | public static void setDebug(boolean debug) {
33 | DEBUG = debug;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/auth-manager/src/main/java/com/shiftconnects/android/auth/util/Crypto.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.util;
18 |
19 | import android.support.annotation.NonNull;
20 |
21 | /**
22 | * Interface to encrypt and decrypt a string.
23 | */
24 | public interface Crypto {
25 |
26 | /**
27 | * Encrypt the passed in decrypted string
28 | * @param password - the password to be used to encrypt the string
29 | * @param decryptedString - the decrypted string
30 | * @return the encrypted string
31 | * @throws Exception
32 | */
33 | @NonNull
34 | public String encrypt(@NonNull String password, @NonNull String decryptedString) throws Exception;
35 |
36 | /**
37 | * Decrypt the passed in encrypted string
38 | * @param password - - the password to be used to decrypt the string
39 | * @param encryptedString - the encrypted string
40 | * @return the decrypted string
41 | * @throws Exception
42 | */
43 | @NonNull
44 | public String decrypt(@NonNull String password, @NonNull String encryptedString) throws Exception;
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.1.2'
9 | classpath 'com.github.dcendents:android-maven-plugin:1.2'
10 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | jcenter()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shiftconnects/android-auth-manager/2975434d14b9f55ab55f1c30cdc0d94e01b698d8/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
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-2.2.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 |
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.2"
6 |
7 | defaultConfig {
8 | applicationId "com.shiftconnects.android.auth.example"
9 | minSdkVersion 16
10 | targetSdkVersion 21
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:21.0.3'
25 | compile 'com.google.android.gms:play-services:6.5.87'
26 | compile 'com.squareup.retrofit:retrofit:1.9.0'
27 | compile 'com.squareup.retrofit:retrofit-mock:1.9.0'
28 | compile project(':auth-manager')
29 | }
30 |
--------------------------------------------------------------------------------
/sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/mattkranzler/.android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/shiftconnects/android/auth/example/ExampleApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.example;
18 |
19 | import android.accounts.AccountManager;
20 | import android.app.Application;
21 | import android.content.Context;
22 |
23 | import com.google.gson.Gson;
24 | import com.shiftconnects.android.auth.AccountAuthenticator;
25 | import com.shiftconnects.android.auth.AuthenticationManager;
26 | import com.shiftconnects.android.auth.example.service.BitlyOAuthTokenService;
27 | import com.shiftconnects.android.auth.example.service.BitlyRetrofitService;
28 | import com.shiftconnects.android.auth.example.util.GsonConverter;
29 | import com.shiftconnects.android.auth.util.AESCrypto;
30 | import com.shiftconnects.android.auth.util.AuthConstants;
31 |
32 | import retrofit.RestAdapter;
33 |
34 | /**
35 | * Created by mattkranzler on 2/25/15.
36 | */
37 | public class ExampleApplication extends Application {
38 |
39 | private static final String BITLY_CLIENT_ID = "9c8d2f12f6ab02e84f2692cc5acc01717ac807c1";
40 | private static final String BITLY_CLIENT_SECRET = "49a498e19d3e92e23749b43af77b1663d447bb3c";
41 |
42 | public static AccountAuthenticator ACCOUNT_AUTHENTICATOR;
43 | public static AuthenticationManager AUTHENTICATION_MANAGER;
44 | public static BitlyRetrofitService BITLY_SERVICE;
45 |
46 | @Override public void onCreate() {
47 | super.onCreate();
48 |
49 | BITLY_SERVICE = new RestAdapter.Builder()
50 | .setEndpoint("https://api-ssl.bitly.com")
51 | .setLogLevel(RestAdapter.LogLevel.FULL)
52 | .setConverter(new GsonConverter(new Gson()))
53 | .build()
54 | .create(BitlyRetrofitService.class);
55 |
56 | AuthConstants.setDebug(true);
57 |
58 | AUTHENTICATION_MANAGER = new AuthenticationManager(
59 | AccountManager.get(this),
60 | new BitlyOAuthTokenService(BITLY_SERVICE),
61 | new AESCrypto(getSharedPreferences("crypto", Context.MODE_PRIVATE)),
62 | BITLY_CLIENT_ID,
63 | BITLY_CLIENT_SECRET
64 | );
65 |
66 | ACCOUNT_AUTHENTICATOR = new AccountAuthenticator(
67 | this,
68 | ExampleLoginActivity.class,
69 | AUTHENTICATION_MANAGER
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/shiftconnects/android/auth/example/ExampleAuthenticatedActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.example;
18 |
19 | import android.accounts.Account;
20 | import android.app.Activity;
21 | import android.os.AsyncTask;
22 | import android.os.Bundle;
23 | import android.text.TextUtils;
24 | import android.util.Log;
25 | import android.view.View;
26 | import android.widget.Button;
27 | import android.widget.EditText;
28 |
29 | import com.shiftconnects.android.auth.AuthenticationManager;
30 | import com.shiftconnects.android.auth.example.model.ShortenedUrl;
31 | import com.shiftconnects.android.auth.example.util.Constants;
32 |
33 | /**
34 | * This is an example of an authenticated activity. It will attempt to authenticate in {@link #onCreate(android.os.Bundle)} and
35 | * if there is no valid account for the provided account type and auth token type the user will be provided
36 | * with the login screen to login. Upon successful login a call to {@link #onAuthenticationSuccessful(String)} with the
37 | * auth token will be called.
38 | */
39 | public class ExampleAuthenticatedActivity extends Activity implements AuthenticationManager.Callbacks, View.OnClickListener {
40 |
41 | private static final String TAG = ExampleAuthenticatedActivity.class.getSimpleName();
42 |
43 | private EditText mLongUrl;
44 | private EditText mShortUrl;
45 | private Button mShortenButton;
46 | private Button mLogoutButton;
47 |
48 | private String mAuthToken;
49 |
50 | @Override protected void onCreate(Bundle savedInstanceState) {
51 | super.onCreate(savedInstanceState);
52 | setContentView(R.layout.activity_example);
53 | mLongUrl = (EditText) findViewById(R.id.long_url);
54 | mShortUrl = (EditText) findViewById(R.id.short_url);
55 | mShortenButton = (Button) findViewById(R.id.shorten_button);
56 | mLogoutButton = (Button) findViewById(R.id.logout_button);
57 | mLogoutButton.setOnClickListener(new View.OnClickListener() {
58 | @Override public void onClick(View v) {
59 | logout();
60 | }
61 | });
62 | mShortenButton.setOnClickListener(new View.OnClickListener() {
63 | @Override public void onClick(View v) {
64 | shortenUrl();
65 | }
66 | });
67 | ExampleApplication.AUTHENTICATION_MANAGER.addCallbacks(this);
68 | if (TextUtils.isEmpty(mAuthToken)) {
69 | authenticate();
70 | }
71 | }
72 |
73 | private void logout() {
74 | Account loggedInAccount = ExampleApplication.AUTHENTICATION_MANAGER.getSingleAccountForType(Constants.ACCOUNT_TYPE);
75 | if (loggedInAccount != null) {
76 | ExampleApplication.AUTHENTICATION_MANAGER.logout(loggedInAccount, Constants.AUTH_TOKEN_TYPE);
77 | }
78 | }
79 |
80 | @Override protected void onDestroy() {
81 | super.onDestroy();
82 | ExampleApplication.AUTHENTICATION_MANAGER.removeCallbacks(this);
83 | }
84 |
85 | private void authenticate() {
86 | ExampleApplication.AUTHENTICATION_MANAGER.authenticate(this, Constants.ACCOUNT_TYPE, Constants.AUTH_TOKEN_TYPE);
87 | }
88 |
89 | @Override public void onAuthenticationCanceled() {
90 | mAuthToken = null;
91 | Log.d(TAG, "onAuthenticationCanceled()");
92 | finish();
93 | }
94 |
95 | @Override public void onAuthenticationSuccessful(String authToken) {
96 | mAuthToken = authToken;
97 | Log.d(TAG, "onAuthenticationSuccessful(" + authToken + ")");
98 | }
99 |
100 | @Override public void onAuthenticationNetworkError() {
101 | mAuthToken = null;
102 | Log.d(TAG, "onAuthenticationNetworkError()");
103 | authenticate();
104 | }
105 |
106 | @Override public void onAuthenticationFailed(Exception e) {
107 | mAuthToken = null;
108 | Log.e(TAG, "onAuthenticationFailed()", e);
109 | authenticate();
110 | }
111 |
112 | @Override public void onAuthenticationInvalidated(String invalidatedAuthToken) {
113 | mAuthToken = null;
114 | Log.d(TAG, "onAuthenticationInvalidated(" + invalidatedAuthToken + ")");
115 | authenticate();
116 | }
117 |
118 | @Override public void onClick(View v) {
119 | shortenUrl();
120 | }
121 |
122 | private void shortenUrl() {
123 | String urlToShorten = mLongUrl.getText().toString();
124 | // TODO the url should be checked to be valid, etc...
125 | if (!TextUtils.isEmpty(urlToShorten)) {
126 | if (!urlToShorten.startsWith("http://")) {
127 | urlToShorten = "http://" + urlToShorten;
128 | }
129 | new AsyncTask() {
130 |
131 | @Override protected ShortenedUrl doInBackground(String... params) {
132 | return ExampleApplication.BITLY_SERVICE.shortenUrl(mAuthToken, params[0]);
133 | }
134 |
135 | @Override protected void onPostExecute(ShortenedUrl shortenResponse) {
136 | if (shortenResponse != null) {
137 | if (shortenResponse.getStatusCode() == null || shortenResponse.getStatusCode() == 200) {
138 | mShortUrl.setText(shortenResponse.getData().getUrl());
139 | } else {
140 | mShortUrl.setText("ERROR: " + shortenResponse.getStatusText());
141 | }
142 | }
143 | }
144 |
145 | }.execute(urlToShorten);
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/shiftconnects/android/auth/example/ExampleAuthenticatorService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.example;
18 |
19 | import com.shiftconnects.android.auth.AccountAuthenticator;
20 | import com.shiftconnects.android.auth.AccountAuthenticatorService;
21 |
22 | /**
23 | * Created by mattkranzler on 2/25/15.
24 | */
25 | public class ExampleAuthenticatorService extends AccountAuthenticatorService {
26 |
27 | @Override protected AccountAuthenticator getAccountAuthenticator() {
28 | return ExampleApplication.ACCOUNT_AUTHENTICATOR;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/shiftconnects/android/auth/example/ExampleLoginActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.example;
18 |
19 | import android.accounts.AccountAuthenticatorActivity;
20 | import android.animation.Animator;
21 | import android.animation.AnimatorListenerAdapter;
22 | import android.annotation.TargetApi;
23 | import android.app.LoaderManager.LoaderCallbacks;
24 | import android.content.CursorLoader;
25 | import android.content.Intent;
26 | import android.content.Loader;
27 | import android.database.Cursor;
28 | import android.net.Uri;
29 | import android.os.AsyncTask;
30 | import android.os.Build;
31 | import android.os.Bundle;
32 | import android.provider.ContactsContract;
33 | import android.text.TextUtils;
34 | import android.view.KeyEvent;
35 | import android.view.View;
36 | import android.view.View.OnClickListener;
37 | import android.view.inputmethod.EditorInfo;
38 | import android.widget.ArrayAdapter;
39 | import android.widget.AutoCompleteTextView;
40 | import android.widget.Button;
41 | import android.widget.EditText;
42 | import android.widget.TextView;
43 |
44 | import com.shiftconnects.android.auth.AuthenticatorActivity;
45 | import com.shiftconnects.android.auth.example.util.Constants;
46 |
47 | import java.util.ArrayList;
48 | import java.util.List;
49 |
50 |
51 | /**
52 | * A login screen that offers login via email/password.
53 | */
54 | public class ExampleLoginActivity extends AccountAuthenticatorActivity implements AuthenticatorActivity, LoaderCallbacks {
55 |
56 | /**
57 | * Keep track of the login task to ensure we can cancel it if requested.
58 | */
59 | private UserLoginTask mAuthTask = null;
60 |
61 | // UI references.
62 | private AutoCompleteTextView mEmailView;
63 | private EditText mPasswordView;
64 | private View mProgressView;
65 | private View mLoginFormView;
66 |
67 | @Override
68 | protected void onCreate(Bundle savedInstanceState) {
69 | super.onCreate(savedInstanceState);
70 | setContentView(R.layout.activity_login);
71 |
72 | // Set up the login form.
73 | mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
74 | populateAutoComplete();
75 |
76 | mPasswordView = (EditText) findViewById(R.id.password);
77 | mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
78 | @Override
79 | public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
80 | if (id == R.id.login || id == EditorInfo.IME_NULL) {
81 | attemptLogin();
82 | return true;
83 | }
84 | return false;
85 | }
86 | });
87 |
88 | Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
89 | mEmailSignInButton.setOnClickListener(new OnClickListener() {
90 | @Override
91 | public void onClick(View view) {
92 | attemptLogin();
93 | }
94 | });
95 |
96 | mLoginFormView = findViewById(R.id.login_form);
97 | mProgressView = findViewById(R.id.login_progress);
98 | }
99 |
100 | private void populateAutoComplete() {
101 | getLoaderManager().initLoader(0, null, this);
102 | }
103 |
104 |
105 | /**
106 | * Attempts to sign in or register the account specified by the login form.
107 | * If there are form errors (invalid email, missing fields, etc.), the
108 | * errors are presented and no actual login attempt is made.
109 | */
110 | public void attemptLogin() {
111 | if (mAuthTask != null) {
112 | return;
113 | }
114 |
115 | // Reset errors.
116 | mEmailView.setError(null);
117 | mPasswordView.setError(null);
118 |
119 | // Store values at the time of the login attempt.
120 | String email = mEmailView.getText().toString();
121 | String password = mPasswordView.getText().toString();
122 |
123 | boolean cancel = false;
124 | View focusView = null;
125 |
126 |
127 | // Check for a valid password, if the user entered one.
128 | if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
129 | mPasswordView.setError(getString(R.string.error_invalid_password));
130 | focusView = mPasswordView;
131 | cancel = true;
132 | }
133 |
134 | // Check for a valid email address.
135 | if (TextUtils.isEmpty(email)) {
136 | mEmailView.setError(getString(R.string.error_field_required));
137 | focusView = mEmailView;
138 | cancel = true;
139 | } else if (!isEmailValid(email)) {
140 | mEmailView.setError(getString(R.string.error_invalid_email));
141 | focusView = mEmailView;
142 | cancel = true;
143 | }
144 |
145 | if (cancel) {
146 | // There was an error; don't attempt login and focus the first
147 | // form field with an error.
148 | focusView.requestFocus();
149 | } else {
150 | // Show a progress spinner, and kick off a background task to
151 | // perform the user login attempt.
152 | showProgress(true);
153 | mAuthTask = new UserLoginTask(email, password);
154 | mAuthTask.execute((Void) null);
155 | }
156 | }
157 |
158 | private boolean isEmailValid(String email) {
159 | //TODO: Replace this with your own logic
160 | return true;
161 | }
162 |
163 | private boolean isPasswordValid(String password) {
164 | //TODO: Replace this with your own logic
165 | return password.length() > 4;
166 | }
167 |
168 | private void finishLogin(Intent intent) {
169 | setAccountAuthenticatorResult(intent.getExtras());
170 | setResult(RESULT_OK, intent);
171 | finish();
172 | }
173 |
174 | @Override public void onBackPressed() {
175 | setResult(RESULT_CANCELED);
176 | super.onBackPressed();
177 | }
178 |
179 | /**
180 | * Shows the progress UI and hides the login form.
181 | */
182 | @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
183 | public void showProgress(final boolean show) {
184 | // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
185 | // for very easy animations. If available, use these APIs to fade-in
186 | // the progress spinner.
187 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
188 | int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
189 |
190 | mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
191 | mLoginFormView.animate().setDuration(shortAnimTime).alpha(
192 | show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
193 | @Override
194 | public void onAnimationEnd(Animator animation) {
195 | mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
196 | }
197 | });
198 |
199 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
200 | mProgressView.animate().setDuration(shortAnimTime).alpha(
201 | show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
202 | @Override
203 | public void onAnimationEnd(Animator animation) {
204 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
205 | }
206 | });
207 | } else {
208 | // The ViewPropertyAnimator APIs are not available, so simply show
209 | // and hide the relevant UI components.
210 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
211 | mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
212 | }
213 | }
214 |
215 | @Override
216 | public Loader onCreateLoader(int i, Bundle bundle) {
217 | return new CursorLoader(this,
218 | // Retrieve data rows for the device user's 'profile' contact.
219 | Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
220 | ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
221 |
222 | // Select only email addresses.
223 | ContactsContract.Contacts.Data.MIMETYPE +
224 | " = ?", new String[]{ContactsContract.CommonDataKinds.Email
225 | .CONTENT_ITEM_TYPE},
226 |
227 | // Show primary email addresses first. Note that there won't be
228 | // a primary email address if the user hasn't specified one.
229 | ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
230 | }
231 |
232 | @Override
233 | public void onLoadFinished(Loader cursorLoader, Cursor cursor) {
234 | List emails = new ArrayList();
235 | cursor.moveToFirst();
236 | while (!cursor.isAfterLast()) {
237 | emails.add(cursor.getString(ProfileQuery.ADDRESS));
238 | cursor.moveToNext();
239 | }
240 |
241 | addEmailsToAutoComplete(emails);
242 | }
243 |
244 | @Override
245 | public void onLoaderReset(Loader cursorLoader) {
246 |
247 | }
248 |
249 | private interface ProfileQuery {
250 | String[] PROJECTION = {
251 | ContactsContract.CommonDataKinds.Email.ADDRESS,
252 | ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
253 | };
254 |
255 | int ADDRESS = 0;
256 | int IS_PRIMARY = 1;
257 | }
258 |
259 |
260 | private void addEmailsToAutoComplete(List emailAddressCollection) {
261 | //Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
262 | ArrayAdapter adapter =
263 | new ArrayAdapter(ExampleLoginActivity.this,
264 | android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
265 |
266 | mEmailView.setAdapter(adapter);
267 | }
268 |
269 | /**
270 | * Represents an asynchronous login/registration task used to authenticate
271 | * the user.
272 | */
273 | public class UserLoginTask extends AsyncTask {
274 |
275 | private final String mEmail;
276 | private final String mPassword;
277 |
278 | UserLoginTask(String email, String password) {
279 | mEmail = email;
280 | mPassword = password;
281 | }
282 |
283 | @Override
284 | protected Intent doInBackground(Void... params) {
285 | try {
286 | String authToken = ExampleApplication.AUTHENTICATION_MANAGER.loginWithUserNamePassword(
287 | mEmail,
288 | mPassword,
289 | Constants.ACCOUNT_TYPE,
290 | Constants.AUTH_TOKEN_TYPE,
291 | true
292 | );
293 | final Intent intent = new Intent();
294 | final Bundle data = new Bundle();
295 | final String accountType = getIntent().getStringExtra(KEY_ACCOUNT_TYPE);
296 | data.putString(KEY_ACCOUNT_NAME, mEmail);
297 | data.putString(KEY_ACCOUNT_TYPE, accountType);
298 | data.putString(KEY_AUTHTOKEN, authToken);
299 | intent.putExtras(data);
300 |
301 | return intent;
302 | } catch (Exception e) {
303 | return null;
304 | }
305 | }
306 |
307 | @Override
308 | protected void onPostExecute(final Intent intent) {
309 | mAuthTask = null;
310 | showProgress(false);
311 |
312 | if (intent != null) {
313 | finishLogin(intent);
314 | } else {
315 | mPasswordView.setError(getString(R.string.error_incorrect_password));
316 | mPasswordView.requestFocus();
317 | }
318 | }
319 |
320 | @Override
321 | protected void onCancelled() {
322 | mAuthTask = null;
323 | showProgress(false);
324 | }
325 | }
326 | }
327 |
328 |
329 |
330 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/shiftconnects/android/auth/example/model/BitlyOAuthToken.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.example.model;
18 |
19 | import com.google.gson.annotations.SerializedName;
20 | import com.shiftconnects.android.auth.model.OAuthToken;
21 |
22 | /**
23 | * Object representing an oauth token from bitly
24 | */
25 | public class BitlyOAuthToken implements OAuthToken {
26 |
27 | @SerializedName("access_token")
28 | private String accessToken;
29 |
30 | @SerializedName("refresh_token")
31 | private String refreshToken;
32 |
33 | @SerializedName("status_code")
34 | Integer statusCode;
35 |
36 | @SerializedName("data")
37 | String data;
38 |
39 | @SerializedName("status_txt")
40 | String statusText;
41 |
42 | @Override public String getAuthToken() {
43 | return accessToken;
44 | }
45 |
46 | @Override public String getRefreshToken() {
47 | return refreshToken;
48 | }
49 |
50 | @Override public long getExpiresIn() {
51 | return 0;
52 | }
53 |
54 | public Integer getStatusCode() {
55 | return statusCode;
56 | }
57 |
58 | public String getData() {
59 | return data;
60 | }
61 |
62 | public String getStatusText() {
63 | return statusText;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/shiftconnects/android/auth/example/model/ShortenedUrl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.example.model;
18 |
19 | import com.google.gson.annotations.SerializedName;
20 |
21 | /**
22 | * Object wrapping the response from shortening a url with bitly
23 | */
24 | public class ShortenedUrl {
25 |
26 | @SerializedName("data")
27 | private Data data;
28 |
29 | @SerializedName("status_code")
30 | private Integer statusCode;
31 |
32 | @SerializedName("status_txt")
33 | private String statusText;
34 |
35 | public Data getData() {
36 | return data;
37 | }
38 |
39 | public Integer getStatusCode() {
40 | return statusCode;
41 | }
42 |
43 | public String getStatusText() {
44 | return statusText;
45 | }
46 |
47 | public static class Data {
48 |
49 | @SerializedName("url")
50 | private String url;
51 |
52 | @SerializedName("long_url")
53 | private String longUrl;
54 |
55 | public String getUrl() {
56 | return url;
57 | }
58 |
59 | public String getLongUrl() {
60 | return longUrl;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/shiftconnects/android/auth/example/service/BitlyOAuthTokenService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.example.service;
18 |
19 | import android.util.Base64;
20 |
21 | import com.shiftconnects.android.auth.example.model.BitlyOAuthToken;
22 | import com.shiftconnects.android.auth.service.OAuthTokenService;
23 |
24 | import java.nio.charset.Charset;
25 |
26 | /**
27 | * Service which wraps a retrofit Bit.ly service in order to process a request before trying to retrieve
28 | * an oauth token
29 | */
30 | public class BitlyOAuthTokenService implements OAuthTokenService {
31 |
32 | private BitlyRetrofitService mRetrofitService;
33 |
34 | public BitlyOAuthTokenService(BitlyRetrofitService bitlyRetrofitService) {
35 | mRetrofitService = bitlyRetrofitService;
36 | }
37 |
38 | @Override public BitlyOAuthToken getTokenWithPassword(String clientId, String clientSecret, String userName, String password) {
39 | String clientIdAndSecret = clientId + ":" + clientSecret;
40 | String authorizationHeader = BitlyRetrofitService.BASIC + " " + Base64.encodeToString(clientIdAndSecret.getBytes(Charset.forName("UTF-8")), Base64.NO_WRAP);
41 | BitlyOAuthToken response = mRetrofitService.getToken(authorizationHeader, GrantType.password.name(), userName, password);
42 | if (response.getStatusCode() != null && response.getStatusCode() != 200) {
43 | return null;
44 | }
45 | return response;
46 | }
47 |
48 | @Override public BitlyOAuthToken getTokenWithRefreshToken(String clientId, String clientSecret, String refreshToken) {
49 | throw new UnsupportedOperationException("This service does not support refresh token auth");
50 | }
51 |
52 | @Override public BitlyOAuthToken getTokenWithClientCredentials(String clientId, String clientSecret) {
53 | throw new UnsupportedOperationException("This service does not support client credential auth");
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/shiftconnects/android/auth/example/service/BitlyRetrofitService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.example.service;
18 |
19 | import com.shiftconnects.android.auth.example.model.BitlyOAuthToken;
20 | import com.shiftconnects.android.auth.example.model.ShortenedUrl;
21 |
22 | import retrofit.http.Field;
23 | import retrofit.http.FormUrlEncoded;
24 | import retrofit.http.GET;
25 | import retrofit.http.Header;
26 | import retrofit.http.Headers;
27 | import retrofit.http.POST;
28 | import retrofit.http.Query;
29 |
30 | /**
31 | * Retrofit service which interfaces with the bitly api
32 | */
33 | public interface BitlyRetrofitService {
34 | public static final String ACCEPT_JSON_HEADER = "Accept: application/json";
35 | public static final String BASIC = "Basic";
36 |
37 | @Headers({ACCEPT_JSON_HEADER})
38 | @FormUrlEncoded
39 | @POST("/oauth/access_token")
40 | BitlyOAuthToken getToken(@Header("Authorization") String authorizationHeader,
41 | @Field("grant_type") String grantType,
42 | @Field("username") String username,
43 | @Field("password") String password);
44 |
45 |
46 | @Headers({ACCEPT_JSON_HEADER})
47 | @GET("/v3/shorten")
48 | ShortenedUrl shortenUrl(@Query("access_token") String accessToken, @Query("longUrl") String longUrl);
49 | }
50 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/shiftconnects/android/auth/example/util/Constants.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.shiftconnects.android.auth.example.util;
18 |
19 | /**
20 | * Created by mattkranzler on 2/25/15.
21 | */
22 | public class Constants {
23 | public static final String ACCOUNT_TYPE = "com.shiftconnects.android.auth.example.ACCOUNT_TYPE";
24 | public static final String AUTH_TOKEN_TYPE = "com.shiftconnects.android.auth.example.AUTH_TOKEN_TYPE";
25 | }
26 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/shiftconnects/android/auth/example/util/GsonConverter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 P100 OG, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.shiftconnects.android.auth.example.util;
17 |
18 | import com.google.gson.Gson;
19 | import com.google.gson.JsonParseException;
20 |
21 | import java.io.IOException;
22 | import java.io.InputStreamReader;
23 | import java.io.OutputStream;
24 | import java.io.UnsupportedEncodingException;
25 | import java.lang.reflect.Type;
26 |
27 | import retrofit.converter.ConversionException;
28 | import retrofit.converter.Converter;
29 | import retrofit.mime.MimeUtil;
30 | import retrofit.mime.TypedInput;
31 | import retrofit.mime.TypedOutput;
32 |
33 | /**
34 | * A {@link Converter} which uses GSON for serialization and deserialization of entities.
35 | *
36 | * @author Jake Wharton (jw@squareup.com)
37 | */
38 | public class GsonConverter implements Converter {
39 | private final Gson gson;
40 | private String charset;
41 |
42 | /**
43 | * Create an instance using the supplied {@link Gson} object for conversion. Encoding to JSON and
44 | * decoding from JSON (when no charset is specified by a header) will use UTF-8.
45 | */
46 | public GsonConverter(Gson gson) {
47 | this(gson, "UTF-8");
48 | }
49 |
50 | /**
51 | * Create an instance using the supplied {@link Gson} object for conversion. Encoding to JSON and
52 | * decoding from JSON (when no charset is specified by a header) will use the specified charset.
53 | */
54 | public GsonConverter(Gson gson, String charset) {
55 | this.gson = gson;
56 | this.charset = charset;
57 | }
58 |
59 | @Override public Object fromBody(TypedInput body, Type type) throws ConversionException {
60 | String charset = this.charset;
61 | if (body.mimeType() != null) {
62 | charset = MimeUtil.parseCharset(body.mimeType(), charset);
63 | }
64 | InputStreamReader isr = null;
65 | try {
66 | isr = new InputStreamReader(body.in(), charset);
67 | return gson.fromJson(isr, type);
68 | } catch (IOException e) {
69 | throw new ConversionException(e);
70 | } catch (JsonParseException e) {
71 | throw new ConversionException(e);
72 | } finally {
73 | if (isr != null) {
74 | try {
75 | isr.close();
76 | } catch (IOException ignored) {
77 | }
78 | }
79 | }
80 | }
81 |
82 | @Override public TypedOutput toBody(Object object) {
83 | try {
84 | return new JsonTypedOutput(gson.toJson(object).getBytes(charset), charset);
85 | } catch (UnsupportedEncodingException e) {
86 | throw new AssertionError(e);
87 | }
88 | }
89 |
90 | private static class JsonTypedOutput implements TypedOutput {
91 | private final byte[] jsonBytes;
92 | private final String mimeType;
93 |
94 | JsonTypedOutput(byte[] jsonBytes, String encode) {
95 | this.jsonBytes = jsonBytes;
96 | this.mimeType = "application/json; charset=" + encode;
97 | }
98 |
99 | @Override public String fileName() {
100 | return null;
101 | }
102 |
103 | @Override public String mimeType() {
104 | return mimeType;
105 | }
106 |
107 | @Override public long length() {
108 | return jsonBytes.length;
109 | }
110 |
111 | @Override public void writeTo(OutputStream out) throws IOException {
112 | out.write(jsonBytes);
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_example.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
17 |
18 |
22 |
23 |
26 |
27 |
32 |
33 |
38 |
39 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
21 |
22 |
27 |
28 |
33 |
34 |
42 |
43 |
54 |
55 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shiftconnects/android-auth-manager/2975434d14b9f55ab55f1c30cdc0d94e01b698d8/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shiftconnects/android-auth-manager/2975434d14b9f55ab55f1c30cdc0d94e01b698d8/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shiftconnects/android-auth-manager/2975434d14b9f55ab55f1c30cdc0d94e01b698d8/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shiftconnects/android-auth-manager/2975434d14b9f55ab55f1c30cdc0d94e01b698d8/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ExampleAuth
3 | URL to shorten
4 | Shorten
5 | Logout
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings_activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Email
5 | Password (optional)
6 | Sign in or register
7 | Sign in
8 |
9 | This email address is invalid
10 | This password is too short
11 | This password is incorrect
12 | This field is required
13 |
14 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/sample/src/main/res/xml/authenticator.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':auth-manager', ':sample'
2 |
--------------------------------------------------------------------------------
/versions.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shiftconnects/android-auth-manager/2975434d14b9f55ab55f1c30cdc0d94e01b698d8/versions.properties
--------------------------------------------------------------------------------