PM-12296: Only match port when present on both uris (#4091)

This commit is contained in:
David Perez 2024-10-15 08:56:11 -05:00 committed by GitHub
parent 12afbea83e
commit 499ab2d2d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 66 additions and 3 deletions

View file

@ -7,8 +7,10 @@ import com.x8bit.bitwarden.data.platform.manager.ResourceCacheManager
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.util.firstWithTimeoutOrNull import com.x8bit.bitwarden.data.platform.util.firstWithTimeoutOrNull
import com.x8bit.bitwarden.data.platform.util.getDomainOrNull import com.x8bit.bitwarden.data.platform.util.getDomainOrNull
import com.x8bit.bitwarden.data.platform.util.getHostOrNull
import com.x8bit.bitwarden.data.platform.util.getHostWithPortOrNull import com.x8bit.bitwarden.data.platform.util.getHostWithPortOrNull
import com.x8bit.bitwarden.data.platform.util.getWebHostFromAndroidUriOrNull import com.x8bit.bitwarden.data.platform.util.getWebHostFromAndroidUriOrNull
import com.x8bit.bitwarden.data.platform.util.hasPort
import com.x8bit.bitwarden.data.platform.util.isAndroidApp import com.x8bit.bitwarden.data.platform.util.isAndroidApp
import com.x8bit.bitwarden.data.platform.util.regexOrNull import com.x8bit.bitwarden.data.platform.util.regexOrNull
import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.data.vault.repository.VaultRepository
@ -186,6 +188,7 @@ private fun checkForCipherMatch(
* @param matchingDomains The set of domains that match the domain of [matchUri]. * @param matchingDomains The set of domains that match the domain of [matchUri].
* @param matchUri The uri that this [LoginUriView] is being matched to. * @param matchUri The uri that this [LoginUriView] is being matched to.
*/ */
@Suppress("CyclomaticComplexMethod")
private fun LoginUriView.checkForMatch( private fun LoginUriView.checkForMatch(
resourceCacheManager: ResourceCacheManager, resourceCacheManager: ResourceCacheManager,
defaultUriMatchType: UriMatchType, defaultUriMatchType: UriMatchType,
@ -210,9 +213,15 @@ private fun LoginUriView.checkForMatch(
UriMatchType.EXACT -> exactIfTrue(loginViewUri == matchUri) UriMatchType.EXACT -> exactIfTrue(loginViewUri == matchUri)
UriMatchType.HOST -> { UriMatchType.HOST -> {
val loginUriHost = loginViewUri.getHostWithPortOrNull() if (loginViewUri.hasPort() && matchUri.hasPort()) {
val matchUriHost = matchUri.getHostWithPortOrNull() val loginUriHost = loginViewUri.getHostWithPortOrNull()
exactIfTrue(matchUriHost != null && loginUriHost == matchUriHost) val matchUriHost = matchUri.getHostWithPortOrNull()
exactIfTrue(matchUriHost != null && loginUriHost == matchUriHost)
} else {
val loginUriHost = loginViewUri.getHostOrNull()
val matchUriHost = matchUri.getHostOrNull()
exactIfTrue(matchUriHost != null && loginUriHost == matchUriHost)
}
} }
UriMatchType.NEVER -> MatchResult.NONE UriMatchType.NEVER -> MatchResult.NONE

View file

@ -58,6 +58,21 @@ fun String.getDomainOrNull(resourceCacheManager: ResourceCacheManager): String?
.toUriOrNull() .toUriOrNull()
?.parseDomainOrNull(resourceCacheManager = resourceCacheManager) ?.parseDomainOrNull(resourceCacheManager = resourceCacheManager)
/**
* Returns `true` if the [String] uri has a port, `false` otherwise.
*/
@OmitFromCoverage
fun String.hasPort(): Boolean {
val uri = this.toUriOrNull() ?: return false
return uri.port != -1
}
/**
* Extract the host from this [String] if possible, otherwise return null.
*/
@OmitFromCoverage
fun String.getHostOrNull(): String? = this.toUriOrNull()?.host
/** /**
* Extract the host with optional port from this [String] if possible, otherwise return null. * Extract the host with optional port from this [String] if possible, otherwise return null.
*/ */

View file

@ -8,8 +8,10 @@ import com.x8bit.bitwarden.data.platform.manager.ResourceCacheManager
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.DataState import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.platform.util.getDomainOrNull import com.x8bit.bitwarden.data.platform.util.getDomainOrNull
import com.x8bit.bitwarden.data.platform.util.getHostOrNull
import com.x8bit.bitwarden.data.platform.util.getHostWithPortOrNull import com.x8bit.bitwarden.data.platform.util.getHostWithPortOrNull
import com.x8bit.bitwarden.data.platform.util.getWebHostFromAndroidUriOrNull import com.x8bit.bitwarden.data.platform.util.getWebHostFromAndroidUriOrNull
import com.x8bit.bitwarden.data.platform.util.hasPort
import com.x8bit.bitwarden.data.platform.util.isAndroidApp import com.x8bit.bitwarden.data.platform.util.isAndroidApp
import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
@ -111,6 +113,23 @@ class CipherMatchingManagerTest {
private val hostMatchCipher: CipherView = mockk { private val hostMatchCipher: CipherView = mockk {
every { login } returns hostMatchLoginView every { login } returns hostMatchLoginView
} }
private val hostNoPortMatchLoginUriViewMatching: LoginUriView = mockk {
every { match } returns UriMatchType.HOST
every { uri } returns HOST_NO_PORT_LOGIN_VIEW_URI_MATCHING
}
private val hostNoPortMatchLoginUriViewNotMatching: LoginUriView = mockk {
every { match } returns UriMatchType.HOST
every { uri } returns HOST_NO_PORT_LOGIN_VIEW_URI_NOT_MATCHING
}
private val hostNoPortMatchLoginView: LoginView = mockk {
every { uris } returns listOf(
hostNoPortMatchLoginUriViewMatching,
hostNoPortMatchLoginUriViewNotMatching,
)
}
private val hostNoPortMatchCipher: CipherView = mockk {
every { login } returns hostNoPortMatchLoginView
}
private val neverMatchLoginUriView: LoginUriView = mockk { private val neverMatchLoginUriView: LoginUriView = mockk {
every { match } returns UriMatchType.NEVER every { match } returns UriMatchType.NEVER
every { uri } returns "google.com" every { uri } returns "google.com"
@ -159,6 +178,7 @@ class CipherMatchingManagerTest {
defaultMatchCipher, defaultMatchCipher,
exactMatchCipher, exactMatchCipher,
hostMatchCipher, hostMatchCipher,
hostNoPortMatchCipher,
neverMatchCipher, neverMatchCipher,
regexMatchCipher, regexMatchCipher,
startsWithMatchCipher, startsWithMatchCipher,
@ -222,6 +242,7 @@ class CipherMatchingManagerTest {
defaultMatchCipher, defaultMatchCipher,
exactMatchCipher, exactMatchCipher,
hostMatchCipher, hostMatchCipher,
hostNoPortMatchCipher,
regexMatchCipher, regexMatchCipher,
startsWithMatchCipher, startsWithMatchCipher,
) )
@ -250,6 +271,7 @@ class CipherMatchingManagerTest {
// and therefore is at the end of the list. // and therefore is at the end of the list.
val expected = listOf( val expected = listOf(
hostMatchCipher, hostMatchCipher,
hostNoPortMatchCipher,
regexMatchCipher, regexMatchCipher,
defaultMatchCipher, defaultMatchCipher,
) )
@ -278,6 +300,7 @@ class CipherMatchingManagerTest {
defaultMatchCipher, defaultMatchCipher,
exactMatchCipher, exactMatchCipher,
hostMatchCipher, hostMatchCipher,
hostNoPortMatchCipher,
regexMatchCipher, regexMatchCipher,
startsWithMatchCipher, startsWithMatchCipher,
) )
@ -304,6 +327,7 @@ class CipherMatchingManagerTest {
val uri = "difficultToMatch.com" val uri = "difficultToMatch.com"
val expected = listOf( val expected = listOf(
hostMatchCipher, hostMatchCipher,
hostNoPortMatchCipher,
regexMatchCipher, regexMatchCipher,
) )
setupMocksForMatchingCiphers( setupMocksForMatchingCiphers(
@ -353,12 +377,15 @@ class CipherMatchingManagerTest {
private fun setupMocksForMatchingCiphers( private fun setupMocksForMatchingCiphers(
isAndroidApp: Boolean, isAndroidApp: Boolean,
uri: String, uri: String,
hasPort: Boolean = true,
) { ) {
with(uri) { with(uri) {
every { isAndroidApp() } returns isAndroidApp every { isAndroidApp() } returns isAndroidApp
every { every {
getDomainOrNull(resourceCacheManager = resourceCacheManager) getDomainOrNull(resourceCacheManager = resourceCacheManager)
} returns this.takeIf { isAndroidApp } } returns this.takeIf { isAndroidApp }
every { hasPort() } returns hasPort
every { getHostOrNull() } returns HOST
every { getHostWithPortOrNull() } returns HOST_WITH_PORT every { getHostWithPortOrNull() } returns HOST_WITH_PORT
every { every {
getWebHostFromAndroidUriOrNull() getWebHostFromAndroidUriOrNull()
@ -382,8 +409,15 @@ class CipherMatchingManagerTest {
DEFAULT_LOGIN_VIEW_URI_FIVE.getDomainOrNull(resourceCacheManager = resourceCacheManager) DEFAULT_LOGIN_VIEW_URI_FIVE.getDomainOrNull(resourceCacheManager = resourceCacheManager)
} returns null } returns null
every { HOST_LOGIN_VIEW_URI_MATCHING.hasPort() } returns true
every { HOST_LOGIN_VIEW_URI_MATCHING.getHostWithPortOrNull() } returns HOST_WITH_PORT every { HOST_LOGIN_VIEW_URI_MATCHING.getHostWithPortOrNull() } returns HOST_WITH_PORT
every { HOST_LOGIN_VIEW_URI_NOT_MATCHING.hasPort() } returns true
every { HOST_LOGIN_VIEW_URI_NOT_MATCHING.getHostWithPortOrNull() } returns null every { HOST_LOGIN_VIEW_URI_NOT_MATCHING.getHostWithPortOrNull() } returns null
every { HOST_NO_PORT_LOGIN_VIEW_URI_MATCHING.hasPort() } returns false
every { HOST_NO_PORT_LOGIN_VIEW_URI_MATCHING.getHostOrNull() } returns HOST
every { HOST_NO_PORT_LOGIN_VIEW_URI_NOT_MATCHING.hasPort() } returns false
every { HOST_NO_PORT_LOGIN_VIEW_URI_NOT_MATCHING.getHostOrNull() } returns null
} }
} }
@ -419,4 +453,9 @@ private const val DEFAULT_LOGIN_VIEW_URI_FIVE: String = "DEFAULT_LOGIN_VIEW_URI_
// Setup state for host ciphers // Setup state for host ciphers
private const val HOST_LOGIN_VIEW_URI_MATCHING: String = "DEFAULT_LOGIN_VIEW_URI_MATCHING" private const val HOST_LOGIN_VIEW_URI_MATCHING: String = "DEFAULT_LOGIN_VIEW_URI_MATCHING"
private const val HOST_LOGIN_VIEW_URI_NOT_MATCHING: String = "DEFAULT_LOGIN_VIEW_URI_NOT_MATCHING" private const val HOST_LOGIN_VIEW_URI_NOT_MATCHING: String = "DEFAULT_LOGIN_VIEW_URI_NOT_MATCHING"
private const val HOST_NO_PORT_LOGIN_VIEW_URI_MATCHING: String =
"HOST_NO_PORT_LOGIN_VIEW_URI_MATCHING"
private const val HOST_NO_PORT_LOGIN_VIEW_URI_NOT_MATCHING: String =
"HOST_NO_PORT_LOGIN_VIEW_URI_NOT_MATCHING"
private const val HOST_WITH_PORT: String = "HOST_WITH_PORT" private const val HOST_WITH_PORT: String = "HOST_WITH_PORT"
private const val HOST: String = "HOST"