diff --git a/.gitignore b/.gitignore
index 39fb081..6259ca0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,9 @@
-*.iml
-.gradle
+.gradle/
/local.properties
-/.idea/workspace.xml
-/.idea/libraries
+
+.idea/
+
.DS_Store
-/build
-/captures
+/build/
+/captures/
.externalNativeBuild
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index 5b05804..0000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 3963879..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index ba113a8..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
deleted file mode 100644
index 7f68460..0000000
--- a/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index e619035..bc60760 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ MobileAuthentication supports Android API 23 and above so AES encryption can be
## Getting Started
project build.gradle:
-```
+```groovy
allprojects {
repositories {
...
@@ -16,7 +16,7 @@ allprojects {
```
app build.gradle:
-```
+```groovy
dependencies {
implementation "com.github.bcgov:mobile-authentication-android:"
}
@@ -25,20 +25,34 @@ dependencies {
### Prerequisites
A Red Hat Single Sign-On server component that is setup to handle an OAuth2 authorization code flow.
-### Android Manifest
-You will need to add this to your AndroidManifest and specify what custom schema you're using in your redirectUri.
-```xml
-
-
-
-
-
-
-
-
+### Handling Custom Scheme Redirects
+
+Specify the redirect value by setting the manifest placeholder value `appAuthRedirectScheme` in your project's build.gradle file:
+
+```groovy
+android {
+ defaultConfig {
+ ...
+
+ manifestPlaceholders = [
+ 'appAuthRedirectScheme': ''
+ ]
+
+ ...
+ }
+}
```
+Only the scheme is required:
+
+> 'appAuthRedirectScheme': 'custom-scheme' <- happiness
+
+Adding any more isn't valid:
+
+> 'appAuthRedirectScheme': 'custom-scheme://somePath' <- runtime issues
+
+***Note:*** you were previously required to add RedirectActivity with an intent filter to your application's manifest. This is no longer required because the functionality is handled by this manifest placeholder value.
+
## Usage
There are four main commands for handling tokens that will be called using the MobileAuthenticationClient class.
1. Authenticate
@@ -62,7 +76,7 @@ The parameters needed for the client can all be found on your Red Hat Single Sig
The hint param is optional and can be used to directly send the user to the specified login.
-We recommened you use a custom application schema for your redirectUri such as ://android
+We recommend you use a custom application schema for your redirectUri such as ://android
Please use the context of the activity in which you are going to call authenticate to create the client.
@@ -100,7 +114,7 @@ If the token is expired and the refresh token is NOT expired the token will auto
Exceptions:
1. If there is no token locally a `TokenNotFoundException` will be thrown in the onError of the TokenCallback. This means the user has not yet been authenticated so no token exists locally.
-2. If the token's refresh token is expired a `RefreshExpiredException` will be thrown in the onError of the TokenCallback. In this case the user will need to reauthenticate.
+2. If the token's refresh token is expired a `RefreshExpiredException` will be thrown in the onError of the TokenCallback. In this case the user will need to re-authenticate.
3. If the token does not have a refresh token then a `NoRefreshTokenException` will be thrown in the onError of the TokenCallback. This means the Token data being returned does not contain the required refreshToken for this lib to work.
Calling getToken:
@@ -130,7 +144,7 @@ client?.getToken(object: MobileAuthenticationClient.TokenCallback {
Refresh token will refresh the locally stored token.
Exceptions:
-1. If the token's refresh token is expired a `RefreshExpiredException` will be thrown in the onError of the TokenCallback. In this case the user will need to reauthenticate.
+1. If the token's refresh token is expired a `RefreshExpiredException` will be thrown in the onError of the TokenCallback. In this case the user will need to re-authenticate.
2. If the token does not have a refresh token then a `NoRefreshTokenException` will be thrown in the onError of the TokenCallback. This means the Token data being returned does not contain the required refreshToken for this lib to work.
Calling refreshToken:
diff --git a/app/build.gradle b/app/build.gradle
index 39c173c..c15782b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -5,14 +5,29 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
- compileSdkVersion 26
+
defaultConfig {
+ compileSdkVersion rootProject.ext.compileSdkVersion
applicationId "ca.bc.gov.mobileauthenticationandroidexample"
- minSdkVersion 23
- targetSdkVersion 26
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+
+ manifestPlaceholders = [
+ /*
+ Instead of an in AndroidManifest.xml the redirectUri scheme is
+ defined here. Only give the scheme itself:
+
+ 'appAuthRedirectScheme': 'custom-scheme' <- happiness
+
+ Adding any more isn't valid:
+
+ 'appAuthRedirectScheme': 'custom-scheme://somePath' <- runtime issues
+ */
+ 'appAuthRedirectScheme': 'secure-image'
+ ]
}
buildTypes {
release {
@@ -20,17 +35,18 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
+ namespace 'ca.bc.gov.mobileauthenticationandroidexample'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':mobileauthentication')
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
- implementation "com.android.support:appcompat-v7:26.1.0"
- implementation "com.android.support.constraint:constraint-layout:1.0.2"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
+ implementation 'androidx.appcompat:appcompat:1.0.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation "junit:junit:4.12"
- androidTestImplementation "com.android.support.test:runner:1.0.1"
- androidTestImplementation "com.android.support.test.espresso:espresso-core:3.0.1"
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6656cb8..fdd0973 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,5 @@
-
+
@@ -14,24 +13,14 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">
-
+
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/java/ca/bc/gov/mobileauthenticationandroidexample/MainActivity.kt b/app/src/main/java/ca/bc/gov/mobileauthenticationandroidexample/MainActivity.kt
index 398cd7a..ec9d5d5 100644
--- a/app/src/main/java/ca/bc/gov/mobileauthenticationandroidexample/MainActivity.kt
+++ b/app/src/main/java/ca/bc/gov/mobileauthenticationandroidexample/MainActivity.kt
@@ -1,7 +1,7 @@
package ca.bc.gov.mobileauthenticationandroidexample
import android.content.Intent
-import android.support.v7.app.AppCompatActivity
+import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import ca.bc.gov.mobileauthentication.MobileAuthenticationClient
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 092ef3c..70be574 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,9 +1,8 @@
-
-
+
diff --git a/build.gradle b/build.gradle
index 2354353..33fd42b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,14 +1,19 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.2.21'
+ ext.kotlinVersion = '1.7.10'
+
+ ext.compileSdkVersion = 28
+ ext.minSdkVersion = 23
+ ext.targetSdkVersion = 26
+
repositories {
google()
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.0.1'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath 'com.android.tools.build:gradle:7.2.0'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gradle.properties b/gradle.properties
index aac7c9b..9e6fce1 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,6 +9,8 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
+android.enableJetifier=true
+android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index eae11e6..081467e 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Feb 07 13:43:17 PST 2018
+#Mon Sep 19 11:37:24 PDT 2022
distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
+zipStoreBase=GRADLE_USER_HOME
diff --git a/mobileauthentication/build.gradle b/mobileauthentication/build.gradle
index b95f3e8..f2725e0 100644
--- a/mobileauthentication/build.gradle
+++ b/mobileauthentication/build.gradle
@@ -6,17 +6,14 @@ apply plugin: 'kotlin-android-extensions'
android {
- compileSdkVersion 26
defaultConfig {
- minSdkVersion 23
- targetSdkVersion 26
- compileSdkVersion 26
+ compileSdkVersion rootProject.ext.compileSdkVersion
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
buildToolsVersion '26.0.2'
- versionCode 1
- versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
@@ -31,25 +28,25 @@ android {
}
}
}
+ namespace 'ca.bc.gov.mobileauthentication'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation "com.android.support:appcompat-v7:26.1.0"
- implementation "com.android.support:customtabs:26.1.0"
- implementation "com.android.support.constraint:constraint-layout:1.0.2"
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.21"
+ implementation 'net.openid:appauth:0.11.1'
+
+ implementation 'androidx.appcompat:appcompat:1.0.0'
+ implementation 'androidx.browser:browser:1.0.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
implementation "io.reactivex.rxjava2:rxandroid:2.0.1"
implementation "io.reactivex.rxjava2:rxjava:2.1.7"
implementation "io.reactivex.rxjava2:rxkotlin:2.2.0"
- implementation "com.squareup.retrofit2:retrofit:2.3.0"
implementation "com.squareup.retrofit2:converter-gson:2.3.0"
- implementation "com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0"
- implementation "com.squareup.okhttp3:logging-interceptor:3.9.1"
testImplementation "junit:junit:4.12"
testImplementation "com.nhaarman:mockito-kotlin:1.5.0"
diff --git a/mobileauthentication/src/main/AndroidManifest.xml b/mobileauthentication/src/main/AndroidManifest.xml
index 02b8573..22a4fd5 100644
--- a/mobileauthentication/src/main/AndroidManifest.xml
+++ b/mobileauthentication/src/main/AndroidManifest.xml
@@ -1,12 +1,12 @@
-
+
-
+ android:theme="@style/AppTheme"
+ android:launchMode="singleInstance" />
+
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/MobileAuthenticationClient.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/MobileAuthenticationClient.kt
index 985bedc..af79231 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/MobileAuthenticationClient.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/MobileAuthenticationClient.kt
@@ -4,10 +4,8 @@ import android.app.Activity
import android.content.Context
import android.content.Intent
import android.preference.PreferenceManager
-import ca.bc.gov.mobileauthentication.common.Constants
import ca.bc.gov.mobileauthentication.common.exceptions.TokenNotFoundException
-import ca.bc.gov.mobileauthentication.common.utils.UrlUtils
-import ca.bc.gov.mobileauthentication.data.AuthApi
+import ca.bc.gov.mobileauthentication.data.AppAuthApi
import ca.bc.gov.mobileauthentication.data.models.Token
import ca.bc.gov.mobileauthentication.data.repos.token.TokenRepo
import ca.bc.gov.mobileauthentication.di.Injection
@@ -51,11 +49,9 @@ class MobileAuthenticationClient(
private val disposables = CompositeDisposable()
private val gson: Gson = Injection.provideGson()
- private val grantType: String = Constants.GRANT_TYPE_AUTH_CODE
- private val authApi: AuthApi = InjectionUtils.getAuthApi(UrlUtils.cleanBaseUrl(baseUrl))
+ private val appauthApi: AppAuthApi = AppAuthApi(context, baseUrl, realmName, authEndpoint, redirectUri, clientId, hint)
private val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
- private val tokenRepo: TokenRepo = InjectionUtils.getTokenRepo(
- authApi, realmName, grantType, redirectUri, clientId, sharedPrefs)
+ private val tokenRepo: TokenRepo = InjectionUtils.getTokenRepo(appauthApi, sharedPrefs)
private var passedRequestCode: Int = DEFAULT_REQUEST_CODE
@@ -64,6 +60,7 @@ class MobileAuthenticationClient(
*/
override fun authenticate(requestCode: Int) {
this.passedRequestCode = requestCode
+
Intent(context, RedirectActivity::class.java)
.putExtra(RedirectActivity.BASE_URL, baseUrl)
.putExtra(RedirectActivity.REALM_NAME, realmName)
@@ -78,21 +75,34 @@ class MobileAuthenticationClient(
* Handles on activity result and determines if the authentication was successful
* or an error occurred.
*/
- override fun handleAuthResult(
- requestCode: Int, resultCode: Int, data: Intent?,
- tokenCallback: TokenCallback) {
-
- if (resultCode == Activity.RESULT_OK && requestCode == passedRequestCode && data != null) {
- val success = data.getBooleanExtra(MobileAuthenticationClient.SUCCESS, false)
- if (success) {
- val tokenJson = data.getStringExtra(MobileAuthenticationClient.TOKEN_JSON)
- val token: Token = gson.fromJson(tokenJson, Token::class.java)
- tokenCallback.onSuccess(token)
- }
- else {
- val errorMessage = data.getStringExtra(MobileAuthenticationClient.ERROR_MESSAGE)
- tokenCallback.onError(Throwable(errorMessage))
- }
+ override fun handleAuthResult(requestCode: Int, resultCode: Int, data: Intent?,
+ tokenCallback: TokenCallback) {
+ if (requestCode != passedRequestCode)
+ return
+
+ if (resultCode != Activity.RESULT_OK) {
+ val message = data?.getStringExtra(ERROR_MESSAGE) ?: "Unknown authentication error"
+ tokenCallback.onError(Throwable(message))
+ return
+ }
+
+ if (data == null) {
+ tokenCallback.onError(Throwable("Result OK but authentication response is malformed"))
+ return
+ }
+
+ handleConsumerResult(data, tokenCallback)
+ }
+
+ private fun handleConsumerResult(data: Intent, tokenCallback: TokenCallback) {
+ val success = data.getBooleanExtra(SUCCESS, false)
+ if (success) {
+ val tokenJson = data.getStringExtra(TOKEN_JSON)
+ val token: Token = gson.fromJson(tokenJson, Token::class.java)
+ tokenCallback.onSuccess(token)
+ } else {
+ val errorMessage = data.getStringExtra(ERROR_MESSAGE)
+ tokenCallback.onError(Throwable(errorMessage))
}
}
@@ -104,7 +114,7 @@ class MobileAuthenticationClient(
* If a token does not exist a @see ca.bc.gov.mobileauthentication.common.exceptions.TokenNotFoundException will be thrown
*/
override fun getToken(tokenCallback: TokenCallback) {
- tokenRepo.getToken()
+ tokenRepo.getToken(null)
.firstElement()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribeBy(
@@ -123,7 +133,7 @@ class MobileAuthenticationClient(
/**
* Gets Token from local storage as a RxJava2 Observable
*/
- override fun getTokenAsObservable(): Observable = tokenRepo.getToken()
+ override fun getTokenAsObservable(): Observable = tokenRepo.getToken(null)
/**
* Refreshes token
@@ -148,7 +158,7 @@ class MobileAuthenticationClient(
/**
* Refreshes Token that is stored in local storage as a RxJava2 Observable
*/
- override fun refreshTokenAsObservable(): Observable = tokenRepo.getToken()
+ override fun refreshTokenAsObservable(): Observable = tokenRepo.getToken(null)
.flatMap { token -> tokenRepo.refreshToken(token) }
/**
@@ -190,6 +200,7 @@ class MobileAuthenticationClient(
companion object {
const val DEFAULT_REQUEST_CODE = 1012
+ const val APPAUTH_REQUEST_CODE = 2024
const val SUCCESS = "SUCCESS"
const val ERROR_MESSAGE = "ERROR_MESSAGE"
const val TOKEN_JSON = "TOKEN_JSON"
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/Constants.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/Constants.kt
index 4d997c4..be96932 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/Constants.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/Constants.kt
@@ -18,9 +18,6 @@ package ca.bc.gov.mobileauthentication.common
*
*/
object Constants {
- const val READ_TIME_OUT = 20L
- const val CONNECT_TIME_OUT = 20L
- const val GRANT_TYPE_AUTH_CODE = "authorization_code"
const val RESPONSE_TYPE_CODE = "code"
}
\ No newline at end of file
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/InvalidOperationException.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/InvalidOperationException.kt
index 48894b7..4dca56a 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/InvalidOperationException.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/InvalidOperationException.kt
@@ -19,6 +19,6 @@ package ca.bc.gov.mobileauthentication.common.exceptions
*/
class InvalidOperationException: Throwable() {
- override val message: String? get() = "Invalid operation"
+ override val message: String get() = "Invalid operation"
}
\ No newline at end of file
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/NoCodeException.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/NoCodeException.kt
index 00432f7..f081934 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/NoCodeException.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/NoCodeException.kt
@@ -18,6 +18,6 @@ package ca.bc.gov.mobileauthentication.common.exceptions
*
*/
class NoCodeException : Throwable() {
- override val message: String?
+ override val message: String
get() = "Code is required for getting token"
}
\ No newline at end of file
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/NoRefreshTokenException.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/NoRefreshTokenException.kt
index 7f8fbab..f050acd 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/NoRefreshTokenException.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/NoRefreshTokenException.kt
@@ -18,6 +18,6 @@ package ca.bc.gov.mobileauthentication.common.exceptions
*
*/
class NoRefreshTokenException : Throwable() {
- override val message: String?
+ override val message: String
get() = "No refresh token"
}
\ No newline at end of file
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/RefreshExpiredException.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/RefreshExpiredException.kt
index 177e8fb..fa252a1 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/RefreshExpiredException.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/RefreshExpiredException.kt
@@ -18,6 +18,6 @@ package ca.bc.gov.mobileauthentication.common.exceptions
*
*/
class RefreshExpiredException : Throwable() {
- override val message: String?
+ override val message: String
get() = "Refresh token has expired. Please get new token using authenticate."
}
\ No newline at end of file
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/TokenNotFoundException.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/TokenNotFoundException.kt
index 1f02363..9837936 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/TokenNotFoundException.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/common/exceptions/TokenNotFoundException.kt
@@ -18,6 +18,6 @@ package ca.bc.gov.mobileauthentication.common.exceptions
*
*/
class TokenNotFoundException : Throwable() {
- override val message: String?
+ override val message: String
get() = "No token found. Please call authenticate before trying to retrieve a token locally."
}
\ No newline at end of file
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/AppAuthApi.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/AppAuthApi.kt
new file mode 100644
index 0000000..f648b6f
--- /dev/null
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/AppAuthApi.kt
@@ -0,0 +1,123 @@
+package ca.bc.gov.mobileauthentication.data
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.browser.customtabs.CustomTabsIntent
+import ca.bc.gov.mobileauthentication.common.utils.UrlUtils
+import ca.bc.gov.mobileauthentication.data.models.Token
+import io.reactivex.Observable
+import io.reactivex.schedulers.Schedulers
+import net.openid.appauth.*
+
+
+class AppAuthApi(private val context: Context,
+ baseUrl: String,
+ private val realmName: String,
+ authEndpoint: String,
+ redirectUri: String,
+ private val clientId: String,
+ hint: String = "") {
+
+ companion object {
+ const val REFRESH_EXPIRES_IN = "refresh_expires_in"
+ const val NOT_BEFORE_POLICY = "not-before-policy"
+ const val SESSION_STATE = "session_state"
+ }
+
+ private fun buildTokenUrl(baseUrl: String): String {
+ return UrlUtils.cleanBaseUrl(baseUrl) + "auth/realms/$realmName/protocol/openid-connect/token"
+ }
+
+ private val authorizationConfig: AuthorizationServiceConfiguration
+ private val authorizationRequest: AuthorizationRequest
+
+ init {
+ authorizationConfig = AuthorizationServiceConfiguration(
+ Uri.parse(authEndpoint),
+ Uri.parse(buildTokenUrl(baseUrl)))
+
+ val reqBuilder = AuthorizationRequest.Builder(
+ authorizationConfig,
+ clientId,
+ ResponseTypeValues.CODE,
+ Uri.parse(redirectUri))
+
+ if (hint.isNotEmpty())
+ reqBuilder.setLoginHint(hint)
+
+ authorizationRequest = reqBuilder.build()
+ }
+
+ private fun buildRefreshTokenRequest(token: Token): TokenRequest {
+ return TokenRequest.Builder(authorizationConfig, clientId)
+ .setGrantType(GrantTypeValues.REFRESH_TOKEN)
+ .setRefreshToken(token.refreshToken)
+ .build()
+ }
+
+ private fun convertToToken(tokenResponse: TokenResponse): Token {
+ val current = System.currentTimeMillis()
+ val currentUnixTime = current / 1000
+
+ val accessExpirationUnixTimeMillis = tokenResponse.accessTokenExpirationTime
+ val accessExpirationUnixTime = accessExpirationUnixTimeMillis?.div(1000)
+ val accessExpiresInSeconds = accessExpirationUnixTime?.minus(currentUnixTime)
+ val accessExpiresInMillis = accessExpiresInSeconds?.times(1000)
+
+ val refreshExpiresInSeconds = tokenResponse.additionalParameters[REFRESH_EXPIRES_IN]?.toLong()
+ val refreshExpirationUnixTime = refreshExpiresInSeconds?.plus(currentUnixTime)
+ val refreshExpirationUnixTimeMillis = refreshExpirationUnixTime?.times(1000)
+
+ return Token(
+ tokenResponse.accessToken,
+ accessExpiresInMillis,
+ refreshExpiresInSeconds,
+ tokenResponse.refreshToken,
+ tokenResponse.tokenType,
+ tokenResponse.idToken,
+ tokenResponse.additionalParameters[NOT_BEFORE_POLICY]?.toLong(),
+ tokenResponse.additionalParameters[SESSION_STATE],
+ accessExpirationUnixTimeMillis,
+ refreshExpirationUnixTimeMillis
+ )
+ }
+
+ /**
+ * Using configuration provided to the Constructor: builds an Intent that will be used to
+ * initiate the OAuth authentication flow (using startActivityForResult)
+ */
+ fun getAuthRequestIntent(customTabsIntent: CustomTabsIntent? = null): Intent {
+ val service = AuthorizationService(context)
+
+ return if (customTabsIntent != null)
+ service.getAuthorizationRequestIntent(authorizationRequest, customTabsIntent)
+ else
+ service.getAuthorizationRequestIntent(authorizationRequest)
+ }
+
+ private fun performTokenRequest(tokenReq: TokenRequest): Observable {
+ return Observable.create {
+ AuthorizationService(context).performTokenRequest(tokenReq) { response, ex ->
+ if (response != null)
+ it.onNext(convertToToken(response))
+ else if (ex != null)
+ it.onError(ex)
+ }
+ }.subscribeOn(Schedulers.io())
+ }
+
+ /**
+ * Makes an OAuth request to exchange an Authorization code for an access token.
+ */
+ fun getToken(authResponse: AuthorizationResponse): Observable {
+ return performTokenRequest(authResponse.createTokenExchangeRequest())
+ }
+
+ /**
+ * Using the provided refresh token: makes an OAuth request to exchange for a new access token.
+ */
+ fun refreshToken(token: Token): Observable {
+ return performTokenRequest(buildRefreshTokenRequest(token))
+ }
+}
\ No newline at end of file
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/AuthApi.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/AuthApi.kt
deleted file mode 100644
index 90e8446..0000000
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/AuthApi.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-package ca.bc.gov.mobileauthentication.data
-
-import ca.bc.gov.mobileauthentication.data.models.Token
-import io.reactivex.Observable
-import retrofit2.http.Field
-import retrofit2.http.FormUrlEncoded
-import retrofit2.http.POST
-import retrofit2.http.Path
-
-/**
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Created by Aidan Laing on 2017-12-12.
- *
- */
-interface AuthApi {
-
- /**
- * OAuth2 get Token call
- */
- @POST("/auth/realms/{realm_name}/protocol/openid-connect/token")
- @FormUrlEncoded
- fun getToken(
- @Path("realm_name") realmName: String,
- @Field("grant_type") grantType: String,
- @Field("redirect_uri") redirectUri: String,
- @Field("client_id") client_id: String,
- @Field("code") code: String
- ): Observable
-
- /**
- * OAuth2 refresh Token call
- */
- @POST("/auth/realms/{realm_name}/protocol/openid-connect/token")
- @FormUrlEncoded
- fun refreshToken(
- @Path("realm_name") realmName: String,
- @Field("redirect_uri") redirectUri: String,
- @Field("client_id") client_id: String,
- @Field("refresh_token") refreshToken: String,
- @Field("grant_type") grantType: String = "refresh_token"
- ): Observable
-
-}
\ No newline at end of file
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/SecureSharedPrefs.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/SecureSharedPrefs.kt
index 887c526..7eb2410 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/SecureSharedPrefs.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/SecureSharedPrefs.kt
@@ -154,7 +154,7 @@ class SecureSharedPrefs(
}
companion object {
- private val AES_TRANSFORMATION = "AES/GCM/NoPadding"
+ private const val AES_TRANSFORMATION = "AES/GCM/NoPadding"
}
}
\ No newline at end of file
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenDataSource.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenDataSource.kt
index 1876049..4d754ed 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenDataSource.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenDataSource.kt
@@ -2,6 +2,7 @@ package ca.bc.gov.mobileauthentication.data.repos.token
import ca.bc.gov.mobileauthentication.data.models.Token
import io.reactivex.Observable
+import net.openid.appauth.AuthorizationResponse
/**
*
@@ -22,7 +23,7 @@ import io.reactivex.Observable
*/
interface TokenDataSource {
- fun getToken(code: String? = null): Observable
+ fun getToken(authResponse: AuthorizationResponse? = null): Observable
fun saveToken(token: Token): Observable
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenLocalDataSource.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenLocalDataSource.kt
index aec6933..fcf1d61 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenLocalDataSource.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenLocalDataSource.kt
@@ -4,6 +4,7 @@ import ca.bc.gov.mobileauthentication.common.exceptions.InvalidOperationExceptio
import ca.bc.gov.mobileauthentication.data.models.Token
import com.google.gson.Gson
import io.reactivex.Observable
+import net.openid.appauth.AuthorizationResponse
/**
*
@@ -45,7 +46,7 @@ private constructor(
/**
* Gets token from local db and returns
*/
- override fun getToken(code: String?): Observable {
+ override fun getToken(authResponse: AuthorizationResponse?): Observable {
return Observable.create { emitter ->
val tokenJson = secureSharedPrefs.getString(TOKEN_KEY)
if (tokenJson.isNotBlank()) {
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenRemoteDataSource.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenRemoteDataSource.kt
index 1aaf13e..16fff34 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenRemoteDataSource.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenRemoteDataSource.kt
@@ -3,9 +3,10 @@ package ca.bc.gov.mobileauthentication.data.repos.token
import ca.bc.gov.mobileauthentication.common.exceptions.InvalidOperationException
import ca.bc.gov.mobileauthentication.common.exceptions.NoCodeException
import ca.bc.gov.mobileauthentication.common.exceptions.NoRefreshTokenException
-import ca.bc.gov.mobileauthentication.data.AuthApi
+import ca.bc.gov.mobileauthentication.data.AppAuthApi
import ca.bc.gov.mobileauthentication.data.models.Token
import io.reactivex.Observable
+import net.openid.appauth.AuthorizationResponse
/**
*
@@ -25,27 +26,15 @@ import io.reactivex.Observable
*
*/
class TokenRemoteDataSource
-private constructor(
- private val authApi: AuthApi,
- private val realmName: String,
- private val grantType: String,
- private val redirectUri: String,
- private val clientId: String
-) : TokenDataSource {
+private constructor(private val authApi: AppAuthApi) : TokenDataSource {
companion object {
@Volatile
private var INSTANCE: TokenRemoteDataSource? = null
- fun getInstance(
- authApi: AuthApi,
- realmName: String,
- grantType: String,
- redirectUri: String,
- clientId: String): TokenRemoteDataSource = INSTANCE ?: synchronized(this) {
- INSTANCE ?: TokenRemoteDataSource(
- authApi, realmName, grantType, redirectUri, clientId).also { INSTANCE = it }
+ fun getInstance(authApi: AppAuthApi): TokenRemoteDataSource = INSTANCE ?: synchronized(this) {
+ INSTANCE ?: TokenRemoteDataSource(authApi).also { INSTANCE = it }
}
}
@@ -53,14 +42,11 @@ private constructor(
* Exchanges code for token using authentication api
* Returns error if code is null
*/
- override fun getToken(code: String?): Observable {
- if (code == null) return Observable.error(NoCodeException())
- return authApi.getToken(realmName, grantType, redirectUri, clientId, code)
- .map { token ->
- token.expiresAt = System.currentTimeMillis() + ((token.expiresIn ?: 0) * 1000)
- token.refreshExpiresAt = System.currentTimeMillis() + ((token.refreshExpiresIn ?: 0) * 1000)
- token
- }
+ override fun getToken(authResponse: AuthorizationResponse?): Observable {
+ if (authResponse == null)
+ return Observable.error(NoCodeException())
+
+ return authApi.getToken(authResponse)
}
/**
@@ -75,13 +61,10 @@ private constructor(
* Returns error if there is no refresh token
*/
override fun refreshToken(token: Token): Observable {
- val refreshToken = token.refreshToken ?: return Observable.error(NoRefreshTokenException())
- return authApi.refreshToken(realmName, redirectUri, clientId, refreshToken)
- .map { refreshedToken ->
- refreshedToken.expiresAt = System.currentTimeMillis() + ((refreshedToken.expiresIn ?: 0) * 1000)
- refreshedToken.refreshExpiresAt = System.currentTimeMillis() + ((refreshedToken.refreshExpiresIn ?: 0) * 1000)
- refreshedToken
- }
+ if (token.refreshToken == null)
+ return Observable.error(NoRefreshTokenException())
+
+ return authApi.refreshToken(token)
}
/**
* Invalid operation for remote data source
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenRepo.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenRepo.kt
index ef6c902..e40683a 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenRepo.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/data/repos/token/TokenRepo.kt
@@ -3,6 +3,7 @@ package ca.bc.gov.mobileauthentication.data.repos.token
import ca.bc.gov.mobileauthentication.common.exceptions.RefreshExpiredException
import ca.bc.gov.mobileauthentication.data.models.Token
import io.reactivex.Observable
+import net.openid.appauth.AuthorizationResponse
/**
*
@@ -52,12 +53,12 @@ private constructor(
* If token from local db refresh is expired then @see ca.bc.gov.mobileauthentication.common.exceptions.RefreshExpiredException will be thrown.
* If refresh token is not expired and token is expired then token will be refreshed and returned
*/
- override fun getToken(code: String?): Observable {
- return if (code != null) {
- remoteDataSource.getToken(code)
+ override fun getToken(authResponse: AuthorizationResponse?): Observable {
+ return if (authResponse != null) {
+ remoteDataSource.getToken(authResponse)
.flatMap { localDataSource.saveToken(it) }
} else {
- localDataSource.getToken()
+ localDataSource.getToken(null)
.flatMap {
when {
it.isRefreshExpired() -> Observable.error(RefreshExpiredException())
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/di/Injection.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/di/Injection.kt
index b0d47c2..9cbd201 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/di/Injection.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/di/Injection.kt
@@ -1,22 +1,10 @@
package ca.bc.gov.mobileauthentication.di
import android.content.SharedPreferences
-import ca.bc.gov.mobileauthentication.data.AuthApi
import ca.bc.gov.mobileauthentication.data.repos.token.SecureSharedPrefs
-import ca.bc.gov.mobileauthentication.data.repos.token.TokenLocalDataSource
-import ca.bc.gov.mobileauthentication.data.repos.token.TokenRemoteDataSource
-import ca.bc.gov.mobileauthentication.data.repos.token.TokenRepo
import com.google.gson.Gson
import com.google.gson.GsonBuilder
-import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
-import okhttp3.OkHttpClient
-import okhttp3.logging.HttpLoggingInterceptor
-import retrofit2.CallAdapter
-import retrofit2.Converter
-import retrofit2.Retrofit
-import retrofit2.converter.gson.GsonConverterFactory
import java.security.KeyStore
-import java.util.concurrent.TimeUnit
/**
*
@@ -37,25 +25,6 @@ import java.util.concurrent.TimeUnit
*/
object Injection {
- // OkHttpClient
- @JvmStatic
- fun provideOkHttpClient(
- readTimeOut: Long,
- connectTimeOut: Long,
- interceptor: HttpLoggingInterceptor
- ): OkHttpClient = OkHttpClient.Builder()
- .readTimeout(readTimeOut, TimeUnit.SECONDS)
- .connectTimeout(connectTimeOut, TimeUnit.SECONDS)
- .addInterceptor(interceptor)
- .build()
-
- // Logging interceptor
- @JvmStatic
- fun provideHttpLoggingInterceptor(
- loggingLevel: HttpLoggingInterceptor.Level
- ): HttpLoggingInterceptor = HttpLoggingInterceptor()
- .apply { level = loggingLevel }
-
// Gson
private var cachedGson: Gson? = null
@@ -65,51 +34,6 @@ object Injection {
.create()
.also { cachedGson = it }
- // Converter Factory
- @JvmStatic
- fun provideConverterFactory(gson: Gson): Converter.Factory = GsonConverterFactory.create(gson)
-
- // Call Adapter Factory
- private var cachedCallAdapterFactory: CallAdapter.Factory? = null
-
- @JvmStatic
- fun provideCallAdapterFactory(): CallAdapter.Factory = cachedCallAdapterFactory
- ?:
- RxJava2CallAdapterFactory.create()
- .also { cachedCallAdapterFactory = it }
-
- // Retrofit
- @JvmStatic
- fun provideRetrofit(
- apiDomain: String,
- okHttpClient: OkHttpClient,
- converterFactory: Converter.Factory,
- callAdapterFactory: CallAdapter.Factory
- ): Retrofit = Retrofit.Builder()
- .baseUrl(apiDomain)
- .client(okHttpClient)
- .addConverterFactory(converterFactory)
- .addCallAdapterFactory(callAdapterFactory)
- .build()
-
- // Auth Api
- @JvmStatic
- fun provideAuthApi(retrofit: Retrofit): AuthApi = retrofit.create(AuthApi::class.java)
-
- // Token Repo
- @JvmStatic
- fun provideTokenRepo(
- authApi: AuthApi,
- realmName: String,
- grantType: String,
- redirectUri: String,
- clientId: String,
- gson: Gson,
- secureSharedPrefs: SecureSharedPrefs
- ): TokenRepo = TokenRepo.getInstance(
- TokenRemoteDataSource.getInstance(authApi, realmName, grantType, redirectUri, clientId),
- TokenLocalDataSource.getInstance(gson, secureSharedPrefs))
-
// Secure shared prefs
@JvmStatic
fun provideSecureSharedPrefs(
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/di/InjectionUtils.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/di/InjectionUtils.kt
index 8eb5904..4607886 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/di/InjectionUtils.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/di/InjectionUtils.kt
@@ -1,18 +1,12 @@
package ca.bc.gov.mobileauthentication.di
import android.content.SharedPreferences
-import ca.bc.gov.mobileauthentication.data.AuthApi
-import ca.bc.gov.mobileauthentication.common.Constants
+import ca.bc.gov.mobileauthentication.data.AppAuthApi
import ca.bc.gov.mobileauthentication.data.repos.token.SecureSharedPrefs
import ca.bc.gov.mobileauthentication.data.repos.token.TokenLocalDataSource
import ca.bc.gov.mobileauthentication.data.repos.token.TokenRemoteDataSource
import ca.bc.gov.mobileauthentication.data.repos.token.TokenRepo
import com.google.gson.Gson
-import okhttp3.OkHttpClient
-import okhttp3.logging.HttpLoggingInterceptor
-import retrofit2.CallAdapter
-import retrofit2.Converter
-import retrofit2.Retrofit
import java.security.KeyStore
/**
@@ -34,41 +28,17 @@ import java.security.KeyStore
*/
object InjectionUtils {
- /**
- * Gets Auth Api with standard params
- */
- fun getAuthApi(
- apiDomain: String,
- gson: Gson = Injection.provideGson(),
- converterFactory: Converter.Factory = Injection.provideConverterFactory(gson),
- callAdapterFactory : CallAdapter.Factory = Injection.provideCallAdapterFactory(),
- readTimeOut: Long = Constants.READ_TIME_OUT,
- connectTimeOut: Long = Constants.CONNECT_TIME_OUT,
- loggingLevel: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.BODY,
- httpLoggingInterceptor: HttpLoggingInterceptor = Injection.provideHttpLoggingInterceptor(
- loggingLevel),
- okHttpClient: OkHttpClient = Injection.provideOkHttpClient(
- readTimeOut, connectTimeOut, httpLoggingInterceptor),
- retrofit: Retrofit = Injection.provideRetrofit(
- apiDomain, okHttpClient, converterFactory, callAdapterFactory)
- ): AuthApi = Injection.provideAuthApi(retrofit)
-
/**
* Gets Token Repo with standard params
*/
fun getTokenRepo(
- authApi: AuthApi,
- realmName: String,
- grantType: String,
- redirectUri: String,
- clientId: String,
+ authApi: AppAuthApi,
sharedPreferences: SharedPreferences,
gson: Gson = Injection.provideGson(),
keyStore: KeyStore = Injection.provideKeyStore(),
secureSharedPrefs: SecureSharedPrefs = Injection.provideSecureSharedPrefs(keyStore, sharedPreferences)
): TokenRepo = TokenRepo.getInstance(
- TokenRemoteDataSource.getInstance(authApi, realmName, grantType, redirectUri, clientId),
+ TokenRemoteDataSource.getInstance(authApi),
TokenLocalDataSource.getInstance(gson, secureSharedPrefs)
)
-
}
\ No newline at end of file
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/screens/redirect/RedirectActivity.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/screens/redirect/RedirectActivity.kt
index af1fdb4..4ecedb7 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/screens/redirect/RedirectActivity.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/screens/redirect/RedirectActivity.kt
@@ -2,20 +2,20 @@ package ca.bc.gov.mobileauthentication.screens.redirect
import android.app.Activity
import android.content.Intent
-import android.net.Uri
import android.os.Bundle
import android.preference.PreferenceManager
-import android.widget.Toast
-import ca.bc.gov.mobileauthentication.R
-import ca.bc.gov.mobileauthentication.di.InjectionUtils
-import android.support.customtabs.CustomTabsIntent
-import android.support.v4.content.ContextCompat
-import android.support.v7.app.AppCompatActivity
import android.view.View
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.browser.customtabs.CustomTabColorSchemeParams
+import androidx.browser.customtabs.CustomTabsIntent
+import androidx.core.content.ContextCompat
import ca.bc.gov.mobileauthentication.MobileAuthenticationClient
+import ca.bc.gov.mobileauthentication.R
import ca.bc.gov.mobileauthentication.common.Constants
-import ca.bc.gov.mobileauthentication.common.utils.UrlUtils
+import ca.bc.gov.mobileauthentication.data.AppAuthApi
import ca.bc.gov.mobileauthentication.di.Injection
+import ca.bc.gov.mobileauthentication.di.InjectionUtils
import kotlinx.android.synthetic.main.activity_login.*
/**
@@ -41,6 +41,8 @@ class RedirectActivity : AppCompatActivity(), RedirectContract.View {
override var loading: Boolean = false
+ private lateinit var appauthApi: AppAuthApi
+
companion object {
const val BASE_URL = "BASE_URL"
const val REALM_NAME = "REALM_NAME"
@@ -94,13 +96,11 @@ class RedirectActivity : AppCompatActivity(), RedirectContract.View {
}
// Building presenter params
- val grantType = Constants.GRANT_TYPE_AUTH_CODE
val responseType = Constants.RESPONSE_TYPE_CODE
- val authApi = InjectionUtils.getAuthApi(UrlUtils.cleanBaseUrl(baseUrl))
+ appauthApi = AppAuthApi(this, baseUrl, realmName, authEndpoint, redirectUri, clientId, hint)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
- val tokenRepo = InjectionUtils.getTokenRepo(
- authApi, realmName, grantType, redirectUri, clientId, sharedPreferences)
+ val tokenRepo = InjectionUtils.getTokenRepo(appauthApi, sharedPreferences)
val gson = Injection.provideGson()
@@ -128,10 +128,15 @@ class RedirectActivity : AppCompatActivity(), RedirectContract.View {
checkIntentForRedirect(intent)
}
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+
+ if (requestCode == MobileAuthenticationClient.APPAUTH_REQUEST_CODE)
+ checkIntentForRedirect(data)
+ }
+
private fun checkIntentForRedirect(intent: Intent?) {
- if (intent != null && intent.action == Intent.ACTION_VIEW && intent.data != null) {
- presenter?.redirectReceived(intent.data.toString())
- }
+ presenter?.redirectReceived(intent)
}
// Loading
@@ -162,12 +167,18 @@ class RedirectActivity : AppCompatActivity(), RedirectContract.View {
* Goes to Chrome custom tab
*/
override fun loadWithChrome(url: String) {
- CustomTabsIntent.Builder()
- .addDefaultShareMenuItem()
+ val colorScheme = CustomTabColorSchemeParams.Builder()
.setToolbarColor(ContextCompat.getColor(this, R.color.colorPrimary))
+ .build()
+
+ val customTabsIntent = CustomTabsIntent.Builder()
+ .setShareState(CustomTabsIntent.SHARE_STATE_OFF)
+ .setDefaultColorSchemeParams(colorScheme)
.setShowTitle(true)
.build()
- .launchUrl(this, Uri.parse(url))
+
+ val authIntent = appauthApi.getAuthRequestIntent(customTabsIntent)
+ startActivityForResult(authIntent, MobileAuthenticationClient.APPAUTH_REQUEST_CODE)
}
// Results
@@ -184,4 +195,9 @@ class RedirectActivity : AppCompatActivity(), RedirectContract.View {
data.putExtra(MobileAuthenticationClient.TOKEN_JSON, tokenJson)
setResult(Activity.RESULT_OK, data)
}
+
+ override fun onBackPressed() {
+ setResultError("Login cancelled by user")
+ super.onBackPressed()
+ }
}
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/screens/redirect/RedirectContract.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/screens/redirect/RedirectContract.kt
index 5ae4acf..a151248 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/screens/redirect/RedirectContract.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/screens/redirect/RedirectContract.kt
@@ -1,5 +1,6 @@
package ca.bc.gov.mobileauthentication.screens.redirect
+import android.content.Intent
import ca.bc.gov.mobileauthentication.common.BasePresenter
import ca.bc.gov.mobileauthentication.common.BaseView
@@ -43,7 +44,7 @@ interface RedirectContract {
interface Presenter: BasePresenter {
fun loginClicked()
- fun redirectReceived(redirectUrl: String)
+ fun redirectReceived(redirectIntent: Intent?)
}
}
\ No newline at end of file
diff --git a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/screens/redirect/RedirectPresenter.kt b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/screens/redirect/RedirectPresenter.kt
index 276fcf1..a90323a 100644
--- a/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/screens/redirect/RedirectPresenter.kt
+++ b/mobileauthentication/src/main/java/ca/bc/gov/mobileauthentication/screens/redirect/RedirectPresenter.kt
@@ -1,6 +1,6 @@
package ca.bc.gov.mobileauthentication.screens.redirect
-import ca.bc.gov.mobileauthentication.common.utils.UrlUtils
+import android.content.Intent
import ca.bc.gov.mobileauthentication.data.repos.token.TokenRepo
import com.google.gson.Gson
import io.reactivex.android.schedulers.AndroidSchedulers
@@ -8,6 +8,7 @@ import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.schedulers.Schedulers
+import net.openid.appauth.AuthorizationResponse
/**
*
@@ -61,20 +62,23 @@ class RedirectPresenter(
"$authEndpoint?response_type=$responseType&client_id=$clientId&redirect_uri=$redirectUri&kc_idp_hint=$hint"
// Redirect
- override fun redirectReceived(redirectUrl: String) {
- if (!redirectUrl.contains("code=".toRegex())) {
+ override fun redirectReceived(redirectIntent: Intent?) {
+ if (redirectIntent == null)
return
- }
- val code = UrlUtils.extractCode(redirectUrl)
- getToken(code)
+ val response = AuthorizationResponse.fromIntent(redirectIntent) ?: return
+
+ if (response.authorizationCode == null || response.authorizationCode!!.isEmpty())
+ return
+
+ getToken(response)
}
/**
* Gets token remotely using Authorization Code and saves locally
*/
- fun getToken(code: String) {
- tokenRepo.getToken(code)
+ fun getToken(authResponse: AuthorizationResponse) {
+ tokenRepo.getToken(authResponse)
.firstOrError()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
diff --git a/mobileauthentication/src/main/res/layout/activity_login.xml b/mobileauthentication/src/main/res/layout/activity_login.xml
index 4d41546..0466c09 100644
--- a/mobileauthentication/src/main/res/layout/activity_login.xml
+++ b/mobileauthentication/src/main/res/layout/activity_login.xml
@@ -1,5 +1,5 @@
-
-
\ No newline at end of file
+
\ No newline at end of file