[PM-20309] Migrate Environment and EnvironmentUrlDataJson to data module (#5063)

This commit is contained in:
Patrick Honkonen
2025-04-16 12:37:29 -04:00
committed by GitHub
parent c912a3f12a
commit 3311086dfc
100 changed files with 156 additions and 678 deletions

View File

@@ -1,54 +0,0 @@
package com.bitwarden.authenticator.data.auth.datasource.disk.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Represents URLs for various Bitwarden domains.
*
* @property base The overall base URL.
* @property api Separate base URL for the "/api" domain (if applicable).
* @property identity Separate base URL for the "/identity" domain (if applicable).
* @property icon Separate base URL for the icon domain (if applicable).
* @property notifications Separate base URL for the notifications domain (if applicable).
* @property webVault Separate base URL for the web vault domain (if applicable).
* @property events Separate base URL for the events domain (if applicable).
*/
@Serializable
data class EnvironmentUrlDataJson(
@SerialName("base")
val base: String,
@SerialName("api")
val api: String? = null,
@SerialName("identity")
val identity: String? = null,
@SerialName("icons")
val icon: String? = null,
@SerialName("notifications")
val notifications: String? = null,
@SerialName("webVault")
val webVault: String? = null,
@SerialName("events")
val events: String? = null,
) {
@Suppress("UndocumentedPublicClass")
companion object {
/**
* Default [EnvironmentUrlDataJson] for the US region.
*/
val DEFAULT_US: EnvironmentUrlDataJson =
EnvironmentUrlDataJson(base = "https://vault.bitwarden.com")
/**
* Default [EnvironmentUrlDataJson] for the EU region.
*/
val DEFAULT_EU: EnvironmentUrlDataJson =
EnvironmentUrlDataJson(base = "https://vault.bitwarden.eu")
}
}

View File

@@ -1,7 +1,7 @@
package com.bitwarden.authenticator.data.platform.datasource.network.interceptor
import com.bitwarden.authenticator.data.platform.repository.model.Environment
import com.bitwarden.authenticator.data.platform.repository.util.baseApiUrl
import com.bitwarden.data.repository.model.Environment
import com.bitwarden.data.repository.util.baseApiUrl
import javax.inject.Inject
import javax.inject.Singleton

View File

@@ -1,68 +0,0 @@
package com.bitwarden.authenticator.data.platform.repository.model
import com.bitwarden.authenticator.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import com.bitwarden.authenticator.data.platform.repository.util.labelOrBaseUrlHost
/**
* A higher-level wrapper around [EnvironmentUrlDataJson] that provides type-safety, enumerability,
* and human-readable labels.
*/
sealed class Environment {
/**
* The [Type] of the environment.
*/
abstract val type: Type
/**
* The raw [environmentUrlData] that contains specific base URLs for each relevant domain.
*/
abstract val environmentUrlData: EnvironmentUrlDataJson
/**
* A human-readable label for the environment based in some way on its base URL.
*/
abstract val label: String
/**
* The default US environment.
*/
data object Us : Environment() {
override val type: Type get() = Type.US
override val environmentUrlData: EnvironmentUrlDataJson
get() = EnvironmentUrlDataJson.DEFAULT_US
override val label: String
get() = "bitwarden.com"
}
/**
* The default EU environment.
*/
data object Eu : Environment() {
override val type: Type get() = Type.EU
override val environmentUrlData: EnvironmentUrlDataJson
get() = EnvironmentUrlDataJson.DEFAULT_EU
override val label: String
get() = "bitwarden.eu"
}
/**
* A custom self-hosted environment with a fully configurable [environmentUrlData].
*/
data class SelfHosted(
override val environmentUrlData: EnvironmentUrlDataJson,
) : Environment() {
override val type: Type get() = Type.SELF_HOSTED
override val label: String
get() = environmentUrlData.labelOrBaseUrlHost
}
/**
* A summary of the various types that can be enumerated over and which contains a
* human-readable [label].
*/
enum class Type {
US,
EU,
SELF_HOSTED,
}
}

View File

@@ -1,122 +0,0 @@
package com.bitwarden.authenticator.data.platform.repository.util
import com.bitwarden.authenticator.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import com.bitwarden.authenticator.data.platform.repository.model.Environment
import java.net.URI
private const val DEFAULT_API_URL: String = "https://api.bitwarden.com"
private const val DEFAULT_WEB_VAULT_URL: String = "https://vault.bitwarden.com"
private const val DEFAULT_WEB_SEND_URL: String = "https://send.bitwarden.com/#"
private const val DEFAULT_ICON_URL: String = "https://icons.bitwarden.net"
/**
* Returns the base api URL or the default value if one is not present.
*/
val EnvironmentUrlDataJson.baseApiUrl: String
get() = this.base.sanitizeUrl?.let { "$it/api" }
?: this.api.sanitizeUrl
?: DEFAULT_API_URL
/**
* Returns the base web vault URL. This will check for a custom [EnvironmentUrlDataJson.webVault]
* before falling back to the [EnvironmentUrlDataJson.base]. This can still return null if both are
* null or blank.
*/
val EnvironmentUrlDataJson.baseWebVaultUrlOrNull: String?
get() = this.webVault.sanitizeUrl
?: this.base.sanitizeUrl
/**
* Returns the base web vault URL or the default value if one is not present.
*
* See [baseWebVaultUrlOrNull] for more details.
*/
val EnvironmentUrlDataJson.baseWebVaultUrlOrDefault: String
get() = this.baseWebVaultUrlOrNull ?: DEFAULT_WEB_VAULT_URL
/**
* Returns the base web send URL or the default value if one is not present.
*/
val EnvironmentUrlDataJson.baseWebSendUrl: String
get() =
this
.baseWebVaultUrlOrNull
?.let { "$it/#/send/" }
?: DEFAULT_WEB_SEND_URL
/**
* Returns the base web vault import URL or the default value if one is not present.
*/
val EnvironmentUrlDataJson.toBaseWebVaultImportUrl: String
get() =
this
.baseWebVaultUrlOrDefault
.let { "$it/#/tools/import" }
/**
* Returns a base icon url based on the environment or the default value if values are missing.
*/
val EnvironmentUrlDataJson.baseIconUrl: String
get() = this.icon.sanitizeUrl
?: this.base.sanitizeUrl?.let { "$it/icons" }
?: DEFAULT_ICON_URL
/**
* Returns the appropriate pre-defined labels for environments matching the known US/EU values.
* Otherwise returns the host of the custom base URL.
*
* @see getSelfHostedUrlOrNull
*/
val EnvironmentUrlDataJson.labelOrBaseUrlHost: String
get() = when (this) {
EnvironmentUrlDataJson.DEFAULT_US -> Environment.Us.label
EnvironmentUrlDataJson.DEFAULT_EU -> Environment.Eu.label
else -> {
// Grab the domain
// Ex:
// - "https://www.abc.com/path-1/path-1" -> "www.abc.com"
URI
.create(getSelfHostedUrlOrNull().orEmpty())
.host
.orEmpty()
}
}
/**
* Returns the first self-hosted environment URL from
* [EnvironmentUrlDataJson.webVault], [EnvironmentUrlDataJson.base],
* [EnvironmentUrlDataJson.api], and finally [EnvironmentUrlDataJson.identity]. Returns `null` if
* all self-host environment URLs are null.
*/
private fun EnvironmentUrlDataJson.getSelfHostedUrlOrNull(): String? =
this.webVault.sanitizeUrl
?: this.base.sanitizeUrl
?: this.api.sanitizeUrl
?: this.identity.sanitizeUrl
/**
* A helper method to filter out blank urls and remove any trailing forward slashes.
*/
private val String?.sanitizeUrl: String?
get() = this?.trimEnd('/').takeIf { !it.isNullOrBlank() }
/**
* Converts a raw [EnvironmentUrlDataJson] to an externally-consumable [Environment].
*/
fun EnvironmentUrlDataJson.toEnvironmentUrls(): Environment =
when (this) {
EnvironmentUrlDataJson.DEFAULT_US,
-> Environment.Us
EnvironmentUrlDataJson.DEFAULT_EU,
-> Environment.Eu
else -> Environment.SelfHosted(environmentUrlData = this)
}
/**
* Converts a nullable [EnvironmentUrlDataJson] to an [Environment], where `null` values default to
* the US environment.
*/
fun EnvironmentUrlDataJson?.toEnvironmentUrlsOrDefault(): Environment =
this?.toEnvironmentUrls() ?: Environment.Us

View File

@@ -1,7 +1,7 @@
package com.bitwarden.authenticator.data.platform.datasource.network.interceptor
import com.bitwarden.authenticator.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import com.bitwarden.authenticator.data.platform.repository.model.Environment
import com.bitwarden.data.datasource.disk.model.EnvironmentUrlDataJson
import com.bitwarden.data.repository.model.Environment
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

View File

@@ -1,278 +0,0 @@
package com.bitwarden.authenticator.data.platform.repository.util
import com.bitwarden.authenticator.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
class EnvironmentUrlsDataJsonExtensionsTest {
@Test
fun `baseApiUrl should return base if it is present`() {
Assertions.assertEquals(
"base/api",
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.baseApiUrl,
)
}
@Test
fun `baseApiUrl should return api value if base is empty`() {
Assertions.assertEquals(
"api",
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.copy(
base = "",
).baseApiUrl,
)
}
@Test
fun `baseApiUrl should return default url if base is empty and api is null`() {
Assertions.assertEquals(
"https://api.bitwarden.com",
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.copy(
base = "",
api = null,
).baseApiUrl,
)
}
@Test
fun `baseWebVaultUrlOrNull should return webVault when populated`() {
val result =
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.baseWebVaultUrlOrNull
Assertions.assertEquals("webVault", result)
}
@Test
fun `baseWebVaultUrlOrNull should return base when webvault is not populated`() {
val result =
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA
.copy(webVault = null)
.baseWebVaultUrlOrNull
Assertions.assertEquals("base", result)
}
@Test
fun `baseWebVaultUrlOrNull should return null when webvault and base are not populated`() {
val result =
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA
.copy(
webVault = null,
base = "",
)
.baseWebVaultUrlOrNull
Assertions.assertNull(result)
}
@Test
fun `baseWebVaultUrlOrDefault should return webVault when populated`() {
val result =
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.baseWebVaultUrlOrDefault
Assertions.assertEquals("webVault", result)
}
@Test
fun `baseWebVaultUrlOrDefault should return base when webvault is not populated`() {
val result =
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA
.copy(webVault = null)
.baseWebVaultUrlOrDefault
Assertions.assertEquals("base", result)
}
@Suppress("MaxLineLength")
@Test
fun `baseWebVaultUrlOrDefault should return the default when webvault and base are not populated`() {
val result =
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA
.copy(
webVault = null,
base = "",
)
.baseWebVaultUrlOrDefault
Assertions.assertEquals("https://vault.bitwarden.com", result)
}
@Test
fun `baseWebSendUrl should return the correct result when webVault when populated`() {
val result =
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.baseWebSendUrl
Assertions.assertEquals("webVault/#/send/", result)
}
@Test
fun `baseWebSendUrl should return the correct result when webvault is not populated`() {
val result =
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA
.copy(webVault = null)
.baseWebSendUrl
Assertions.assertEquals("base/#/send/", result)
}
@Test
fun `baseWebSendUrl should return the default when webvault and base are not populated`() {
val result =
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA
.copy(
webVault = null,
base = "",
)
.baseWebSendUrl
Assertions.assertEquals("https://send.bitwarden.com/#", result)
}
@Test
fun `labelOrBaseUrlHost should correctly convert US environment to the correct label`() {
val environment =
EnvironmentUrlDataJson.Companion.DEFAULT_US
Assertions.assertEquals(
com.bitwarden.authenticator.data.platform.repository.model.Environment.Us.label,
environment.labelOrBaseUrlHost,
)
}
@Test
fun `labelOrBaseUrlHost should correctly convert EU environment to the correct label`() {
val environment =
EnvironmentUrlDataJson.Companion.DEFAULT_EU
Assertions.assertEquals(
com.bitwarden.authenticator.data.platform.repository.model.Environment.Eu.label,
environment.labelOrBaseUrlHost,
)
}
@Suppress("MaxLineLength")
@Test
fun `labelOrBaseUrlHost should correctly convert self hosted environment to the correct label`() {
val environment =
EnvironmentUrlDataJson(base = "https://vault.qa.bitwarden.pw")
Assertions.assertEquals(
"vault.qa.bitwarden.pw",
environment.labelOrBaseUrlHost,
)
}
@Test
fun `toEnvironmentUrls should correctly convert US urls to the expected type`() {
Assertions.assertEquals(
com.bitwarden.authenticator.data.platform.repository.model.Environment.Us,
EnvironmentUrlDataJson.Companion.DEFAULT_US.toEnvironmentUrls(),
)
}
@Test
fun `toEnvironmentUrls should correctly convert EU urls to the expected type`() {
Assertions.assertEquals(
com.bitwarden.authenticator.data.platform.repository.model.Environment.Eu,
EnvironmentUrlDataJson.Companion.DEFAULT_EU.toEnvironmentUrls(),
)
}
@Test
fun `toEnvironmentUrls should correctly convert custom urls to the expected type`() {
Assertions.assertEquals(
com.bitwarden.authenticator.data.platform.repository.model.Environment.SelfHosted(
environmentUrlData = DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA,
),
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.toEnvironmentUrls(),
)
}
@Test
fun `toEnvironmentUrlsOrDefault should correctly convert US urls to the expected type`() {
Assertions.assertEquals(
com.bitwarden.authenticator.data.platform.repository.model.Environment.Us,
EnvironmentUrlDataJson.Companion.DEFAULT_US.toEnvironmentUrlsOrDefault(),
)
}
@Test
fun `toEnvironmentUrlsOrDefault should correctly convert EU urls to the expected type`() {
Assertions.assertEquals(
com.bitwarden.authenticator.data.platform.repository.model.Environment.Eu,
EnvironmentUrlDataJson.Companion.DEFAULT_EU.toEnvironmentUrlsOrDefault(),
)
}
@Test
fun `toEnvironmentUrlsOrDefault should correctly convert custom urls to the expected type`() {
Assertions.assertEquals(
com.bitwarden.authenticator.data.platform.repository.model.Environment.SelfHosted(
environmentUrlData = DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA,
),
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.toEnvironmentUrlsOrDefault(),
)
}
@Test
fun `toEnvironmentUrlsOrDefault should convert null types to US values`() {
Assertions.assertEquals(
com.bitwarden.authenticator.data.platform.repository.model.Environment.Us,
(null as EnvironmentUrlDataJson?).toEnvironmentUrlsOrDefault(),
)
}
@Test
fun `toIconBaseurl should return icon if value is present`() {
Assertions.assertEquals(
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.baseIconUrl,
"icon",
)
}
@Test
fun `toIconBaseurl should return base value if icon is null`() {
Assertions.assertEquals(
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA
.copy(icon = null)
.baseIconUrl,
"base/icons",
)
}
@Test
fun `toIconBaseurl should return default url if base is empty and icon is null`() {
val expectedUrl = "https://icons.bitwarden.net"
Assertions.assertEquals(
expectedUrl,
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA
.copy(
base = "",
icon = null,
)
.baseIconUrl,
)
}
@Test
fun `toBaseWebVaultImportUrl should return correct url if webVault is empty`() {
val expectedUrl = "base/#/tools/import"
Assertions.assertEquals(
expectedUrl,
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.copy(
webVault = null,
)
.toBaseWebVaultImportUrl,
)
}
@Test
fun `toBaseWebVaultImportUrl should correctly convert to the import url`() {
val expectedUrl = "webVault/#/tools/import"
Assertions.assertEquals(
expectedUrl,
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.toBaseWebVaultImportUrl,
)
}
}
private val DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA = EnvironmentUrlDataJson(
base = "base",
api = "api",
identity = "identity",
icon = "icon",
notifications = "notifications",
webVault = "webVault",
events = "events",
)