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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@

- Add `installGroupsOverride` parameter and `installGroups` property to Build Distribution SDK ([#5062](https://github.com/getsentry/sentry-java/pull/5062))
- Update Android targetSdk to API 36 (Android 16) ([#5016](https://github.com/getsentry/sentry-java/pull/5016))
- Add AndroidManifest support for Spotlight configuration via `io.sentry.spotlight.enable` and `io.sentry.spotlight.url` ([#5064](https://github.com/getsentry/sentry-java/pull/5064))

### Fixes

- Extract `SpotlightIntegration` to separate `sentry-spotlight` module to prevent insecure HTTP URLs from appearing in release APKs ([#5064](https://github.com/getsentry/sentry-java/pull/5064))
- **Breaking:** Users who enable Spotlight must now add the `io.sentry:sentry-spotlight` dependency:
```kotlin
dependencies {
debugImplementation("io.sentry:sentry-spotlight:<version>")
}
```

### Fixes

Expand Down
1 change: 1 addition & 0 deletions sentry-android-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ dependencies {
testImplementation(libs.mockito.kotlin)
testImplementation(libs.mockito.inline)
testImplementation(projects.sentryTestSupport)
testImplementation(projects.sentrySpotlight)
testImplementation(projects.sentryAndroidFragment)
testImplementation(projects.sentryAndroidTimber)
testImplementation(projects.sentryAndroidReplay)
Expand Down
5 changes: 5 additions & 0 deletions sentry-android-core/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,8 @@
-dontwarn io.sentry.android.distribution.DistributionIntegration
-keepnames class io.sentry.android.distribution.DistributionIntegration
##---------------End: proguard configuration for sentry-android-distribution ----------

##---------------Begin: proguard configuration for sentry-spotlight ----------
-dontwarn io.sentry.spotlight.SpotlightIntegration
-keepnames class io.sentry.spotlight.SpotlightIntegration
##---------------End: proguard configuration for sentry-spotlight ----------
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ final class ManifestMetadataReader {

static final String FEEDBACK_SHOW_BRANDING = "io.sentry.feedback.show-branding";

static final String SPOTLIGHT_ENABLE = "io.sentry.spotlight.enable";

static final String SPOTLIGHT_CONNECTION_URL = "io.sentry.spotlight.url";

/** ManifestMetadataReader ctor */
private ManifestMetadataReader() {}

Expand Down Expand Up @@ -642,6 +646,15 @@ static void applyMetadata(
metadata, logger, FEEDBACK_USE_SENTRY_USER, feedbackOptions.isUseSentryUser()));
feedbackOptions.setShowBranding(
readBool(metadata, logger, FEEDBACK_SHOW_BRANDING, feedbackOptions.isShowBranding()));

options.setEnableSpotlight(
readBool(metadata, logger, SPOTLIGHT_ENABLE, options.isEnableSpotlight()));

final @Nullable String spotlightUrl =
readString(metadata, logger, SPOTLIGHT_CONNECTION_URL, null);
if (spotlightUrl != null) {
options.setSpotlightConnectionUrl(spotlightUrl);
}
}
options
.getLogger()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2217,4 +2217,76 @@ class ManifestMetadataReaderTest {
assertTrue(headers.contains("Authorization"))
assertTrue(headers.contains("X-Custom-Header"))
}

// Spotlight Configuration Tests

@Test
fun `applyMetadata reads spotlight enabled and keeps default value if not found`() {
// Arrange
val context = fixture.getContext()

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertFalse(fixture.options.isEnableSpotlight)
}

@Test
fun `applyMetadata reads spotlight enabled to options`() {
// Arrange
val bundle = bundleOf(ManifestMetadataReader.SPOTLIGHT_ENABLE to true)
val context = fixture.getContext(metaData = bundle)

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertTrue(fixture.options.isEnableSpotlight)
}

@Test
fun `applyMetadata reads spotlight url and keeps null if not found`() {
// Arrange
val context = fixture.getContext()

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertNull(fixture.options.spotlightConnectionUrl)
}

@Test
fun `applyMetadata reads spotlight url to options`() {
// Arrange
val expectedUrl = "http://10.0.2.2:8969/stream"
val bundle = bundleOf(ManifestMetadataReader.SPOTLIGHT_CONNECTION_URL to expectedUrl)
val context = fixture.getContext(metaData = bundle)

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertEquals(expectedUrl, fixture.options.spotlightConnectionUrl)
}

@Test
fun `applyMetadata reads both spotlight enabled and url to options`() {
// Arrange
val expectedUrl = "http://localhost:8969/stream"
val bundle =
bundleOf(
ManifestMetadataReader.SPOTLIGHT_ENABLE to true,
ManifestMetadataReader.SPOTLIGHT_CONNECTION_URL to expectedUrl,
)
val context = fixture.getContext(metaData = bundle)

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertTrue(fixture.options.isEnableSpotlight)
assertEquals(expectedUrl, fixture.options.spotlightConnectionUrl)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import io.sentry.SentryOptions
import io.sentry.SentryOptions.BeforeSendCallback
import io.sentry.Session
import io.sentry.ShutdownHookIntegration
import io.sentry.SpotlightIntegration
import io.sentry.SystemOutLogger
import io.sentry.UncaughtExceptionHandlerIntegration
import io.sentry.android.core.cache.AndroidEnvelopeCache
Expand All @@ -46,6 +45,7 @@ import io.sentry.cache.PersistingScopeObserver.TRANSACTION_FILENAME
import io.sentry.cache.tape.QueueFile
import io.sentry.protocol.Contexts
import io.sentry.protocol.SentryId
import io.sentry.spotlight.SpotlightIntegration
import io.sentry.test.applyTestOptions
import io.sentry.transport.NoOpEnvelopeCache
import io.sentry.util.StringUtils
Expand Down
1 change: 1 addition & 0 deletions sentry-samples/sentry-samples-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ dependencies {
implementation(projects.sentryCompose)
implementation(projects.sentryKotlinExtensions)
implementation(projects.sentryOkhttp)
implementation(projects.sentrySpotlight)

// how to exclude androidx if release health feature is disabled
// implementation(projects.sentryAndroid) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,5 +253,8 @@
<meta-data
android:name="io.sentry.session-replay.network-response-headers"
android:value="X-Response-Time,X-Cache-Status,X-Test-Response" />
<meta-data
android:name="io.sentry.spotlight.enable"
android:value="true" />
</application>
</manifest>
11 changes: 11 additions & 0 deletions sentry-spotlight/api/sentry-spotlight.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
public final class io/sentry/spotlight/BuildConfig {
public static final field VERSION_NAME Ljava/lang/String;
}

public final class io/sentry/spotlight/SpotlightIntegration : io/sentry/Integration, io/sentry/SentryOptions$BeforeEnvelopeCallback, java/io/Closeable {
public fun <init> ()V
public fun close ()V
public fun execute (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
}

86 changes: 86 additions & 0 deletions sentry-spotlight/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import net.ltgt.gradle.errorprone.errorprone
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
`java-library`
id("io.sentry.javadoc")
alias(libs.plugins.kotlin.jvm)
jacoco
alias(libs.plugins.errorprone)
alias(libs.plugins.gradle.versions)
alias(libs.plugins.animalsniffer)
alias(libs.plugins.buildconfig)
}

tasks.withType<KotlinCompile>().configureEach {
compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
}

dependencies {
api(projects.sentry)

errorprone(libs.errorprone.core)
errorprone(libs.nopen.checker)
errorprone(libs.nullaway)
compileOnly(libs.jetbrains.annotations)
compileOnly(libs.nopen.annotations)

// tests
testImplementation(kotlin(Config.kotlinStdLib))
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.mockito.kotlin)
testImplementation(libs.mockito.inline)
testImplementation(projects.sentryTestSupport)

val gummyBearsModule = libs.gummy.bears.api21.get().module
signature("${gummyBearsModule}:${libs.versions.gummyBears.get()}@signature")
}

configure<SourceSetContainer> { test { java.srcDir("src/test/java") } }

jacoco { toolVersion = libs.versions.jacoco.get() }

tasks.jacocoTestReport {
reports {
xml.required.set(true)
html.required.set(false)
}
}

tasks {
jacocoTestCoverageVerification {
violationRules { rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } } }
}
check {
dependsOn(jacocoTestCoverageVerification)
dependsOn(jacocoTestReport)
dependsOn(animalsnifferMain)
}
}

buildConfig {
useJavaOutput()
packageName("io.sentry.spotlight")
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
}

tasks.withType<JavaCompile>().configureEach {
dependsOn(tasks.generateBuildConfig)
options.errorprone {
check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR)
option("NullAway:AnnotatedPackages", "io.sentry")
}
}

tasks.jar {
manifest {
attributes(
"Sentry-Version-Name" to project.version,
"Sentry-SDK-Name" to Config.Sentry.SENTRY_JAVA_SDK_NAME,
"Sentry-SDK-Package-Name" to "maven:io.sentry:sentry-spotlight",
"Implementation-Vendor" to "Sentry",
"Implementation-Title" to project.name,
"Implementation-Version" to project.version,
)
}
}
10 changes: 10 additions & 0 deletions sentry-spotlight/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
##---------------Begin: proguard configuration for sentry-spotlight ----------

# The SDK checks at runtime if this class is available via Class.forName
-keep class io.sentry.spotlight.SpotlightIntegration { <init>(...); }

# To ensure that stack traces is unambiguous
# https://developer.android.com/studio/build/shrink-code#decode-stack-trace
-keepattributes LineNumberTable,SourceFile

##---------------End: proguard configuration for sentry-spotlight ----------
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
package io.sentry;
package io.sentry.spotlight;

import static io.sentry.SentryLevel.DEBUG;
import static io.sentry.SentryLevel.ERROR;
import static io.sentry.SentryLevel.WARNING;
import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion;

import io.sentry.Hint;
import io.sentry.ILogger;
import io.sentry.IScopes;
import io.sentry.ISentryExecutorService;
import io.sentry.Integration;
import io.sentry.NoOpLogger;
import io.sentry.NoOpSentryExecutorService;
import io.sentry.SentryEnvelope;
import io.sentry.SentryExecutorService;
import io.sentry.SentryIntegrationPackageStorage;
import io.sentry.SentryOptions;
import io.sentry.util.Platform;
import java.io.Closeable;
import java.io.IOException;
Expand All @@ -16,12 +27,16 @@
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

@ApiStatus.Internal
public final class SpotlightIntegration
implements Integration, SentryOptions.BeforeEnvelopeCallback, Closeable {

static {
SentryIntegrationPackageStorage.getInstance()
.addPackage("maven:io.sentry:sentry-spotlight", BuildConfig.VERSION_NAME);
}

private @Nullable SentryOptions options;
private @NotNull ILogger logger = NoOpLogger.getInstance();
private @NotNull ISentryExecutorService executorService = NoOpSentryExecutorService.getInstance();
Expand Down Expand Up @@ -78,8 +93,7 @@ private void sendEnvelope(final @NotNull SentryEnvelope envelope) {
}
}

@TestOnly
public String getSpotlightConnectionUrl() {
String getSpotlightConnectionUrl() {
if (options != null && options.getSpotlightConnectionUrl() != null) {
return options.getSpotlightConnectionUrl();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package io.sentry.internal
package io.sentry.spotlight

import io.sentry.IScopes
import io.sentry.SentryOptions
import io.sentry.SentryOptions.BeforeEnvelopeCallback
import io.sentry.SpotlightIntegration
import io.sentry.util.PlatformTestManipulator
import io.sentry.util.SpotlightPlatformTestManipulator
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
Expand Down Expand Up @@ -52,10 +51,10 @@ class SpotlightIntegrationTest {
fun `spotlight connection url falls back to platform defaults`() {
val spotlight = SpotlightIntegration()

PlatformTestManipulator.pretendIsAndroid(true)
SpotlightPlatformTestManipulator.pretendIsAndroid(true)
assertEquals("http://10.0.2.2:8969/stream", spotlight.spotlightConnectionUrl)

PlatformTestManipulator.pretendIsAndroid(false)
SpotlightPlatformTestManipulator.pretendIsAndroid(false)
assertEquals("http://localhost:8969/stream", spotlight.spotlightConnectionUrl)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.sentry.util

object SpotlightPlatformTestManipulator {
fun pretendIsAndroid(isAndroid: Boolean) {
Platform.isAndroid = isAndroid
}
}
18 changes: 10 additions & 8 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -1786,6 +1786,16 @@ public final class io/sentry/NoOpScopesStorage : io/sentry/IScopesStorage {
public fun set (Lio/sentry/IScopes;)Lio/sentry/ISentryLifecycleToken;
}

public final class io/sentry/NoOpSentryExecutorService : io/sentry/ISentryExecutorService {
public fun close (J)V
public static fun getInstance ()Lio/sentry/ISentryExecutorService;
public fun isClosed ()Z
public fun prewarm ()V
public fun schedule (Ljava/lang/Runnable;J)Ljava/util/concurrent/Future;
public fun submit (Ljava/lang/Runnable;)Ljava/util/concurrent/Future;
public fun submit (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future;
}

public final class io/sentry/NoOpSocketTagger : io/sentry/ISocketTagger {
public static fun getInstance ()Lio/sentry/ISocketTagger;
public fun tagSockets ()V
Expand Down Expand Up @@ -4342,14 +4352,6 @@ public final class io/sentry/SpanStatus$Deserializer : io/sentry/JsonDeserialize
public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object;
}

public final class io/sentry/SpotlightIntegration : io/sentry/Integration, io/sentry/SentryOptions$BeforeEnvelopeCallback, java/io/Closeable {
public fun <init> ()V
public fun close ()V
public fun execute (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V
public fun getSpotlightConnectionUrl ()Ljava/lang/String;
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/SystemOutLogger : io/sentry/ILogger {
public fun <init> ()V
public fun isEnabled (Lio/sentry/SentryLevel;)Z
Expand Down
Loading
Loading