Skip to content
This repository was archived by the owner on Aug 30, 2023. It is now read-only.

Commit ae0219d

Browse files
Add option to set user information via SentryUserProvider (#549)
1 parent c891d6a commit ae0219d

File tree

16 files changed

+451
-219
lines changed

16 files changed

+451
-219
lines changed

sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/SecurityConfiguration.java

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package io.sentry.samples.spring.boot;
22

3-
import io.sentry.core.IHub;
4-
import io.sentry.core.SentryOptions;
5-
import io.sentry.spring.SentrySecurityFilter;
63
import org.jetbrains.annotations.NotNull;
74
import org.springframework.context.annotation.Bean;
85
import org.springframework.context.annotation.Configuration;
@@ -14,32 +11,15 @@
1411
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
1512
import org.springframework.security.crypto.password.PasswordEncoder;
1613
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
17-
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
1814

1915
@Configuration
2016
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
2117

22-
private final @NotNull IHub hub;
23-
private final @NotNull SentryOptions options;
24-
25-
public SecurityConfiguration(final @NotNull IHub hub, final @NotNull SentryOptions options) {
26-
this.hub = hub;
27-
this.options = options;
28-
}
29-
3018
// this API is meant to be consumed by non-browser clients thus the CSRF protection is not needed.
3119
@Override
3220
@SuppressWarnings("lgtm[java/spring-disabled-csrf-protection]")
3321
protected void configure(final @NotNull HttpSecurity http) throws Exception {
34-
// register SentrySecurityFilter to attach user information to SentryEvents
35-
http.addFilterAfter(new SentrySecurityFilter(hub, options), AnonymousAuthenticationFilter.class)
36-
.csrf()
37-
.disable()
38-
.authorizeRequests()
39-
.anyRequest()
40-
.authenticated()
41-
.and()
42-
.httpBasic();
22+
http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().httpBasic();
4323
}
4424

4525
@Bean

sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/SecurityConfiguration.java

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package io.sentry.samples.spring;
22

3-
import io.sentry.core.IHub;
4-
import io.sentry.core.SentryOptions;
5-
import io.sentry.spring.SentrySecurityFilter;
63
import org.jetbrains.annotations.NotNull;
74
import org.springframework.context.annotation.Bean;
85
import org.springframework.context.annotation.Configuration;
@@ -14,32 +11,15 @@
1411
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
1512
import org.springframework.security.crypto.password.PasswordEncoder;
1613
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
17-
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
1814

1915
@Configuration
2016
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
2117

22-
private final @NotNull IHub hub;
23-
private final @NotNull SentryOptions options;
24-
25-
public SecurityConfiguration(final @NotNull IHub hub, final @NotNull SentryOptions options) {
26-
this.hub = hub;
27-
this.options = options;
28-
}
29-
3018
// this API is meant to be consumed by non-browser clients thus the CSRF protection is not needed.
3119
@Override
3220
@SuppressWarnings("lgtm[java/spring-disabled-csrf-protection]")
3321
protected void configure(final @NotNull HttpSecurity http) throws Exception {
34-
// register SentrySecurityFilter to attach user information to SentryEvents
35-
http.addFilterAfter(new SentrySecurityFilter(hub, options), AnonymousAuthenticationFilter.class)
36-
.csrf()
37-
.disable()
38-
.authorizeRequests()
39-
.anyRequest()
40-
.authenticated()
41-
.and()
42-
.httpBasic();
22+
http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().httpBasic();
4323
}
4424

4525
@Bean

sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import io.sentry.core.protocol.SdkVersion;
1111
import io.sentry.core.transport.ITransport;
1212
import io.sentry.core.transport.ITransportGate;
13+
import io.sentry.spring.SentryUserProvider;
14+
import io.sentry.spring.SentryUserProviderEventProcessor;
1315
import io.sentry.spring.SentryWebConfiguration;
1416
import java.util.List;
1517
import org.jetbrains.annotations.NotNull;
@@ -43,12 +45,17 @@ static class HubConfiguration {
4345
final @NotNull List<EventProcessor> eventProcessors,
4446
final @NotNull List<Integration> integrations,
4547
final @NotNull ObjectProvider<ITransportGate> transportGate,
48+
final @NotNull ObjectProvider<SentryUserProvider> sentryUserProviders,
4649
final @NotNull ObjectProvider<ITransport> transport) {
4750
return options -> {
4851
beforeSendCallback.ifAvailable(options::setBeforeSend);
4952
beforeBreadcrumbCallback.ifAvailable(options::setBeforeBreadcrumb);
5053
eventProcessors.forEach(options::addEventProcessor);
5154
integrations.forEach(options::addIntegration);
55+
sentryUserProviders.forEach(
56+
sentryUserProvider ->
57+
options.addEventProcessor(
58+
new SentryUserProviderEventProcessor(sentryUserProvider)));
5259
transportGate.ifAvailable(options::setTransportGate);
5360
transport.ifAvailable(options::setTransport);
5461
};

sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentrySpringIntegrationTest.kt

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package io.sentry.spring.boot
22

33
import com.nhaarman.mockitokotlin2.verify
4-
import io.sentry.core.IHub
54
import io.sentry.core.Sentry
6-
import io.sentry.core.SentryOptions
75
import io.sentry.core.transport.ITransport
8-
import io.sentry.spring.SentrySecurityFilter
96
import io.sentry.test.checkEvent
107
import java.lang.RuntimeException
118
import org.assertj.core.api.Assertions.assertThat
@@ -30,7 +27,6 @@ import org.springframework.security.core.userdetails.UserDetailsService
3027
import org.springframework.security.crypto.factory.PasswordEncoderFactories
3128
import org.springframework.security.crypto.password.PasswordEncoder
3229
import org.springframework.security.provisioning.InMemoryUserDetailsManager
33-
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
3430
import org.springframework.test.context.junit4.SpringRunner
3531
import org.springframework.web.bind.annotation.GetMapping
3632
import org.springframework.web.bind.annotation.RestController
@@ -120,15 +116,10 @@ class HelloController {
120116
}
121117

122118
@Configuration
123-
open class SecurityConfiguration(
124-
private val hub: IHub,
125-
private val options: SentryOptions
126-
) : WebSecurityConfigurerAdapter() {
119+
open class SecurityConfiguration : WebSecurityConfigurerAdapter() {
127120

128121
override fun configure(http: HttpSecurity) {
129-
http
130-
.addFilterAfter(SentrySecurityFilter(hub, options), AnonymousAuthenticationFilter::class.java)
131-
.csrf().disable()
122+
http.csrf().disable()
132123
.authorizeRequests().anyRequest().authenticated()
133124
.and()
134125
.httpBasic()
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.sentry.spring;
2+
3+
import io.sentry.core.SentryOptions;
4+
import io.sentry.core.protocol.User;
5+
import io.sentry.core.util.Objects;
6+
import javax.servlet.http.HttpServletRequest;
7+
import org.jetbrains.annotations.NotNull;
8+
import org.jetbrains.annotations.Nullable;
9+
import org.springframework.web.context.request.RequestAttributes;
10+
import org.springframework.web.context.request.RequestContextHolder;
11+
import org.springframework.web.context.request.ServletRequestAttributes;
12+
13+
/**
14+
* Resolves user information from {@link HttpServletRequest} obtained via {@link
15+
* RequestContextHolder}.
16+
*/
17+
public final class HttpServletRequestSentryUserProvider implements SentryUserProvider {
18+
private final @NotNull SentryOptions options;
19+
20+
public HttpServletRequestSentryUserProvider(final @NotNull SentryOptions options) {
21+
this.options = Objects.requireNonNull(options, "options are required");
22+
}
23+
24+
@Override
25+
public @Nullable User provideUser() {
26+
if (options.isSendDefaultPii()) {
27+
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
28+
if (requestAttributes instanceof ServletRequestAttributes) {
29+
final ServletRequestAttributes servletRequestAttributes =
30+
(ServletRequestAttributes) requestAttributes;
31+
final HttpServletRequest request = servletRequestAttributes.getRequest();
32+
33+
final User user = new User();
34+
user.setIpAddress(toIpAddress(request));
35+
if (request.getUserPrincipal() != null) {
36+
user.setUsername(request.getUserPrincipal().getName());
37+
}
38+
return user;
39+
}
40+
}
41+
return null;
42+
}
43+
44+
// it is advised to not use `String#split` method but since we do not have 3rd party libraries
45+
// this is our only option.
46+
@SuppressWarnings("StringSplitter")
47+
private static @NotNull String toIpAddress(final @NotNull HttpServletRequest request) {
48+
final String ipAddress = request.getHeader("X-FORWARDED-FOR");
49+
if (ipAddress != null) {
50+
return ipAddress.contains(",") ? ipAddress.split(",")[0].trim() : ipAddress;
51+
} else {
52+
return request.getRemoteAddr();
53+
}
54+
}
55+
}

sentry-spring/src/main/java/io/sentry/spring/SentryInitBeanPostProcessor.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,39 @@
44
import io.sentry.core.Sentry;
55
import io.sentry.core.SentryOptions;
66
import org.jetbrains.annotations.NotNull;
7+
import org.jetbrains.annotations.Nullable;
78
import org.springframework.beans.BeansException;
89
import org.springframework.beans.factory.config.BeanPostProcessor;
10+
import org.springframework.context.ApplicationContext;
11+
import org.springframework.context.ApplicationContextAware;
912

1013
/** Initializes Sentry after all beans are registered. */
1114
@Open
12-
public class SentryInitBeanPostProcessor implements BeanPostProcessor {
15+
public class SentryInitBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
16+
private @Nullable ApplicationContext applicationContext;
17+
1318
@Override
1419
public Object postProcessAfterInitialization(
1520
final @NotNull Object bean, @NotNull final String beanName) throws BeansException {
1621
if (bean instanceof SentryOptions) {
17-
Sentry.init((SentryOptions) bean);
22+
final SentryOptions options = (SentryOptions) bean;
23+
24+
if (applicationContext != null) {
25+
applicationContext
26+
.getBeanProvider(SentryUserProvider.class)
27+
.forEach(
28+
sentryUserProvider ->
29+
options.addEventProcessor(
30+
new SentryUserProviderEventProcessor(sentryUserProvider)));
31+
}
32+
Sentry.init(options);
1833
}
1934
return bean;
2035
}
36+
37+
@Override
38+
public void setApplicationContext(final @NotNull ApplicationContext applicationContext)
39+
throws BeansException {
40+
this.applicationContext = applicationContext;
41+
}
2142
}

sentry-spring/src/main/java/io/sentry/spring/SentrySecurityFilter.java

Lines changed: 0 additions & 54 deletions
This file was deleted.

sentry-spring/src/main/java/io/sentry/spring/SentryUserHttpServletRequestProcessor.java

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.sentry.spring;
2+
3+
import io.sentry.core.protocol.User;
4+
import org.jetbrains.annotations.Nullable;
5+
6+
/**
7+
* Provides user information that's set on {@link io.sentry.core.SentryEvent} using {@link
8+
* SentryUserProviderEventProcessor}.
9+
*
10+
* <p>Out of the box Spring integration configures single {@link SentryUserProvider} - {@link
11+
* HttpServletRequestSentryUserProvider}.
12+
*/
13+
@FunctionalInterface
14+
public interface SentryUserProvider {
15+
@Nullable
16+
User provideUser();
17+
}

0 commit comments

Comments
 (0)