From b7055b2678fd0d79d415930ef60d6691dae5c992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20=C3=81lvarez=20=C3=81lvarez?= Date: Thu, 19 Dec 2024 18:23:59 +0100 Subject: [PATCH] Use V3 of the login events in Java --- manifests/java.yml | 68 ++++++++++++------- tests/appsec/test_automated_login_events.py | 11 +-- ...est_automated_user_and_session_tracking.py | 23 +++++-- .../springboot/WebSecurityConfig.java | 9 ++- .../security/AppSecAuthenticationFilter.java | 4 +- .../AppSecAuthenticationProvider.java | 37 +++++----- .../security/AppSecSecurityController.java | 24 +++++++ .../{AppSecSdkToken.java => AppSecToken.java} | 23 ++----- .../security/AppSecUserDetailsManager.java | 53 +++++++++++++++ 9 files changed, 178 insertions(+), 74 deletions(-) create mode 100644 utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecSecurityController.java rename utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/{AppSecSdkToken.java => AppSecToken.java} (53%) create mode 100644 utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecUserDetailsManager.java diff --git a/manifests/java.yml b/manifests/java.yml index f9269e00c59..8663b893d25 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -1026,58 +1026,78 @@ tests/: test_automated_login_events.py: Test_Login_Events: irrelevant (was v1.36.0 but will be replaced by V2) Test_Login_Events_Extended: irrelevant (was v1.36.0 but will be replaced by V2) - Test_V2_Login_Events: - '*': v1.38.0 + Test_V2_Login_Events: irrelevant (v1.38.0, replaced by V3) + Test_V2_Login_Events_Anon: irrelevant (v1.38.0, replaced by V3) + Test_V2_Login_Events_RC: irrelevant (v1.38.0, replaced by V3) + Test_V3_Auto_User_Instrum_Mode_Capability: v1.45.0 + Test_V3_Login_Events: + '*': v1.45.0 akka-http: missing_feature (login endpoints not implemented) jersey-grizzly2: missing_feature (login endpoints not implemented) play: missing_feature (login endpoints not implemented) ratpack: missing_feature (login endpoints not implemented) resteasy-netty3: missing_feature (login endpoints not implemented) - spring-boot-3-native: missing_feature (GraalVM. Tracing support only) + spring-boot-3-native: flaky (APMAPI-979) spring-boot-openliberty: missing_feature (weblog returns error 500) vertx3: missing_feature (login endpoints not implemented) vertx4: missing_feature (login endpoints not implemented) - Test_V2_Login_Events_Anon: - '*': v1.38.0 + Test_V3_Login_Events_Anon: + '*': v1.45.0 akka-http: missing_feature (login endpoints not implemented) jersey-grizzly2: missing_feature (login endpoints not implemented) play: missing_feature (login endpoints not implemented) ratpack: missing_feature (login endpoints not implemented) resteasy-netty3: missing_feature (login endpoints not implemented) - spring-boot-3-native: missing_feature (GraalVM. Tracing support only) + spring-boot-3-native: flaky (APMAPI-979) spring-boot-openliberty: missing_feature (weblog returns error 500) vertx3: missing_feature (login endpoints not implemented) vertx4: missing_feature (login endpoints not implemented) - Test_V2_Login_Events_RC: - '*': v1.38.0 + Test_V3_Login_Events_Blocking: + '*': v1.45.0 akka-http: missing_feature (login endpoints not implemented) jersey-grizzly2: missing_feature (login endpoints not implemented) play: missing_feature (login endpoints not implemented) ratpack: missing_feature (login endpoints not implemented) resteasy-netty3: missing_feature (login endpoints not implemented) - spring-boot-3-native: missing_feature (GraalVM. Tracing support only) + spring-boot-3-native: flaky (APMAPI-979) spring-boot-openliberty: missing_feature (weblog returns error 500) vertx3: missing_feature (login endpoints not implemented) vertx4: missing_feature (login endpoints not implemented) - Test_V3_Auto_User_Instrum_Mode_Capability: - '*': missing_feature - spring-boot-3-native: flaky (APMAPI-979) - Test_V3_Login_Events: - '*': missing_feature - spring-boot-3-native: flaky (APMAPI-979) - Test_V3_Login_Events_Anon: - '*': missing_feature - spring-boot-3-native: flaky (APMAPI-979) - Test_V3_Login_Events_Blocking: - '*': missing_feature - spring-boot-3-native: flaky (APMAPI-979) Test_V3_Login_Events_RC: - '*': missing_feature + '*': v1.45.0 + akka-http: missing_feature (login endpoints not implemented) + jersey-grizzly2: missing_feature (login endpoints not implemented) + play: missing_feature (login endpoints not implemented) + ratpack: missing_feature (login endpoints not implemented) + resteasy-netty3: missing_feature (login endpoints not implemented) spring-boot-3-native: flaky (APMAPI-979) + spring-boot-openliberty: missing_feature (weblog returns error 500) + vertx3: missing_feature (login endpoints not implemented) + vertx4: missing_feature (login endpoints not implemented) test_automated_user_and_session_tracking.py: Test_Automated_Session_Blocking: missing_feature - Test_Automated_User_Blocking: missing_feature - Test_Automated_User_Tracking: missing_feature + Test_Automated_User_Blocking: + '*': v1.45.0 + akka-http: missing_feature (login endpoints not implemented) + jersey-grizzly2: missing_feature (login endpoints not implemented) + play: missing_feature (login endpoints not implemented) + ratpack: missing_feature (login endpoints not implemented) + resteasy-netty3: missing_feature (login endpoints not implemented) + spring-boot-3-native: flaky (APMAPI-979) + spring-boot-openliberty: missing_feature (weblog returns error 500) + vertx3: missing_feature (login endpoints not implemented) + vertx4: missing_feature (login endpoints not implemented) + Test_Automated_User_Tracking: + '*': v1.45.0 + akka-http: missing_feature (login endpoints not implemented) + jersey-grizzly2: missing_feature (login endpoints not implemented) + play: missing_feature (login endpoints not implemented) + ratpack: missing_feature (login endpoints not implemented) + resteasy-netty3: missing_feature (login endpoints not implemented) + spring-boot-3-native: flaky (APMAPI-979) + spring-boot-openliberty: missing_feature (weblog returns error 500) + vertx3: missing_feature (login endpoints not implemented) + vertx4: missing_feature (login endpoints not implemented) test_blocking_addresses.py: Test_BlockingGraphqlResolvers: missing_feature Test_Blocking_client_ip: diff --git a/tests/appsec/test_automated_login_events.py b/tests/appsec/test_automated_login_events.py index 1a1205501ad..bd4bead6862 100644 --- a/tests/appsec/test_automated_login_events.py +++ b/tests/appsec/test_automated_login_events.py @@ -1221,9 +1221,9 @@ def validate_iden(meta): self._assert_response(self.tests[2], validate_anon) -libs_without_user_id = [] -libs_without_user_exist = ["nodejs"] -libs_without_user_id_on_failure = ["nodejs"] +libs_without_user_id = ["java"] +libs_without_user_exist = ["nodejs", "java"] +libs_without_user_id_on_failure = ["nodejs", "java"] @rfc("https://docs.google.com/document/d/1RT38U6dTTcB-8muiYV4-aVDCsT_XrliyakjtAPyjUpw") @@ -1932,8 +1932,9 @@ def test_login_event_blocking_auto_id(self): assert self.config_state_2[rc.RC_STATE] == rc.ApplyState.ACKNOWLEDGED assert self.config_state_3[rc.RC_STATE] == rc.ApplyState.ACKNOWLEDGED - interfaces.library.assert_waf_attack(self.r_login_blocked, rule="block-user-id") - assert self.r_login_blocked.status_code == 403 + if context.library not in libs_without_user_id: + interfaces.library.assert_waf_attack(self.r_login_blocked, rule="block-user-id") + assert self.r_login_blocked.status_code == 403 def setup_login_event_blocking_auto_login(self): rc.rc_state.reset().apply() diff --git a/tests/appsec/test_automated_user_and_session_tracking.py b/tests/appsec/test_automated_user_and_session_tracking.py index fe9a27ab3c3..2b0938a13aa 100644 --- a/tests/appsec/test_automated_user_and_session_tracking.py +++ b/tests/appsec/test_automated_user_and_session_tracking.py @@ -29,6 +29,8 @@ UUID_USER = "testuuid" PASSWORD = "1234" +libs_without_user_id = ["java"] + def login_data(context, user, password): """In Rails the parameters are group by scope. In the case of the test the scope is user. @@ -55,8 +57,13 @@ def test_user_tracking_auto(self): assert self.r_home.status_code == 200 for _, _, span in interfaces.library.get_spans(request=self.r_home): meta = span.get("meta", {}) - assert meta["usr.id"] == "social-security-id" - assert meta["_dd.appsec.usr.id"] == "social-security-id" + if context.library in libs_without_user_id: + assert meta["usr.id"] == USER + assert meta["_dd.appsec.usr.id"] == USER + else: + assert meta["usr.id"] == "social-security-id" + assert meta["_dd.appsec.usr.id"] == "social-security-id" + assert meta["_dd.appsec.user.collection_mode"] == "identification" def setup_user_tracking_sdk_overwrite(self): @@ -69,7 +76,11 @@ def test_user_tracking_sdk_overwrite(self): for _, _, span in interfaces.library.get_spans(request=self.r_login): meta = span.get("meta", {}) assert meta["usr.id"] == "sdkUser" - assert meta["_dd.appsec.usr.id"] == "social-security-id" + if context.library in libs_without_user_id: + assert meta["_dd.appsec.usr.id"] == USER + else: + assert meta["_dd.appsec.usr.id"] == "social-security-id" + assert meta["_dd.appsec.user.collection_mode"] == "sdk" @@ -102,7 +113,11 @@ def test_user_tracking_sdk_overwrite(self): { "id": "blocked_users", "type": "data_with_expiration", - "data": [{"value": "social-security-id", "expiration": 0}, {"value": "sdkUser", "expiration": 0}], + "data": [ + {"value": "test", "expiration": 0}, + {"value": "social-security-id", "expiration": 0}, + {"value": "sdkUser", "expiration": 0}, + ], }, ], }, diff --git a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/WebSecurityConfig.java b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/WebSecurityConfig.java index 57cb5489dd7..86941a99b77 100644 --- a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/WebSecurityConfig.java +++ b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/WebSecurityConfig.java @@ -2,6 +2,7 @@ import com.datadoghq.system_tests.springboot.security.AppSecAuthenticationFilter; import com.datadoghq.system_tests.springboot.security.AppSecAuthenticationProvider; +import com.datadoghq.system_tests.springboot.security.AppSecUserDetailsManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; @@ -12,6 +13,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.provisioning.UserDetailsManager; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -25,7 +27,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Bean public AuthenticationManager authenticationManager() throws Exception { - return new ProviderManager(new AppSecAuthenticationProvider()); + return new ProviderManager(new AppSecAuthenticationProvider(userDetailsManager())); + } + + @Bean + public UserDetailsManager userDetailsManager() { + return new AppSecUserDetailsManager(); } @Override diff --git a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecAuthenticationFilter.java b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecAuthenticationFilter.java index cddc641cd99..c0e9b653c21 100644 --- a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecAuthenticationFilter.java +++ b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecAuthenticationFilter.java @@ -53,9 +53,9 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ if (sdkEvent != null) { String sdkUser = request.getParameter("sdk_user"); boolean sdkUserExists = Boolean.parseBoolean(request.getParameter("sdk_user_exists")); - authentication = new AppSecSdkToken(username, password, sdkEvent, sdkUser, sdkUserExists); + authentication = new AppSecToken(username, password, sdkEvent, sdkUser, sdkUserExists); } else { - authentication = new AppSecSdkToken(username, password); + authentication = new AppSecToken(username, password); } return this.getAuthenticationManager().authenticate(authentication); } diff --git a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecAuthenticationProvider.java b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecAuthenticationProvider.java index d71afcf32b6..0d686988e7f 100644 --- a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecAuthenticationProvider.java +++ b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecAuthenticationProvider.java @@ -6,27 +6,25 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.provisioning.UserDetailsManager; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; public class AppSecAuthenticationProvider implements AuthenticationProvider { - private static final Map USERS = new HashMap<>(); + private final UserDetailsManager userDetailsManager; - static { - Arrays.asList( - new AppSecUser("social-security-id", "test", "1234", "testuser@ddog.com"), - new AppSecUser("591dc126-8431-4d0f-9509-b23318d3dce4", "testuuid", "1234", "testuseruuid@ddog.com") - ).forEach(user -> USERS.put(user.getUsername(), user)); + public AppSecAuthenticationProvider(final UserDetailsManager userDetailsManager) { + this.userDetailsManager = userDetailsManager; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - AppSecSdkToken token = (AppSecSdkToken) authentication; + AppSecToken token = (AppSecToken) authentication; if (token.getSdkEvent() == null) { return loginUserPassword(token); } else { @@ -34,32 +32,31 @@ public Authentication authenticate(Authentication authentication) throws Authent } } - private Authentication loginUserPassword(final AppSecSdkToken auth) { + private Authentication loginUserPassword(final AppSecToken auth) { String username = auth.getName(); - if (!USERS.containsKey(username)) { + if (!userDetailsManager.userExists(username)) { throw new UsernameNotFoundException(username); } - final AppSecUser user = USERS.get(username); + final AppSecUser user = (AppSecUser) userDetailsManager.loadUserByUsername(username); if (!user.getPassword().equals(auth.getCredentials())) { throw new BadCredentialsException(username); } - return new AppSecSdkToken(new AppSecUser(user), auth.getCredentials(), Collections.emptyList()); + return new AppSecToken(new AppSecUser(user), auth.getCredentials(), Collections.emptyList()); } - private Authentication loginSdk(final AppSecSdkToken auth) { - String username = auth.getSdkUser(); + private Authentication loginSdk(final AppSecToken auth) { Map metadata = new HashMap<>(); EventTracker tracker = GlobalTracer.getEventTracker(); switch (auth.getSdkEvent()) { case "success": - tracker.trackLoginSuccessEvent(username, metadata); - return new AppSecSdkToken(username, auth.getCredentials(), Collections.emptyList()); + tracker.trackLoginSuccessEvent(auth.getSdkUser(), metadata); + return new AppSecToken(auth.getName(), auth.getCredentials(), Collections.emptyList()); case "failure": - tracker.trackLoginFailureEvent(username, auth.isSdkUserExists(), metadata); + tracker.trackLoginFailureEvent(auth.getSdkUser(), auth.isSdkUserExists(), metadata); if (auth.isSdkUserExists()) { - throw new BadCredentialsException(username); + throw new BadCredentialsException(auth.getSdkUser()); } else { - throw new UsernameNotFoundException(username); + throw new UsernameNotFoundException(auth.getSdkUser()); } default: throw new IllegalArgumentException("Invalid SDK event: " + auth.getSdkEvent()); @@ -69,7 +66,7 @@ private Authentication loginSdk(final AppSecSdkToken auth) { @Override public boolean supports(Class authentication) { - return AppSecSdkToken.class.isAssignableFrom(authentication); + return AppSecToken.class.isAssignableFrom(authentication); } diff --git a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecSecurityController.java b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecSecurityController.java new file mode 100644 index 00000000000..cfb56a6e547 --- /dev/null +++ b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecSecurityController.java @@ -0,0 +1,24 @@ +package com.datadoghq.system_tests.springboot.security; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.provisioning.UserDetailsManager; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +public class AppSecSecurityController { + + private final UserDetailsManager userDetailsManager; + + public AppSecSecurityController(final UserDetailsManager userDetailsManager) { + this.userDetailsManager = userDetailsManager; + } + + @PostMapping("/signup") + public ResponseEntity signUp(@RequestParam String username, @RequestParam String password) { + userDetailsManager.createUser(User.withUsername(username).password(password).roles("USER").build()); + return ResponseEntity.ok("Signup successful"); + } +} diff --git a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecSdkToken.java b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecToken.java similarity index 53% rename from utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecSdkToken.java rename to utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecToken.java index 996f1d697e9..f937d40eeb5 100644 --- a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecSdkToken.java +++ b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecToken.java @@ -8,7 +8,7 @@ /** * Token used to bypass appsec auto user instrumentation when using the SDK */ -public class AppSecSdkToken extends UsernamePasswordAuthenticationToken { +public class AppSecToken extends UsernamePasswordAuthenticationToken { private String sdkEvent; @@ -16,35 +16,22 @@ public class AppSecSdkToken extends UsernamePasswordAuthenticationToken { private boolean sdkUserExists; - public AppSecSdkToken(Object principal, Object credentials) { + public AppSecToken(Object principal, Object credentials) { this(principal, credentials, null, null, false); } - public AppSecSdkToken(Object principal, Object credentials, String sdkEvent, String sdkUser, boolean sdkUserExists) { + public AppSecToken(Object principal, Object credentials, String sdkEvent, String sdkUser, boolean sdkUserExists) { super(principal, credentials); this.sdkEvent = sdkEvent; this.sdkUser = sdkUser; this.sdkUserExists = sdkUserExists; } - public AppSecSdkToken(Object principal, Object credentials, - Collection authorities) { + public AppSecToken(Object principal, Object credentials, + Collection authorities) { super(principal, credentials, authorities); } - @Override - public String getName() { - if (sdkEvent != null) { - // report the provided username - return sdkUser; - } else if (getPrincipal() instanceof AppSecUser) { - // report the id instead of the username - return ((AppSecUser) getPrincipal()).getId(); - } else { - return super.getName(); - } - } - public String getSdkEvent() { return sdkEvent; } diff --git a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecUserDetailsManager.java b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecUserDetailsManager.java new file mode 100644 index 00000000000..ad746bffbb7 --- /dev/null +++ b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/security/AppSecUserDetailsManager.java @@ -0,0 +1,53 @@ +package com.datadoghq.system_tests.springboot.security; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.provisioning.UserDetailsManager; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class AppSecUserDetailsManager implements UserDetailsManager { + + private static final Map USERS = new HashMap<>(); + + static { + Arrays.asList( + new AppSecUser("social-security-id", "test", "1234", "testuser@ddog.com"), + new AppSecUser("591dc126-8431-4d0f-9509-b23318d3dce4", "testuuid", "1234", "testuseruuid@ddog.com") + ).forEach(user -> USERS.put(user.getUsername(), user)); + } + + @Override + public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { + return USERS.get(username); + } + + @Override + public void createUser(final UserDetails user) { + // ignore it + } + + @Override + public void updateUser(UserDetails user) { + throw new UnsupportedOperationException(); + } + + @Override + public void deleteUser(String username) { + throw new UnsupportedOperationException(); + } + + @Override + public void changePassword(String oldPassword, String newPassword) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean userExists(String username) { + return USERS.containsKey(username); + } + + +}