OIDC configuration (#4623)
* Login: more logs. * Login: map Oidc error to provide more information in the error dialog. * Oidc: use the application name. * Oidc: move configuration from OidcConfigurationProvider to OidcConfig and add some comments. * Oidc: limit to only 1 contact in the configuration. * Oidc: Move configuration to BuildConfig file. * Remove unused const. * Add missing test on Exception mapping * Remove contacts from OidcConfiguration. https://github.com/matrix-org/matrix-rust-sdk/pull/4958
This commit is contained in:
@@ -60,6 +60,7 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<!-- Note: the scheme must match the scheme of the value of OidcConfig.REDIRECT_URI -->
|
||||
<data android:scheme="io.element" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
|
||||
@@ -12,18 +12,24 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.login.impl.R
|
||||
import io.element.android.libraries.matrix.api.auth.AuthenticationException
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
sealed class ChangeServerError : Throwable() {
|
||||
data class Error(@StringRes val messageId: Int) : ChangeServerError() {
|
||||
data class Error(
|
||||
@StringRes val messageId: Int? = null,
|
||||
val messageStr: String? = null,
|
||||
) : ChangeServerError() {
|
||||
@Composable
|
||||
fun message(): String = stringResource(messageId)
|
||||
fun message(): String = messageStr ?: stringResource(messageId ?: CommonStrings.error_unknown)
|
||||
}
|
||||
|
||||
data object SlidingSyncAlert : ChangeServerError()
|
||||
|
||||
companion object {
|
||||
fun from(error: Throwable): ChangeServerError = when (error) {
|
||||
is AuthenticationException.SlidingSyncVersion -> SlidingSyncAlert
|
||||
else -> Error(R.string.screen_change_server_error_invalid_homeserver)
|
||||
is AuthenticationException.Oidc -> Error(messageStr = error.message)
|
||||
else -> Error(messageId = R.string.screen_change_server_error_invalid_homeserver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import config.BuildTimeConfig
|
||||
import extension.buildConfigFieldStr
|
||||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
@@ -19,6 +21,32 @@ android {
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
buildConfigFieldStr(
|
||||
name = "CLIENT_URI",
|
||||
value = BuildTimeConfig.URL_WEBSITE ?: "https://element.io"
|
||||
)
|
||||
buildConfigFieldStr(
|
||||
name = "REDIRECT_URI",
|
||||
value = buildString {
|
||||
append(BuildTimeConfig.METADATA_HOST_REVERSED ?: "io.element")
|
||||
append(":/callback")
|
||||
}
|
||||
)
|
||||
buildConfigFieldStr(
|
||||
name = "LOGO_URI",
|
||||
value = BuildTimeConfig.URL_LOGO ?: "https://element.io/mobile-icon.png"
|
||||
)
|
||||
buildConfigFieldStr(
|
||||
name = "TOS_URI",
|
||||
value = BuildTimeConfig.URL_ACCEPTABLE_USE ?: "https://element.io/acceptable-use-policy-terms"
|
||||
)
|
||||
buildConfigFieldStr(
|
||||
name = "POLICY_URI",
|
||||
value = BuildTimeConfig.URL_POLICY ?: "https://element.io/privacy"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setupAnvil()
|
||||
|
||||
@@ -10,5 +10,6 @@ package io.element.android.libraries.matrix.api.auth
|
||||
sealed class AuthenticationException(message: String) : Exception(message) {
|
||||
class InvalidServerName(message: String) : AuthenticationException(message)
|
||||
class SlidingSyncVersion(message: String) : AuthenticationException(message)
|
||||
class Oidc(message: String) : AuthenticationException(message)
|
||||
class Generic(message: String) : AuthenticationException(message)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,27 @@
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
import io.element.android.libraries.matrix.api.BuildConfig
|
||||
|
||||
object OidcConfig {
|
||||
const val REDIRECT_URI = "io.element:/callback"
|
||||
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
|
||||
const val LOGO_URI = BuildConfig.LOGO_URI
|
||||
|
||||
// Note: host must match with the host of CLIENT_URI
|
||||
const val TOS_URI = BuildConfig.TOS_URI
|
||||
|
||||
// Note: host must match with the host of CLIENT_URI
|
||||
const val POLICY_URI = BuildConfig.POLICY_URI
|
||||
|
||||
// Some homeservers/auth issuers don't support dynamic client registration, and have to be registered manually
|
||||
val STATIC_REGISTRATIONS = mapOf(
|
||||
"https://id.thirdroom.io/realms/thirdroom" to "elementx",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.auth
|
||||
|
||||
import io.element.android.libraries.matrix.api.auth.AuthenticationException
|
||||
import org.matrix.rustcomponents.sdk.ClientBuildException
|
||||
import org.matrix.rustcomponents.sdk.OidcException
|
||||
|
||||
fun Throwable.mapAuthenticationException(): AuthenticationException {
|
||||
val message = this.message ?: "Unknown error"
|
||||
@@ -24,6 +25,13 @@ fun Throwable.mapAuthenticationException(): AuthenticationException {
|
||||
is ClientBuildException.WellKnownLookupFailed -> AuthenticationException.Generic(message)
|
||||
is ClientBuildException.EventCache -> AuthenticationException.Generic(message)
|
||||
}
|
||||
is OidcException -> when (this) {
|
||||
is OidcException.Generic -> AuthenticationException.Oidc(message)
|
||||
is OidcException.CallbackUrlInvalid -> AuthenticationException.Oidc(message)
|
||||
is OidcException.Cancelled -> AuthenticationException.Oidc(message)
|
||||
is OidcException.MetadataInvalid -> AuthenticationException.Oidc(message)
|
||||
is OidcException.NotSupported -> AuthenticationException.Oidc(message)
|
||||
}
|
||||
else -> AuthenticationException.Generic(message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,24 +7,22 @@
|
||||
|
||||
package io.element.android.libraries.matrix.impl.auth
|
||||
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.auth.OidcConfig
|
||||
import org.matrix.rustcomponents.sdk.OidcConfiguration
|
||||
import javax.inject.Inject
|
||||
|
||||
class OidcConfigurationProvider @Inject constructor() {
|
||||
class OidcConfigurationProvider @Inject constructor(
|
||||
private val buildMeta: BuildMeta,
|
||||
) {
|
||||
fun get(): OidcConfiguration = OidcConfiguration(
|
||||
clientName = "Element",
|
||||
clientName = buildMeta.applicationName,
|
||||
redirectUri = OidcConfig.REDIRECT_URI,
|
||||
clientUri = "https://element.io",
|
||||
logoUri = "https://element.io/mobile-icon.png",
|
||||
tosUri = "https://element.io/acceptable-use-policy-terms",
|
||||
policyUri = "https://element.io/privacy",
|
||||
contacts = listOf(
|
||||
"support@element.io",
|
||||
),
|
||||
// Some homeservers/auth issuers don't support dynamic client registration, and have to be registered manually
|
||||
staticRegistrations = mapOf(
|
||||
"https://id.thirdroom.io/realms/thirdroom" to "elementx",
|
||||
),
|
||||
clientUri = OidcConfig.CLIENT_URI,
|
||||
logoUri = OidcConfig.LOGO_URI,
|
||||
tosUri = OidcConfig.TOS_URI,
|
||||
policyUri = OidcConfig.POLICY_URI,
|
||||
contacts = null,
|
||||
staticRegistrations = OidcConfig.STATIC_REGISTRATIONS,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
}.onFailure {
|
||||
clear()
|
||||
}.mapFailure { failure ->
|
||||
Timber.e(failure, "Failed to set homeserver to $homeserver")
|
||||
failure.mapAuthenticationException()
|
||||
}
|
||||
}
|
||||
@@ -162,6 +163,7 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
|
||||
SessionId(sessionData.userId)
|
||||
}.mapFailure { failure ->
|
||||
Timber.e(failure, "Failed to login")
|
||||
failure.mapAuthenticationException()
|
||||
}
|
||||
}
|
||||
@@ -197,6 +199,7 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
pendingOAuthAuthorizationData = oAuthAuthorizationData
|
||||
OidcDetails(url)
|
||||
}.mapFailure { failure ->
|
||||
Timber.e(failure, "Failed to get OIDC URL")
|
||||
failure.mapAuthenticationException()
|
||||
}
|
||||
}
|
||||
@@ -210,6 +213,7 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
}
|
||||
pendingOAuthAuthorizationData = null
|
||||
}.mapFailure { failure ->
|
||||
Timber.e(failure, "Failed to cancel OIDC login")
|
||||
failure.mapAuthenticationException()
|
||||
}
|
||||
}
|
||||
@@ -243,6 +247,7 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
|
||||
SessionId(sessionData.userId)
|
||||
}.mapFailure { failure ->
|
||||
Timber.e(failure, "Failed to login with OIDC")
|
||||
failure.mapAuthenticationException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.auth.AuthenticationException
|
||||
import org.junit.Test
|
||||
import org.matrix.rustcomponents.sdk.ClientBuildException
|
||||
import org.matrix.rustcomponents.sdk.OidcException
|
||||
|
||||
class AuthenticationExceptionMappingTest {
|
||||
@Test
|
||||
@@ -56,6 +57,20 @@ class AuthenticationExceptionMappingTest {
|
||||
.isException<AuthenticationException.Generic>("EventCache error")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `mapping Oidc exceptions map to the Oidc Kotlin`() {
|
||||
assertThat(OidcException.Generic("Generic").mapAuthenticationException())
|
||||
.isException<AuthenticationException.Oidc>("Generic")
|
||||
assertThat(OidcException.CallbackUrlInvalid("CallbackUrlInvalid").mapAuthenticationException())
|
||||
.isException<AuthenticationException.Oidc>("CallbackUrlInvalid")
|
||||
assertThat(OidcException.Cancelled("Cancelled").mapAuthenticationException())
|
||||
.isException<AuthenticationException.Oidc>("Cancelled")
|
||||
assertThat(OidcException.MetadataInvalid("MetadataInvalid").mapAuthenticationException())
|
||||
.isException<AuthenticationException.Oidc>("MetadataInvalid")
|
||||
assertThat(OidcException.NotSupported("NotSupported").mapAuthenticationException())
|
||||
.isException<AuthenticationException.Oidc>("NotSupported")
|
||||
}
|
||||
|
||||
private inline fun <reified T> ThrowableSubject.isException(message: String) {
|
||||
isInstanceOf(T::class.java)
|
||||
hasMessageThat().isEqualTo(message)
|
||||
|
||||
@@ -9,12 +9,18 @@ package io.element.android.libraries.matrix.impl.auth
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.auth.OidcConfig
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import org.junit.Test
|
||||
|
||||
class OidcConfigurationProviderTest {
|
||||
@Test
|
||||
fun get() {
|
||||
val result = OidcConfigurationProvider().get()
|
||||
val result = OidcConfigurationProvider(
|
||||
aBuildMeta(
|
||||
applicationName = "myName",
|
||||
)
|
||||
).get()
|
||||
assertThat(result.clientName).isEqualTo("myName")
|
||||
assertThat(result.redirectUri).isEqualTo(OidcConfig.REDIRECT_URI)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.impl.createRustMatrixClientFactory
|
||||
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||
@@ -48,7 +49,7 @@ class RustMatrixAuthenticationServiceTest {
|
||||
sessionStore = sessionStore,
|
||||
rustMatrixClientFactory = rustMatrixClientFactory,
|
||||
passphraseGenerator = FakePassphraseGenerator(),
|
||||
oidcConfigurationProvider = OidcConfigurationProvider(),
|
||||
oidcConfigurationProvider = OidcConfigurationProvider(aBuildMeta()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ object BuildTimeConfig {
|
||||
const val GOOGLE_APP_ID_DEBUG = "1:912726360885:android:def0a4e454042e9b00427c"
|
||||
const val GOOGLE_APP_ID_NIGHTLY = "1:912726360885:android:e17435e0beb0303000427c"
|
||||
|
||||
val METADATA_HOST: String? = null
|
||||
val METADATA_HOST_REVERSED: String? = null
|
||||
val URL_WEBSITE: String? = null
|
||||
val URL_LOGO: String? = null
|
||||
val URL_COPYRIGHT: String? = null
|
||||
|
||||
Reference in New Issue
Block a user