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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.x8bit.bitwarden.data.credentials.manager
import androidx.credentials.provider.CallingAppInfo
import com.bitwarden.network.service.DigitalAssetLinkService
import com.bitwarden.ui.platform.base.util.prefixHttpsIfNecessary
import com.bitwarden.ui.platform.base.util.prefixWwwIfNecessary
import com.x8bit.bitwarden.data.credentials.model.ValidateOriginResult
import com.x8bit.bitwarden.data.credentials.repository.PrivilegedAppRepository
import com.x8bit.bitwarden.data.platform.manager.AssetManager
Expand Down Expand Up @@ -41,13 +40,7 @@ class OriginManagerImpl(
): ValidateOriginResult {
return digitalAssetLinkService
.checkDigitalAssetLinksRelations(
sourceWebSite = relyingPartyId
// The DAL API does not allow redirects, so we add `www.` to prevent redirects
// when it is absent from the `relyingPartyId`. This ensures that relying
// parties storing their `assetlinks.json` at the `www.` subdomain do not fail
// verification checks.
.prefixWwwIfNecessary()
.prefixHttpsIfNecessary(),
sourceWebSite = relyingPartyId.prefixHttpsIfNecessary(),
targetPackageName = callingAppInfo.packageName,
targetCertificateFingerprint = callingAppInfo
.getSignatureFingerprintAsHexString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,64 +242,6 @@ class OriginManagerTest {
),
)
}

@Test
fun `validateOrigin should prefix www to rpId without www before checking asset links`() =
runTest {
coEvery {
mockDigitalAssetLinkService.checkDigitalAssetLinksRelations(
sourceWebSite = "https://www.example.com",
targetPackageName = DEFAULT_PACKAGE_NAME,
targetCertificateFingerprint = DEFAULT_CERT_FINGERPRINT,
relations = listOf("delegate_permission/common.handle_all_urls"),
)
} returns DEFAULT_ASSET_LINKS_CHECK_RESPONSE.asSuccess()

val result = originManager.validateOrigin(
relyingPartyId = "example.com",
callingAppInfo = mockNonPrivilegedAppInfo,
)

assertEquals(ValidateOriginResult.Success(null), result)
}

@Test
fun `validateOrigin should preserve existing www prefix when present`() = runTest {
coEvery {
mockDigitalAssetLinkService.checkDigitalAssetLinksRelations(
sourceWebSite = "https://www.example.com",
targetPackageName = DEFAULT_PACKAGE_NAME,
targetCertificateFingerprint = DEFAULT_CERT_FINGERPRINT,
relations = listOf("delegate_permission/common.handle_all_urls"),
)
} returns DEFAULT_ASSET_LINKS_CHECK_RESPONSE.asSuccess()

val result = originManager.validateOrigin(
relyingPartyId = "www.example.com",
callingAppInfo = mockNonPrivilegedAppInfo,
)

assertEquals(ValidateOriginResult.Success(null), result)
}

@Test
fun `validateOrigin should handle rpId with https scheme correctly`() = runTest {
coEvery {
mockDigitalAssetLinkService.checkDigitalAssetLinksRelations(
sourceWebSite = "https://www.example.com",
targetPackageName = DEFAULT_PACKAGE_NAME,
targetCertificateFingerprint = DEFAULT_CERT_FINGERPRINT,
relations = listOf("delegate_permission/common.handle_all_urls"),
)
} returns DEFAULT_ASSET_LINKS_CHECK_RESPONSE.asSuccess()

val result = originManager.validateOrigin(
relyingPartyId = "https://example.com",
callingAppInfo = mockNonPrivilegedAppInfo,
)

assertEquals(ValidateOriginResult.Success(null), result)
}
}

private const val DEFAULT_PACKAGE_NAME = "com.x8bit.bitwarden"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,34 +239,6 @@ fun String.prefixHttpsIfNecessaryOrNull(): String? =
fun String.prefixHttpsIfNecessary(): String =
prefixHttpsIfNecessaryOrNull() ?: this

/**
* If the given [String] is a valid URI, "www." will be prepended (or inserted after the scheme
* if present) if it is not already present. Otherwise `null` will be returned.
*
* Examples:
* - "example.com" -> "www.example.com"
* - "www.example.com" -> "www.example.com"
* - "https://example.com" -> "https://www.example.com"
* - "https://www.example.com" -> "https://www.example.com"
*/
fun String.prefixWwwIfNecessaryOrNull(): String? =
when {
this.isBlank() || !this.isValidUri() -> null
this.startsWith("www.") -> this
this.startsWith("http://") || this.startsWith("https://") -> {
if ("://www." in this) this else this.replaceFirst("://", "://www.")
}

else -> "www.$this"
}

/**
* If the given [String] is a valid URI, "www." will be prepended (or inserted after the scheme
* if present) if it is not already present. Otherwise the original [String] will be returned.
*/
fun String.prefixWwwIfNecessary(): String =
prefixWwwIfNecessaryOrNull() ?: this

/**
* Checks if a string is using base32 digits.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,65 +281,4 @@ class StringExtensionsTest {
fun `orZeroWidthSpace returns the original value for a non-blank string`() {
assertEquals("test", "test".orZeroWidthSpace())
}

@Suppress("MaxLineLength")
@Test
fun `prefixWwwIfNecessaryOrNull should prefix www when URI is valid and no scheme and no www`() {
val uri = "example.com"
val expected = "www.$uri"
val actual = uri.prefixWwwIfNecessaryOrNull()

assertEquals(expected, actual)
}

@Test
fun `prefixWwwIfNecessaryOrNull should return URI unchanged when starts with www`() {
val uri = "www.example.com"
val actual = uri.prefixWwwIfNecessaryOrNull()
assertEquals(uri, actual)
}

@Test
fun `prefixWwwIfNecessaryOrNull should prefix www when scheme is http and no www`() {
val uri = "http://example.com"
val expected = "http://www.example.com"
val actual = uri.prefixWwwIfNecessaryOrNull()
assertEquals(expected, actual)
}

@Test
fun `prefixWwwIfNecessaryOrNull should prefix www when scheme is https and no www`() {
val uri = "https://example.com"
val expected = "https://www.example.com"
val actual = uri.prefixWwwIfNecessaryOrNull()
assertEquals(expected, actual)
}

@Suppress("MaxLineLength")
@Test
fun `prefixWwwIfNecessaryOrNull should return URI unchanged when scheme is http and www is present`() {
val uri = "http://www.example.com"
val actual = uri.prefixWwwIfNecessaryOrNull()
assertEquals(uri, actual)
}

@Suppress("MaxLineLength")
@Test
fun `prefixWwwIfNecessaryOrNull should return URI unchanged when scheme is https and www is present`() {
val uri = "https://www.example.com"
val actual = uri.prefixWwwIfNecessaryOrNull()
assertEquals(uri, actual)
}

@Test
fun `prefixWwwIfNecessaryOrNull should return null when URI is empty string`() {
val uri = ""
assertNull(uri.prefixWwwIfNecessaryOrNull())
}

@Test
fun `prefixWwwIfNecessaryOrNull should return null when URI is invalid`() {
val invalidUri = "invalid uri"
assertNull(invalidUri.prefixWwwIfNecessaryOrNull())
}
}
Loading