Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADST-422 #73

Merged
merged 3 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions auth/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ apply plugin: 'org.jetbrains.dokka'
android {
namespace 'com.alfresco.auth'
defaultConfig {
versionName "0.8.1"
versionName "0.8.2"
}
}

dependencies {
implementation libs.appauth
implementation libs.jwtdecode

implementation libs.auth0
implementation libs.kotlin.serialization.json

implementation libs.androidx.appcompat
Expand Down
14 changes: 12 additions & 2 deletions auth/src/main/java/com/alfresco/auth/AuthConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,16 @@ data class AuthConfig(
/**
* Path to content service
*/
var contentServicePath: String
var contentServicePath: String,

/**
* scheme for Auth0
*/
var scheme: String = "",
/**
* selected AuthType
*/
var authType: String = ""
) {
/**
* Convenience method for JSON serialization.
Expand All @@ -51,7 +60,8 @@ data class AuthConfig(
/**
* Convenience method for deserializing a JSON string representation.
*/
@JvmStatic fun jsonDeserialize(str: String): AuthConfig? {
@JvmStatic
fun jsonDeserialize(str: String): AuthConfig? {
return try {
Json.decodeFromString(serializer(), str)
} catch (ex: SerializationException) {
Expand Down
18 changes: 18 additions & 0 deletions auth/src/main/java/com/alfresco/auth/AuthInterceptor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class AuthInterceptor(
provider = when (authType) {
AuthType.BASIC -> BasicProvider(stateString)
AuthType.PKCE -> PkceProvider(stateString, config)
AuthType.OIDC -> OIDCProvider(stateString)
AuthType.UNKNOWN -> PlainProvider()
}
}
Expand Down Expand Up @@ -224,10 +225,27 @@ class AuthInterceptor(
}
}

private inner class OIDCProvider (val accessToken : String) : Provider{
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(AuthType.OIDC, accessToken)

// If still error notify listener of failure
if (response.code == HTTP_RESPONSE_401_UNAUTHORIZED) {
listener?.onAuthFailure(accountId, response.request.url.toString())
}
return response
}

override fun finish() {
localScope.coroutineContext.cancelChildren()
}
}

private fun Interceptor.Chain.proceed(type: AuthType, token: String?): Response {
val headerValue = when (type) {
AuthType.BASIC -> "Basic $token"
AuthType.PKCE -> "Bearer $token"
AuthType.OIDC -> "bearer $token"
AuthType.UNKNOWN -> null
}
return proceedWithAuthorization(headerValue)
Expand Down
5 changes: 5 additions & 0 deletions auth/src/main/java/com/alfresco/auth/AuthType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ enum class AuthType(val value: String) {
*/
PKCE("pkce"),

/**
* Used to specify the need of SSO auth
*/
OIDC("oidc"),

/**
* Used to specify that the auth type is unknown
*/
Expand Down
6 changes: 5 additions & 1 deletion auth/src/main/java/com/alfresco/auth/Credentials.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ data class Credentials(
/**
* String representation of authentication type.
*/
val authType: String
val authType: String,

val hostName: String = "",

val clientId: String = ""
)
60 changes: 57 additions & 3 deletions auth/src/main/java/com/alfresco/auth/DiscoveryService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ package com.alfresco.auth

import android.content.Context
import android.net.Uri
import com.alfresco.auth.data.AppConfigDetails
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
import java.net.URL
import java.util.concurrent.TimeUnit

/**
* Class that facilitates service discovery process.
Expand All @@ -24,10 +25,27 @@ class DiscoveryService(
* Determine which [AuthType] is supported by the [endpoint].
*/
suspend fun getAuthType(endpoint: String): AuthType {

when (authConfig.authType.lowercase()) {
AuthType.OIDC.value -> {
if (isOIDC(endpoint)) {
return AuthType.OIDC
}
}

AuthType.PKCE.value -> {
if (isPkceType(endpoint)) {
return AuthType.PKCE
}
}
}

return when {

isPkceType(endpoint) -> AuthType.PKCE

isOIDC(endpoint) -> AuthType.OIDC

isBasicType(endpoint) -> AuthType.BASIC

else -> AuthType.UNKNOWN
Expand Down Expand Up @@ -62,6 +80,31 @@ class DiscoveryService(
}
}

suspend fun isOIDCInstalled(appConfigURL: String): Boolean {
val uri = PkceAuthService.discoveryUriWithAuth0(appConfigURL).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 = AppConfigDetails.jsonDeserialize(body)
return@withContext data?.oauth2?.audience?.isNotBlank() == true
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}

/**
* returns content server details based on [endpoint].
*/
Expand Down Expand Up @@ -97,10 +140,15 @@ class DiscoveryService(
val result = try {
val authService = PkceAuthService(context, null, authConfig)
authService.fetchDiscoveryFromUrl(uri)
} catch (exception: Exception) { null }
} catch (exception: Exception) {
null
}
return result != null
}

private suspend fun isOIDC(endpoint: String): Boolean =
isOIDCInstalled(endpoint) && authConfig.realm.isBlank()

/**
* Return content service url based on [endpoint].
*/
Expand All @@ -110,6 +158,12 @@ class DiscoveryService(
.appendPath(authConfig.contentServicePath)
.build()

fun oidcUrl(endpoint: String): Uri =
PkceAuthService.endpointWith(endpoint, authConfig)
.buildUpon()
.appendPath("alfresco")
.build()

private fun contentServiceDiscoveryUrl(endpoint: String): Uri =
contentServiceUrl(endpoint)
.buildUpon()
Expand Down
42 changes: 42 additions & 0 deletions auth/src/main/java/com/alfresco/auth/data/AppConfigDetails.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.alfresco.auth.data

import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json

@Serializable
data class OAuth2Data(
val host: String,
val clientId: String,
val secret: String,
val scope: String,
val implicitFlow: Boolean,
val codeFlow: Boolean,
val silentLogin: Boolean,
val publicUrls: List<String>,
val redirectSilentIframeUri: String,
val redirectUri: String,
val logoutUrl: String,
val logoutParameters: List<String>,
val redirectUriLogout: String,
val audience: String,
val skipIssuerCheck: Boolean,
val strictDiscoveryDocumentValidation: Boolean
)

@Serializable
internal data class AppConfigDetails(
val oauth2: OAuth2Data
) {
companion object {
private val json = Json { ignoreUnknownKeys = true }

fun jsonDeserialize(str: String): AppConfigDetails? {
return try {
json.decodeFromString(serializer(), str)
} catch (ex: SerializationException) {
null
}
}
}
}
Loading
Loading