Improve the callback uri format and customization. (#4664)
* Remove unused SUPPORT_EMAIL_ADDRESS * Improve the callback uri format and customization. Use io.element.android for the scheme of Oidc redirection for Element X. For nightly the scheme will be io.element.android.nightly For debug the scheme will be io.element.android.debug Element Pro is using `io.element`
This commit is contained in:
@@ -106,14 +106,25 @@ android {
|
|||||||
logger.warnInBox("Building ${defaultConfig.applicationId} ($baseAppName)")
|
logger.warnInBox("Building ${defaultConfig.applicationId} ($baseAppName)")
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
val oidcRedirectSchemeBase = BuildTimeConfig.METADATA_HOST_REVERSED ?: "io.element.android"
|
||||||
getByName("debug") {
|
getByName("debug") {
|
||||||
resValue("string", "app_name", "$baseAppName dbg")
|
resValue("string", "app_name", "$baseAppName dbg")
|
||||||
|
resValue(
|
||||||
|
"string",
|
||||||
|
"login_redirect_scheme",
|
||||||
|
"$oidcRedirectSchemeBase.debug",
|
||||||
|
)
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
getByName("release") {
|
getByName("release") {
|
||||||
resValue("string", "app_name", baseAppName)
|
resValue("string", "app_name", baseAppName)
|
||||||
|
resValue(
|
||||||
|
"string",
|
||||||
|
"login_redirect_scheme",
|
||||||
|
oidcRedirectSchemeBase,
|
||||||
|
)
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
|
||||||
postprocessing {
|
postprocessing {
|
||||||
@@ -131,6 +142,11 @@ android {
|
|||||||
applicationIdSuffix = ".nightly"
|
applicationIdSuffix = ".nightly"
|
||||||
versionNameSuffix = "-nightly"
|
versionNameSuffix = "-nightly"
|
||||||
resValue("string", "app_name", "$baseAppName nightly")
|
resValue("string", "app_name", "$baseAppName nightly")
|
||||||
|
resValue(
|
||||||
|
"string",
|
||||||
|
"login_redirect_scheme",
|
||||||
|
"$oidcRedirectSchemeBase.nightly",
|
||||||
|
)
|
||||||
matchingFallbacks += listOf("release")
|
matchingFallbacks += listOf("release")
|
||||||
signingConfig = signingConfigs.getByName("nightly")
|
signingConfig = signingConfigs.getByName("nightly")
|
||||||
|
|
||||||
@@ -284,6 +300,7 @@ dependencies {
|
|||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
testImplementation(projects.services.toolbox.test)
|
||||||
|
|
||||||
koverDependencies()
|
koverDependencies()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,8 +60,7 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<!-- Note: the scheme must match the scheme of the value of OidcConfig.REDIRECT_URI -->
|
<data android:scheme="@string/login_redirect_scheme" />
|
||||||
<data android:scheme="io.element" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!--
|
<!--
|
||||||
Element web links
|
Element web links
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.x.oidc
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.matrix.api.auth.OidcRedirectUrlProvider
|
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
import io.element.android.x.R
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultOidcRedirectUrlProvider @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
) : OidcRedirectUrlProvider {
|
||||||
|
override fun provide() = buildString {
|
||||||
|
append(stringProvider.getString(R.string.login_redirect_scheme))
|
||||||
|
append(":/")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.x.oidc
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||||
|
import io.element.android.x.R
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class DefaultOidcRedirectUrlProviderTest {
|
||||||
|
@Test
|
||||||
|
fun `test provide`() {
|
||||||
|
val stringProvider = FakeStringProvider(
|
||||||
|
defaultResult = "str"
|
||||||
|
)
|
||||||
|
val sut = DefaultOidcRedirectUrlProvider(
|
||||||
|
stringProvider = stringProvider,
|
||||||
|
)
|
||||||
|
val result = sut.provide()
|
||||||
|
assertThat(result).isEqualTo("str:/")
|
||||||
|
assertThat(stringProvider.lastResIdParam).isEqualTo(R.string.login_redirect_scheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,10 +20,11 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
|||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||||
|
import io.element.android.libraries.matrix.test.auth.FakeOidcRedirectUrlProvider
|
||||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||||
import io.element.android.libraries.oidc.api.OidcAction
|
import io.element.android.libraries.oidc.api.OidcAction
|
||||||
import io.element.android.libraries.oidc.impl.DefaultOidcIntentResolver
|
import io.element.android.libraries.oidc.impl.DefaultOidcIntentResolver
|
||||||
import io.element.android.libraries.oidc.impl.OidcUrlParser
|
import io.element.android.libraries.oidc.impl.DefaultOidcUrlParser
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import org.junit.Assert.assertThrows
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -119,7 +120,7 @@ class IntentResolverTest {
|
|||||||
val sut = createIntentResolver()
|
val sut = createIntentResolver()
|
||||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||||
action = Intent.ACTION_VIEW
|
action = Intent.ACTION_VIEW
|
||||||
data = "io.element:/callback?error=access_denied&state=IFF1UETGye2ZA8pO".toUri()
|
data = "io.element.android:/?error=access_denied&state=IFF1UETGye2ZA8pO".toUri()
|
||||||
}
|
}
|
||||||
val result = sut.resolve(intent)
|
val result = sut.resolve(intent)
|
||||||
assertThat(result).isEqualTo(
|
assertThat(result).isEqualTo(
|
||||||
@@ -134,13 +135,13 @@ class IntentResolverTest {
|
|||||||
val sut = createIntentResolver()
|
val sut = createIntentResolver()
|
||||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||||
action = Intent.ACTION_VIEW
|
action = Intent.ACTION_VIEW
|
||||||
data = "io.element:/callback?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB".toUri()
|
data = "io.element.android:/?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB".toUri()
|
||||||
}
|
}
|
||||||
val result = sut.resolve(intent)
|
val result = sut.resolve(intent)
|
||||||
assertThat(result).isEqualTo(
|
assertThat(result).isEqualTo(
|
||||||
ResolvedIntent.Oidc(
|
ResolvedIntent.Oidc(
|
||||||
oidcAction = OidcAction.Success(
|
oidcAction = OidcAction.Success(
|
||||||
url = "io.element:/callback?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB"
|
url = "io.element.android:/?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -151,7 +152,7 @@ class IntentResolverTest {
|
|||||||
val sut = createIntentResolver()
|
val sut = createIntentResolver()
|
||||||
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply {
|
||||||
action = Intent.ACTION_VIEW
|
action = Intent.ACTION_VIEW
|
||||||
data = "io.element:/callback/invalid".toUri()
|
data = "io.element.android:/invalid".toUri()
|
||||||
}
|
}
|
||||||
assertThrows(IllegalStateException::class.java) {
|
assertThrows(IllegalStateException::class.java) {
|
||||||
sut.resolve(intent)
|
sut.resolve(intent)
|
||||||
@@ -246,7 +247,9 @@ class IntentResolverTest {
|
|||||||
return IntentResolver(
|
return IntentResolver(
|
||||||
deeplinkParser = DeeplinkParser(),
|
deeplinkParser = DeeplinkParser(),
|
||||||
oidcIntentResolver = DefaultOidcIntentResolver(
|
oidcIntentResolver = DefaultOidcIntentResolver(
|
||||||
oidcUrlParser = OidcUrlParser()
|
oidcUrlParser = DefaultOidcUrlParser(
|
||||||
|
oidcRedirectUrlProvider = FakeOidcRedirectUrlProvider(),
|
||||||
|
)
|
||||||
),
|
),
|
||||||
permalinkParser = FakePermalinkParser(
|
permalinkParser = FakePermalinkParser(
|
||||||
result = permalinkParserResult
|
result = permalinkParserResult
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Server list: https://github.com/element-hq/oidc-playground
|
|||||||
Metadata iOS: (from https://github.com/element-hq/element-x-ios/blob/5f9d07377cebc4f21d9668b1a25f6e3bb22f64a1/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift#L28)
|
Metadata iOS: (from https://github.com/element-hq/element-x-ios/blob/5f9d07377cebc4f21d9668b1a25f6e3bb22f64a1/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift#L28)
|
||||||
|
|
||||||
clientName: InfoPlistReader.main.bundleDisplayName,
|
clientName: InfoPlistReader.main.bundleDisplayName,
|
||||||
redirectUri: "io.element:/callback",
|
redirectUri: "io.element.android:/",
|
||||||
clientUri: "https://element.io",
|
clientUri: "https://element.io",
|
||||||
tosUri: "https://element.io/user-terms-of-service",
|
tosUri: "https://element.io/user-terms-of-service",
|
||||||
policyUri: "https://element.io/privacy"
|
policyUri: "https://element.io/privacy"
|
||||||
@@ -19,7 +19,7 @@ policyUri: "https://element.io/privacy"
|
|||||||
|
|
||||||
Android:
|
Android:
|
||||||
clientName = "Element",
|
clientName = "Element",
|
||||||
redirectUri = "io.element:/callback",
|
redirectUri = "io.element.android:/",
|
||||||
clientUri = "https://element.io",
|
clientUri = "https://element.io",
|
||||||
tosUri = "https://element.io/user-terms-of-service",
|
tosUri = "https://element.io/user-terms-of-service",
|
||||||
policyUri = "https://element.io/privacy"
|
policyUri = "https://element.io/privacy"
|
||||||
|
|||||||
@@ -27,13 +27,6 @@ android {
|
|||||||
name = "CLIENT_URI",
|
name = "CLIENT_URI",
|
||||||
value = BuildTimeConfig.URL_WEBSITE ?: "https://element.io"
|
value = BuildTimeConfig.URL_WEBSITE ?: "https://element.io"
|
||||||
)
|
)
|
||||||
buildConfigFieldStr(
|
|
||||||
name = "REDIRECT_URI",
|
|
||||||
value = buildString {
|
|
||||||
append(BuildTimeConfig.METADATA_HOST_REVERSED ?: "io.element")
|
|
||||||
append(":/callback")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
buildConfigFieldStr(
|
buildConfigFieldStr(
|
||||||
name = "LOGO_URI",
|
name = "LOGO_URI",
|
||||||
value = BuildTimeConfig.URL_LOGO ?: "https://element.io/mobile-icon.png"
|
value = BuildTimeConfig.URL_LOGO ?: "https://element.io/mobile-icon.png"
|
||||||
|
|||||||
@@ -12,11 +12,6 @@ import io.element.android.libraries.matrix.api.BuildConfig
|
|||||||
object OidcConfig {
|
object OidcConfig {
|
||||||
const val CLIENT_URI = BuildConfig.CLIENT_URI
|
const val CLIENT_URI = BuildConfig.CLIENT_URI
|
||||||
|
|
||||||
// Notes:
|
|
||||||
// 1. the scheme must match the value declared in the AndroidManifest.xml
|
|
||||||
// 2. the scheme must be the reverse of the host of CLIENT_URI
|
|
||||||
const val REDIRECT_URI = BuildConfig.REDIRECT_URI
|
|
||||||
|
|
||||||
// Note: host must match with the host of CLIENT_URI
|
// Note: host must match with the host of CLIENT_URI
|
||||||
const val LOGO_URI = BuildConfig.LOGO_URI
|
const val LOGO_URI = BuildConfig.LOGO_URI
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.api.auth
|
||||||
|
|
||||||
|
interface OidcRedirectUrlProvider {
|
||||||
|
fun provide(): String
|
||||||
|
}
|
||||||
@@ -9,15 +9,17 @@ package io.element.android.libraries.matrix.impl.auth
|
|||||||
|
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.matrix.api.auth.OidcConfig
|
import io.element.android.libraries.matrix.api.auth.OidcConfig
|
||||||
|
import io.element.android.libraries.matrix.api.auth.OidcRedirectUrlProvider
|
||||||
import org.matrix.rustcomponents.sdk.OidcConfiguration
|
import org.matrix.rustcomponents.sdk.OidcConfiguration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class OidcConfigurationProvider @Inject constructor(
|
class OidcConfigurationProvider @Inject constructor(
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
|
private val oidcRedirectUrlProvider: OidcRedirectUrlProvider,
|
||||||
) {
|
) {
|
||||||
fun get(): OidcConfiguration = OidcConfiguration(
|
fun get(): OidcConfiguration = OidcConfiguration(
|
||||||
clientName = buildMeta.applicationName,
|
clientName = buildMeta.applicationName,
|
||||||
redirectUri = OidcConfig.REDIRECT_URI,
|
redirectUri = oidcRedirectUrlProvider.provide(),
|
||||||
clientUri = OidcConfig.CLIENT_URI,
|
clientUri = OidcConfig.CLIENT_URI,
|
||||||
logoUri = OidcConfig.LOGO_URI,
|
logoUri = OidcConfig.LOGO_URI,
|
||||||
tosUri = OidcConfig.TOS_URI,
|
tosUri = OidcConfig.TOS_URI,
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
package io.element.android.libraries.matrix.impl.auth
|
package io.element.android.libraries.matrix.impl.auth
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.matrix.api.auth.OidcConfig
|
import io.element.android.libraries.matrix.test.auth.FAKE_REDIRECT_URL
|
||||||
|
import io.element.android.libraries.matrix.test.auth.FakeOidcRedirectUrlProvider
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@@ -16,11 +17,12 @@ class OidcConfigurationProviderTest {
|
|||||||
@Test
|
@Test
|
||||||
fun get() {
|
fun get() {
|
||||||
val result = OidcConfigurationProvider(
|
val result = OidcConfigurationProvider(
|
||||||
aBuildMeta(
|
buildMeta = aBuildMeta(
|
||||||
applicationName = "myName",
|
applicationName = "myName",
|
||||||
)
|
),
|
||||||
|
oidcRedirectUrlProvider = FakeOidcRedirectUrlProvider(),
|
||||||
).get()
|
).get()
|
||||||
assertThat(result.clientName).isEqualTo("myName")
|
assertThat(result.clientName).isEqualTo("myName")
|
||||||
assertThat(result.redirectUri).isEqualTo(OidcConfig.REDIRECT_URI)
|
assertThat(result.redirectUri).isEqualTo(FAKE_REDIRECT_URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.google.common.truth.Truth.assertThat
|
|||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.impl.createRustMatrixClientFactory
|
import io.element.android.libraries.matrix.impl.createRustMatrixClientFactory
|
||||||
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
|
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
|
||||||
|
import io.element.android.libraries.matrix.test.auth.FakeOidcRedirectUrlProvider
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||||
import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore
|
import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore
|
||||||
@@ -49,7 +50,10 @@ class RustMatrixAuthenticationServiceTest {
|
|||||||
sessionStore = sessionStore,
|
sessionStore = sessionStore,
|
||||||
rustMatrixClientFactory = rustMatrixClientFactory,
|
rustMatrixClientFactory = rustMatrixClientFactory,
|
||||||
passphraseGenerator = FakePassphraseGenerator(),
|
passphraseGenerator = FakePassphraseGenerator(),
|
||||||
oidcConfigurationProvider = OidcConfigurationProvider(aBuildMeta()),
|
oidcConfigurationProvider = OidcConfigurationProvider(
|
||||||
|
buildMeta = aBuildMeta(),
|
||||||
|
oidcRedirectUrlProvider = FakeOidcRedirectUrlProvider(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.test.auth
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.auth.OidcRedirectUrlProvider
|
||||||
|
|
||||||
|
const val FAKE_REDIRECT_URL = "io.element.android:/"
|
||||||
|
|
||||||
|
class FakeOidcRedirectUrlProvider(
|
||||||
|
private val provideResult: String = FAKE_REDIRECT_URL,
|
||||||
|
) : OidcRedirectUrlProvider {
|
||||||
|
override fun provide() = provideResult
|
||||||
|
}
|
||||||
@@ -39,5 +39,5 @@ fun aBuildMeta(
|
|||||||
gitRevision = gitRevision,
|
gitRevision = gitRevision,
|
||||||
gitBranchName = gitBranchName,
|
gitBranchName = gitBranchName,
|
||||||
flavorDescription = flavorDescription,
|
flavorDescription = flavorDescription,
|
||||||
flavorShortDescription = flavorShortDescription
|
flavorShortDescription = flavorShortDescription,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,25 +7,34 @@
|
|||||||
|
|
||||||
package io.element.android.libraries.oidc.impl
|
package io.element.android.libraries.oidc.impl
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.auth.OidcConfig
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.matrix.api.auth.OidcRedirectUrlProvider
|
||||||
import io.element.android.libraries.oidc.api.OidcAction
|
import io.element.android.libraries.oidc.api.OidcAction
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
fun interface OidcUrlParser {
|
||||||
|
fun parse(url: String): OidcAction?
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple parser for oidc url interception.
|
* Simple parser for oidc url interception.
|
||||||
* TODO Find documentation about the format.
|
* TODO Find documentation about the format.
|
||||||
*/
|
*/
|
||||||
class OidcUrlParser @Inject constructor() {
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultOidcUrlParser @Inject constructor(
|
||||||
|
private val oidcRedirectUrlProvider: OidcRedirectUrlProvider,
|
||||||
|
) : OidcUrlParser {
|
||||||
/**
|
/**
|
||||||
* Return a OidcAction, or null if the url is not a OidcUrl.
|
* Return a OidcAction, or null if the url is not a OidcUrl.
|
||||||
* Note:
|
* Note:
|
||||||
* When user press button "Cancel", we get the url:
|
* When user press button "Cancel", we get the url:
|
||||||
* `io.element:/callback?error=access_denied&state=IFF1UETGye2ZA8pO`
|
* `io.element.android:/?error=access_denied&state=IFF1UETGye2ZA8pO`
|
||||||
* On success, we get:
|
* On success, we get:
|
||||||
* `io.element:/callback?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB`
|
* `io.element.android:/?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB`
|
||||||
*/
|
*/
|
||||||
fun parse(url: String): OidcAction? {
|
override fun parse(url: String): OidcAction? {
|
||||||
if (url.startsWith(OidcConfig.REDIRECT_URI).not()) return null
|
if (url.startsWith(oidcRedirectUrlProvider.provide()).not()) return null
|
||||||
if (url.contains("error=access_denied")) return OidcAction.GoBack
|
if (url.contains("error=access_denied")) return OidcAction.GoBack
|
||||||
if (url.contains("code=")) return OidcAction.Success(url)
|
if (url.contains("code=")) return OidcAction.Success(url)
|
||||||
|
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ import io.element.android.libraries.architecture.NodeInputs
|
|||||||
import io.element.android.libraries.architecture.inputs
|
import io.element.android.libraries.architecture.inputs
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||||
|
import io.element.android.libraries.oidc.impl.OidcUrlParser
|
||||||
|
|
||||||
@ContributesNode(AppScope::class)
|
@ContributesNode(AppScope::class)
|
||||||
class OidcNode @AssistedInject constructor(
|
class OidcNode @AssistedInject constructor(
|
||||||
@Assisted buildContext: BuildContext,
|
@Assisted buildContext: BuildContext,
|
||||||
@Assisted plugins: List<Plugin>,
|
@Assisted plugins: List<Plugin>,
|
||||||
presenterFactory: OidcPresenter.Factory,
|
presenterFactory: OidcPresenter.Factory,
|
||||||
|
private val oidcUrlParser: OidcUrlParser,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
data class Inputs(
|
data class Inputs(
|
||||||
val oidcDetails: OidcDetails,
|
val oidcDetails: OidcDetails,
|
||||||
@@ -38,6 +40,7 @@ class OidcNode @AssistedInject constructor(
|
|||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
OidcView(
|
OidcView(
|
||||||
state = state,
|
state = state,
|
||||||
|
oidcUrlParser = oidcUrlParser,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onNavigateBack = ::navigateUp,
|
onNavigateBack = ::navigateUp,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ import io.element.android.libraries.oidc.impl.OidcUrlParser
|
|||||||
@Composable
|
@Composable
|
||||||
fun OidcView(
|
fun OidcView(
|
||||||
state: OidcState,
|
state: OidcState,
|
||||||
|
oidcUrlParser: OidcUrlParser,
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val isPreview = LocalInspectionMode.current
|
val isPreview = LocalInspectionMode.current
|
||||||
val oidcUrlParser = remember { OidcUrlParser() }
|
|
||||||
var webView by remember { mutableStateOf<WebView?>(null) }
|
var webView by remember { mutableStateOf<WebView?>(null) }
|
||||||
fun shouldOverrideUrl(url: String): Boolean {
|
fun shouldOverrideUrl(url: String): Boolean {
|
||||||
val action = oidcUrlParser.parse(url)
|
val action = oidcUrlParser.parse(url)
|
||||||
@@ -111,6 +111,7 @@ fun OidcView(
|
|||||||
internal fun OidcViewPreview(@PreviewParameter(OidcStateProvider::class) state: OidcState) = ElementPreview {
|
internal fun OidcViewPreview(@PreviewParameter(OidcStateProvider::class) state: OidcState) = ElementPreview {
|
||||||
OidcView(
|
OidcView(
|
||||||
state = state,
|
state = state,
|
||||||
|
oidcUrlParser = { null },
|
||||||
onNavigateBack = {},
|
onNavigateBack = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,44 +8,51 @@
|
|||||||
package io.element.android.libraries.oidc.impl
|
package io.element.android.libraries.oidc.impl
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.matrix.api.auth.OidcConfig
|
import io.element.android.libraries.matrix.test.auth.FAKE_REDIRECT_URL
|
||||||
|
import io.element.android.libraries.matrix.test.auth.FakeOidcRedirectUrlProvider
|
||||||
import io.element.android.libraries.oidc.api.OidcAction
|
import io.element.android.libraries.oidc.api.OidcAction
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class OidcUrlParserTest {
|
class DefaultOidcUrlParserTest {
|
||||||
@Test
|
@Test
|
||||||
fun `test empty url`() {
|
fun `test empty url`() {
|
||||||
val sut = OidcUrlParser()
|
val sut = createDefaultOidcUrlParser()
|
||||||
assertThat(sut.parse("")).isNull()
|
assertThat(sut.parse("")).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test regular url`() {
|
fun `test regular url`() {
|
||||||
val sut = OidcUrlParser()
|
val sut = createDefaultOidcUrlParser()
|
||||||
assertThat(sut.parse("https://matrix.org")).isNull()
|
assertThat(sut.parse("https://matrix.org")).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test cancel url`() {
|
fun `test cancel url`() {
|
||||||
val sut = OidcUrlParser()
|
val sut = createDefaultOidcUrlParser()
|
||||||
val aCancelUrl = OidcConfig.REDIRECT_URI + "?error=access_denied&state=IFF1UETGye2ZA8pO"
|
val aCancelUrl = "$FAKE_REDIRECT_URL?error=access_denied&state=IFF1UETGye2ZA8pO"
|
||||||
assertThat(sut.parse(aCancelUrl)).isEqualTo(OidcAction.GoBack)
|
assertThat(sut.parse(aCancelUrl)).isEqualTo(OidcAction.GoBack)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test success url`() {
|
fun `test success url`() {
|
||||||
val sut = OidcUrlParser()
|
val sut = createDefaultOidcUrlParser()
|
||||||
val aSuccessUrl = OidcConfig.REDIRECT_URI + "?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB"
|
val aSuccessUrl = "$FAKE_REDIRECT_URL?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB"
|
||||||
assertThat(sut.parse(aSuccessUrl)).isEqualTo(OidcAction.Success(aSuccessUrl))
|
assertThat(sut.parse(aSuccessUrl)).isEqualTo(OidcAction.Success(aSuccessUrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test unknown url`() {
|
fun `test unknown url`() {
|
||||||
val sut = OidcUrlParser()
|
val sut = createDefaultOidcUrlParser()
|
||||||
val anUnknownUrl = OidcConfig.REDIRECT_URI + "?state=IFF1UETGye2ZA8pO&goat=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB"
|
val anUnknownUrl = "$FAKE_REDIRECT_URL?state=IFF1UETGye2ZA8pO&goat=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB"
|
||||||
Assert.assertThrows(IllegalStateException::class.java) {
|
Assert.assertThrows(IllegalStateException::class.java) {
|
||||||
assertThat(sut.parse(anUnknownUrl))
|
assertThat(sut.parse(anUnknownUrl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createDefaultOidcUrlParser(): DefaultOidcUrlParser {
|
||||||
|
return DefaultOidcUrlParser(
|
||||||
|
oidcRedirectUrlProvider = FakeOidcRedirectUrlProvider(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,6 @@ object BuildTimeConfig {
|
|||||||
val URL_ACCEPTABLE_USE: String? = null
|
val URL_ACCEPTABLE_USE: String? = null
|
||||||
val URL_PRIVACY: String? = null
|
val URL_PRIVACY: String? = null
|
||||||
val URL_POLICY: String? = null
|
val URL_POLICY: String? = null
|
||||||
val SUPPORT_EMAIL_ADDRESS: String? = null
|
|
||||||
val SERVICES_MAPTILER_BASE_URL: String? = null
|
val SERVICES_MAPTILER_BASE_URL: String? = null
|
||||||
val SERVICES_MAPTILER_APIKEY: String? = null
|
val SERVICES_MAPTILER_APIKEY: String? = null
|
||||||
val SERVICES_MAPTILER_LIGHT_MAPID: String? = null
|
val SERVICES_MAPTILER_LIGHT_MAPID: String? = null
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
# Format is:
|
# Format is:
|
||||||
|
|
||||||
# Error
|
# Error
|
||||||
# adb shell am start -a android.intent.action.VIEW -d "io.element:/callback?error=access_denied\\&state=IFF1UETGye2ZA8pO"
|
# adb shell am start -a android.intent.action.VIEW -d "io.element.android:/?error=access_denied\\&state=IFF1UETGye2ZA8pO"
|
||||||
|
|
||||||
# Success
|
# Success
|
||||||
adb shell am start -a android.intent.action.VIEW -d "io.element:/callback?state=IFF1UETGye2ZA8pO\\&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB"
|
adb shell am start -a android.intent.action.VIEW -d "io.element.android:/?state=IFF1UETGye2ZA8pO\\&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB"
|
||||||
|
|||||||
Reference in New Issue
Block a user