Skip to content
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
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
<!--
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
28 changes: 28 additions & 0 deletions libraries/matrix/api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import config.BuildTimeConfig
import extension.buildConfigFieldStr
import extension.setupAnvil

/*
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pixlwave this map is not "externally" configurable, do you know if we should do something about it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine for now. IIRC we added the static registrations specifically for compatibility with Third Room, but given basically everyone will be using MAS, dynamic registration will be supported.

}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
}
Comment on lines +28 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're mapping them to the same type, are we sure the messages are clear enough to track the root cause of the issue?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, for what I have seen, it seems to be clear enough. We may want to improve the mapping if it's not the case.

Previously the homeserver reachability was designed as the cause of the failure, so I guess this is already much better.

else -> AuthenticationException.Generic(message)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
"[email protected]",
),
// 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,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
}.onFailure {
clear()
}.mapFailure { failure ->
Timber.e(failure, "Failed to set homeserver to $homeserver")

Check warning on line 140 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt#L140

Added line #L140 was not covered by tests
failure.mapAuthenticationException()
}
}
Expand All @@ -162,6 +163,7 @@

SessionId(sessionData.userId)
}.mapFailure { failure ->
Timber.e(failure, "Failed to login")

Check warning on line 166 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt#L166

Added line #L166 was not covered by tests
failure.mapAuthenticationException()
}
}
Expand Down Expand Up @@ -197,6 +199,7 @@
pendingOAuthAuthorizationData = oAuthAuthorizationData
OidcDetails(url)
}.mapFailure { failure ->
Timber.e(failure, "Failed to get OIDC URL")

Check warning on line 202 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt#L202

Added line #L202 was not covered by tests
failure.mapAuthenticationException()
}
}
Expand All @@ -210,6 +213,7 @@
}
pendingOAuthAuthorizationData = null
}.mapFailure { failure ->
Timber.e(failure, "Failed to cancel OIDC login")

Check warning on line 216 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt#L216

Added line #L216 was not covered by tests
failure.mapAuthenticationException()
}
}
Expand Down Expand Up @@ -243,6 +247,7 @@

SessionId(sessionData.userId)
}.mapFailure { failure ->
Timber.e(failure, "Failed to login with OIDC")

Check warning on line 250 in libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt

View check run for this annotation

Codecov / codecov/patch

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt#L250

Added line #L250 was not covered by tests
failure.mapAuthenticationException()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -48,7 +49,7 @@ class RustMatrixAuthenticationServiceTest {
sessionStore = sessionStore,
rustMatrixClientFactory = rustMatrixClientFactory,
passphraseGenerator = FakePassphraseGenerator(),
oidcConfigurationProvider = OidcConfigurationProvider(),
oidcConfigurationProvider = OidcConfigurationProvider(aBuildMeta()),
)
}
}
2 changes: 1 addition & 1 deletion plugins/src/main/kotlin/config/BuildTimeConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading