Rename the custom recovery passphrase well-known to match the merged schema

The merged element-enterprise#261 well-known schema advertises the feature
under custom_recovery_passphrase (no _settings/_requirements suffix), so the
parser previously looking for custom_recovery_passphrase_settings would never
activate the flow. Update the @SerialName and drop the narrow "Requirements"
suffix from the type — the block is now general, extensible feature settings —
to mirror the .pkl CustomRecoveryPassphrase class. Bumps the enterprise
submodule to the matching consumer change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jenna Vassar
2026-06-11 15:03:24 -07:00
parent cc9d787602
commit fdac92a9ea
8 changed files with 41 additions and 41 deletions
@@ -8,11 +8,11 @@
package io.element.android.libraries.wellknown.api
/**
* Server-driven requirements for a user-chosen recovery passphrase. Today the only rule
* is a minimum character count; additional rules can be added here as the well-known
* schema (`custom_recovery_passphrase_settings`) grows.
* Server-driven settings for a user-chosen recovery passphrase, advertised under the well-known
* `custom_recovery_passphrase` key. Today the only rule is a minimum character count; additional
* settings can be added here as the schema grows.
*/
data class CustomRecoveryPassphraseRequirements(
data class CustomRecoveryPassphrase(
val minCharacterCount: Int,
) {
/** True when [input] meets every active rule. */
@@ -15,5 +15,5 @@ data class ElementWellKnown(
val brandColor: String?,
val notificationSound: String?,
val identityProviderAppScheme: String?,
val customRecoveryPassphraseRequirements: CustomRecoveryPassphraseRequirements?,
val customRecoveryPassphrase: CustomRecoveryPassphrase?,
)
@@ -21,7 +21,7 @@ import kotlinx.serialization.Serializable
* "brand_color": "#FF0000",
* "notification_sound": "ring.flac",
* "idp_app_scheme": "io.element.app",
* "custom_recovery_passphrase_settings": {
* "custom_recovery_passphrase": {
* "min_character_count": 8
* }
* }
@@ -42,12 +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,
@SerialName("custom_recovery_passphrase")
val customRecoveryPassphrase: InternalCustomRecoveryPassphrase? = null,
)
@Serializable
data class InternalCustomRecoveryPassphraseRequirements(
data class InternalCustomRecoveryPassphrase(
@SerialName("min_character_count")
val minCharacterCount: Int? = null,
)
@@ -9,7 +9,7 @@
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.CustomRecoveryPassphrase
import io.element.android.libraries.wellknown.api.ElementWellKnown
import timber.log.Timber
@@ -29,20 +29,20 @@ internal fun InternalElementWellKnown.map() = ElementWellKnown(
brandColor = brandColor,
notificationSound = notificationSound,
identityProviderAppScheme = identityProviderAppScheme,
customRecoveryPassphraseRequirements = customRecoveryPassphraseRequirements?.toPublic(),
customRecoveryPassphrase = customRecoveryPassphrase?.toPublic(),
)
private fun InternalCustomRecoveryPassphraseRequirements.toPublic(): CustomRecoveryPassphraseRequirements {
private fun InternalCustomRecoveryPassphrase.toPublic(): CustomRecoveryPassphrase {
// Whenever the homeserver advertises the settings block we run the custom passphrase flow, flooring
// the minimum at 1 so the passphrase can never be empty even if the server omits min_character_count
// or advertises a non-positive value. The operator owns any stronger minimum.
val min = (minCharacterCount ?: MINIMUM_PASSPHRASE_CHARACTER_COUNT).coerceAtLeast(MINIMUM_PASSPHRASE_CHARACTER_COUNT)
if (min != minCharacterCount) {
Timber.tag(loggerTag.value).w(
"custom_recovery_passphrase_settings.min_character_count was %s; flooring to %d",
"custom_recovery_passphrase.min_character_count was %s; flooring to %d",
minCharacterCount,
min,
)
}
return CustomRecoveryPassphraseRequirements(minCharacterCount = min)
return CustomRecoveryPassphrase(minCharacterCount = min)
}
@@ -10,24 +10,24 @@ package io.element.android.libraries.wellknown.api
import com.google.common.truth.Truth.assertThat
import org.junit.Test
class CustomRecoveryPassphraseRequirementsTest {
class CustomRecoveryPassphraseTest {
@Test
fun `empty input never satisfies a positive minimum`() {
assertThat(CustomRecoveryPassphraseRequirements(minCharacterCount = 1).isSatisfiedBy("")).isFalse()
assertThat(CustomRecoveryPassphrase(minCharacterCount = 1).isSatisfiedBy("")).isFalse()
}
@Test
fun `input shorter than minimum is not satisfied`() {
assertThat(CustomRecoveryPassphraseRequirements(minCharacterCount = 8).isSatisfiedBy("abc")).isFalse()
assertThat(CustomRecoveryPassphrase(minCharacterCount = 8).isSatisfiedBy("abc")).isFalse()
}
@Test
fun `input exactly at minimum is satisfied`() {
assertThat(CustomRecoveryPassphraseRequirements(minCharacterCount = 8).isSatisfiedBy("abcdefgh")).isTrue()
assertThat(CustomRecoveryPassphrase(minCharacterCount = 8).isSatisfiedBy("abcdefgh")).isTrue()
}
@Test
fun `input longer than minimum is satisfied`() {
assertThat(CustomRecoveryPassphraseRequirements(minCharacterCount = 4).isSatisfiedBy("abcdefgh")).isTrue()
assertThat(CustomRecoveryPassphrase(minCharacterCount = 4).isSatisfiedBy("abcdefgh")).isTrue()
}
}
@@ -19,7 +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.CustomRecoveryPassphrase
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
@@ -52,7 +52,7 @@ class DefaultSessionWellknownRetrieverTest {
brandColor = null,
notificationSound = null,
identityProviderAppScheme = null,
customRecoveryPassphraseRequirements = null,
customRecoveryPassphrase = null,
)
)
)
@@ -78,7 +78,7 @@ class DefaultSessionWellknownRetrieverTest {
brandColor = "#FF0000",
notificationSound = "a_notification_sound.flac",
identityProviderAppScheme = "an_app_scheme",
customRecoveryPassphraseRequirements = null,
customRecoveryPassphrase = null,
)
)
)
@@ -108,19 +108,19 @@ class DefaultSessionWellknownRetrieverTest {
brandColor = null,
notificationSound = null,
identityProviderAppScheme = null,
customRecoveryPassphraseRequirements = null,
customRecoveryPassphrase = null,
)
)
)
}
@Test
fun `get element wellknown with custom recovery passphrase requirements`() = runTest {
fun `get element wellknown with custom recovery passphrase settings`() = runTest {
val sut = createDefaultSessionWellknownRetriever(
getUrlLambda = {
Result.success(
"""{
"custom_recovery_passphrase_settings": {
"custom_recovery_passphrase": {
"min_character_count": 8
}
}""".trimIndent().toByteArray()
@@ -130,19 +130,19 @@ class DefaultSessionWellknownRetrieverTest {
assertThat(sut.getElementWellKnown()).isEqualTo(
WellknownRetrieverResult.Success(
anElementWellKnown(
customRecoveryPassphraseRequirements = CustomRecoveryPassphraseRequirements(minCharacterCount = 8)
customRecoveryPassphrase = CustomRecoveryPassphrase(minCharacterCount = 8)
)
)
)
}
@Test
fun `get element wellknown with custom recovery passphrase requirements missing min character count floors to 1`() = runTest {
fun `get element wellknown with custom recovery passphrase settings missing min character count floors to 1`() = runTest {
val sut = createDefaultSessionWellknownRetriever(
getUrlLambda = {
Result.success(
"""{
"custom_recovery_passphrase_settings": {}
"custom_recovery_passphrase": {}
}""".trimIndent().toByteArray()
)
},
@@ -150,7 +150,7 @@ class DefaultSessionWellknownRetrieverTest {
assertThat(sut.getElementWellKnown()).isEqualTo(
WellknownRetrieverResult.Success(
anElementWellKnown(
customRecoveryPassphraseRequirements = CustomRecoveryPassphraseRequirements(minCharacterCount = 1)
customRecoveryPassphrase = CustomRecoveryPassphrase(minCharacterCount = 1)
)
)
)
@@ -162,7 +162,7 @@ class DefaultSessionWellknownRetrieverTest {
getUrlLambda = {
Result.success(
"""{
"custom_recovery_passphrase_settings": {
"custom_recovery_passphrase": {
"min_character_count": 0
}
}""".trimIndent().toByteArray()
@@ -172,7 +172,7 @@ class DefaultSessionWellknownRetrieverTest {
assertThat(sut.getElementWellKnown()).isEqualTo(
WellknownRetrieverResult.Success(
anElementWellKnown(
customRecoveryPassphraseRequirements = CustomRecoveryPassphraseRequirements(minCharacterCount = 1)
customRecoveryPassphrase = CustomRecoveryPassphrase(minCharacterCount = 1)
)
)
)
@@ -184,7 +184,7 @@ class DefaultSessionWellknownRetrieverTest {
getUrlLambda = {
Result.success(
"""{
"custom_recovery_passphrase_settings": {
"custom_recovery_passphrase": {
"min_character_count": -5
}
}""".trimIndent().toByteArray()
@@ -194,7 +194,7 @@ class DefaultSessionWellknownRetrieverTest {
assertThat(sut.getElementWellKnown()).isEqualTo(
WellknownRetrieverResult.Success(
anElementWellKnown(
customRecoveryPassphraseRequirements = CustomRecoveryPassphraseRequirements(minCharacterCount = 1)
customRecoveryPassphrase = CustomRecoveryPassphrase(minCharacterCount = 1)
)
)
)
@@ -247,7 +247,7 @@ class DefaultSessionWellknownRetrieverTest {
brandColor = "#FF0000",
notificationSound = "a_notification_sound.flac",
identityProviderAppScheme = "an_app_scheme",
customRecoveryPassphraseRequirements = null,
customRecoveryPassphrase = null,
)
)
)
@@ -301,7 +301,7 @@ class DefaultSessionWellknownRetrieverTest {
brandColor = "#FF0000",
notificationSound = "a_notification_sound.flac",
identityProviderAppScheme = "an_app_scheme",
customRecoveryPassphraseRequirements = null,
customRecoveryPassphrase = null,
)
)
)
@@ -8,7 +8,7 @@
package io.element.android.features.wellknown.test
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphraseRequirements
import io.element.android.libraries.wellknown.api.CustomRecoveryPassphrase
import io.element.android.libraries.wellknown.api.ElementWellKnown
fun anElementWellKnown(
@@ -18,7 +18,7 @@ fun anElementWellKnown(
brandColor: String? = null,
notificationSound: String? = null,
identityProviderAppScheme: String? = null,
customRecoveryPassphraseRequirements: CustomRecoveryPassphraseRequirements? = null,
customRecoveryPassphrase: CustomRecoveryPassphrase? = null,
) = ElementWellKnown(
registrationHelperUrl = registrationHelperUrl,
enforceElementPro = enforceElementPro,
@@ -26,11 +26,11 @@ fun anElementWellKnown(
brandColor = brandColor,
notificationSound = notificationSound,
identityProviderAppScheme = identityProviderAppScheme,
customRecoveryPassphraseRequirements = customRecoveryPassphraseRequirements,
customRecoveryPassphrase = customRecoveryPassphrase,
)
fun aCustomRecoveryPassphraseRequirements(
fun aCustomRecoveryPassphrase(
minCharacterCount: Int = 8,
) = CustomRecoveryPassphraseRequirements(
) = CustomRecoveryPassphrase(
minCharacterCount = minCharacterCount,
)