Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feature/programming-exercises/re…
Browse files Browse the repository at this point in the history
…factor-and-add-filtering-for-erroranalysis' into feature/programming-exercises/refactor-and-add-filtering-for-erroranalysis
  • Loading branch information
az108 committed Sep 15, 2024
2 parents c4b5503 + f03def0 commit e6f41e1
Show file tree
Hide file tree
Showing 27 changed files with 709 additions and 96 deletions.
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ spotless {
"**/src/main/resources/templates/**",
"/docker/**",
"checked-out-repos/**",
"**/src/main/java/org/eclipse/**"
"**/src/main/java/org/eclipse/**",
"supporting_scripts/**"
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,11 @@ public final class Constants {
*/
public static final String PROFILE_LTI = "lti";

/**
* The name of the Spring profile used for activating SAML2 in Artemis, see {@link de.tum.cit.aet.artemis.core.service.connectors.SAML2Service}.
*/
public static final String PROFILE_SAML2 = "saml2";

public static final String PROFILE_SCHEDULING = "scheduling";

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
* Describes the security configuration for SAML2.
*/
@Configuration
@Profile("saml2")
@Profile(Constants.PROFILE_SAML2)
public class SAML2Configuration {

private static final Logger log = LoggerFactory.getLogger(SAML2Configuration.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
@Profile(PROFILE_CORE)
@Component
@ConfigurationProperties("saml2")
@ConfigurationProperties(Constants.PROFILE_SAML2)
public class SAML2Properties {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Repository;

import de.tum.cit.aet.artemis.core.config.Constants;
import de.tum.cit.aet.artemis.core.config.audit.AuditEventConverter;
import de.tum.cit.aet.artemis.core.domain.PersistentAuditEvent;

Expand All @@ -24,6 +28,10 @@
@Repository
public class CustomAuditEventRepository implements AuditEventRepository {

private final boolean isSaml2Active;

private static final String AUTHENTICATION_SUCCESS = "AUTHENTICATION_SUCCESS";

private static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";

/**
Expand All @@ -37,9 +45,10 @@ public class CustomAuditEventRepository implements AuditEventRepository {

private static final Logger log = LoggerFactory.getLogger(CustomAuditEventRepository.class);

public CustomAuditEventRepository(PersistenceAuditEventRepository persistenceAuditEventRepository, AuditEventConverter auditEventConverter) {
public CustomAuditEventRepository(Environment environment, PersistenceAuditEventRepository persistenceAuditEventRepository, AuditEventConverter auditEventConverter) {
this.persistenceAuditEventRepository = persistenceAuditEventRepository;
this.auditEventConverter = auditEventConverter;
this.isSaml2Active = Set.of(environment.getActiveProfiles()).contains(Constants.PROFILE_SAML2);
}

@Override
Expand All @@ -51,6 +60,11 @@ public List<AuditEvent> find(String principal, Instant after, String type) {
@Override
public void add(AuditEvent event) {
if (!AUTHORIZATION_FAILURE.equals(event.getType())) {
if (isSaml2Active && AUTHENTICATION_SUCCESS.equals(event.getType()) && SecurityContextHolder.getContext().getAuthentication() == null) {
// If authentication is null, Auth is success, and SAML2 profile is active => SAML2 authentication is running.
// Logging is handled manually.
return;
}

PersistentAuditEvent persistentAuditEvent = new PersistentAuditEvent();
persistentAuditEvent.setPrincipal(event.getPrincipal());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package de.tum.cit.aet.artemis.core.service;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_SCHEDULING;

import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;

@Service
@Profile(PROFILE_SCHEDULING)
public class TelemetrySendingService {

private static final Logger log = LoggerFactory.getLogger(TelemetrySendingService.class);

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record TelemetryData(String version, String serverUrl, String operator, String contact, List<String> profiles, String adminName) {
}

private final Environment env;

private final RestTemplate restTemplate;

public TelemetrySendingService(Environment env, RestTemplate restTemplate) {
this.env = env;
this.restTemplate = restTemplate;
}

@Value("${artemis.version}")
private String version;

@Value("${server.url}")
private String serverUrl;

@Value("${info.operatorName}")
private String operator;

@Value("${info.operatorAdminName}")
private String operatorAdminName;

@Value("${info.contact}")
private String contact;

@Value("${artemis.telemetry.sendAdminDetails}")
private boolean sendAdminDetails;

@Value("${artemis.telemetry.destination}")
private String destination;

/**
* Assembles the telemetry data, and sends it to the external telemetry server.
*
* @throws Exception if the writing the telemetry data to a json format fails, or the connection to the telemetry server fails
*/
@Async
public void sendTelemetryByPostRequest() throws Exception {
List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
TelemetryData telemetryData;
if (sendAdminDetails) {
telemetryData = new TelemetryData(version, serverUrl, operator, contact, activeProfiles, operatorAdminName);
}
else {
telemetryData = new TelemetryData(version, serverUrl, operator, null, activeProfiles, null);
}

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter();

var telemetryJson = objectWriter.writeValueAsString(telemetryData);
HttpEntity<String> requestEntity = new HttpEntity<>(telemetryJson, headers);
var response = restTemplate.postForEntity(destination + "/api/telemetry", requestEntity, String.class);
log.info("Successfully sent telemetry data. {}", response.getBody());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,71 +2,35 @@

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_SCHEDULING;

import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Profile;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;

@Service
@Profile(PROFILE_SCHEDULING)
public class TelemetryService {

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record TelemetryData(String version, String serverUrl, String operator, String contact, List<String> profiles, String adminName) {
}

private static final Logger log = LoggerFactory.getLogger(TelemetryService.class);

private final Environment env;

private final RestTemplate restTemplate;

private final ProfileService profileService;

private final TelemetrySendingService telemetrySendingService;

@Value("${artemis.telemetry.enabled}")
public boolean useTelemetry;

@Value("${artemis.telemetry.sendAdminDetails}")
private boolean sendAdminDetails;

@Value("${artemis.telemetry.destination}")
private String destination;

@Value("${artemis.version}")
private String version;

@Value("${server.url}")
private String serverUrl;

@Value("${info.operatorName}")
private String operator;

@Value("${info.operatorAdminName}")
private String operatorAdminName;

@Value("${info.contact}")
private String contact;

public TelemetryService(Environment env, RestTemplate restTemplate, ProfileService profileService) {
this.env = env;
this.restTemplate = restTemplate;
public TelemetryService(ProfileService profileService, TelemetrySendingService telemetrySendingService) {
this.profileService = profileService;
this.telemetrySendingService = telemetrySendingService;
}

/**
Expand All @@ -82,39 +46,13 @@ public void sendTelemetry() {

log.info("Sending telemetry information");
try {
sendTelemetryByPostRequest();
telemetrySendingService.sendTelemetryByPostRequest();
}
catch (JsonProcessingException e) {
log.warn("JsonProcessingException in sendTelemetry.", e);
}
catch (Exception e) {
log.warn("Exception in sendTelemetry, with dst URI: {}", destination, e);
}

}

/**
* Assembles the telemetry data, and sends it to the external telemetry server.
*
* @throws Exception if the writing the telemetry data to a json format fails, or the connection to the telemetry server fails
*/
public void sendTelemetryByPostRequest() throws Exception {
List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
TelemetryData telemetryData;
if (sendAdminDetails) {
telemetryData = new TelemetryData(version, serverUrl, operator, contact, activeProfiles, operatorAdminName);
}
else {
telemetryData = new TelemetryData(version, serverUrl, operator, null, activeProfiles, null);
}

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter();

var telemetryJson = objectWriter.writeValueAsString(telemetryData);
HttpEntity<String> requestEntity = new HttpEntity<>(telemetryJson, headers);
var response = restTemplate.postForEntity(destination + "/api/telemetry", requestEntity, String.class);
log.info("Successfully sent telemetry data. {}", response.getBody());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package de.tum.cit.aet.artemis.core.service.connectors;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_SAML2;
import static de.tum.cit.aet.artemis.core.config.Constants.SYSTEM_ACCOUNT;

import java.time.Instant;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
Expand All @@ -12,6 +17,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.context.annotation.Profile;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
Expand All @@ -35,7 +42,7 @@
/**
* This class describes a service for SAML2 authentication.
* <p>
* The main method is {@link #handleAuthentication(Saml2AuthenticatedPrincipal)}. The service extracts the user information
* The main method is {@link #handleAuthentication(Authentication,Saml2AuthenticatedPrincipal)}. The service extracts the user information
* from the {@link Saml2AuthenticatedPrincipal} and creates the user, if it does not exist already.
* <p>
* When the user gets created, the SAML2 attributes can be used to fill in user information. The configuration happens
Expand All @@ -45,9 +52,11 @@
* This is needed, since the client "does not know" that he is already authenticated via SAML2.
*/
@Service
@Profile("saml2")
@Profile(PROFILE_SAML2)
public class SAML2Service {

private final AuditEventRepository auditEventRepository;

@Value("${info.saml2.enable-password:#{null}}")
private Optional<Boolean> saml2EnablePassword;

Expand All @@ -68,12 +77,14 @@ public class SAML2Service {
/**
* Constructs a new instance.
*
* @param userRepository The user repository
* @param properties The properties
* @param userCreationService The user creation service
* @param auditEventRepository The audit event repository
* @param userRepository The user repository
* @param properties The properties
* @param userCreationService The user creation service
*/
public SAML2Service(final UserRepository userRepository, final SAML2Properties properties, final UserCreationService userCreationService, MailService mailService,
UserService userService) {
public SAML2Service(final AuditEventRepository auditEventRepository, final UserRepository userRepository, final SAML2Properties properties,
final UserCreationService userCreationService, MailService mailService, UserService userService) {
this.auditEventRepository = auditEventRepository;
this.userRepository = userRepository;
this.properties = properties;
this.userCreationService = userCreationService;
Expand All @@ -93,10 +104,13 @@ private Map<String, Pattern> generateExtractionPatterns(final SAML2Properties pr
* <p>
* Registers new users and returns a new {@link UsernamePasswordAuthenticationToken} matching the SAML2 user.
*
* @param principal the principal, containing the user information
* @param originalAuth the original authentication with details
* @param principal the principal, containing the user information
* @return a new {@link UsernamePasswordAuthenticationToken} matching the SAML2 user
*/
public Authentication handleAuthentication(final Saml2AuthenticatedPrincipal principal) {
public Authentication handleAuthentication(final Authentication originalAuth, final Saml2AuthenticatedPrincipal principal) {
Map<String, Object> details = originalAuth.getDetails() == null ? Map.of() : Map.of("details", originalAuth.getDetails());

Authentication auth = SecurityContextHolder.getContext().getAuthentication();

log.debug("SAML2 User '{}' logged in, attributes {}", auth.getName(), principal.getAttributes());
Expand All @@ -107,6 +121,9 @@ public Authentication handleAuthentication(final Saml2AuthenticatedPrincipal pri
if (user.isEmpty()) {
// create User if not exists
user = Optional.of(createUser(username, principal));
Map<String, Object> accountCreationDetails = new HashMap<>(details);
accountCreationDetails.put("user", user.get().getLogin());
auditEventRepository.add(new AuditEvent(Instant.now(), SYSTEM_ACCOUNT, "SAML2_ACCOUNT_CREATE", accountCreationDetails));

if (saml2EnablePassword.isPresent() && Boolean.TRUE.equals(saml2EnablePassword.get())) {
log.debug("Sending SAML2 creation mail");
Expand All @@ -125,6 +142,7 @@ public Authentication handleAuthentication(final Saml2AuthenticatedPrincipal pri
}

auth = new UsernamePasswordAuthenticationToken(user.get().getLogin(), user.get().getPassword(), toGrantedAuthorities(user.get().getAuthorities()));
auditEventRepository.add(new AuditEvent(Instant.now(), user.get().getLogin(), "SAML2_AUTHENTICATION_SUCCESS", details));
return auth;
}

Expand Down
Loading

0 comments on commit e6f41e1

Please sign in to comment.