Skip to content

Commit

Permalink
Use V3 of the login events in Java
Browse files Browse the repository at this point in the history
  • Loading branch information
manuel-alvarez-alvarez committed Dec 20, 2024
1 parent 57a2ea5 commit e5ac522
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 74 deletions.
70 changes: 46 additions & 24 deletions manifests/java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1026,58 +1026,80 @@ 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)
spring-boot-payara: bug (APPSEC-54985)
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)
spring-boot-payara: bug (APPSEC-54985)
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:
Expand Down
11 changes: 6 additions & 5 deletions tests/appsec/test_automated_login_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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()
Expand Down
23 changes: 19 additions & 4 deletions tests/appsec/test_automated_user_and_session_tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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):
Expand All @@ -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"


Expand Down Expand Up @@ -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},
],
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,60 +6,57 @@
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<String, AppSecUser> USERS = new HashMap<>();
private final UserDetailsManager userDetailsManager;

static {
Arrays.asList(
new AppSecUser("social-security-id", "test", "1234", "[email protected]"),
new AppSecUser("591dc126-8431-4d0f-9509-b23318d3dce4", "testuuid", "1234", "[email protected]")
).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 {
return loginSdk(token);
}
}

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<String, String> 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());
Expand All @@ -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);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> signUp(@RequestParam String username, @RequestParam String password) {
userDetailsManager.createUser(User.withUsername(username).password(password).roles("USER").build());
return ResponseEntity.ok("Signup successful");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,30 @@
/**
* 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;

private String sdkUser;

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<? extends GrantedAuthority> authorities) {
public AppSecToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> 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;
}
Expand Down
Loading

0 comments on commit e5ac522

Please sign in to comment.