mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Add local network address classifier and permission advisor
This commit is contained in:
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.permissions.impl
|
||||
|
||||
import android.Manifest.permission.ACCESS_LOCAL_NETWORK
|
||||
import android.os.Build
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.androidutils.network.LocalNetworkAddressClassifier
|
||||
import io.element.android.libraries.androidutils.network.LocalNetworkClassification
|
||||
import io.element.android.libraries.permissions.api.LocalNetworkPermissionAdvisor
|
||||
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultLocalNetworkPermissionAdvisor(
|
||||
private val classifier: LocalNetworkAddressClassifier,
|
||||
private val permissionStateProvider: PermissionStateProvider,
|
||||
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||
) : LocalNetworkPermissionAdvisor {
|
||||
override suspend fun shouldRequestPermissionFor(homeserverUrl: String): Boolean {
|
||||
if (!buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.CINNAMON_BUN)) return false
|
||||
if (permissionStateProvider.isPermissionGranted(ACCESS_LOCAL_NETWORK)) return false
|
||||
return when (classifier.classify(homeserverUrl)) {
|
||||
LocalNetworkClassification.LocalIp -> true
|
||||
LocalNetworkClassification.PublicIp,
|
||||
// Unresolvable: we can't confirm the host is local, so we don't prompt, acceptable trade-off vs. over-prompting.
|
||||
LocalNetworkClassification.Unresolvable -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.permissions.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.androidutils.network.LocalNetworkAddressClassifier
|
||||
import io.element.android.libraries.androidutils.network.LocalNetworkClassification
|
||||
import io.element.android.libraries.permissions.test.FakePermissionStateProvider
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
private const val LOCAL_URL = "https://matrix.corp.internal"
|
||||
private const val PUBLIC_URL = "https://matrix.org"
|
||||
|
||||
class DefaultLocalNetworkPermissionAdvisorTest {
|
||||
private fun advisor(
|
||||
sdkInt: Int,
|
||||
permissionGranted: Boolean,
|
||||
classification: LocalNetworkClassification,
|
||||
) = DefaultLocalNetworkPermissionAdvisor(
|
||||
classifier = FakeLocalNetworkAddressClassifier(classification),
|
||||
permissionStateProvider = FakePermissionStateProvider(permissionGranted = permissionGranted),
|
||||
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(sdkInt),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `returns false when SDK below 37`() = runTest {
|
||||
val result = advisor(sdkInt = 36, permissionGranted = false, classification = LocalNetworkClassification.LocalIp)
|
||||
.shouldRequestPermissionFor(LOCAL_URL)
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when permission already granted`() = runTest {
|
||||
val result = advisor(sdkInt = 37, permissionGranted = true, classification = LocalNetworkClassification.LocalIp)
|
||||
.shouldRequestPermissionFor(LOCAL_URL)
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when URL classified as public IP`() = runTest {
|
||||
val result = advisor(sdkInt = 37, permissionGranted = false, classification = LocalNetworkClassification.PublicIp)
|
||||
.shouldRequestPermissionFor(PUBLIC_URL)
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when SDK 37, permission missing, URL is local`() = runTest {
|
||||
val result = advisor(sdkInt = 37, permissionGranted = false, classification = LocalNetworkClassification.LocalIp)
|
||||
.shouldRequestPermissionFor(LOCAL_URL)
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when SDK 37, permission missing, URL unresolvable`() = runTest {
|
||||
val result = advisor(sdkInt = 37, permissionGranted = false, classification = LocalNetworkClassification.Unresolvable)
|
||||
.shouldRequestPermissionFor(LOCAL_URL)
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when SDK above 37, permission missing, URL is local`() = runTest {
|
||||
val result = advisor(sdkInt = 38, permissionGranted = false, classification = LocalNetworkClassification.LocalIp)
|
||||
.shouldRequestPermissionFor(LOCAL_URL)
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeLocalNetworkAddressClassifier(
|
||||
private val classification: LocalNetworkClassification,
|
||||
) : LocalNetworkAddressClassifier {
|
||||
override suspend fun classify(url: String): LocalNetworkClassification = classification
|
||||
}
|
||||
Reference in New Issue
Block a user