mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Parse custom_recovery_passphrase_settings from Element .well-known
Add CustomRecoveryPassphraseRequirements to ElementWellKnown and decode the custom_recovery_passphrase_settings block (currently just min_character_count) from the homeserver's element.json. Invalid specs (missing or non-positive min_character_count) are logged and mapped to null so the setup flow falls back to the generated-key path. Field names match the ESS well-known schema (element-enterprise#261). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-1
@@ -15,7 +15,15 @@ import kotlinx.serialization.Serializable
|
||||
* Example:
|
||||
* <pre>
|
||||
* {
|
||||
* "registration_helper_url": "https://element.io"
|
||||
* "registration_helper_url": "https://element.io",
|
||||
* "enforce_element_pro": true,
|
||||
* "rageshake_url": "https://example.org/rageshake",
|
||||
* "brand_color": "#FF0000",
|
||||
* "notification_sound": "ring.flac",
|
||||
* "idp_app_scheme": "io.element.app",
|
||||
* "custom_recovery_passphrase_settings": {
|
||||
* "min_character_count": 8
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* .
|
||||
@@ -34,4 +42,12 @@ data class InternalElementWellKnown(
|
||||
val notificationSound: String? = null,
|
||||
@SerialName("idp_app_scheme")
|
||||
val identityProviderAppScheme: String? = null,
|
||||
@SerialName("custom_recovery_passphrase_settings")
|
||||
val customRecoveryPassphraseRequirements: InternalCustomRecoveryPassphraseRequirements? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class InternalCustomRecoveryPassphraseRequirements(
|
||||
@SerialName("min_character_count")
|
||||
val minCharacterCount: Int? = null,
|
||||
)
|
||||
|
||||
+18
@@ -8,7 +8,12 @@
|
||||
|
||||
package io.element.android.libraries.wellknown.impl
|
||||
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphraseRequirements
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||
import timber.log.Timber
|
||||
|
||||
private val loggerTag = LoggerTag("Wellknown")
|
||||
|
||||
internal fun InternalElementWellKnown.map() = ElementWellKnown(
|
||||
registrationHelperUrl = registrationHelperUrl,
|
||||
@@ -17,4 +22,17 @@ internal fun InternalElementWellKnown.map() = ElementWellKnown(
|
||||
brandColor = brandColor,
|
||||
notificationSound = notificationSound,
|
||||
identityProviderAppScheme = identityProviderAppScheme,
|
||||
customRecoveryPassphraseRequirements = customRecoveryPassphraseRequirements?.toPublic(),
|
||||
)
|
||||
|
||||
private fun InternalCustomRecoveryPassphraseRequirements.toPublic(): CustomRecoveryPassphraseRequirements? {
|
||||
val min = minCharacterCount ?: run {
|
||||
Timber.tag(loggerTag.value).w("custom_recovery_passphrase_settings missing min_character_count; ignoring spec")
|
||||
return null
|
||||
}
|
||||
if (min <= 0) {
|
||||
Timber.tag(loggerTag.value).w("custom_recovery_passphrase_settings.min_character_count must be > 0; ignoring spec")
|
||||
return null
|
||||
}
|
||||
return CustomRecoveryPassphraseRequirements(minCharacterCount = min)
|
||||
}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations 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.wellknown.api
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class CustomRecoveryPassphraseRequirementsTest {
|
||||
@Test
|
||||
fun `empty input never satisfies a positive minimum`() {
|
||||
assertThat(CustomRecoveryPassphraseRequirements(minCharacterCount = 1).isSatisfiedBy("")).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `input shorter than minimum is not satisfied`() {
|
||||
assertThat(CustomRecoveryPassphraseRequirements(minCharacterCount = 8).isSatisfiedBy("abc")).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `input exactly at minimum is satisfied`() {
|
||||
assertThat(CustomRecoveryPassphraseRequirements(minCharacterCount = 8).isSatisfiedBy("abcdefgh")).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `input longer than minimum is satisfied`() {
|
||||
assertThat(CustomRecoveryPassphraseRequirements(minCharacterCount = 4).isSatisfiedBy("abcdefgh")).isTrue()
|
||||
}
|
||||
}
|
||||
+80
@@ -19,6 +19,7 @@ import io.element.android.libraries.cachestore.api.CacheStore
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.sessionstorage.test.InMemoryCacheStore
|
||||
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphraseRequirements
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
@@ -51,6 +52,7 @@ class DefaultSessionWellknownRetrieverTest {
|
||||
brandColor = null,
|
||||
notificationSound = null,
|
||||
identityProviderAppScheme = null,
|
||||
customRecoveryPassphraseRequirements = null,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -76,6 +78,7 @@ class DefaultSessionWellknownRetrieverTest {
|
||||
brandColor = "#FF0000",
|
||||
notificationSound = "a_notification_sound.flac",
|
||||
identityProviderAppScheme = "an_app_scheme",
|
||||
customRecoveryPassphraseRequirements = null,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -105,11 +108,86 @@ class DefaultSessionWellknownRetrieverTest {
|
||||
brandColor = null,
|
||||
notificationSound = null,
|
||||
identityProviderAppScheme = null,
|
||||
customRecoveryPassphraseRequirements = null,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get element wellknown with custom recovery passphrase requirements`() = runTest {
|
||||
val sut = createDefaultSessionWellknownRetriever(
|
||||
getUrlLambda = {
|
||||
Result.success(
|
||||
"""{
|
||||
"custom_recovery_passphrase_settings": {
|
||||
"min_character_count": 8
|
||||
}
|
||||
}""".trimIndent().toByteArray()
|
||||
)
|
||||
},
|
||||
)
|
||||
assertThat(sut.getElementWellKnown()).isEqualTo(
|
||||
WellknownRetrieverResult.Success(
|
||||
anElementWellKnown(
|
||||
customRecoveryPassphraseRequirements = CustomRecoveryPassphraseRequirements(minCharacterCount = 8)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get element wellknown with custom recovery passphrase requirements missing min character count maps to null`() = runTest {
|
||||
val sut = createDefaultSessionWellknownRetriever(
|
||||
getUrlLambda = {
|
||||
Result.success(
|
||||
"""{
|
||||
"custom_recovery_passphrase_settings": {}
|
||||
}""".trimIndent().toByteArray()
|
||||
)
|
||||
},
|
||||
)
|
||||
assertThat(sut.getElementWellKnown()).isEqualTo(
|
||||
WellknownRetrieverResult.Success(anElementWellKnown())
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get element wellknown with non-positive min character count maps to null`() = runTest {
|
||||
val sut = createDefaultSessionWellknownRetriever(
|
||||
getUrlLambda = {
|
||||
Result.success(
|
||||
"""{
|
||||
"custom_recovery_passphrase_settings": {
|
||||
"min_character_count": 0
|
||||
}
|
||||
}""".trimIndent().toByteArray()
|
||||
)
|
||||
},
|
||||
)
|
||||
assertThat(sut.getElementWellKnown()).isEqualTo(
|
||||
WellknownRetrieverResult.Success(anElementWellKnown())
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get element wellknown with negative min character count maps to null`() = runTest {
|
||||
val sut = createDefaultSessionWellknownRetriever(
|
||||
getUrlLambda = {
|
||||
Result.success(
|
||||
"""{
|
||||
"custom_recovery_passphrase_settings": {
|
||||
"min_character_count": -5
|
||||
}
|
||||
}""".trimIndent().toByteArray()
|
||||
)
|
||||
},
|
||||
)
|
||||
assertThat(sut.getElementWellKnown()).isEqualTo(
|
||||
WellknownRetrieverResult.Success(anElementWellKnown())
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get element wellknown json error`() = runTest {
|
||||
val sut = createDefaultSessionWellknownRetriever(
|
||||
@@ -157,6 +235,7 @@ class DefaultSessionWellknownRetrieverTest {
|
||||
brandColor = "#FF0000",
|
||||
notificationSound = "a_notification_sound.flac",
|
||||
identityProviderAppScheme = "an_app_scheme",
|
||||
customRecoveryPassphraseRequirements = null,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -210,6 +289,7 @@ class DefaultSessionWellknownRetrieverTest {
|
||||
brandColor = "#FF0000",
|
||||
notificationSound = "a_notification_sound.flac",
|
||||
identityProviderAppScheme = "an_app_scheme",
|
||||
customRecoveryPassphraseRequirements = null,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user