From e2b0efbb37d991f5c13eed26ddfc16e4bef604bb Mon Sep 17 00:00:00 2001 From: aman-alfresco <87360222+aman-alfresco@users.noreply.github.com> Date: Tue, 21 Jun 2022 09:57:11 +0530 Subject: [PATCH] removed unused code --- alfresco-auth/.gitignore | 1 - alfresco-auth/README.md | 172 --------- alfresco-auth/build.gradle | 27 -- alfresco-auth/proguard-rules.pro | 21 -- alfresco-auth/src/main/AndroidManifest.xml | 12 - .../main/java/com/alfresco/auth/AuthConfig.kt | 62 ---- .../java/com/alfresco/auth/AuthInterceptor.kt | 280 --------------- .../main/java/com/alfresco/auth/AuthType.kt | 31 -- .../java/com/alfresco/auth/Credentials.kt | 21 -- .../com/alfresco/auth/DiscoveryService.kt | 124 ------- .../auth/data/ContentServerDetails.kt | 51 --- .../alfresco/auth/data/MutableLiveEvent.kt | 73 ---- .../com/alfresco/auth/pkce/PkceAuthService.kt | 325 ------------------ .../auth/pkce/PkceConnectionBuilder.kt | 39 --- .../auth/pkce/RedirectUriReceiverActivity.kt | 46 --- .../auth/ui/AuthenticationActivity.kt | 181 ---------- .../alfresco/auth/ui/EndSessionActivity.kt | 83 ----- .../java/com/alfresco/auth/ui/LiveData.kt | 11 - .../auth/data/ContentServerDetailsTests.kt | 59 ---- .../alfresco/auth/pkce/PkceServiceTests.kt | 95 ----- app/build.gradle | 9 - 21 files changed, 1723 deletions(-) delete mode 100644 alfresco-auth/.gitignore delete mode 100644 alfresco-auth/README.md delete mode 100644 alfresco-auth/build.gradle delete mode 100644 alfresco-auth/proguard-rules.pro delete mode 100644 alfresco-auth/src/main/AndroidManifest.xml delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/AuthConfig.kt delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/AuthInterceptor.kt delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/AuthType.kt delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/Credentials.kt delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/DiscoveryService.kt delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/data/ContentServerDetails.kt delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/data/MutableLiveEvent.kt delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/pkce/PkceAuthService.kt delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/pkce/PkceConnectionBuilder.kt delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/pkce/RedirectUriReceiverActivity.kt delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/ui/AuthenticationActivity.kt delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/ui/EndSessionActivity.kt delete mode 100644 alfresco-auth/src/main/java/com/alfresco/auth/ui/LiveData.kt delete mode 100644 alfresco-auth/src/test/java/com/alfresco/auth/data/ContentServerDetailsTests.kt delete mode 100644 alfresco-auth/src/test/java/com/alfresco/auth/pkce/PkceServiceTests.kt diff --git a/alfresco-auth/.gitignore b/alfresco-auth/.gitignore deleted file mode 100644 index 796b96d1c..000000000 --- a/alfresco-auth/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/alfresco-auth/README.md b/alfresco-auth/README.md deleted file mode 100644 index 50d7afced..000000000 --- a/alfresco-auth/README.md +++ /dev/null @@ -1,172 +0,0 @@ -# Authentication - -The **auth** module offers several abstractions to work with the Alfresco Identity Service and Basic Authentication. - -The easiest way to integrate it is by using the abstract activities we have provided. - -Simply extend the classes and implement the abstract methods. - -```kotlin -class MyLoginActivity() : AuthenticationActivity() { - override val viewModel: MyLoginViewModel by viewModels - - override fun onCredentials(credentials: Credentials) { - // Called on successful login - } - - override fun onError(error: String) { - // Called on login error - } -} -``` - -When logging in as a first step you should call `viewModel.checkAuthType(endpoint, authConfig, onResult)` to identify if an Alfresco instance is running at an URL and which authentication method it supports. - -### Basic Authentication (Not Recommended) - -For basic authentication build your own UI to collect credentials. - -On login call `viewModel.basicAuth(username, password)` which will compute an `AuthInterceptor` -compatible state and return it via `onCredentials`. - -**Note**: `viewModel.basicAuth` does not provide any crendential validation. It's at this point where you could fetch the user's profile or permissions to complete the authentication process and validate the crendetials. - -### SSO Authentication - -For SSO, your activity will have to present the user a WebView to log in to. On success the session information will be returned to the app. - -To trigger the process call `viewModel.pkceLogin()` which will prepare the auth service and then present the SSO WebView to the user: -```kotlin -class MyLoginActivity() : AuthenticationActivity() { - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - loginButton.setOnClickListener { viewModel.pkceLogin(endpoint, authConfig) } - } - ... -} -``` -Upon successful login the server will callback with your token via `onCredentials`. If any issue occurred it will trigger `onError` or call `onPkceAuthCancelled` if the user cancelled the process. - -To ensure a token is returned you'll need to declare the callback uri used in the process in your **AndroidManifest.xml**: -``` - - - - - - - - - - -``` -This **needs to match** your configuration in the Identity Service and the one provided in your **AuthConfig** - -### After Authentication - -Because authentication is an independent step we **strongly** recommend you do extra validation after receiving **onCredentials** before letting the user into the app. - -Things you could check at this time, could be profile information or permissions, which would require calling the Alfresco service and thus verifying your authentication session is working correctly. - -### Logging out - -While normally you could just destroy the persisted credentials, in case of SSO the session needs to be invalidated or the user will log back in without a credentials prompt. - -First, extend `EndSessionActivity` and provide the credentials you were previously given. - -```kotlin -class MyLogoutViewModel(context: Context, authType: AuthType?, authState: String, authConfig: AuthConfig) - : EndSessionViewModel(context, authType, authState, authConfig) { - ... -} - -class MyLogoutActivity() : EndSessionActivity() { - override val viewModel: MyLogoutViewModel by viewModels -} -``` - -Then, to logout, call `startActivityForResult`: - -```kotlin -fun logout() { - val intent = Intent(this, LogoutActivity::class.java) - startActivityForResult(intent, REQUEST_CODE) -} - -override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == REQUEST_CODE) - if (resultCode == Activity.RESULT_OK) { - // success - } else { - // failure - } - } else { - super.onActivityResult(requestCode, resultCode, data) - } -} -``` - -### Authentication Interceptor - -During the lifetime of a session it may become invalid. - -While using SSO the refresh token may expire or a remote actor may invalidate the user's session in the Identity Service. - -Even during basic authentication this may happen if the password gets changed. - -To help with this we provide the `AuthInterceptor` class that works with [OkHttp](https://square.github.io/okhttp/). - -Simply create it, attach it to your OkHttp session and register your listener to get notified for changes in the authentication state. - -``` kotlin -val authInterceptor = AuthInterceptor( - context, - username, - authType, - authState, - authConfig -) - -val okHttpClient: OkHttpClient = OkHttpClient() - .newBuilder() - .addInterceptor(authInterceptor) - .build() -``` - -Creation takes the same parameters we provide via the `onCredentials()` callback. - -When a request fails due to authentication problems we call back with `onAuthFailure()`. - -In case of SSO this class also takes care of automatic token refresh, and also calls back with `onAuthStateChange()` so you can persist the new session information. - -**Note: all** callbacks from the `authInterceptor` are being called on the OkHttp I/O thread. Before proceeding with UI work please transfer your work to the main thread using something like `runOnUiThread()` - -### Re-authenticating - -As mentioned before a session may become invalid for various reasons. - -When getting `onAuthFailure()` we recommend you prompt the user that they'll have to relogin and reuse the same `MyLoginActivity` created above to collect the user's credentials. - -For basic authentication it's up to you to figure out the re-authentication flow. - -For SSO we already provide a special re-authentication flow. - -To trigger it call `viewModel.pkceLogin()` but this time also provide the existing `authState`. During the process you can query `viewModel.isRelogin` if you need to figure out if the user is doing re-authentication. - -```kotlin -class MyLoginActivity() : AuthenticationActivity() { - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - reLoginButton.setOnClickListener { - viewModel.pkceLogin(endpoint, authConfig, authState) - } - } - ... -} -``` - -On success the new session will be returned again via `onCredentials()` and after updating the stored credentials you can let the user resume their activities. diff --git a/alfresco-auth/build.gradle b/alfresco-auth/build.gradle deleted file mode 100644 index 4d4959798..000000000 --- a/alfresco-auth/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' -apply plugin: 'org.jetbrains.dokka' - -android { - defaultConfig { - versionName "0.8.0" - } -} - -dependencies { - implementation libs.appauth - implementation libs.jwtdecode - - implementation libs.kotlin.serialization.json - - implementation libs.androidx.appcompat - implementation libs.androidx.lifecycle.viewmodelKtx - implementation libs.androidx.browser - - implementation libs.okhttp - - testImplementation libs.junit -} - -apply from: "$rootDir/config/publish.gradle" diff --git a/alfresco-auth/proguard-rules.pro b/alfresco-auth/proguard-rules.pro deleted file mode 100644 index f1b424510..000000000 --- a/alfresco-auth/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/alfresco-auth/src/main/AndroidManifest.xml b/alfresco-auth/src/main/AndroidManifest.xml deleted file mode 100644 index 1873472b2..000000000 --- a/alfresco-auth/src/main/AndroidManifest.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/AuthConfig.kt b/alfresco-auth/src/main/java/com/alfresco/auth/AuthConfig.kt deleted file mode 100644 index 463c68794..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/AuthConfig.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.alfresco.auth - -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException -import kotlinx.serialization.json.Json - -/** - * Data class holding authentication configuration. - */ -@Serializable -data class AuthConfig( - /** - * Defines if the connection should be https or not - */ - var https: Boolean, - - /** - * The id of the client - */ - var clientId: String, - - /** - * The realm. Used to generate the final url. - * The place of realm should be here https://identity-service/auth/realms/REALM/ - */ - var realm: String, - - /** - * The redirect url - */ - var redirectUrl: String, - - /** - * Port for the network connection - */ - var port: String, - - /** - * Path to content service - */ - var contentServicePath: String -) { - /** - * Convenience method for JSON serialization. - */ - fun jsonSerialize(): String { - return Json.encodeToString(serializer(), this) - } - - companion object { - /** - * Convenience method for deserializing a JSON string representation. - */ - @JvmStatic fun jsonDeserialize(str: String): AuthConfig? { - return try { - Json.decodeFromString(serializer(), str) - } catch (ex: SerializationException) { - null - } - } - } -} diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/AuthInterceptor.kt b/alfresco-auth/src/main/java/com/alfresco/auth/AuthInterceptor.kt deleted file mode 100644 index 5d0ec7df7..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/AuthInterceptor.kt +++ /dev/null @@ -1,280 +0,0 @@ -package com.alfresco.auth - -import android.content.Context -import android.util.Base64 -import com.alfresco.auth.pkce.PkceAuthService -import java.lang.Exception -import java.lang.ref.WeakReference -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancelChildren -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import net.openid.appauth.AuthState -import okhttp3.Interceptor -import okhttp3.Request -import okhttp3.Response -import org.json.JSONException - -/** - * OkHttp [Interceptor] which deals with managing session information and attaching authentication headers. - */ -class AuthInterceptor( - private val context: Context, - private val accountId: String, - typeString: String, - stateString: String, - config: String -) : Interceptor { - - private val localScope = CoroutineScope(Dispatchers.IO) - private var listener: Listener? = null - private val authType = AuthType.fromValue(typeString) - private val provider: Provider - - init { - requireNotNull(authType) - - provider = when (authType) { - AuthType.BASIC -> BasicProvider(stateString) - AuthType.PKCE -> PkceProvider(stateString, config) - AuthType.UNKNOWN -> PlainProvider() - } - } - - /** - * Associates event [listener] with current object. - */ - fun setListener(listener: Listener) { - this.listener = listener - } - - /** - * Call to cleanup any running tasks before the object is GCed to avoid any leaks. - */ - fun finish() { - this.provider.finish() - } - - override fun intercept(chain: Interceptor.Chain): Response = provider.intercept(chain) - - private interface Provider { - - fun intercept(chain: Interceptor.Chain): Response - - fun finish() - } - - private inner class PlainProvider : Provider { - - override fun intercept(chain: Interceptor.Chain): Response { - return chain.proceed(chain.request()) - } - - override fun finish() { - // no-op - } - } - - private inner class BasicProvider(private var credentials: String) : Provider { - - override fun intercept(chain: Interceptor.Chain): Response { - val response = chain.proceed(AuthType.BASIC, credentials) - - // When unauthorized notify of failure - if (response.code == HTTP_RESPONSE_401_UNAUTHORIZED) { - listener?.onAuthFailure(accountId) - } - - return response - } - - override fun finish() { - // no-op - } - } - - private inner class PkceProvider(stateString: String, configString: String) : Provider { - - private val pkceAuthService: PkceAuthService - private var lastRefresh = 0L - private var scheduledRefreshJob: Job? = null - private var expirationTime: Long = 0L - - init { - val state = try { AuthState.jsonDeserialize(stateString) } catch (ex: JSONException) { null } - val config = AuthConfig.jsonDeserialize(configString) - - requireNotNull(state) - requireNotNull(config) - - pkceAuthService = PkceAuthService(context, state, config) - } - - @Synchronized override fun finish() { - cancelScheduledTokenRefresh() - localScope.coroutineContext.cancelChildren() - } - - override fun intercept(chain: Interceptor.Chain): Response { - var state = pkceAuthService.getAuthState() ?: return chain.proceed(chain.request()) - - // Preemptive token refresh when close to expiration - refreshTokenIfNeeded(state)?.let { state = it } - - var response = chain.proceed(AuthType.PKCE, state.accessToken) - - // When unauthorized try to refresh - if (response.code == HTTP_RESPONSE_401_UNAUTHORIZED) { - val newState = refreshTokenNow() - - if (newState != null) { - response.close() - response = chain.proceed(AuthType.PKCE, newState.accessToken) - } - } - - // If still error notify listener of failure - if (response.code == HTTP_RESPONSE_401_UNAUTHORIZED) { - listener?.onAuthFailure(accountId) - } - - return response - } - - @Synchronized private fun refreshTokenNow(): AuthState? { - val state = pkceAuthService.getAuthState() ?: return null - - // Another thread might've refreshed the token already - val current = System.currentTimeMillis() - if (current - lastRefresh < REFRESH_THROTTLE_DELAY) { - return state - } - - cancelScheduledTokenRefresh() - lastRefresh = current - - val result = runBlocking { runTokenRefresh() } - - if (result != null) { - scheduleTokenRefresh(result) - } - - return result - } - - @Synchronized private fun refreshTokenIfNeeded(state: AuthState): AuthState? { - val expiration = state.accessTokenExpirationTime ?: return null - val delta = expiration - System.currentTimeMillis() - - if (delta < REFRESH_DELTA_BEFORE_EXPIRY) { - return refreshTokenNow() - } else { - scheduleTokenRefresh(state) - } - - return null - } - - @Synchronized fun scheduleTokenRefresh(state: AuthState) { - val expiration = state.accessTokenExpirationTime ?: return - - val delta = expiration - System.currentTimeMillis() - REFRESH_DELTA_BEFORE_EXPIRY - if (delta < 0) return - if (expirationTime != expiration) { - expirationTime = expiration - - cancelScheduledTokenRefresh() - val weakThis = WeakReference(this) - scheduledRefreshJob = localScope.launch { - delay(delta) - if (isActive) { - weakThis.get()?.refreshTokenNow() - } - } - } - } - - private fun cancelScheduledTokenRefresh() { - scheduledRefreshJob?.cancel() - scheduledRefreshJob = null - } - - private suspend fun runTokenRefresh(): AuthState? { - return try { - pkceAuthService.refreshToken() - val state = pkceAuthService.getAuthState() - state?.jsonSerializeString()?.let { - listener?.onAuthStateChange(accountId, it) - } - return state - } catch (ex: Exception) { - null - } - } - } - - private fun Interceptor.Chain.proceed(type: AuthType, token: String?): Response { - val headerValue = when (type) { - AuthType.BASIC -> "Basic $token" - AuthType.PKCE -> "Bearer $token" - AuthType.UNKNOWN -> null - } - return proceedWithAuthorization(headerValue) - } - - private fun Interceptor.Chain.proceedWithAuthorization(value: String?): Response { - val request = if (value != null) request().newBuilder().addAuthorization(value).build() else request() - return proceed(request) - } - - private fun Request.Builder.addAuthorization(credentials: String) = - this.apply { removeHeader("Authorization") } - .apply { header("Authorization", credentials) } - - companion object { - private const val HTTP_RESPONSE_401_UNAUTHORIZED = 401 - private const val REFRESH_THROTTLE_DELAY = 15000L - private const val REFRESH_DELTA_BEFORE_EXPIRY = 20000L - - /** - * Returns compatible state representation for basic authorization - */ - @JvmStatic fun basicState(username: String, password: String): String { - val credentials = "$username:$password" - return Base64.encodeToString(credentials.toByteArray(), Base64.NO_WRAP) - } - - /** - * Returns a [Pair] from provided basic [state] - * Please try to avoid using this function if possible. - */ - @JvmStatic fun decodeBasicState(state: String): Pair? { - return try { - val decoded = String(Base64.decode(state, Base64.NO_WRAP)) - val split = decoded.split(":") - Pair(split[0], split[1]) - } catch (_: Exception) { - null - } - } - } - - /** - * Interface definition for a callback to be invoked on authentication events. - */ - interface Listener { - /** - * Called when [authState] changes during a refresh. - */ - fun onAuthStateChange(accountId: String, authState: String) - - /** - * Called when a non-recoverable authentication failure occurs. - */ - fun onAuthFailure(accountId: String) - } -} diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/AuthType.kt b/alfresco-auth/src/main/java/com/alfresco/auth/AuthType.kt deleted file mode 100644 index 8e77bb98d..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/AuthType.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.alfresco.auth - -/** - * Enum representing currently supported authentication types. - */ -enum class AuthType(val value: String) { - - /** - * Used to specify the need of basic auth with username and password - */ - BASIC("basic"), - - /** - * Used to specify the need of SSO auth - */ - PKCE("pkce"), - - /** - * Used to specify that the auth type is unknown - */ - UNKNOWN(""); - - companion object { - private val map = values().associateBy(AuthType::value) - - /** - * Convert string representation to enum. - */ - @JvmStatic fun fromValue(value: String) = map[value] - } -} diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/Credentials.kt b/alfresco-auth/src/main/java/com/alfresco/auth/Credentials.kt deleted file mode 100644 index 14ee237fc..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/Credentials.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.alfresco.auth - -/** - * Data class holding authentication credentials. - */ -data class Credentials( - /** - * Username associated with credentials. - */ - val username: String, - - /** - * JSON representation of authentication state. - */ - val authState: String, - - /** - * String representation of authentication type. - */ - val authType: String -) diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/DiscoveryService.kt b/alfresco-auth/src/main/java/com/alfresco/auth/DiscoveryService.kt deleted file mode 100644 index 7912713a1..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/DiscoveryService.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.alfresco.auth - -import android.content.Context -import android.net.Uri -import com.alfresco.auth.data.ContentServerDetails -import com.alfresco.auth.data.ContentServerDetailsData -import com.alfresco.auth.pkce.PkceAuthService -import java.net.URL -import java.util.concurrent.TimeUnit -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import okhttp3.OkHttpClient -import okhttp3.Request - -/** - * Class that facilitates service discovery process. - */ -class DiscoveryService( - private val context: Context, - private val authConfig: AuthConfig -) { - - /** - * Determine which [AuthType] is supported by the [endpoint]. - */ - suspend fun getAuthType(endpoint: String): AuthType { - return when { - - isPkceType(endpoint) -> AuthType.PKCE - - isBasicType(endpoint) -> AuthType.BASIC - - else -> AuthType.UNKNOWN - } - } - - /** - * Check whether the content service is running on [endpoint]. - */ - suspend fun isContentServiceInstalled(endpoint: String): Boolean { - val uri = contentServiceDiscoveryUrl(endpoint).toString() - - return withContext(Dispatchers.IO) { - try { - val client = OkHttpClient.Builder() - .connectTimeout(30, TimeUnit.SECONDS) - .build() - val request = Request.Builder() - .url(URL(uri)) - .get() - .build() - val response = client.newCall(request).execute() - - if (response.code != 200) return@withContext false - - val body = response.body?.string() ?: "" - val data = ContentServerDetails.jsonDeserialize(body) - data?.isAtLeast(MIN_ACS_VERSION) ?: false - } catch (e: Exception) { - false - } - } - } - - /** - * returns content server details based on [endpoint]. - */ - suspend fun getContentServiceDetails(endpoint: String): ContentServerDetailsData? { - val uri = contentServiceDiscoveryUrl(endpoint).toString() - - return withContext(Dispatchers.IO) { - try { - val client = OkHttpClient.Builder() - .connectTimeout(30, TimeUnit.SECONDS) - .build() - val request = Request.Builder() - .url(URL(uri)) - .get() - .build() - val response = client.newCall(request).execute() - - if (response.code != 200) return@withContext null - - val body = response.body?.string() ?: "" - val data = ContentServerDetails.jsonDeserialize(body) - data?.data - } catch (e: Exception) { - null - } - } - } - - private suspend fun isBasicType(endpoint: String): Boolean = isContentServiceInstalled(endpoint) - - private suspend fun isPkceType(endpoint: String): Boolean { - val uri = PkceAuthService.discoveryUriWith(endpoint, authConfig) - val result = try { - val authService = PkceAuthService(context, null, authConfig) - authService.fetchDiscoveryFromUrl(uri) - } catch (exception: Exception) { null } - return result != null - } - - /** - * Return content service url based on [endpoint]. - */ - fun contentServiceUrl(endpoint: String): Uri = - PkceAuthService.endpointWith(endpoint, authConfig) - .buildUpon() - .appendPath(authConfig.contentServicePath) - .build() - - private fun contentServiceDiscoveryUrl(endpoint: String): Uri = - contentServiceUrl(endpoint) - .buildUpon() - .appendEncodedPath(ACS_SERVER_DETAILS) - .build() - - private companion object { - const val ACS_SERVER_DETAILS = "service/api/server" - const val MIN_ACS_VERSION = "5.2.2" - const val ENTERPRISE = "Enterprise" - } -} diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/data/ContentServerDetails.kt b/alfresco-auth/src/main/java/com/alfresco/auth/data/ContentServerDetails.kt deleted file mode 100644 index 04bcedca8..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/data/ContentServerDetails.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.alfresco.auth.data - -import java.lang.NumberFormatException -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException -import kotlinx.serialization.json.Json - -@Serializable -data class ContentServerDetailsData( - val edition: String, - val version: String, - val schema: String -) - -@Serializable -internal data class ContentServerDetails( - val data: ContentServerDetailsData -) { - /** - * Verify current [data] version, is at lest [minVersion]. - * Extra build information is ignored, e.g. 6.2.0 (b120) - */ - fun isAtLeast(minVersion: String): Boolean { - val minParts = minVersion.split(" ")[0].split(".") - val verParts = data.version.split(" ")[0].split(".") - try { - for ((index, value) in minParts.withIndex()) { - val part = if (index > verParts.size - 1) 0 else verParts[index].toInt() - val min = value.toInt() - if (part > min) { - return true - } else if (part < min) { - return false - } - } - } catch (_: NumberFormatException) { - return false - } - return true - } - - companion object { - fun jsonDeserialize(str: String): ContentServerDetails? { - return try { - Json.decodeFromString(serializer(), str) - } catch (ex: SerializationException) { - null - } - } - } -} diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/data/MutableLiveEvent.kt b/alfresco-auth/src/main/java/com/alfresco/auth/data/MutableLiveEvent.kt deleted file mode 100644 index 23379303e..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/data/MutableLiveEvent.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.alfresco.auth.data - -import android.util.Log -import androidx.annotation.MainThread -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer -import java.util.concurrent.atomic.AtomicBoolean - -/** - * A lifecycle-aware observable that sends only new updates after subscription. - * - * This avoids a common problem with events: on configuration change (like rotation) an update - * can be emitted if the observer is active. This LiveData only calls the observable if there's an - * explicit call to setValue() or call(). - * - * Note that only one observer is going to be notified of changes. - */ -abstract class LiveEvent : LiveData() - -/** - * [LiveEvent] which publicly exposes [setValue] and [postValue] method. - */ -class MutableLiveEvent : LiveEvent() { - private val mPending = AtomicBoolean(false) - - /** - * Similar to [LiveData.observe] adds the given [observer] to the observers list within the - * lifespan of the given [owner]. The events are dispatched on the main thread. Data will only - * be delivered to the observer only on explicit [setValue]/[postValue]. - */ - @MainThread - override fun observe(owner: LifecycleOwner, observer: Observer) { - if (hasActiveObservers()) { - Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") - } - - // Observe the internal MutableLiveData - super.observe(owner, { t -> - if (mPending.compareAndSet(true, false)) { - observer.onChanged(t) - } - }) - } - - /** - * Similar to [LiveData.setValue] but ensures data is only delivered during an explicit call. - */ - @MainThread - public override fun setValue(value: T?) { - mPending.set(true) - super.setValue(value) - } - - /** - * Similar to [LiveData.postValue]. - */ - public override fun postValue(value: T?) { - super.postValue(value) - } - - /** - * Used for cases where [T] is Void, to make calls cleaner. - */ - @MainThread - fun call() { - value = null - } - - private companion object { - const val TAG = "MutableLiveEvent" - } -} diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/pkce/PkceAuthService.kt b/alfresco-auth/src/main/java/com/alfresco/auth/pkce/PkceAuthService.kt deleted file mode 100644 index 05add6bdb..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/pkce/PkceAuthService.kt +++ /dev/null @@ -1,325 +0,0 @@ -package com.alfresco.auth.pkce - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.net.Uri -import com.alfresco.auth.AuthConfig -import com.auth0.android.jwt.JWT -import java.util.Locale -import java.util.concurrent.atomic.AtomicReference -import kotlin.coroutines.resumeWithException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withContext -import net.openid.appauth.AppAuthConfiguration -import net.openid.appauth.AuthState -import net.openid.appauth.AuthorizationException -import net.openid.appauth.AuthorizationRequest -import net.openid.appauth.AuthorizationResponse -import net.openid.appauth.AuthorizationService -import net.openid.appauth.AuthorizationServiceConfiguration -import net.openid.appauth.EndSessionRequest -import net.openid.appauth.ResponseTypeValues -import net.openid.appauth.TokenResponse -import net.openid.appauth.browser.AnyBrowserMatcher -import net.openid.appauth.connectivity.ConnectionBuilder - -internal class PkceAuthService(context: Context, authState: AuthState?, authConfig: AuthConfig) { - - private val connectionBuilder: ConnectionBuilder - private val authService: AuthorizationService - private val authConfig: AuthConfig - private var authState: AtomicReference - - init { - checkConfig(authConfig) - this.authState = AtomicReference() - this.authState.set(authState) - this.authConfig = authConfig - - this.connectionBuilder = getConnectionBuilder() - authService = AuthorizationService( - context, - AppAuthConfiguration.Builder() - .setBrowserMatcher(AnyBrowserMatcher.INSTANCE) - .setConnectionBuilder(connectionBuilder) - .setSkipIssuerHttpsCheck(!authConfig.https) - .build() - ) - } - - /** - * Fetch an [AuthorizationServiceConfiguration] from an [openIdConnectIssuerUri]. - * This method automatically appends the OpenID connect well-known configuration path to the - * url. - * @see OpenID Connect discovery 1.0 - */ - suspend fun fetchDiscoveryFromUrl(openIdConnectIssuerUri: Uri) = - suspendCancellableCoroutine { - AuthorizationServiceConfiguration.fetchFromUrl( - openIdConnectIssuerUri, - { serviceConfiguration, ex -> - when { - serviceConfiguration != null -> { - it.resumeWith(Result.success(serviceConfiguration)) - } - ex != null -> { - it.resumeWithException(ex) - } - else -> it.resumeWithException(Exception()) - } - }, - connectionBuilder - ) - } - - /** - * Initiates the login in [activity] with activity result [requestCode] - */ - suspend fun initiateLogin(endpoint: String, activity: Activity, requestCode: Int) { - require(endpoint.isNotBlank()) { "Identity url is blank or empty" } - checkConfig(authConfig) - - // build discovery url using auth configuration - val discoveryUri = discoveryUriWith(endpoint, authConfig) - - withContext(Dispatchers.IO) { - val config = fetchDiscoveryFromUrl(discoveryUri) - - // save the authorization configuration - authState.set(AuthState(config)) - - val authRequest = generateAuthorizationRequest(config) - val authIntent = generateAuthIntent(authRequest) - - withContext(Dispatchers.Main) { - activity.startActivityForResult(authIntent, requestCode) - } - } - } - - fun initiateReLogin(activity: Activity, requestCode: Int) { - requireNotNull(authState.get()) - - val authRequest = generateAuthorizationRequest(authState.get().authorizationServiceConfiguration!!) - val authIntent = generateAuthIntent(authRequest) - - activity.startActivityForResult(authIntent, requestCode) - } - - /** - * Generate an intent to start the authentication flow - * - * @param authRequest - * @return the [Intent] used to start the authentication flow - */ - private fun generateAuthIntent(authRequest: AuthorizationRequest): Intent { - return authService.getAuthorizationRequestIntent(authRequest) - } - - /** - * - * @param intent the intent received as a result from the initiation of the pkce login action - */ - suspend fun getAuthResponse(intent: Intent): String { - val authResponse = AuthorizationResponse.fromIntent(intent) - val exception = AuthorizationException.fromIntent(intent) - - authState.get()?.update(authResponse, exception) - - if (authResponse != null) { - return getToken(authResponse) - } - - throw exception ?: Exception() - } - - /** - * Sends a request to the authorization service to exchange a code granted as part of an - * authorization request for a token. - * - * @param authorizationResponse - */ - private suspend fun getToken(authorizationResponse: AuthorizationResponse) = - withContext(Dispatchers.IO) { - suspendCancellableCoroutine { - authService.performTokenRequest( - authorizationResponse.createTokenExchangeRequest(), - authState.get().clientAuthentication - ) { response: TokenResponse?, ex: AuthorizationException? -> - authState.get().update(response, ex) - - when { - response != null -> { - it.resumeWith(Result.success(authState.get().jsonSerializeString())) - } - ex != null -> { - it.resumeWithException(ex) - } - else -> it.resumeWithException(Exception()) - } - } - } - } - - suspend fun refreshToken() = - withContext(Dispatchers.IO) { - suspendCancellableCoroutine { - authService.performTokenRequest( - authState.get().createTokenRefreshRequest(), - authState.get().clientAuthentication - ) { response: TokenResponse?, ex: AuthorizationException? -> - - authState.get().update(response, ex) - - when { - response != null -> { - it.resumeWith(Result.success(response)) - } - ex != null -> { - it.resumeWithException(ex) - } - else -> it.resumeWithException(Exception()) - } - } - } - } - - suspend fun endSession(activity: Activity, requestCode: Int) { - withContext(Dispatchers.IO) { - val request = makeEndSessionRequest(authState.get().authorizationServiceConfiguration!!) - val intent = authService.getEndSessionRequestIntent(request) - withContext(Dispatchers.Main) { - activity.startActivityForResult(intent, requestCode) - } - } - } - - fun getUserEmail(): String? { - try { - - val idToken = authState.get().idToken - if (idToken != null) { - val decodedToken = JWT(idToken) - - return decodedToken.getClaim("email").asString() - } - - return null - } catch (ex: java.lang.Exception) { - return null - } - } - - fun getAuthState(): AuthState? { - return authState.get() - } - - /** - * Generates [AuthorizationRequest] used for creating the auth flow intent - */ - private fun generateAuthorizationRequest(serviceAuthorization: AuthorizationServiceConfiguration): - AuthorizationRequest { - - val builder = AuthorizationRequest.Builder( - serviceAuthorization, - authConfig.clientId, - ResponseTypeValues.CODE, - Uri.parse(authConfig.redirectUrl) - ) - - builder.setScope("openid") - - return builder.build() - } - - private fun makeEndSessionRequest(serviceAuthorization: AuthorizationServiceConfiguration): EndSessionRequest { - return EndSessionRequest.Builder(serviceAuthorization) - .setIdTokenHint(authState.get().idToken!!) - .setPostLogoutRedirectUri(Uri.parse(authConfig.redirectUrl)) - .build() - } - - private fun getConnectionBuilder(): PkceConnectionBuilder { - return PkceConnectionBuilder.INSTANCE - } - - /** - * Checks if [AuthConfig] has all the necessary information - * @throws [IllegalArgumentException] - */ - private fun checkConfig(authConfig: AuthConfig) { - requireNotNull(authConfig.port.toIntOrNull()) { "Invalid port or empty" } - require(authConfig.contentServicePath.isNotBlank()) { "Content service path is blank or empty" } - require(authConfig.realm.isNotBlank()) { "Realm is blank or empty" } - require(authConfig.clientId.isNotBlank()) { "Client id is blank or empty" } - require(authConfig.redirectUrl.isNotBlank()) { "Redirect url is blank or empty" } - } - - companion object { - - /** - * The standard base path for auth - */ - private const val AUTH = "auth" - - /** - * The standard base path for realms - */ - private const val REALMS = "realms" - - /** - * The standard base path for well-known resources on domains. - * - * @see "Defining Well-Known Uniform Resource Identifiers - */ - private const val WELL_KNOWN_PATH = ".well-known" - - /** - * The standard resource under [.well-known][.WELL_KNOWN_PATH] at which an OpenID Connect - * discovery document can be found under an issuer's base URI. - * - * @see OpenID Connect discovery 1.0 - */ - private const val OPENID_CONFIGURATION_RESOURCE = "openid-configuration" - - /** - * Creates an [endpoint] uri using [config]. - * If the [endpoint] contains either schema or port that will override [config] information. - */ - fun endpointWith(endpoint: String, config: AuthConfig): Uri { - val src = endpoint.trim().toLowerCase(Locale.ROOT) - - var uri = Uri.parse(src) - var uriBuilder = uri.buildUpon() - - // e.g. hostname:port is not hierarchical - if (!uri.isHierarchical || uri.isRelative || uri.authority == null) { - uriBuilder = Uri.Builder().encodedAuthority(src) - uri = uriBuilder.build() - } - - if (uri.scheme == null || (uri.scheme != "http" && uri.scheme != "https")) { - uriBuilder = uriBuilder.scheme(if (config.https) "https" else "http") - } - - if (uri.port == -1 && ((config.https && config.port != "443") || (!config.https && config.port != "80"))) { - uriBuilder = uriBuilder.encodedAuthority(uri.authority + ":${config.port}") - } - - return uriBuilder.build() - } - - fun discoveryUriWith(endpoint: String, config: AuthConfig): Uri { - return endpointWith(endpoint, config) - .buildUpon() - .appendPath(AUTH) - .appendPath(REALMS) - .appendPath(config.realm) - .appendPath(WELL_KNOWN_PATH) - .appendPath(OPENID_CONFIGURATION_RESOURCE) - .build() - } - } -} diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/pkce/PkceConnectionBuilder.kt b/alfresco-auth/src/main/java/com/alfresco/auth/pkce/PkceConnectionBuilder.kt deleted file mode 100644 index 7dd41377e..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/pkce/PkceConnectionBuilder.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.alfresco.auth.pkce - -import android.net.Uri -import java.io.IOException -import java.net.HttpURLConnection -import java.net.URL -import java.util.concurrent.TimeUnit -import net.openid.appauth.connectivity.ConnectionBuilder - -/** - * Creates [HttpURLConnection] instances using the default, platform-provided - * mechanism, with sensible production defaults. - */ -internal class PkceConnectionBuilder private constructor() // no need to construct new instances - : ConnectionBuilder { - - @Throws(IOException::class) - override fun openConnection(uri: Uri): HttpURLConnection { - check(HTTP_SCHEME == uri.scheme || HTTPS_SCHEME == uri.scheme) { "scheme or uri must be http or https" } - - return with(URL(uri.toString()).openConnection() as HttpURLConnection) { - connectTimeout = CONNECTION_TIMEOUT_MS - readTimeout = READ_TIMEOUT_MS - instanceFollowRedirects = false - - this - } - } - - companion object { - val INSTANCE = PkceConnectionBuilder() - - private val CONNECTION_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30).toInt() - private val READ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(15).toInt() - - private const val HTTP_SCHEME = "http" - private const val HTTPS_SCHEME = "https" - } -} diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/pkce/RedirectUriReceiverActivity.kt b/alfresco-auth/src/main/java/com/alfresco/auth/pkce/RedirectUriReceiverActivity.kt deleted file mode 100644 index 5fafbb77e..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/pkce/RedirectUriReceiverActivity.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.alfresco.auth.pkce - -import android.app.Activity -import android.os.Bundle -import net.openid.appauth.AuthorizationManagementActivity -import net.openid.appauth.AuthorizationService - -/** - * Activity that receives the redirect Uri sent by the OpenID endpoint. It forwards the data - * received as part of this redirect to [AuthorizationManagementActivity], which - * destroys the browser tab before returning the result to the completion - * [android.app.PendingIntent] - * provided to [AuthorizationService.performAuthorizationRequest]. - * - * App developers using this library must override the `appAuthRedirectScheme` - * property in their `build.gradle` to specify the custom scheme that will be used for - * the OAuth2 redirect. If custom scheme redirect cannot be used with the identity provider - * you are integrating with, then a custom intent filter should be defined in your - * application manifest instead. For example, to handle - * `myapp://aims/auth`: - * - * ```xml - * - * - * - * - * - * - * ``` - */ -class RedirectUriReceiverActivity : Activity() { - - override fun onCreate(savedInstanceBundle: Bundle?) { - super.onCreate(savedInstanceBundle) - - // while this does not appear to be achieving much, handling the redirect in this way - // ensures that we can remove the browser tab from the back stack. See the documentation - // on AuthorizationManagementActivity for more details. - startActivity( - AuthorizationManagementActivity.createResponseHandlingIntent( - this, intent.data - ) - ) - finish() - } -} diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/ui/AuthenticationActivity.kt b/alfresco-auth/src/main/java/com/alfresco/auth/ui/AuthenticationActivity.kt deleted file mode 100644 index 25cdb55fc..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/ui/AuthenticationActivity.kt +++ /dev/null @@ -1,181 +0,0 @@ -package com.alfresco.auth.ui - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.alfresco.auth.AuthConfig -import com.alfresco.auth.AuthInterceptor -import com.alfresco.auth.AuthType -import com.alfresco.auth.Credentials -import com.alfresco.auth.DiscoveryService -import com.alfresco.auth.data.LiveEvent -import com.alfresco.auth.data.MutableLiveEvent -import com.alfresco.auth.pkce.PkceAuthService -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import net.openid.appauth.AuthState - -/** - * Companion [ViewModel] to [AuthenticationActivity] which facilitates authentication. - */ -abstract class AuthenticationViewModel : ViewModel() { - - protected abstract var context: Context - protected lateinit var discoveryService: DiscoveryService - - protected val _onPkceLogin = MutableLiveEvent() - protected val _onCredentials = MutableLiveEvent() - protected val _onError = MutableLiveEvent() - - val onPkceLogin: LiveEvent get() = _onPkceLogin - val onCredentials: LiveEvent get() = _onCredentials - val onError: LiveEvent get() = _onError - - /** - * true if the session expired or was invalidated and user has to re-login. - */ - var isReLogin = false - - /** - * Check which [AuthType] is supported by the [endpoint] based on the provided [authConfig]. - */ - fun checkAuthType( - endpoint: String, - authConfig: AuthConfig, - onResult: (authType: AuthType) -> Unit - ) = viewModelScope.launch { - discoveryService = DiscoveryService(context, authConfig) - val authType = withContext(Dispatchers.IO) { discoveryService.getAuthType(endpoint) } - onResult(authType) - } - - /** - * Function takes [username] and [password] and returns result via [onCredentials]. - * Note: This function does not provide any credential validation. - */ - fun basicLogin(username: String, password: String) { - val state = AuthInterceptor.basicState(username, password) - _onCredentials.value = Credentials(username, state, AuthType.BASIC.value) - } - - /** - * Initiate PKCE login, against [endpoint] with provided [authConfig]. - * If an [authState] is provided the invocation is considered a re-login. - * On success credentials are provided via [onCredentials]. - * On error [onError] is invoked instead. - * If the flow is cancelled by the user [onPkceAuthCancelled] is called. - */ - fun pkceLogin(endpoint: String, authConfig: AuthConfig, authState: String? = null) { - if (authState != null) { - isReLogin = true - } - - pkceAuth.initServiceWith(authConfig, authState) - - _onPkceLogin.value = endpoint - } - - /** - * Called if the user cancels the login by dismissing the webView. - */ - open fun onPkceAuthCancelled() {} - - internal val pkceAuth = PkceAuth() - internal inner class PkceAuth { - private lateinit var authService: PkceAuthService - - /** - * Recreates [authService] with provided [AuthConfig] - * @throws [IllegalArgumentException] - */ - fun initServiceWith(authConfig: AuthConfig, authState: String? = null) { - var state: AuthState? = null - if (authState != null) { - state = AuthState.jsonDeserialize(authState) - } - - authService = PkceAuthService(context, state, authConfig) - } - - fun login(endpoint: String, activity: Activity, requestCode: Int) { - viewModelScope.launch { - try { - authService.initiateLogin(endpoint, activity, requestCode) - } catch (ex: Exception) { - _onError.value = ex.message - } - } - } - - fun reLogin(activity: Activity, requestCode: Int) { - authService.initiateReLogin(activity, requestCode) - } - - fun handleActivityResult(intent: Intent) { - viewModelScope.launch { - try { - val result = authService.getAuthResponse(intent) - val userEmail = authService.getUserEmail() ?: "" - _onCredentials.value = Credentials(userEmail, result, AuthType.PKCE.value) - } catch (ex: Exception) { - _onError.value = ex.message ?: "" - } - } - } - } -} - -/** - * Abstract activity used to build the authentication flow. - */ -abstract class AuthenticationActivity : AppCompatActivity() { - - protected abstract val viewModel: T - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - observe(viewModel.onPkceLogin, ::onPkceLogin) - observe(viewModel.onCredentials, ::onCredentials) - observe(viewModel.onError, ::onError) - } - - protected fun onPkceLogin(endpoint: String) { - if (viewModel.isReLogin) { - viewModel.pkceAuth.reLogin(this, REQUEST_CODE_AUTHENTICATE) - } else { - viewModel.pkceAuth.login(endpoint, this, REQUEST_CODE_AUTHENTICATE) - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == REQUEST_CODE_AUTHENTICATE) { - if (resultCode == Activity.RESULT_CANCELED) { - viewModel.onPkceAuthCancelled() - } else { - data?.let { viewModel.pkceAuth.handleActivityResult(it) } - } - } - - super.onActivityResult(requestCode, resultCode, data) - } - - /** - * Called when [credentials] become available. - */ - abstract fun onCredentials(credentials: Credentials) - - /** - * Called on [error] during the authentication process. - */ - abstract fun onError(error: String) - - private companion object { - const val REQUEST_CODE_AUTHENTICATE = 20 - } -} diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/ui/EndSessionActivity.kt b/alfresco-auth/src/main/java/com/alfresco/auth/ui/EndSessionActivity.kt deleted file mode 100644 index 616548c2c..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/ui/EndSessionActivity.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.alfresco.auth.ui - -import android.app.Activity -import android.content.Context -import android.content.Intent -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.alfresco.auth.AuthConfig -import com.alfresco.auth.AuthType -import com.alfresco.auth.pkce.PkceAuthService -import kotlinx.coroutines.launch -import net.openid.appauth.AuthState -import org.json.JSONException - -/** - * Companion [ViewModel] to [EndSessionActivity] for invoking the logout procedure. - */ -open class EndSessionViewModel( - context: Context, - authType: AuthType?, - authState: String, - authConfig: AuthConfig -) : ViewModel() { - private val authType = authType - private val authService: PkceAuthService? - - init { - val state = try { AuthState.jsonDeserialize(authState) } catch (ex: JSONException) { null } - - authService = if (authType == AuthType.PKCE) { - PkceAuthService(context, state, authConfig) - } else { - null - } - } - - /** - * Invoke logout procedure, presenting extra activities if necessary. - */ - fun logout(activity: Activity, requestCode: Int) { - viewModelScope.launch { - if (authType == AuthType.PKCE) { - authService?.endSession(activity, requestCode) - } else { - activity.setResult(Activity.RESULT_OK) - activity.finish() - } - } - } -} - -/** - * Abstract activity that will trigger logout [onResume] and return the result to the caller. - * Can be used with all [AuthType]s. - */ -abstract class EndSessionActivity : AppCompatActivity() { - protected abstract val viewModel: T - - override fun onResume() { - super.onResume() - - viewModel.logout(this, REQUEST_CODE_END_SESSION) - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == REQUEST_CODE_END_SESSION) { - if (resultCode == Activity.RESULT_CANCELED) { - setResult(Activity.RESULT_CANCELED) - finish() - } else { - setResult(Activity.RESULT_OK) - finish() - } - } else { - super.onActivityResult(requestCode, resultCode, data) - } - } - - private companion object { - const val REQUEST_CODE_END_SESSION = 1 - } -} diff --git a/alfresco-auth/src/main/java/com/alfresco/auth/ui/LiveData.kt b/alfresco-auth/src/main/java/com/alfresco/auth/ui/LiveData.kt deleted file mode 100644 index 2c6637c77..000000000 --- a/alfresco-auth/src/main/java/com/alfresco/auth/ui/LiveData.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.alfresco.auth.ui - -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer - -/** - * Convenience method for observing [LiveData] in a [LifecycleOwner]. - */ -fun > LifecycleOwner.observe(liveData: L, body: (T) -> Unit) = - liveData.observe(this, Observer(body)) diff --git a/alfresco-auth/src/test/java/com/alfresco/auth/data/ContentServerDetailsTests.kt b/alfresco-auth/src/test/java/com/alfresco/auth/data/ContentServerDetailsTests.kt deleted file mode 100644 index d0eedbc50..000000000 --- a/alfresco-auth/src/test/java/com/alfresco/auth/data/ContentServerDetailsTests.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.alfresco.auth.data - -import org.junit.Test - -class ContentServerDetailsTests { - @Test - fun `version 6-2 greater than 5-2-2`() { - assert(testData("6.2").isAtLeast("5.2.2")) - } - - @Test - fun `version 5-2-3 greater than 5-2`() { - assert(testData("5.2.3").isAtLeast("5.2")) - } - - @Test - fun `version 5-2-1 greater than 5-2-2`() { - assert(testData("5.2.3").isAtLeast("5.2")) - } - - @Test - fun `version 5-2 smaller than 5-2-2`() { - assert(!testData("5.2").isAtLeast("5.2.2")) - } - - @Test - fun `version 5-2-0 at least to 5-2`() { - assert(testData("5.2.0").isAtLeast("5.2")) - } - - @Test - fun `version 5-2 at least to 5-2-0`() { - assert(testData("5.2").isAtLeast("5.2.0")) - } - - @Test - fun `version 6-2 (120) at least to 5-2-0`() { - assert(testData("6.2 (b120)").isAtLeast("5.2.2")) - } - - @Test - fun `version 5-2-0 at least to 5-2-0 (b120)`() { - assert(testData("5.2.0").isAtLeast("5.2.0 (b120)")) - } - - @Test - fun `version invalid not greater 5-2-0`() { - assert(!testData("invalid").isAtLeast("5.2.0 (b120)")) - } - - private fun testData(version: String): ContentServerDetails { - return ContentServerDetails(ContentServerDetailsData(EDITION, version, SCHEMA)) - } - - companion object { - const val EDITION = "community" - const val SCHEMA = "100" - } -} diff --git a/alfresco-auth/src/test/java/com/alfresco/auth/pkce/PkceServiceTests.kt b/alfresco-auth/src/test/java/com/alfresco/auth/pkce/PkceServiceTests.kt deleted file mode 100644 index f5c913de8..000000000 --- a/alfresco-auth/src/test/java/com/alfresco/auth/pkce/PkceServiceTests.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.alfresco.auth.pkce - -import com.alfresco.auth.AuthConfig -import org.junit.Before as before -import org.junit.Test as test - -class PkceServiceTests { - - private lateinit var config: AuthConfig - - @before fun setUp() { - config = AuthConfig( - https = false, - port = "80", - clientId = "clientId", - realm = "realm", - redirectUrl = "redirectUrl", - contentServicePath = "path" - ) - } - - @test fun `endpoint - hostname`() { - val uri = PkceAuthService.endpointWith("sub.test.com", config) - assert(uri.toString() == "http://sub.test.com") - } - - @test fun `endpoint - hostname and port`() { - val uri = PkceAuthService.endpointWith("sub.test.com:8080", config) - assert(uri.toString() == "http://sub.test.com:8080") - } - - @test fun `endpoint - schema, hostname and port`() { - val uri = PkceAuthService.endpointWith("https://sub.test.com:8080", config) - assert(uri.toString() == "https://sub.test.com:8080") - } - - @test fun `endpoint - ip`() { - val uri = PkceAuthService.endpointWith("127.0.0.1", config) - assert(uri.toString() == "http://127.0.0.1") - } - - @test fun `endpoint - ip and port`() { - val uri = PkceAuthService.endpointWith("127.0.0.1:8080", config) - assert(uri.toString() == "http://127.0.0.1:8080") - } - - @test fun `endpoint - hostname and path`() { - val uri = PkceAuthService.endpointWith("sub.test.com/app", config) - assert(uri.toString() == "http://sub.test.com/app") - } - - @test fun `endpoint - hostname with port and path`() { - val uri = PkceAuthService.endpointWith("sub.test.com:8080/app", config) - assert(uri.toString() == "http://sub.test.com:8080/app") - } - - @test fun `endpoint - no authority`() { - val uri = PkceAuthService.endpointWith("http:/sub.test.com", config) - assert(uri.toString() == "http://http:/sub.test.com") - } - - @test fun `endpoint - unsupported schema`() { - val uri = PkceAuthService.endpointWith("htp://sub.test.com", config) - assert(uri.toString() == "http://sub.test.com") - } - - @test fun `endpoint - non-hierarchical`() { - val uri = PkceAuthService.endpointWith("mailto:sub.test.com", config) - assert(uri.toString() == "http://mailto:sub.test.com") - } - - @test fun `endpoint - config schema override`() { - val uri = PkceAuthService.endpointWith("https://sub.test.com", config) - assert(uri.toString() == "https://sub.test.com") - } - - @test fun `endpoint - hostname secure`() { - config.https = true - config.port = "443" - val uri = PkceAuthService.endpointWith("sub.test.com", config) - assert(uri.toString() == "https://sub.test.com") - } - - @test fun `endpoint - hostname non-standard port`() { - config.port = "8080" - val uri = PkceAuthService.endpointWith("sub.test.com", config) - assert(uri.toString() == "http://sub.test.com:8080") - } - - @test fun `endpoint - hostname secure non-standard port`() { - config.https = true - val uri = PkceAuthService.endpointWith("sub.test.com", config) - assert(uri.toString() == "https://sub.test.com:80") - } -} diff --git a/app/build.gradle b/app/build.gradle index e076b0dd2..7fd653829 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,15 +59,6 @@ android { packagingOptions { pickFirst 'META-INF/auth_release.kotlin_module' } - - splits { - abi { - enable true - reset() - include 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'mips', 'mips64', 'arm64-v8a' - universalApk false - } - } } project.afterEvaluate {