Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(android): configure http urls to collect events for allowed urls #1020

Merged
merged 2 commits into from
Aug 9, 2024
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
5 changes: 3 additions & 2 deletions android/measure/api/measure.api
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ public final class sh/measure/android/Measure {
public final class sh/measure/android/config/MeasureConfig : sh/measure/android/config/IMeasureConfig {
public static final field $stable I
public fun <init> ()V
public fun <init> (ZZLsh/measure/android/config/ScreenshotMaskLevel;ZZLjava/util/List;Ljava/util/List;ZF)V
public synthetic fun <init> (ZZLsh/measure/android/config/ScreenshotMaskLevel;ZZLjava/util/List;Ljava/util/List;ZFILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (ZZLsh/measure/android/config/ScreenshotMaskLevel;ZZLjava/util/List;Ljava/util/List;Ljava/util/List;ZF)V
public synthetic fun <init> (ZZLsh/measure/android/config/ScreenshotMaskLevel;ZZLjava/util/List;Ljava/util/List;Ljava/util/List;ZFILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getEnableLogging ()Z
public fun getHttpHeadersBlocklist ()Ljava/util/List;
public fun getHttpUrlAllowlist ()Ljava/util/List;
public fun getHttpUrlBlocklist ()Ljava/util/List;
public fun getScreenshotMaskLevel ()Lsh/measure/android/config/ScreenshotMaskLevel;
public fun getSessionSamplingRate ()F
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ internal class MeasureInitializerImpl(
trackHttpBody = inputConfig.trackHttpBody,
httpHeadersBlocklist = inputConfig.httpHeadersBlocklist,
httpUrlBlocklist = inputConfig.httpUrlBlocklist,
httpUrlAllowlist = inputConfig.httpUrlAllowlist,
trackActivityIntentData = inputConfig.trackActivityIntentData,
sessionSamplingRate = inputConfig.sessionSamplingRate,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ internal data class Config(
override val trackHttpBody: Boolean = DefaultConfig.TRACK_HTTP_BODY,
override val httpHeadersBlocklist: List<String> = DefaultConfig.HTTP_HEADERS_BLOCKLIST,
override val httpUrlBlocklist: List<String> = DefaultConfig.HTTP_URL_BLOCKLIST,
override val httpUrlAllowlist: List<String> = DefaultConfig.HTTP_URL_ALLOWLIST,
override val trackActivityIntentData: Boolean = DefaultConfig.TRACK_ACTIVITY_INTENT_DATA,
override val sessionSamplingRate: Float = DefaultConfig.SESSION_SAMPLING_RATE,
) : InternalConfig, IMeasureConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ internal class ConfigProviderImpl(
get() = getMergedConfig { httpHeadersBlocklist }
override val httpUrlBlocklist: List<String>
get() = getMergedConfig { httpUrlBlocklist }
override val httpUrlAllowlist: List<String>
get() = getMergedConfig { httpUrlAllowlist }
override val trackActivityIntentData: Boolean
get() = getMergedConfig { trackActivityIntentData }
override val sessionSamplingRate: Float
Expand All @@ -92,22 +94,38 @@ internal class ConfigProviderImpl(
get() = getMergedConfig { userDefinedAttributeKeyWithSpaces }

override fun shouldTrackHttpBody(url: String, contentType: String?): Boolean {
if (!trackHttpBody) {
return false
}

if (contentType.isNullOrEmpty()) {
return false
}
if (combinedHttpUrlBlocklist.any { value -> value?.let { url.contains(it, ignoreCase = true) } == true }) {

// make sure no body is tracked if the URL is not tracked
if (!shouldTrackHttpUrl(url)) {
return false
}

return httpContentTypeAllowlist.any { contentType.startsWith(it, ignoreCase = true) }
}

override fun shouldTrackHttpUrl(url: String): Boolean {
// If the allowlist is not empty, then only allow the URLs that are in the allowlist.
if (httpUrlAllowlist.isNotEmpty()) {
return httpUrlAllowlist.any { url.contains(it, ignoreCase = true) }
}

// If the allowlist is empty, then block the URLs that are in the blocklist.
return !combinedHttpUrlBlocklist.any { value ->
value?.let { url.contains(it, ignoreCase = true) } ?: false
}
}

override fun shouldTrackHttpHeader(key: String): Boolean {
if (!trackHttpHeaders) {
return false
}
return !combinedHttpHeadersBlocklist.any { key.contains(it, ignoreCase = true) }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ internal object DefaultConfig {
const val TRACK_HTTP_BODY: Boolean = false
val HTTP_HEADERS_BLOCKLIST: List<String> = emptyList()
val HTTP_URL_BLOCKLIST: List<String> = emptyList()
val HTTP_URL_ALLOWLIST: List<String> = emptyList()
const val TRACK_ACTIVITY_INTENT_DATA: Boolean = false
const val SESSION_SAMPLING_RATE: Float = 1.0f
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal interface IMeasureConfig {
val trackHttpBody: Boolean
val httpHeadersBlocklist: List<String>
val httpUrlBlocklist: List<String>
val httpUrlAllowlist: List<String>
val trackActivityIntentData: Boolean
val sessionSamplingRate: Float
}
Expand Down Expand Up @@ -60,11 +61,13 @@ class MeasureConfig(

/**
* Allows disabling collection of `http` events for certain URLs. This is useful to setup if you do not
* want to collect data for certain endpoints or third party domains.
* want to collect data for certain endpoints.
*
* The check is made using [String.contains] to see if the URL contains any of the strings in
* the list.
*
* Note that this config is ignored if [httpUrlAllowlist] is set.
*
* Example:
*
* ```kotlin
Expand All @@ -79,6 +82,26 @@ class MeasureConfig(
*/
override val httpUrlBlocklist: List<String> = DefaultConfig.HTTP_URL_BLOCKLIST,

/**
* Allows enabling collection of `http` events for only certain URLs. This is useful to setup if you do not
* want to collect data for all endpoints except for a few.
*
* The check is made using [String.contains] to see if the URL contains any of the strings in
* the list.
*
* Example:
*
* ```kotlin
* MeasureConfig(
* httpUrlAllowlist = listOf(
* "example.com", // enables a domain
* "api.example.com", // enable a subdomain
* "example.com/order" // enable a particular path
* )
* )
*/
override val httpUrlAllowlist: List<String> = DefaultConfig.HTTP_URL_ALLOWLIST,

/**
* Whether to capture intent data used to launch an Activity. Defaults to `false`.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,40 @@ class ConfigProviderTest {
assertEquals(true, configProvider.trackScreenshotOnCrash)
}

@Test
fun `shouldTrackHttpBody returns false if trackHttpBody is set to false`() {
val configProvider = ConfigProviderImpl(
defaultConfig = Config(
trackHttpBody = false,
httpUrlAllowlist = listOf("example.com"),
),
configLoader = configLoader,
)
Assert.assertFalse(configProvider.shouldTrackHttpBody("example.com", "application/json"))
}

@Test
fun `shouldTrackHttpBody returns true for a URL not blocked and allowed content type`() {
val contentType = "application/json"
val configProvider = ConfigProviderImpl(
defaultConfig = Config(trackHttpBody = true, httpUrlBlocklist = listOf("allowed.com")),
configLoader = configLoader,
)
Assert.assertTrue(configProvider.shouldTrackHttpBody("example.com", contentType))
}

@Test
fun `shouldTrackHttpBody returns true for a allowed URL and content type`() {
val url = "example.com"
val contentType = "application/json"
val configProvider = ConfigProviderImpl(
defaultConfig = Config(httpUrlBlocklist = listOf("allowed.com")),
defaultConfig = Config(trackHttpBody = true, httpUrlAllowlist = listOf("allowed.com")),
configLoader = configLoader,
)
Assert.assertTrue(configProvider.shouldTrackHttpBody(url, contentType))
Assert.assertTrue(configProvider.shouldTrackHttpBody("allowed.com", contentType))
}

@Test
fun `shouldTrackHttpBody returns false for a disallowed URL`() {
fun `shouldTrackHttpBody returns false for a blocked URL`() {
val url = "example.com"
val contentType = "application/json"
val configProvider = ConfigProviderImpl(
Expand All @@ -93,6 +114,16 @@ class ConfigProviderTest {
Assert.assertFalse(configProvider.shouldTrackHttpBody(url, contentType))
}

@Test
fun `shouldTrackHttpBody returns false URL not part of allowList`() {
val contentType = "application/json"
val configProvider = ConfigProviderImpl(
defaultConfig = Config(httpUrlAllowlist = listOf("allowed.com")),
configLoader = configLoader,
)
Assert.assertFalse(configProvider.shouldTrackHttpBody("notinallowlist.com", contentType))
}

@Test
fun `shouldTrackHttpBody returns false for a disallowed content type`() {
val url = "https://example.com/"
Expand Down Expand Up @@ -123,10 +154,19 @@ class ConfigProviderTest {
Assert.assertFalse(configProvider.shouldTrackHttpBody(url, contentType))
}

@Test
fun `shouldTrackHttpHeader returns false if trackHttpHeaders is set to false`() {
val configProvider = ConfigProviderImpl(
defaultConfig = Config(trackHttpHeaders = false),
configLoader = configLoader,
)
Assert.assertFalse(configProvider.shouldTrackHttpHeader("key1"))
}

@Test
fun `shouldTrackHttpHeader returns true for a allowed header`() {
val configProvider = ConfigProviderImpl(
defaultConfig = Config(httpHeadersBlocklist = listOf("key1")),
defaultConfig = Config(trackHttpHeaders = true, httpHeadersBlocklist = listOf("key1")),
configLoader = configLoader,
)
Assert.assertTrue(configProvider.shouldTrackHttpHeader("key2"))
Expand All @@ -140,4 +180,49 @@ class ConfigProviderTest {
)
Assert.assertFalse(configProvider.shouldTrackHttpHeader("key1"))
}

@Test
fun `shouldTrackHttpUrl given urlAllowlist and urlBlocklist are empty, returns true for any url`() {
val configProvider = ConfigProviderImpl(
defaultConfig = Config(httpUrlAllowlist = emptyList(), httpUrlBlocklist = emptyList()),
configLoader = configLoader,
)
Assert.assertTrue(configProvider.shouldTrackHttpUrl("https://allowed.com/"))
}

@Test
fun `shouldTrackHttpUrl given urlAllowlist is not empty, returns true if url is part of allowlist`() {
val configProvider = ConfigProviderImpl(
defaultConfig = Config(httpUrlAllowlist = listOf("allowed.com")),
configLoader = configLoader,
)
Assert.assertTrue(configProvider.shouldTrackHttpUrl("https://allowed.com/"))
Assert.assertFalse(configProvider.shouldTrackHttpUrl("https://blocked.com/"))
}

@Test
fun `shouldTrackHttpUrl given urlAllowlist is empty, urlBlocklist is not empty, returns true if url is not part of blocklist`() {
val configProvider = ConfigProviderImpl(
defaultConfig = Config(
httpUrlBlocklist = listOf("blocked.com"),
httpUrlAllowlist = emptyList(),
),
configLoader = configLoader,
)
Assert.assertTrue(configProvider.shouldTrackHttpUrl("https://allowed.com/"))
Assert.assertFalse(configProvider.shouldTrackHttpUrl("https://blocked.com/"))
}

@Test
fun `shouldTrackHttpUrl given both urlAllowlist and urlBlocklist are not empty, considers only the allowlist`() {
val configProvider = ConfigProviderImpl(
defaultConfig = Config(
httpUrlAllowlist = listOf("allowed.com", "unblocked.com"),
httpUrlBlocklist = listOf("unblocked.com"),
),
configLoader = configLoader,
)
Assert.assertTrue(configProvider.shouldTrackHttpUrl("https://allowed.com/"))
Assert.assertTrue(configProvider.shouldTrackHttpUrl("https://unblocked.com/"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal class FakeConfigProvider : ConfigProvider {
override var trackHttpBody: Boolean = false
override var httpHeadersBlocklist: List<String> = emptyList()
override var httpUrlBlocklist: List<String> = emptyList()
override var httpUrlAllowlist: List<String> = emptyList()
override var trackActivityIntentData: Boolean = false
override var sessionSamplingRate: Float = 1.0f
override var maxAttachmentSizeInEventsBatchInBytes: Int = 3
Expand Down
15 changes: 15 additions & 0 deletions docs/android/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Measure.init(
* [**trackScreenshotOnCrash**](#trackScreenshotOnCrash)
* [**screenshotMaskLevel**](#screenshotMaskLevel)
* [**httpUrlBlocklist**](#httpUrlBlocklist)
* [**httpUrlAllowlist**](#httpUrlAllowlist)
* [**trackHttpHeaders**](#trackHttpHeaders)
* [**httpHeadersBlocklist**](#httpHeadersBlocklist)
* [**trackHttpBody**](#trackHttpBody)
Expand Down Expand Up @@ -132,6 +133,20 @@ Allows enabling/disabling capturing of HTTP request and response body. Disabled
Allows disabling collection of `http` events for certain URLs. This is useful to setup if you do not
want to collect data for certain endpoints or third party domains. By default, no URLs are blocked.

Note that this list is used only if `httpUrlAllowlist` is empty.

The check is made using [String.contains] to see if the URL contains any of the strings in
the list.

## `httpUrlAllowlist`

Allows enabling collection of `http` events only for certain URLs. This is useful to setup if you want
to collect data only for certain endpoints or third party domains. If this list is empty, `httpUrlBlocklist` is
considered. By default, this list is empty.

The check is made using [String.contains] to see if the URL contains any of the strings in
the list.

## `trackActivityIntentData`

Android [Intent](https://developer.android.com/reference/android/content/Intent#standard-extra-data)
Expand Down
Loading